5

It seems that a simple shell redirect from /dev/ptmx gets me a new pseudo-terminal.

$ ls /dev/pts; ls /dev/pts </dev/ptmx
0  1  2  ptmx
0  1  2  3  ptmx

It disappears as soon as the process that owns the fd claim on /dev/ptmx quits, but it is simple enough to retain it:

$ PS1='<$(ls -1 "$@" /dev/pts/*|uniq -u)> $ ' \
  exec 3<>/dev/ptmx "$0" -si -- /dev/pts/*
</dev/pts/3> $ ls /dev/pts; exec 3>&-
0  1  2  3  ptmx
<> $ ls /dev/pts; exec 3<>/dev/ptmx
0  1  2  ptmx
</dev/pts/3> $ exit

So it looks like the simple open() on /dev/ptmx is enough to get a pseudo-terminal. I guess that would make the shell - (or the process for which it does the redirect) - the owner of the master side of the pty.

But how do I assign the slave side - or can I assign the slave side? And if I can - what can I do with it? Can I affect its read/write/flush timings w/ stty? Will a slave side process link through to my new pty when referencing /dev/tty?

4

So, as @muru points out in the comments, there doesn't seem to be a simple way to interface the pty created for you with just the shell. I managed all of it but the unlockpt() part. According to something I read here it may be there are some compile-time options in the kernel for disabling newly created pty locking, but I didn't want to do that. So, I did something else.

I didn't need grantpt() actually. According to the description found here all it does is change the UID/GID for /dev/pts/[num] device file. But according to man mount there are easier ways to handle that. Here are some devpts mount options:

  • uid=value and gid=value
    • This sets the owner or the group of newly created PTYs to the specified values. When nothing is specified, they will be set to the UID and GID of the creating process. For example, if there is a tty group with GID 5, then gid=5 will cause newly created PTYs to belong to the tty group.

That was already the case on my system by default. But after reading that I realized I might want to make a change after all. The very next section reads:

  • ptmxmode=value
    • Set the mode for the new ptmx device node in the devpts filesystem.
    • With the support for multiple instances of devpts (see newinstance option above), each instance has a private ptmx node in the root of the devpts filesystem (typically /dev/pts/ptmx).
    • For compatibility with older versions of the kernel, the default mode of the new ptmx node is 0000. ptmxmode=value specifies a more useful mode for the ptmx node and is highly recommended when the newinstance option is specified.

While it would have worked without doing so, I liked the idea and set that to 0640 as recommended in the kernel documentation. The kernel doc link, by the way, elaborates on the newinstance mount option - which is pretty cool and basically enables you get a separately name-spaced group of ptys per /dev/ptmx mount.

Anyway, so the something else amounted mostly to:

mount -o remount,newinstance,gid=5,ptmxmode=0640 /dev/pts
mount --bind /dev/pts/ptmx /dev/ptmx

...as the kernel docs recommend - see the link about why. I also made the effect of the above commands permanent by adding a couple of lines to my /etc/fstab.

And...

<<\C cc -xc - -o pts
#include <stdio.h>
int main(int argc, char *argv[]) {
        if(unlockpt(0)) return 2;
        char *ptsname(int fd);
        printf("%s\n",ptsname(0));
        return argc - 1;
}
C

Which just compiles a tiny little C program that attempts to call unlockpt() on its stdin and if successful prints the name of the newly created and unlocked pty to stdout or else silently returns 2.

Once that was done I could create my own screened processes like:

exec 3<>/dev/ptmx

...to get the master-side fd in the current shell then...

(setsid -c "$0" -i 2>&1|tee log) <>"$(./pts <&3)" 3>&- >&0 &

That gets an interactive shell running on the other end of the pseudo-terminal in the background. It will interpret anything printed to >&3 as user input.

mikeserv@localhost$ echo echo hey >&3
mikeserv@localhost$ cat log
$ hey
$
mikeserv@localhost$ echo echo hey >&3
mikeserv@localhost$ cat log
$ hey
$ hey
$ 

Which basically nets me a backgrounded, logged, interactive interpreter (or anything else I might care to run on these) ala screen without so much overhead and on any file descriptor I choose.

The master-side fd owned by my current shell is the only means of serving the slave-side input and is only writable by my current shell process (and its children). I can communicate with the other end by writing to >&3 and I can either read from same or from a log file as I wish.

And stty does work on the terminal after all:

mikeserv@localhost$ echo stty -a ">$(tty)" >&3
speed 38400 baud; rows 0; columns 0; line = 0;                                      
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>;
swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W;
lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc
-ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl
echoke
  • Does remounting devpts as a new instance invalidate existing pts devices? – muru Jan 15 '15 at 4:23
  • @muru - not for me. I did it all in the same terminal window - still got one of them open now. But the fstab stuff is the way to go. When you want a private set of ptys you do it - perhaps w/ unshare - without remount though - you mount a new devpts instance anywhere else. So your private set will start at 0 again... or wait...? Is that right? Now i forget if its the whole devpts or just ptmx. Still trying to wrap my head around it. – mikeserv Jan 15 '15 at 4:43
  • 1
    Thanks. This is one thing that I think I'll be using for setting up chroots from now on. – muru Jan 15 '15 at 4:53
  • @muru - im still playing around with it too. Right now ive got fc writing the last executed command-line to my current shell's ">$(tty)" in the background shell's $PS1 eval. Still, though, this was just about curiosity at first and mostly still is. You can already do most of this w/ bash/ksh coprocesses (via pipes) and far better w/ zsh tricks like zpty. But all of that is always so huge - and if im going to bg a shell its probably not going to be zsh and especially not bash - they're both big and slow. – mikeserv Jan 15 '15 at 5:08

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.