1

I've tried to research the differences in these device files on my system, but Google's search results aren't working for me.

xnl2d
  • 13

1 Answers1

1

/dev/ptmx behaves like a symlink ("when opened will look for [...] "pts" in the same directory.") to /dev/pts/ptmx, except the former is world-read-writable and the latter is accessible by none. Unlike a symlink the destination permissions don't matter. It can be deleted and recreated with mknod. You can't delete /dev/pts/ptmx:

/dev# ll ptmx # Show original
crw-rw-rw- 1 root root 5, 2 Jan 31 03:50 ptmx
/dev# rm ptmx # Delete it
/dev# mknod ptmx c 5 2 # Recreate it
/dev# chmod 666 ptmx
/dev# ll ptmx
crw-rw-rw- 1 root root 5, 2 Jan 31 03:52 ptmx
/dev# ll pts/ptmx
c--------- 1 root root 5, 2 Jan 27 16:23 pts/ptmx
/dev# rm pts/ptmx # Can't delete that one
rm: cannot remove 'pts/ptmx': Operation not permitted

The device type is the same, so it should have the same behavior, but the devpts mounted filesystem at /dev/pts is special and doesn't allow its ptmx to be deleted.

$ stat /dev/ptmx
[...]
Device: 0,5     Inode: 90          Links: 1     Device type: 5,2
[...]
$ stat /dev/pts/ptmx
[...]
Device: 0,24    Inode: 2           Links: 1     Device type: 5,2
[...]

The usually important part is the Device type: 5,2 meaning "PTY master multiplex". This identifies where in the kernel the IO gets sent to, but apparently there is special code to make those two cases different.

Keep in mind the mount itself. According to https://www.kernel.org/doc/Documentation/filesystems/devpts.txt ,

Each mount of the devpts filesystem is now distinct such that ptys and their indicies allocated in one mount are independent from ptys and their indicies in all other mounts.

The reason both are available is both forwards and backwards compatibility. They are two different types of mount points, /dev is devtmpfs, while /dev/pts is devpts. Other distributions symlink /dev/ptmx to /dev/pts/ptmx and make /dev/pts/ptmx world-read-write-able. For backwards compatibility, /dev/ptmx can't be removed because today's apps (your terminal, etc.) use it. For forwards compatibility, we want /dev/pts/ptmx to allow for namespaces and containerization. I can mount another devpts filesystem somewhere else and the IDs will be separate from the ones in /dev/pts. This concept of namespaces and having separate IDs forms the basis of containerization technologies such as Docker. You can start a Docker container and look at mount and ls -l /dev && ls -l /dev/pts to see the magic.

Here is some general exploration:

/tmp# mkdir test
/tmp# mount --make-private -t devtmpfs devtmpfs test # private to allow mount --move
/tmp# cd test
/tmp/test# ls ptmx
ptmx
/tmp/test# ls pts
/tmp/test# exec 3<>ptmx # ptmx acts like a broken symlink to pts/ptmx
-bash: ptmx: No such device
/tmp/test# mount -t devpts devpts pts # Now it works
/tmp/test# ls pts
ptmx
/tmp/test# exec 3<>ptmx # Open /dev/ptmx
/tmp/test# ls pts
0  ptmx
/tmp/test# exec 4<>pts/ptmx # Open /dev/pts/ptmx, same effect
/tmp/test# ls pts
0  1  ptmx
/tmp/test# exec 3>&- # Because they act on the same /dev/pts folder
/tmp/test# ls pts
1  ptmx
/tmp/test# exec 4>&-
/tmp/test# ls pts
ptmx
/tmp/test# exec 3<>ptmx # Set up a few examples
/tmp/test# exec 4<>ptmx
/tmp/test# exec 5<>ptmx
/tmp/test# ls pts
0  1  2  ptmx
/tmp/test# mkdir pts.old # If we move the mount, the /dev/ptmx will point to the new /dev/pts
/tmp/test# mount --move pts pts.old
/tmp/test# mount -t devpts devpts pts
/tmp/test# ls pts
ptmx
/tmp/test# ls pts.old
0  1  2  ptmx
/tmp/test# exec 6<>ptmx # Create in new mount
/tmp/test# ls pts/
0  ptmx
/tmp/test# ls pts.old
0  1  2  ptmx
/tmp/test# mount --move pts pts.new # Same thing if we swap the mounts
/tmp/test# mount --move pts.old pts
/tmp/test# mount --move pts.new pts.old
/tmp/test# ls pts
0  1  2  ptmx
/tmp/test# ls pts.old
0  ptmx
/tmp/test# exec 7<>ptmx
/tmp/test# ls pts
0  1  2  3  ptmx
/tmp/test# ls pts.old
0  ptmx
/tmp/test# exec 8<>pts/ptmx # If we access the pts/ptmx, it will use the corresponding namespace
/tmp/test# ls pts
0  1  2  3  4  ptmx
/tmp/test# ls pts.old
0  ptmx
/tmp/test# exec 9<>pts.old/ptmx
/tmp/test# ls pts
0  1  2  3  4  ptmx
/tmp/test# ls pts.old
0  1  ptmx
/tmp/test# cd ..
/tmp# umount -l test
/tmp# mount -t tmpfs tmpfs test
/tmp# cd test
/tmp/test# mknod ptmx c 5 2
/tmp/test# mkdir pts.new
/tmp/test# ln -s pts.new pts # It does not recursively follow symlinks
/tmp/test# mount -t devpts devpts pts.new
/tmp/test# exec 3<>ptmx
-bash: ptmx: No such device
home@daniel-tablet1:~$ unshare -mr # Create a unprivileged namespace where I am a "fake" root
root@daniel-tablet1:~# # Can't do this because every devtmpfs is the same. Docker uses a tmpfs for /dev.
root@daniel-tablet1:~# # If you create a file in /tmp/test, it will show up in /dev
root@daniel-tablet1:~# # But mounts under /tmp/test will NOT show up in /dev even if I didn't add --make-private
root@daniel-tablet1:~# mount -t devtmpfs devtmpfs Downloads # Can't do this because every devtmpfs is the same
mount: /home/home/Downloads: permission denied.
       dmesg(1) may have more information after failed mount system call.
root@daniel-tablet1:~# mount -t devpts devpts Downloads # All users can create a new devpts namespace
root@daniel-tablet1:~# ls Downloads
ptmx
~# cd /dev
/dev# mount -t devpts devpts pts # I can overmount /dev/pts using an unprivileged mount namespace
/dev# ls pts
ptmx
/dev# exec 3<>ptmx
/dev# ls pts
0  ptmx
Daniel T
  • 5,339