Como podemos saber quem está do outro lado de um dispositivo pseudo-terminal?

24

Se eu fizer um:

echo foo > /dev/pts/12

Algum processo irá ler que foo\n do seu descritor de arquivo para o lado mestre.

Existe uma maneira de descobrir o que (aqueles) processo (s) é (são)?

Ou, em outras palavras, como eu poderia descobrir qual xterm / sshd / script / screen / tmux / expect / soc ... está do outro lado de /dev/pts/12 ?

lsof /dev/ptmx me dirá os processos que possuem descritores de arquivo no lado mestre de qualquer pty. Um processo em si pode usar ptsname() ( TIOCGPTN ioctl) para descobrir o dispositivo escravo baseado em seu próprio fd para o lado do mestre, então eu poderia usar:

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

para cada um dos pid / fd retornados por lsof para construir esse mapeamento, mas existe uma maneira mais direta, confiável e menos intrusiva de obter essa informação?

    
por Stéphane Chazelas 11.06.2014 / 22:12

3 respostas

3

No começo, tentei rastrear alguns xterm s de volta para o xterm pid com base nas informações que encontrei em /proc/locks , mas estava solto. Quer dizer, funcionou, eu acho, mas foi na melhor das hipóteses circunstancial - eu não entendo completamente todas as informações que o arquivo fornece e estava apenas combinando o que parecia corresponder entre o conteúdo e os processos terminais conhecidos.

Então eu tentei assistir lsof/strace em um processo write/talk ativo entre ptys. Eu nunca tinha usado nenhum programa antes, mas eles parecem confiar em utmp . Se o meu alvo não tivesse uma entrada utmp por qualquer razão, ambos se recusaram a admitir que existia. Talvez haja uma maneira de contornar isso, mas eu estava confuso o suficiente para abandoná-lo.

Eu tentei alguns udevadm discovery com 136 e 128 nós de dispositivo de número principal como anunciados para pts e ptm respectivamente em /proc/tty/drivers , mas também não tenho nenhuma experiência útil com essa ferramenta e mais uma vez apareceu nada substancial. Curiosamente, no entanto, notei que o intervalo :min para ambos os tipos de dispositivos foi listado em 0-1048575 .

Não foi até que eu revisitei este este documento do kernel que comecei a pensar sobre o problema em termos de mount s, no entanto. Eu tinha lido isso várias vezes antes, mas quando a pesquisa continuada nessa linha me levou a este este 2012 /dev/pts patchset Eu tive uma ideia:

sudo fuser -v /dev/ptmx

Pensei em o que costumo usar para associar processos a um mount ? E com certeza:

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

Então, com essa informação eu posso fazer, por exemplo, de terminology :

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

Como você pode ver, com um pequeno teste explícito, tal processo poderia ser feito para imprimir de forma confiável o processo mestre de um arquivo arbitrário. Em relação aos sockets, tenho quase certeza de que alguém poderia se aproximar dessa direção usando também socat em oposição a um depurador, mas ainda não endireitei como. Ainda assim, suspeito que ss possa ajudar se você estiver mais familiarizado com ele do que eu:

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

Então eu configuro com testes um pouco mais explícitos, na verdade:

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 $$ num $$ null bytes para cada pty e verifica o io de cada processo mestre em uma verificação anterior. Se a diferença for stty , ela associa o pid ao pty. Isso principalmente funciona. Quer dizer, para mim, isso retorna:

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

O que é correto, mas, obviamente, é um pouco atrevido. Quero dizer, se um desses outros estivesse lendo um monte de dados no momento provavelmente perderia. Eu estou tentando descobrir como alterar os modos %code% em outro pty para enviar o bit de parada primeiro ou algo assim para que eu possa consertar isso.

    
por 30.06.2014 / 20:36
2

Se você está apenas procurando por quem é o proprietário da conexão e de onde ela está conectada, os que o comando funcionará bem.

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

Se você também quiser saber o que está escutando nessa conexão, w mostre isso no 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

E para obter os pids, limite um ps à sessão tty que você está vendo. 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

Note que isso pode levar a arenques vermelhos, dependendo do tempo. Mas é um bom lugar para começar.

$ 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
    
por 17.06.2014 / 01:49
2

Eu tive o mesmo problema com o qemu e finalmente encontrei uma solução muito ruim (mas ainda uma solução): analisando a memória do processo.

Isso está funcionando aqui porque eu sei que o qemu está armazenando os pts remotos em uma string com um formato específico e alocados no heap. Pode ser que ele possa funcionar em outras situações também com algumas mudanças e reutilizando o pid da saída do fusor (verifique outra resposta).

O código é adaptado de aqui .

#! /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("
#! /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("%pre%", idx)
            print chunk[idx:end]
maps_file.close()
mem_file.close()
", idx) print chunk[idx:end] maps_file.close() mem_file.close()
    
por 27.05.2016 / 19:03