Terminal Emulator – How to Identify Users on a Pseudo-Terminal Device

open filesptyterminal-emulator

If I do a:

echo foo > /dev/pts/12

Some process will read that foo\n from its file descriptor to the master side.

Is there a way to find out what that(those) process(es) is(are)?

Or in other words, how could I find out which xterm/sshd/script/screen/tmux/expect/socat… is at the other end of /dev/pts/12?

lsof /dev/ptmx will tell me the processes that have file descriptors on the master side of any pty. A process itself can use ptsname() (TIOCGPTN ioctl) to find out the slave device based on its own fd to the master side, so I could use:

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

for each of the pid/fd returned by lsof to build up that mapping, but is there a more direct, reliable and less intrusive way to get that information?

Best Answer

At first I tried tracing a few xterms back to the xterm pid based on info I found in /proc/locks but it was loose. I mean, it worked, I think, but it was at best circumstancial - I don't fully understand all of the information that file provides and was only matching what seemed to correspond between its content and known terminal processes.

Then I tried watching lsof/strace on an active write/talk process between ptys. I had never actually used either program before, but they seem to rely on utmp. If my targeted pty did not have a utmp entry for whatever reason they both refused to admit that it existed. Maybe there's a way around that, but i was confused enough to abandon it.

I tried some udevadm discovery with 136 and 128 major number device nodes as advertised for pts and ptm respectively in /proc/tty/drivers, but I also lack any very useful experience with that tool and once again turned up nothing substantial. Interestingly, though, I noticed the :min range for both device types was listed at a staggering 0-1048575.

It wasn't until I revisited this this kernel doc that I started thinking about the problem in terms of mounts, though. I had read that several times before but when continued research in that line led me to this this 2012 /dev/pts patchset I had an idea:

sudo fuser -v /dev/ptmx

I thought what do I usually use to associate processes with a mount? And sure enough:

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

So with that information I can do, for instance from terminology:

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

As you can see, with a little explicit testing such a process could be made to pretty reliably output the master process of an arbitrary pty. Regarding the sockets, I'm fairly certain one could approach it from that direction as well using socat as opposed to a debugger, but I've yet to straighten out how. Still, I suspect ss might help if you're more familiar with it than I:

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

So I set it up with a little more explicit testing, actually:

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

It prints $$ num \0 null bytes to each pty and checks each master process's io against a previous check. If the difference is $$ then it associates the pid with the pty. This mostly works. I mean, for me, it returns:

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

Which is correct , but, obviously, it's a little racy. I mean, if one of those others was reading in a bunch of data at the time it would probably miss. I'm trying to figure out how to change the stty modes on another pty in order to send the stop bit first or something like that so I can fix that.

Related Question