Comment savoir qui se trouve à l'autre bout d'un pseudo-terminal?

26

Si je fais un:

echo foo > /dev/pts/12

Certains processus liront cela foo\nde son descripteur de fichier vers le côté maître.

Existe-t-il un moyen de savoir ce que ce (ces) processus est (sont)?

Ou en d'autres termes, comment pourrais-je savoir quel xterm / sshd / script / screen / tmux / expect / socat ... est à l'autre bout de /dev/pts/12?

lsof /dev/ptmxme dira les processus qui ont des descripteurs de fichiers du côté maître de tout pty. Un processus lui-même peut utiliser ptsname()( TIOCGPTNioctl) pour trouver le périphérique esclave sur la base de son propre fd côté maître, donc je pourrais utiliser:

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

pour chacun des pid / fd retournés par lsofpour créer ce mappage, mais existe-t-il un moyen plus direct, fiable et moins intrusif d'obtenir ces informations?

Stéphane Chazelas
la source
c'est ce que tu veux? sudo find /proc/*/fd/0 -ls | grep '/dev/pts/4', fournirait la liste des PID ( /proc/PID) en sortie.
slm
@slm, non, en d'autres termes, je veux savoir quel xterm / sshd / script / screen / tmux / expect / socat ... est à l' autre bout de /dev/pts/4. Habituellement, ce sera un ancêtre commun de ces processus /dev/pts/4ouverts, mais pas nécessairement.
Stéphane Chazelas
1
C'est encore pire avec les sockets - vous avez besoin d'un débogueur de noyau!
Gilles 'SO- arrête d'être méchant'
1
@Falsenames - J'ai compris que la question signifiait - peut-être incorrectement - non pas quel processus est passé les données lues - comme le premier shell invoqué dans le terminal - mais quel processus les lit réellement du côté maître. Par exemple, si je lance un shell dans screen, c'est screenqu'il alloue et gère activement l'esclave pty pendant la durée de vie de l'appareil, mais - comme, je pense - le shell devient le leader du processus pour ce tty et ainsi, comme votre montre la sortie, vous obtenez bashou quoi que ce soit de psnon screen. J'en ai retracé quelques-uns xtermsjusqu'au xtermpid basé sur /proc/locksmais il était lâche.
mikeserv

Réponses:

3

Au début, j'ai essayé de remonter quelques secondes xtermau xtermpid en fonction des informations que j'ai trouvées, /proc/locksmais c'était lâche. Je veux dire, cela a fonctionné, je pense, mais c'était au mieux circonstanciel - je ne comprends pas complètement toutes les informations que le fichier fournit et ne correspondait qu'à ce qui semblait correspondre entre son contenu et les processus terminaux connus.

Ensuite, j'ai essayé de regarder lsof/straceun write/talkprocessus actif entre ptys. Je n'avais jamais utilisé aucun des deux programmes auparavant, mais ils semblent s'appuyer sur eux utmp. Si mon pty ciblé n'avait pas d' utmpentrée pour une raison quelconque, ils refusaient tous les deux d'admettre qu'elle existait. Peut-être qu'il y a un moyen de contourner cela, mais j'étais assez confus pour l'abandonner.

J'ai essayé une udevadmdécouverte avec 136 et 128 nœuds de périphérique de nombre majeur comme annoncé ptset ptmrespectivement /proc/tty/drivers, mais je manque également d'expérience très utile avec cet outil et encore une fois rien de substantiel. Fait intéressant, cependant, j'ai remarqué que la :minplage des deux types d'appareils était répertoriée de manière stupéfiante 0-1048575.

Ce n'est que lorsque j'ai revu ce document sur le noyau que j'ai commencé à penser au problème en termes de mounts. J'avais lu cela plusieurs fois auparavant, mais lorsque des recherches continues dans ce domaine m'ont amené à ce patchset 2012,/dev/pts j'ai eu une idée:

sudo fuser -v /dev/ptmx

J'ai pensé à ce que j'utilise habituellement pour associer des processus à un mount? Et bien sûr:

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

Donc, avec ces informations, je peux faire, par exemple à partir de terminology:

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

Comme vous pouvez le voir, avec un peu de test explicite, un tel processus pourrait être fait pour produire de manière assez fiable le processus maître d'un pty arbitraire. En ce qui concerne les sockets, je suis assez certain que l'on pourrait l'approcher dans cette direction en utilisant socatplutôt qu'un débogueur, mais je n'ai pas encore expliqué comment. Pourtant, je pense que cela sspourrait aider si vous le connaissez mieux que moi:

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

Je l'ai donc configuré avec des tests un peu plus explicites, en fait:

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

Il imprime $$num \0octets nuls à chaque pty et vérifie l'io de chaque processus maître par rapport à une vérification précédente. Si la différence est $$alors il associe le pid au pty. Cela fonctionne principalement . Je veux dire, pour moi, ça revient:

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

Ce qui est correct, mais, évidemment, c'est un peu racé. Je veux dire, si l'un de ces autres lisait un tas de données à l'époque, il manquerait probablement. J'essaie de comprendre comment changer les sttymodes sur un autre pty afin d'envoyer le bit d'arrêt en premier ou quelque chose comme ça pour que je puisse résoudre ce problème.

mikeserv
la source
2

Si vous cherchez simplement à qui appartient la connexion et d'où ils sont connectés, la commande who fonctionnera 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 vous voulez aussi savoir ce qui est à l' écoute sur cette connexion, w montrera à la fin.

$ 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

Et pour obtenir les pids, limitez un ps à la session tty que vous regardez. Complètement discret pour démarrer.

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

Notez que cela peut conduire à des harengs rouges, selon le moment. Mais c'est un bon point de départ.

$ 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
la source
Merci, mais ce n'est pas ce que je recherche. Ci-dessus par exemple, j'aimerais trouver le pid de l'application terminale (Xterm / gnome-terminal ...) qui correspond à /dev/pts/4, où vous avez exécuté cette wcommande.
Stéphane Chazelas
Désolé, j'ai complètement raté la partie pid lorsque j'ai numérisé la première fois. Je pensais que tu voulais juste connaître le nom du processus de fin.
Falsenames
2

J'ai eu le même problème avec qemu, et j'ai finalement trouvé une très mauvaise solution (mais toujours une solution): analyser la mémoire du processus.

Cela fonctionne ici parce que je sais que qemu stocke les pts distants dans une chaîne avec un format spécifique et alloués sur le tas. Peut-être que cela peut également fonctionner dans d'autres situations avec quelques modifications et en réutilisant le pid de la sortie de l'unité de fusion (vérifiez l'autre réponse).

Le code est adapté d' ici .

#! /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
la source