1

This question already has an answer here:

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.

marked as duplicate by JdeBP, Stephen Harris, elbarna, Stephen Kitt linux Jan 4 at 7:33

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • this problem is similar to Who's got the other end of this unix socketpair?. If there's another q&a handling this irksome problem for ptys (preferably with some answers ;-)), this should be a dupe of that, not of the question it's currently declared a dupe of. – mosvy Jan 5 at 22:20
  • @StephenKitt wields the dupehammer, so he can edit the list of duplicates if needed. There's no need to reopen this just for that. – muru Oct 9 at 1:44
  • @muru so this is a game, I guess: it should not be reopened because it's not good politics or something. – mosvy Oct 9 at 5:17
  • @mosvy what I meant is that a single user (Stephen Kitt here) can do what would otherwise take at least 5 users. – muru Oct 9 at 5:19
  • I know that, but thanks for reminding the reviewers to respect the pecking order, and not be misled by other considerations. – mosvy Oct 9 at 10:03
7

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()]+/g;
        $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;
}
  • Nice... Just curious, how does gdb tend to be broken, as shipped in distros? – Stephen Kitt Jan 4 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 at 4:35
  • @Tim have you read my answer? I if it doesn't work, you can say it directly. Notice that a process can have both master and slaves ptys open (eg script(1)), and multiple processes can have the same master or slave open (as with any file). On debian you should apt-get install gdb; the default gdb-minimal is broken and will not work. – mosvy Jan 6 at 11:24
  • Thanks. sorry, could you tell me how to run your script? I don't know perl. – Tim Jan 6 at 12:30
  • install gdb and perl (apt get install gdb perl), save the script into a file eg. ptys, make it executable with chmod +x ptys and run it as ./ptys or ./ptys | less -S. Notice that a broken gdb may crash the processes it attaches to, and, if run as a normal user, the script will only print info about the processes it has access to – mosvy Jan 6 at 13:00
4

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

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