Skąd możemy wiedzieć, kto jest na drugim końcu pseudo-terminala?

26

Jeśli zrobię:

echo foo > /dev/pts/12

Niektóre procesy odczytują to foo\nz deskryptora pliku do strony głównej.

Czy istnieje sposób, aby dowiedzieć się, czym są te procesy?

Lub innymi słowy, jak mogę dowiedzieć się, który xterm / sshd / script / screen / tmux / expect / socat ... znajduje się na drugim końcu /dev/pts/12?

lsof /dev/ptmxpowie mi procesy, które mają deskryptory plików po stronie master dowolnego pty. Sam proces może użyć ptsname()( TIOCGPTNioctl), aby znaleźć urządzenie slave na podstawie własnego fd po stronie master, więc mógłbym użyć:

gdb --batch --pid "$the_pid" -ex "print ptsname($the_fd)"

dla każdego pid / fd zwróconego w lsofcelu zbudowania tego mapowania, ale czy istnieje bardziej bezpośredni, niezawodny i mniej ingerujący sposób na uzyskanie tych informacji?

Stéphane Chazelas
źródło
Czy to jest to, czego chcesz? sudo find /proc/*/fd/0 -ls | grep '/dev/pts/4', dostarczy listę PIDs ( /proc/PID) jako dane wyjściowe.
slm
@ slm, nie, innymi słowy, chcę dowiedzieć się, który xterm / sshd / script / screen / tmux / expect / socat ... znajduje się na drugim końcu /dev/pts/4. Zwykle będzie to wspólny przodek tych procesów, które są /dev/pts/4otwarte, ale niekoniecznie.
Stéphane Chazelas,
1
Gorzej jest nawet z gniazdami - potrzebujesz debuggera jądra!
Gilles „SO- przestań być zły”
1
@Falsenames - zrozumiałem, że pytanie to znaczy - być może niepoprawnie - nie to, jaki proces jest przekazywany odczytanym danym - takim jak pierwsza powłoka wywoływana w terminalu - ale jaki proces w rzeczywistości odczytuje to od strony głównej. Na przykład, jeśli uruchomię powłokę screen, to ona screenalokuje i aktywnie zarządza pty slave przez cały okres eksploatacji urządzenia, ale - jak myślę - powłoka staje się liderem procesu dla tego tty i tak, jak twój pokazuje wyniki, otrzymujesz bashlub cokolwiek z psnie screen. Znalazłem kilka z xtermspowrotem do xtermpid na podstawie, /proc/locksale było luźne.
mikeserv

Odpowiedzi:

3

Na początku próbowałem prześledzić kilka xtermsekund z powrotem do xtermpid na podstawie informacji, które znalazłem, /proc/locksale było luźne. Myślę, że zadziałało, jak sądzę, ale w najlepszym razie było okolicznościowe - nie do końca rozumiem wszystkie informacje dostarczane przez plik i pasowały tylko do tego, co wydawało się odpowiadać między jego zawartością a znanymi procesami końcowymi.

Potem próbowałem oglądać lsof/straceaktywny write/talkproces między ptys. Nigdy wcześniej nie korzystałem z żadnego z tych programów, ale wydaje się, że polegają na nich utmp. Jeśli mój celowany pty nie miał żadnego utmpwpisu z jakiegokolwiek powodu, obaj odmówili przyznania się do jego istnienia. Może jest na to jakiś sposób, ale byłem na tyle zdezorientowany, żeby to porzucić.

Próbowałem udevadmodkrycia z 136 i 128 głównymi numerami urządzeń, jak w reklamie ptsi ptmodpowiednio /proc/tty/drivers, ale brakuje mi również bardzo użytecznego doświadczenia z tym narzędziem i po raz kolejny nie znalazłem nic znaczącego. Co ciekawe, zauważyłem, że :minzasięg dla obu typów urządzeń został wymieniony w oszałamiający sposób 0-1048575.

Jednak dopiero po ponownym przejrzeniu tego dokumentu jądra zacząłem myśleć o problemie w kategoriach mounts. Przeczytałem to kilka razy wcześniej, ale kiedy dalsze badania w tej linii doprowadziły mnie do tego zestawu łatek z 2012 roku, /dev/ptswpadłem na pomysł:

sudo fuser -v /dev/ptmx

Pomyślałem, czego zwykle używam do kojarzenia procesów z mount? I na pewno:

                     USER        PID ACCESS COMMAND
/dev/ptmx:           root      410   F.... kmscon
                     mikeserv  710   F.... terminology

Dzięki tym informacjom mogę na przykład terminology:

sudo sh -c '${cmd:=grep rchar /proc/410/io} && printf 1 >/dev/pts/0 && $cmd'
###OUTPUT###
rchar: 667991010
rchar: 667991011

Jak widać, przy odrobinie wyraźnego testowania taki proces mógłby zostać wykonany w celu rzetelnego wyprowadzenia głównego procesu dowolnego pty. Jeśli chodzi o gniazda, jestem całkiem pewien, że można podejść do niego również z tego kierunku, używając socatzamiast debuggera, ale muszę jeszcze wyjaśnić, jak to zrobić. Podejrzewam jednak, że ssmoże ci pomóc, jeśli znasz się bardziej niż ja:

sudo sh -c 'ss -oep | grep "$(printf "pid=%s\n" $(fuser /dev/ptmx))"'

Więc skonfigurowałem to z nieco bardziej wyraźnymi testami, w rzeczywistości:

sudo sh <<\CMD
    chkio() {
        read io io <$1
        dd bs=1 count=$$ </dev/zero >$2 2>/dev/null
        return $((($(read io io <$1; echo $io)-io)!=$$))
    }
    for pts in /dev/pts/[0-9]* ; do
        for ptm in $(fuser /dev/ptmx 2>/dev/null)
            do chkio /proc/$ptm/io $pts && break
        done && set -- "$@" "$ptm owns $pts"
    done
    printf %s\\n "$@"
 CMD

Drukuje $$num \0null bajty na każdym pty i sprawdza io każdego procesu nadrzędnego względem poprzedniego sprawdzenia. Jeśli różnica jest taka, $$to wiąże pid z pty. To głównie działa. To znaczy dla mnie zwraca:

410 owns /dev/pts/0
410 owns /dev/pts/1
710 owns /dev/pts/2

Co jest poprawne, ale oczywiście jest to trochę ryzykowne. Chodzi mi o to, że jeśli któryś z nich czytałby w tym czasie wiele danych, prawdopodobnie by tego nie zauważył. Próbuję wymyślić, jak zmienić sttytryby na innym pty, aby najpierw wysłać bit stopu lub coś w tym rodzaju, aby to naprawić.

mikeserv
źródło
2

Jeśli po prostu szukasz, kto jest właścicielem połączenia i skąd są one połączone, polecenie who będzie działać dobrze.

$ who
falsenames   tty8         Jun 13 16:54 (:0)
falsenames   pts/0        Jun 16 11:18 (:0)
falsenames   pts/1        Jun 16 12:59 (:0)
falsenames   pts/2        Jun 16 13:46 (:0)
falsenames   pts/3        Jun 16 14:10 (:0)
falsenames   pts/4        Jun 16 16:41 (:0)

Jeśli chcesz także wiedzieć, co nasłuchuje na tym połączeniu, w pokaże to na końcu.

$ w
 16:44:09 up 2 days, 23:51,  6 users,  load average: 0.26, 0.98, 1.25
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
falsenames   tty8     :0               Fri16    2days 53:36   0.59s x-session-manager
falsenames   pts/0    :0               11:18    5:25m  1:10   1:10  synergys -a 10.23.8.245 -c .synergy.conf -f -d DEBUG
falsenames   pts/1    :0               12:59    3:44m  0.05s  0.05s bash
falsenames   pts/2    :0               13:46    2:52m  0.11s  0.11s bash
falsenames   pts/3    :0               14:10    2:17   0.07s  0.07s bash
falsenames   pts/4    :0               16:41    1.00s  0.04s  0.00s w

Aby uzyskać stawki, ogranicz ps do sesji tty, na którą patrzysz. Uruchamianie jest całkowicie dyskretne.

$ ps -t pts/0 --forest 
  PID TTY          TIME CMD
23808 pts/0    00:00:00 bash
23902 pts/0    00:03:27  \_ synergys

Uwaga: może to prowadzić do czerwonych śledzi, w zależności od czasu. Ale to dobre miejsce na początek.

$ tty
/dev/pts/4
$ ps -t pts/4 --forest
  PID TTY          TIME CMD
27479 pts/4    00:00:00 bash
 3232 pts/4    00:00:00  \_ ps
27634 pts/4    00:00:00 dbus-launch
Falsenames
źródło
Dzięki, ale nie tego szukam. Powyżej, na przykład, chciałbym znaleźć pid aplikacji terminalowej (Xterm / gnome-terminal ...), która odpowiada /dev/pts/4, gdzie uruchomiłeś to wpolecenie.
Stéphane Chazelas,
Przepraszamy, całkowicie przeoczyłem część dotyczącą pid, gdy skanowałem po raz pierwszy. Myślałem, że po prostu chcesz poznać nazwę procesu końcowego.
Falsenames,
2

Miałem ten sam problem z qemu iw końcu znalazłem bardzo złe rozwiązanie (ale nadal rozwiązanie): analizowanie pamięci procesu.

Działa to tutaj, ponieważ wiem, że qemu przechowuje zdalne pts w ciągu o określonym formacie i przydzielonym na stercie. Może działać również w innych sytuacjach z kilkoma zmianami i ponownie wykorzystując pid z wyjścia utrwalacza (sprawdź inną odpowiedź).

Kod jest dostosowywany stąd .

#! /usr/bin/env python

import sys
pid = sys.argv[1]

import re
maps_file = open("/proc/" + pid + "/maps", 'r')
mem_file = open("/proc/" + pid + "/mem", 'r', 0)
for line in maps_file.readlines():
    # You may want to remove the 'heap' part to search all RAM
    m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r]).*\[heap\]', line)
    if m and m.group(3) == 'r':
        start = int(m.group(1), 16)
        end = int(m.group(2), 16)
        mem_file.seek(start)
        chunk = mem_file.read(end - start)
        # You may want to adapt this one to reduce false matches
        idx = chunk.find("/dev/pts/")
        if idx != -1:
            end = chunk.find("\0", idx)
            print chunk[idx:end]
maps_file.close()
mem_file.close()
calandoa
źródło