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

26

Se eu fizer um:

echo foo > /dev/pts/12

Algum processo lerá isso foo\ndo descritor de arquivo para o lado mestre.

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

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

lsof /dev/ptmxirá me dizer os processos que possuem descritores de arquivos no lado mestre de qualquer pty. Um processo em si pode usar ptsname()( TIOCGPTNioctl) para descobrir o dispositivo escravo com base em seu próprio fd para o lado mestre, para que eu possa usar:

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

para cada pid / fd retornado lsofpara construir esse mapeamento, mas existe uma maneira mais direta, confiável e menos invasiva de obter essas informações?

Stéphane Chazelas
fonte
É isso que voce quer? sudo find /proc/*/fd/0 -ls | grep '/dev/pts/4', forneceria a lista de PIDs ( /proc/PID) como saída.
slm
@ slm, não, em outras palavras, eu quero descobrir qual xterm / sshd / script / tela / tmux / expect / socat ... está na outra extremidade do /dev/pts/4. Geralmente, esse será um ancestral comum dos processos que foram /dev/pts/4abertos, mas não necessariamente.
Stéphane Chazelas
11
É ainda pior com soquetes - você precisa de um depurador de kernel!
Gilles 'SO- stop be evil'
11
@Falsenames - entendi a pergunta como significando - talvez incorretamente - não qual processo é passado nos dados lidos - como o primeiro shell invocado no terminal - mas que processo realmente lê do lado mestre. Por exemplo, se eu lançar um shell noscreen , é screenque ele aloca e gerencia ativamente o escravo pty pela vida útil do dispositivo, mas - como eu acho - o shell se torna o líder do processo para esse tty e assim, como seu saída mostra, você recebe bashou o que quer que psnão screen. Eu rastreei alguns de xtermsvolta ao xtermpid com base em /proc/locksmas estava solto.
mikeserv

Respostas:

3

No começo, tentei rastrear alguns xtermsegundos até o xtermpid, com base nas informações que encontrei, /proc/locksmas estavam soltas. Quero dizer, funcionou, acho, mas foi na melhor das hipóteses circunstanciais - não compreendo completamente todas as informações que o arquivo fornece e correspondia apenas ao que parecia corresponder entre o conteúdo e os processos terminais conhecidos.

Então eu tentei assistir lsof/straceem um write/talkprocesso ativo entre ptys. Eu nunca tinha usado nenhum dos programas antes, mas eles parecem confiar utmp. Se o meu site direcionado não tivesse uma utmpentrada por qualquer motivo, ambos se recusariam a admitir que existia. Talvez haja uma maneira de contornar isso, mas fiquei confuso o suficiente para abandoná-lo.

Tentei alguma udevadmdescoberta com 136 e 128 nós principais de dispositivos, conforme anunciado ptse ptmrespetivamente /proc/tty/drivers, mas também não tenho experiência muito útil com essa ferramenta e, mais uma vez, não achei nada substancial. Curiosamente, porém, notei que o :minintervalo para os dois tipos de dispositivos estava listado de forma impressionante 0-1048575.

Não foi até eu revisitar isso documento do kernel que comecei a pensar sobre o problema em termos de mounts. Eu tinha lido isso várias vezes antes, mas quando a pesquisa contínua nessa linha me levou a esse patchset de 2012/dev/pts , tive uma idéia:

sudo fuser -v /dev/ptmx

Pensei no 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 essas informações eu posso fazer, por exemplo 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 pouco de teste explícito, esse processo pode ser feito para gerar de maneira bastante confiável o processo mestre de um arquivo arbitrário. Em relação aos soquetes, tenho quase certeza de que alguém poderia abordá-lo dessa direção, usando também o socatoposto de um depurador, mas ainda não entendi como. Ainda assim, suspeito que sspossa ajudar se você estiver mais familiarizado com isso do que eu:

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

Então, configurei-o com um pouco de teste mais explícito, 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

Ele imprime um $$número de \0bytes nulos para cada arquivo e compara o io de cada processo mestre com um teste anterior. Se a diferença é $$então, associa o pid ao pty. Isso principalmente funciona . Quero dizer, para mim, 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 na época, provavelmente sentiria falta. Estou tentando descobrir como alterar os sttymodos em outro pty para enviar o bit de parada primeiro ou algo assim, para que eu possa consertar isso.

mikeserv
fonte
2

Se você está apenas procurando quem possui a conexão e de onde eles estão conectados, o comando who 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 quer saber o que está escutando nessa conexão, w mostrará que 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 inicializar.

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

Observe 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
Nomes falsos
fonte
Obrigado, mas não é isso que estou procurando. Acima, por exemplo, gostaria de encontrar o pid do aplicativo de terminal (Xterm / gnome-terminal ...) que corresponde a /dev/pts/4onde você executou esse wcomando.
Stéphane Chazelas
Desculpe, perdi completamente a parte pid quando digitalizei pela primeira vez. Eu pensei que você só queria saber o nome do processo final.
Falsenames
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 pontos remotos em uma string com um formato específico e alocado no heap. Pode ser que ele também funcione em outras situações com algumas alterações e reutilizando o pid da saída do fusor (verifique outra resposta).

O código é adaptado daqui .

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