about tty - xieyunzi/xieyunzi.github.io GitHub Wiki
https://etbe.coker.com.au/2008/02/27/redirecting-output-from-a-running-process/
February 27, 2008 etbe 10 Comments
Someone asked on a mailing list how to redirect output from a running process. They had a program which had been running for a long period of time without having stdout redirected to a file. They wanted to logout (to move the laptop that was used for the ssh session) but not kill the process (or lose output).
Most responses were of the form “you should have used screen or nohup” which is all very well if you had planned to logout and leave it running (or even planned to have it run for a long time).
Fortunately it is quite possible to redirect output of a running process. I will use cat as a trivial example but the same technique will work for most programs that do simple IO (of course programs that do terminal IO may be more tricky – but you could always redirect from the tty device of a ssh session to the tty device of a screen session).
Firstly I run the command “cat > foo1” in one session and test that data from stdin is copied to the file. Then in another session I redirect the output:
Firstly find the PID of the process:
$ ps aux|grep cat
rjc 6760 0.0 0.0 1580 376 pts/5 S+ 15:31 0:00 cat
Now check the file handles it has open:
$ ls -l /proc/6760/fd
total 3
lrwx—— 1 rjc rjc 64 Feb 27 15:32 0 -> /dev/pts/5
l-wx—— 1 rjc rjc 64 Feb 27 15:32 1 -> /tmp/foo1
lrwx—— 1 rjc rjc 64 Feb 27 15:32 2 -> /dev/pts/5
Now run GDB:
$ gdb -p 6760 /bin/cat
GNU gdb 6.4.90-debian
Copyright (C) 2006 Free Software Foundation, Inc
[lots more license stuff snipped]
Attaching to program: /bin/cat, process 6760
[snip other stuff that’s not interesting now]
(gdb) p close(1)
$1 = 0
(gdb) p creat(“/tmp/foo3”, 0600)
$2 = 1
(gdb) q
The program is running. Quit anyway (and detach it)? (y or n) y
Detaching from program: /bin/cat, process 6760
The “p” command in GDB will print the value of an expression, an expression can be a function to call, it can be a system call… So I execute a close() system call and pass file handle 1, then I execute a creat() system call to open a new file. The result of the creat() was 1 which means that it replaced the previous file handle. If I wanted to use the same file for stdout and stderr or if I wanted to replace a file handle with some other number then I would need to call the dup2() system call to achieve that result.
For this example I chose to use creat() instead of open() because there are fewer parameter. The C macros for the flags are not usable from GDB (it doesn’t use C headers) so I would have to read header files to discover this – it’s not that hard to do so but would take more time. Note that 0600 is the octal permission for the owner having read/write access and the group and others having no access. It would also work to use 0 for that parameter and run chmod on the file later on.
After that I verify the result:
ls -l /proc/6760/fd/
total 3
lrwx—— 1 rjc rjc 64 2008-02-27 15:32 0 -> /dev/pts/5
l-wx—— 1 rjc rjc 64 2008-02-27 15:32 1 -> /tmp/foo3 <====
lrwx—— 1 rjc rjc 64 2008-02-27 15:32 2 -> /dev/pts/5
Typing more data in to cat results in the file /tmp/foo3 being appended to.
Update: If you want to close the original session you need to close all file handles for it, open a new device that can be the controlling tty, and then call setsid().
Nothing is stored in /dev/pts. This filesystem lives purely in memory.
Entries in /dev/pts are pseudo-terminals (pty for short). Unix kernels have a generic notion of terminals. A terminal provides a way for applications to display output and to receive input through a terminal device. A process may have a controlling terminal — for a text mode application, this is how it interacts with the user.
Terminals can be either hardware terminals (“tty”, short for “teletype”) or pseudo-terminals (“pty”). Hardware terminals are connected over some interface such as a serial port (ttyS0, …) or USB (ttyUSB0, …) or over a PC screen and keyboard (tty1, …). Pseudo-terminals are provided by a terminal emulator, which is an application. Some types of pseudo-terminals are:
- GUI applications such as xterm, gnome-terminal, konsole, … transform keyboard and mouse events into text input and display output graphically in some font.
- Multiplexer applications such as screen and tmux relay input and output from and to another terminal, to decouple text mode applications from the actual terminal.
- Remote shell applications such as sshd, telnetd, rlogind, … relay input and output between a remote terminal on the client and a pty on the server.
If a program opens a terminal for writing, the output from that program appears on the terminal. It is common to have several programs outputting to a terminal at the same time, though this can be confusing at times as there is no way to tell which part of the output came from which program. Background processes that try to write to their controlling terminal may be automatically suspended by a SIGTTOU signal.
If a program opens a terminal for reading, the input from the user is passed to that program. If multiple programs are reading from the same terminal, each character is routed independently to one of the programs; this is not recommended. Normally there is only a single program actively reading from the terminal at a given time; programs that try to read from their controlling terminal while they are not in the foreground are automatically suspended by a SIGTTIN signal.
To experiment, run tty in a terminal to see what the terminal device is. Let's say it's /dev/pts/42. In a shell in another terminal, run echo hello >/dev/pts/42: the string hello will be displayed on the other terminal. Now run cat /dev/pts/42 and type in the other terminal. To kill that cat command (which will make the other terminal hard to use), press Ctrl+C.
Writing to another terminal is occasionally useful to display a notification; for example the write command does that. Reading from another terminal is not normally done.
The master side replaces the line (the pair of TX/RX wires) that goes to the terminal.
The terminal displays the characters that it receives on one of the wires (some of those are control characters and make it do things like move the cursor, change colour...) and sends on another wire the characters corresponding to the keys you type.
Terminal emulators like xterm are not different except that instead of sending and receiving characters on wires, they read and write characters on their file descriptor to the master side. Once they've spawned the slave terminal, and started your shell on that, they no longer touch that. In addition to emulating the pair of wire, xterm can also change some of the line discipline properties via that file descriptor to the master side. For instance, they can update the size attributes so a SIGWINCH be sent to the applications that interact with the slave pty to notify them of a changed size.
Other than that, there is little intelligence in the terminal/terminal emulator.
What you write to a terminal device (like the pty slave) is what you mean to be displayed there, what you read from it is what you have typed there, so it does not make sense for the terminal emulator to read or write to that. They are the ones at the other end.
A lot of the intelligence is in the tty line discipline. The line discipline is a software module (residing in the driver, in the kernel) pushed on top of a serial/pty device that sits between that device and the line/wire (the master side for a pty).
A serial line can have a terminal at the other end, but also a mouse or another computer for networking. You can attach a SLIP line discipline for instance to get a network interface on top of a serial device (or pty device), or you can have a tty line discipline. The tty line discipline is the default line discipline at least on Linux for serial and pty devices. On Linux, you can change the line discipline with ldattach.
You can see the effect of disabling the tty line discipline by issuing stty raw -echo (note that the bash prompt or other interactive applications like vi set the terminal in the exact mode they need, so you want to use a dumb application like cat to experience with that).
Then, everything that is written to the slave terminal device makes it immediately to the master side for xterm to read, and every character written by xterm to the master side is immediately available for reading from the slave device.
The line discipline is where the terminal device internal line editor is implemented. For instance with stty icanon echo (as is the default), when you type a, xterm writes a to the master, then the line discipline echoes it back (makes a a available for reading by xterm for display), but does not make anything available for reading on the slave side. Then if you type backspace, xterm sends a ^? or ^H character, the line discipline (as that ^? or ^H corresponds to the erase line discipline setting) sends back on the master a ^H, space and ^H for xterm to erase the a you've just typed on its screen and still doesn't send anything to the application reading from the slave side, it just updates its internal line editor buffer to remove that a you've typed before.
Then when you press Enter, xterm sends ^M (CR), which the line discipline converts on input to a ^J (LF), and sends what you've entered so far for reading on the slave side (an application reading on /dev/pts/x will receive what you've typed including the LF, but not the a since you've deleted it), while on the master side, it sends a CR and LF to move the cursor to the next line and the start of the screen.
The line discipline is also responsible for sending the SIGINT signal to the foreground process group of the terminal when it receives a ^C character on the master side etc.
Many interactive terminal applications disable most of the features of that line discipline to implement it themselves. But in any case, beware that the terminal (xterm) has little involvement in that (except displaying what it's told to display).
And there can be only one session per process and per terminal device. A session can have a controlling terminal attached to it but does not have to (all sessions start without a terminal until they open one). xterm, in the process that it forks to execute your shell will typically create a new session (and therefore detach from the terminal where you launched xterm from if any), open the new /dev/pts/x it has spawned, by that attaching that terminal device to the new session. It will then execute your shell in that process, so your shell will become the session leader. Your shell or any interactive shell in that session will typically juggle with process groups and tcsetpgrp(), to set the foreground and background jobs for that terminal.
As to what informations are stored by a terminal device with a tty discipline (serial or pty), that's typically what the stty command displays and modifies. All the discipline configuration: terminal screen size, local, input output flags, settings for special characters (like ^C, ^Z...), input and output speed (not relevant for ptys). That corresponds to the tcgetattr()/tcsetattr() functions which on Linux map to the TCGETS/TCSETS ioctls, and TIOCGWINSZ/TIOCSWINSZ for the screen size. You may argue that the current foreground process group is another information stored in the terminal device (tcsetpgrp()/tcgetpgrp(), TIOC{G,S}PGRP ioctls), or the current input or output buffer.
Note that that screen size information stored in the terminal device may not reflect the reality. The terminal emulator will typically set it (via the same ioctl on the master size) when its window is resized, but it can get out of sync if an application calls the ioctl on the slave side or when the resize is not transmitted (in case of an ssh connection which implies another pty spawned by sshd if ssh ignores the SIGWINCH for instance). Some terminals can also be queried their size via escape sequences, so an application can query it that way, and update the line discipline with that information.
For more details, you can have a look at the termios and tty_ioctl man pages on Debian for instance.
To play with other line disciplines:
-
Emulate a mouse with a pseudo-terminal:
socat pty,link=mouse fifo:fifo sudo inputattach -msc mouse # sets the MOUSE line discipline and specifies protocol xinput list # see the new mouse there exec 3<> fifo printf '\207\12\0' >&3 # moves the cursor 10 pixels to the right
Above, the master side of the pty is terminated by socat onto a named pipe (fifo). We connect that fifo to a process (the shell) that writes 0x87 0x0a 0x00 which in the mouse systems protocol means no button pressed, delta(x,y) = (10,0). Here, we (the shell) are not emulating a terminal, but a mouse, the 3 bytes we send are not to be read (potentially transformed) by an application from the terminal device (mouse above which is a symlink made by socat to some /dev/pts/x device), but are to be interpreted as a mouse input event.
-
Create a SLIP interface:
# on hostA socat tcp-listen:12345,reuseaddr pty,link=interface # after connection from hostB: sudo ldattach SLIP interface ifconfig -a # see the new interface there sudo ifconfig sl0 192.168.123.1/24 # on hostB socat -v -x pty,link=interface tcp:hostA:12345 sudo ldattach SLIP interface sudo ifconfig sl0 192.168.123.2/24 ping 192.168.123.1 # see the packets on socat output
Above, the serial wire is emulated by socat as a TCP socket in-between hostA and hostB. The SLIP line discipline interprets those bytes exchanged over that virtual line as SLIP encapsulated IP packets for delivery on the sl0 interface.
- https://www.isi.edu/~yuri/dupx/
- https://github.com/xbalabala/ttylogger
- https://github.com/jerome-pouiller/reredirect/
- https://github.com/xbalabala/ttyrpld
- linux:
man 4 ptmx,man 4 pts- ptmx, pts - pseudoterminal master and slave - linux:
man stty- change and print terminal line settings - http://poincare.matf.bg.ac.rs/~ivana/courses/ps/sistemi_knjige/pomocno/apue/APUE/0201433079/ch18.html
- https://unix.stackexchange.com/questions/213799/can-bash-write-to-its-own-input-stream
- http://www.humbug.in/2010/utility-to-send-commands-or-data-to-other-terminals-ttypts/