¿Cómo podemos saber quién está en el otro extremo de un dispositivo pseudo-terminal?

26

Si hago un:

echo foo > /dev/pts/12

Algún proceso leerá eso foo\ndesde su descriptor de archivo al lado maestro.

¿Hay alguna manera de averiguar cuáles son esos (esos) procesos?

O en otras palabras, ¿cómo podría averiguar qué xterm / sshd / script / screen / tmux / expect / socat ... está en el otro extremo de /dev/pts/12?

lsof /dev/ptmxme dirá los procesos que tienen descriptores de archivo en el lado maestro de cualquier pty. Un proceso en sí mismo puede usar ptsname()( TIOCGPTNioctl) para encontrar el dispositivo esclavo basado en su propia fd en el lado maestro, por lo que podría usar:

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

para cada uno de los pid / fd devueltos por lsofconstruir esa asignación, pero ¿hay una forma más directa, confiable y menos intrusiva de obtener esa información?

Stéphane Chazelas
fuente
¿Es esto lo que quieres? sudo find /proc/*/fd/0 -ls | grep '/dev/pts/4', proporcionaría la lista de PID ( /proc/PID) como salida.
slm
@slm, no, en otras palabras, quiero saber qué xterm / sshd / script / screen / tmux / expect / socat ... está en el otro extremo de /dev/pts/4. Por lo general, ese será un ancestro común de aquellos procesos que se han /dev/pts/4abierto, pero no necesariamente.
Stéphane Chazelas
1
Es aún peor con los sockets : ¡necesita un depurador de kernel!
Gilles 'SO- deja de ser malvado'
1
@Falsenames: entendí que la pregunta significaba, quizás incorrectamente, no qué proceso se pasa la lectura de datos, como el primer shell invocado en la terminal, sino qué proceso realmente lo lee desde el lado maestro. Por ejemplo, si inicio un shell screen, es screenque asigna y gestiona activamente el esclavo pty durante la vida útil del dispositivo, pero, como creo, el shell se convierte en el líder del proceso para ese tty y así, como su salida muestra, obtienes basho lo que sea de psno screen. Remonté algunos xtermshasta el xtermpid basado en /proc/lockspero estaba suelto.
mikeserv

Respuestas:

3

Al principio intenté rastrear unos pocos xtermsegundos hasta el xtermpid basado en la información que encontré /proc/lockspero estaba flojo. Quiero decir, funcionó, creo, pero fue en el mejor de los casos: no entiendo completamente toda la información que proporciona el archivo y solo coincidía con lo que parecía corresponder entre su contenido y los procesos terminales conocidos.

Luego intenté ver lsof/straceun write/talkproceso activo entre ptys. Nunca antes había usado ninguno de los dos programas, pero parecen confiar en ellos utmp. Si mi pty objetivo no tenía una utmpentrada por alguna razón, ambos se negaron a admitir que existía. Tal vez hay una forma de evitar eso, pero estaba lo suficientemente confundido como para abandonarlo.

Intenté algún udevadmdescubrimiento con 136 y 128 nodos de dispositivos de mayor número como se anunciaba ptsy ptmrespectivamente /proc/tty/drivers, pero también me falta una experiencia muy útil con esa herramienta y una vez más no encontré nada sustancial. Curiosamente, sin embargo, noté que el :minrango para ambos tipos de dispositivos figuraba de manera asombrosa 0-1048575.

Sin embargo, no fue hasta que volví a visitar este documento del kernel que comencé a pensar en el problema en términos de mounts. Lo había leído varias veces antes, pero cuando la investigación continua en esa línea me llevó a este parche de 2012/dev/pts tuve una idea:

sudo fuser -v /dev/ptmx

Pensé, ¿qué suelo usar para asociar procesos con un mount? Y efectivamente:

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

Entonces, con esa información que puedo hacer, por ejemplo de terminology:

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

Como puede ver, con un poco de prueba explícita, se podría hacer un proceso para generar de manera bastante confiable el proceso maestro de una empanada arbitraria. Con respecto a los sockets, estoy bastante seguro de que uno podría abordarlo desde esa dirección y usarlo socaten lugar de un depurador, pero todavía tengo que aclarar cómo. Aún así, sospecho que sspodría ayudar si está más familiarizado con eso que yo:

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

Así que lo configuré con un poco más de pruebas explícitas, en realidad:

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

Imprime bytes $$numéricos \0nulos en cada pty y comprueba el io de cada proceso maestro contra una comprobación previa. Si la diferencia es, $$entonces asocia el pid con el pty. Esto funciona principalmente . Quiero decir, para mí, devuelve:

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

Lo cual es correcto, pero, obviamente, es un poco picante. Quiero decir, si uno de esos otros estuviera leyendo un montón de datos en ese momento, probablemente fallaría. Estoy tratando de descubrir cómo cambiar los sttymodos en otra pty para enviar el bit de parada primero o algo así para poder solucionarlo.

mikeserv
fuente
2

Si solo está buscando quién posee la conexión y desde dónde están conectados, el comando who funcionará bien.

$ 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)

Si también desea saber qué está escuchando en esa conexión, w lo mostrará al final.

$ 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

Y para obtener los pids, limite un ps a la sesión tty que está viendo. Completamente discreto para arrancar.

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

Tenga en cuenta que esto puede conducir a pistas falsas, dependiendo del momento. Pero es un buen lugar para comenzar.

$ 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
fuente
Gracias, pero eso no es lo que estoy buscando. Arriba, por ejemplo, me gustaría encontrar el pid de la aplicación de terminal (Xterm / gnome-terminal ...) que corresponde /dev/pts/4, donde ejecutó ese wcomando.
Stéphane Chazelas
Lo siento, me perdí por completo la parte pid cuando escaneé por primera vez. Pensé que solo querías saber el nombre del proceso final.
Falsenames
2

Tuve el mismo problema con qemu, y finalmente encontré una solución muy mala (pero aún así una solución): analizar la memoria del proceso.

Esto funciona aquí porque sé que qemu está almacenando los puntos remotos en una cadena con un formato específico y asignado en el montón. Es posible que también funcione en otras situaciones con algunos cambios y reutilizando el pid de la salida del fusor (verifique otra respuesta).

El código está adaptado desde aquí .

#! /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
fuente