30

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?

8
  • Is this what you want? sudo find /proc/*/fd/0 -ls | grep '/dev/pts/4', would provide the list of PIDs (/proc/PID) as output. Commented Jun 12, 2014 at 0:51
  • @slm, no, in other words, I want to find out which xterm/sshd/script/screen/tmux/expect/socat... is at the other end of /dev/pts/4. Usually, that will be a common ancestor of those processes that have /dev/pts/4 open, but not necessarily. Commented Jun 12, 2014 at 7:32
  • 2
    It's even worse with sockets — you need a kernel debugger! Commented Jun 12, 2014 at 17:04
  • 1
    @Falsenames - I understood the question to mean - perhaps incorrectly - not what process is passed the data read - such as the first shell invoked in terminal - but what process actually reads it from the master side. For instance, if I launch a shell in screen, it is screen that allocates and actively manages the pty slave for the life of the device, but - as, I think - the shell is made the process-leader for that tty and so, as your output shows, you get bash or whatever from ps not screen. I traced a few xterms back to the xterm pid based on /proc/locks but it was loose. Commented Jun 29, 2014 at 4:18
  • 1
    related: unix.stackexchange.com/questions/492302 Commented Oct 12, 2019 at 9:47

4 Answers 4

5

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.

2

In 2017 Linux got a new feature which can simplify this process a bit (commit d01c3289e7d, available in Linux 4.14 and newer)

After getting the list of processes with /dev/ptmx open:

$ fuser dev/ptmx /dev/ptmx: 1330334 1507443 

The pts number can be received like this:

for pid in $(fuser /dev/ptmx 2>/dev/null); do grep -r tty-index /proc/$pid/fdinfo; done /proc/1330334/fdinfo/13:tty-index: 0 /proc/1330334/fdinfo/14:tty-index: 1 /proc/1330334/fdinfo/27:tty-index: 2 /proc/1330334/fdinfo/28:tty-index: 4 /proc/1507443/fdinfo/3:tty-index: 3 

The result is a mapping from a <pid>:<ptmx fd> to the corresponding /dev/pts/<index>

Since version 4.90, lsof can use that API to report on the other ends of /dev/ptmx and /dev/pts/x open files with -E/+E:

$ lsof -E -ad 0 -p $$ COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME zsh 14335 user 0u CHR 136,8 0t0 11 /dev/pts/8 14333,xterm,5u 
$ lsof +E -ad 0 -p $$ COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME xterm 14333 user 5u CHR 5,2 0t0 87 /dev/ptmx ->/dev/pts/8 14335,zsh,0u 14335,zsh,1u 14335,zsh,2u 14335,zsh,10u 14391,lsof,0u 14391,lsof,1u 14391,lsof,2u zsh 14335 user 0u CHR 136,8 0t0 11 /dev/pts/8 14333,xterm,5u 
2
  • 1
    Thanks! I had noticed that lsof now reported that information, but forgot I had asked that question, and didn't dig further to find out where lsof got it from. Thanks for digging up the commit. Commented Oct 19, 2023 at 7:32
  • And I didn't know lsof has this option, so thank you! Commented Oct 20, 2023 at 12:46
2

If you are just looking for who owns the connection and where they are connected from, the who command will work well.

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

If you also want to know what is listening on that connection, w will show that at the end.

$ 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 

And to get the pids, limit a ps to the tty session you are looking at. Completely unobtrusive to boot.

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

Note, this can lead to red herrings, depending on timing. But it's a good place to start.

$ 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 
2
  • Thanks, but that's not what I'm looking for. Above for instance, I'd like to find the pid of the terminal application (Xterm/gnome-terminal...) that corresponds to /dev/pts/4, where you ran that w command. Commented Jun 17, 2014 at 6:48
  • Sorry, completely missed the pid part when I scanned through the first time. I thought you just wanted to know the end process name. Commented Jun 17, 2014 at 19:27
2

I had the same problem with qemu, and I finally found a very bad solution (but still a solution): parsing the process memory.

This is working here because I know that qemu is storing the remote pts in a string with a specific format and allocated on the heap. May be it can work in other situations too with a few changes and by reusing the pid from the fuser output (check other answer).

The code is adapted from here.

#! /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() 

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.