Having read various resources including http://www.linusakesson.net/programming/tty/ I am still confused and curious about the structure and use of pseudo-terminals In a linux terminal (bash not tty) we have three streams:
- stdin
- stdout
- stderr
There is a file descriptor for each one. These file descriptors map to streams FILE*. For example, we can use fileno() and fdopen() to switch between them.
What can I do with a pty?
I normally think of stdout, stdin and stderr being associated with a process. I don't think it is correct to say that there is a stdout, stdin and stderr associated with a pty. So given a pty (for a process like bash rather than its pid) how could you obtain them and wire them up to a terminal in the bash sense?
How does a single file descriptor (for the pty) map to three streams? (clearly it doesn't) Can it map to a process somehow?
When using openpty(), I think to connect the processes on the master and slave terminals you need to set up separate pipes for stdin, stdout and stderr and have a select loop connect them and transfer data between them.
But could you instead assume that stdin and stdout are connected (via the tty) and just set up a stderr pipe to avoid stdout and stderr being merged?
If we have a master and slave process what does the 'extra' tty actually do for us?
Here's some things I think I know and are relevant:
The openpty() function returns a pair of file descriptors representing the master and slave ends of a pseudo terminal.
Each file descriptor points to character device - see https://man7.org/linux/man-pages/man7/pty.7.html
Now devices in Unix are just special files so a file descriptor makes sense.
A bash terminal has three streams but a tty is just a single channel (no distinction between stdout and stderr). A virtual terminal (tty) also has some 'magic' (for example, a line discipline).
If I was to ask what file does the FD for a pty map to it would be the device file /dev/ptyX or whatever.
There is a 'terminal' at either end of the master/slave channel created by a openpty().
What data does it actually transfer?
I normally think of a file descriptor as an index into a table of kernel structures representing files. Clearly some of those files are streams (an kernel version of FILE*) and some of them are special.
The main userspace structure associated with a terminal (tty) is termios obtained from an FD using tcgetattr. This lets you set parameters for the terminal but it does not identify any streams or processes or the line discipline.
With a FILE* or an fd you think is a file there are a very clear and well trodden set of APIs defining what you can do.
I vaguely know what I can do with a character device (e.g. https://unix.stackexchange.com/questions/37829/how-do-character-device-or-character-special-files-work).
But what can you do with a pty?
Given a stream we can determine if it has an associated tty using isatty() and identify the tty using ttyname(). So internally somewhere there is something that knows that a tty and stream are associated. Also isatty(STDERR_FILENO) is true, so its not just stdin and stdout.
What originally piqued my interest is a program which should be a daemon but creates an ncurses UI. I have been thinking about how you could make the process open its ncurses on demand UI (in a pty). The screen program lets you do this but what does it do under the hood.
A question describing this use case is - attach a terminal to a process running as a daemon (to run an ncurses UI)
Another way to look it this question is from object oriented perspective.
What functions are there that take a 'pty' as an argument. What are they used for? If I had a pty object. What methods would it have?
Some crucial pieces of information are missing from the man pages if you just list these functions. What are the pre-conditions, post-conditions & invariants.