Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

tty

Opens a terminal that already exists: a /dev/pts/N somebody else made, or a serial device. pty allocates one; this one borrows.

$ tocat tty:/dev/ttyUSB0,b115200 tcp-listen:9000,fork

That is a serial console reachable over the network, with the line settings on the endpoint rather than arranged with stty beforehand and hoped to survive.

Why not file:

file: opens a device happily, and gets three things wrong for a terminal.

It is half-duplex by role, so file:/dev/pts/5 writes to the terminal and nothing comes back. A terminal is duplex on one descriptor, and file: has no spelling that opens both ways.

It opens without O_NOCTTY, so a relay that is a session leader with no controlling terminal acquires one, and signals generated by that terminal start arriving at tocat.

It applies no line settings, so the device keeps whatever it had. A serial port at the wrong speed reads as noise rather than as an error, and a terminal left in cooked mode translates carriage returns, turns ^C into a signal instead of a byte, and refuses a line over 4096 bytes.

What gets put back

A borrowed device outlives the relay. Leaving /dev/ttyUSB0 in raw mode changes what the next program to open it sees, and doing it to the terminal you launched from leaves your shell unusable, so the settings found at open are restored when the transfer ends. A pty needs none of that, because it is discarded rather than returned.

Restoration is best effort by necessity, since it runs during teardown. A failure is logged rather than raised, because there is nothing useful left to raise it to.

Terminal settings

The same three pty takes, with the same defaults and the same reasons.

OptionDefaultDescription
rawonPass bytes through untouched
echooffEcho input back at the writer
size=ROWSxCOLSunsetThe window size reported to the program

Line settings

These are the ones a serial cable needs and a pts ignores.

OptionDefaultDescription
bNNNunsetLine speed, as every other serial tool spells it: b115200
speed=NNNunsetThe same thing, as every other tocat option spells it. baud= too
bits=N8Character size, 5 through 8
parity=WHATnonenone, even or odd. n, e and o also work
stop2offTwo stop bits rather than one
flow=WHATnonenone, rts for the hardware lines, xon for XON/XOFF in the data
clocalonIgnore modem control lines
exclusiveoffTIOCEXCL: a second unprivileged open fails with EBUSY

clocal defaults on, and turning it off looks like a hang. Without it the open waits for carrier detect, which a three-wire cable never asserts. Turn it off when you actually have modem control and want to know when the far end goes away.

exclusive is worth setting on anything long-running. Without it a second program can open the same device and the two of you interleave reads, each getting an arbitrary share of the bytes, which looks like corruption rather than contention.

When the transfer ends

It does not, on its own. Both directions run, and a terminal has no half-close the way a socket does, so there is nothing for one side to shut down when it runs out. A tty: relay ends on a signal, or when the endpoint at the other end closes both ways.

That is the trade for duplex, and it is why file: stays half-duplex by role: tocat file:in.bin tcp:host:9000 exits at end of file precisely because the reverse direction does not exist to wait on.

serial as an alias

serial:/dev/ttyUSB0,b9600 is the same scheme under a name that says which of the two cases you are in. The behaviour is identical; the aliases exist because tty: reads oddly for a USB adapter and serial: reads oddly for a /dev/pts.