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.
| Option | Default | Description |
|---|---|---|
raw | on | Pass bytes through untouched |
echo | off | Echo input back at the writer |
size=ROWSxCOLS | unset | The window size reported to the program |
Line settings
These are the ones a serial cable needs and a pts ignores.
| Option | Default | Description |
|---|---|---|
bNNN | unset | Line speed, as every other serial tool spells it: b115200 |
speed=NNN | unset | The same thing, as every other tocat option spells it. baud= too |
bits=N | 8 | Character size, 5 through 8 |
parity=WHAT | none | none, even or odd. n, e and o also work |
stop2 | off | Two stop bits rather than one |
flow=WHAT | none | none, rts for the hardware lines, xon for XON/XOFF in the data |
clocal | on | Ignore modem control lines |
exclusive | off | TIOCEXCL: 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.