5

A pseudoterminal has a pair of master and slave.

How can we find out the master device file from a slave device file (e.g. /etc/pts/3)? I only find /dev/ptmx and /dev/pts/ptmx, but they can't be shared by multiple slaves.

Given one of the processes working on the master and slave, how can we find out the other? For example, ps provides information about the controlling tty of each process. Can it be helpful?

Thanks.

0

3 Answers 3

10

That is one thing that is harder than it should be.

With newer linux kernels, the index of the slave pty paired with a master can be gathered from the tty-index entry from /proc/PID/fdinfo/FD. See this commit.

With older kernels, the only way you can get that is by attaching with a debugger to a process holding a master pty, and call ptsname(3) (or directly ioctl(TIOCGPTN)) on the file descriptor.

[but both methods will run into issues on systems using multiple devpts mounts, see below]

With that info, you can build a list of master-slave pairings, which will also allow you to look up the master starting up from the slave.

Here is a silly script that should do that; it will first try the tty-index way, and if that doesn't work it will fall back to gdb. For the latter case, it needs a working gdb (not gdb-minimal or another half-broken gdb most distros ship with) and because of its use of gdb, it will be very slow.

For each pty pair, it will print something like:

/dev/pts/1
    1227  3     t/ct_test
        1228  +*      t/ct_test
        1230  +      t/ct_test
/dev/pts/3
    975   9     'sshd: root [priv]' '' '' '' '' '' '' '' ''
    978   14,18,19   'sshd: root@pts/3' '' '' '' '' '' '' ''
        979   -*0,1,2,255   -bash
        1222  1     tiocsti
        1393  -0,1,2   sleep 3600
        1231  +0,2   perl ptys.pl
        1232  +1,2   cut -b1-60

the two sshd processes (pids 975 and 978) have open handles to the master side (one as its 9 fd and the other as its 14, 18 and 19 fds). sleep and -bash have open handles to the slave side as their standard (0,1 and 2) fds. The session leader (bash) is also marked with a *, the processes in the foreground (perl and cut) with a +, and those in the background (less and -bash) with a -.

The t/ct_test processes are using the pty as their controlling terminal without having any fd open to it. tiocsti has an open handle to it without it being its controlling terminal.

Tested on Debian 9 and Fedora 28. Info about the magic numbers it's using can be found in procfs(5) and Documentation/admin-guide/devices.txt in the linux kernel source.


This will fail on any system using chroots or namespace containers; that's not fixable without some changes to the kernel, since there's no reliable way to match the tty field from /proc/PID/stat to a pty, and a fd opened via /dev/ptmx to the corresponding /dev/pts mount. See here for a rant about it.

This will also not link in any fd opened via /dev/tty; the real tty could be worked out by attaching to the process and calling ioctl(fd, TIOCGDEV, &dev), but that will mean another dirty heavy use of gdb, and it will run into the same issues as above with the major, minor numbers being ambiguous for pseudo-tty slaves.

ptys.pl:

my (%pty, %ctty);
for(</proc/*[0-9]*/{fd/*,stat}>){
    if(my ($pid, $fd) = m{/proc/(\d+)/fd/(\d+)}){
        next unless -c $_;
        my $rdev = (stat)[6]; my $maj = $rdev >> 8 & 0xfff;
        if($rdev == 0x502){ # /dev/ptmx or /dev/pts/ptmx
            $pty{ptsname($pid, $fd, readlink $_)}{m}{$pid}{$fd} = 1;
        }elsif($maj >= 136 && $maj <= 143){ # /dev/pts/N
            $pty{readlink $_}{s}{$pid}{$fd} = 1;
        }
    }else{
        my @s = readfile($_) =~ /(?<=\().*(?=\))|[^\s()]+/gs;
        $ctty{$s[6]}{$s[0]} =       # ctty{tty}{pid} =
            ($s[4] == $s[7] ? '+' : '-').   # pgrp == tpgid
            ($s[0] == $s[5] ? '*' : '');    # pid == sid
    }
}
for(sort {length($a)<=>length($b) or $a cmp $b} keys %pty){
    print "$_\n";
    pproc(4, $pty{$_}{m}); pproc(8, $pty{$_}{s}, $ctty{(stat)[6]});
}

sub readfile { local $/; my $h; open $h, '<', shift and <$h> }
sub cmdline {
    join ' ', map { s/'/'\\''/g, $_ = "'$_'" if m{^$|[^\w./+=-]}; $_ }
        readfile("/proc/$_[0]/cmdline") =~ /([^\0]*)\0/g;
}
sub pproc {
    my ($px, $h, $sinfo) = @_;
    exists $$h{$_} or $$h{$_} = {''} for keys %$sinfo;
    return printf "%*s???\n", $px, "" unless $h;
    for my $pid (sort {$a<=>$b} keys %$h){
        printf "%*s%-5d %s%-3s   %s\n", $px, "", $pid, $$sinfo{$pid},
            join(',', sort {$a<=>$b} keys %{$$h{$pid}}),
            cmdline $pid;
    }
}
sub ptsname {
    my ($pid, $fd, $ptmx) = @_;
    return '???' unless defined(my $ptn = getptn($pid, $fd));
    $ptmx =~ m{(.*)(?:/pts)?/ptmx$} ? "$1/pts/$ptn" : "$ptmx ..?? pts/$ptn"
}
sub getptn {
    my ($pid, $fd) = @_;
    return $1 if
        readfile("/proc/$pid/fdinfo/$fd") =~ /^tty-index:\s*(\d+)$/m;
    return gdb_ioctl($pid, $fd, 0x80045430);    # TIOCGPTN
}
sub gdb_ioctl {
    my ($pid, $fd, $ioctl) = @_;
    my $cmd = qq{p (int)ioctl($fd, $ioctl, &errno) ? -1 : errno};
    qx{exec 3>&1; gdb -batch -p $pid -ex '$cmd' 2>&1 >&3 |
            grep -v '/sysdeps/.*No such file or directory' >&2}
        =~ /^\$1 *= *(\d+)$/m ? $1 : undef;
}
4
  • Nice... Just curious, how does gdb tend to be broken, as shipped in distros? Jan 4, 2019 at 22:57
  • Thanks. Given one of the processes working on the master and slave, how can we find out the other?
    – Tim
    Jan 6, 2019 at 4:35
  • Thanks. sorry, could you tell me how to run your script? I don't know perl.
    – Tim
    Jan 6, 2019 at 12:30
  • @Tim perl ptys.pl
    – I.Am.A.Guy
    Jan 15, 2021 at 17:14
6

On Linux, using devpts, there is no master device file. The process on the master end uses a file descriptor, which it gets by opening ptmx, but there’s no corresponding device node.

See the ptmx manpage for details.

(With BSD-style ptys on Linux, there are matching device pairs, such as /dev/ptyp1 and /dev/ttyp1, respectively on the master and slave side.)

3
  • Thanks. Given one of the processes working on the master and slave, how can we find out the other?
    – Tim
    Jan 6, 2019 at 4:34
  • Also unix.stackexchange.com/questions/492753/…
    – Tim
    Jan 6, 2019 at 4:42
  • This seems like a new thing, maybe a year or so. I have a script that uses chroot that started to fail because dev/pts was not mounted. Thanks to your answer I was able to figure that out <3
    – phunehehe
    Apr 4, 2020 at 1:43
0

Looks like lsof +E gives you information of both ends of FDs, including ptm/pts.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.