It seems that the pty driver on Linux is replacing VEOF characters (^D
, \4
) with NUL bytes (\0
) in the data already written from the master side if the terminal settings are changed with tcsetattr(TCSANOW)
to non-canonical mode before reading it on the slave side.
Why is this happening? Does it have any justification or it's simply a bug?
Is there any way to avoid it? -- other than waiting for input from the slave on the master side before writing anything, which is not practical, because on the slave side there may be another program -- the routine of setting the terminal into raw mode which I've simplified here is usually what any shell with line-editing capabilities does.
While having eg \r
replaced by \n
could be expected (because the ICRNL
flag was already applied), I cannot make any rationale for those NUL bytes appearing out of nowhere.
Test case below: it will print foo\x00\x00\x00\x00
on Linux, but foo\x04\x04\x04\x04
on *BSD and foo
on Solaris.
#define _XOPEN_SOURCE 600
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdio.h>
#include <termios.h>
#include <err.h>
#ifdef __sun
#include <stropts.h>
#define push_streams(fd)\
if(ioctl(fd, I_PUSH, "ptem")) err(1, "ioctl(I_PUSH, ptem)");\
if(ioctl(fd, I_PUSH, "ldterm")) err(1, "ioctl(I_PUSH, ldterm)");
#else
#define push_streams(sd) /* no need */
#endif
int main(void){
int mt, st; char *sname;
/* openpty()-like boilerplate */
if((mt = posix_openpt(O_RDWR|O_NOCTTY)) == -1) err(1, "posix_openpt");
if(grantpt(mt)) err(1, "grantpt");
if(unlockpt(mt)) err(1, "unlockpt");
if(!(sname = ptsname(mt))) err(1, "ptsname");
if((st = open(sname, O_RDWR|O_NOCTTY)) == -1) err(1, "open %s", sname);
push_streams(st);
/* master */ {
char test[] = "foo\4\4\4\4";
if(write(mt, test, sizeof test - 1) < sizeof test - 1)
err(1, "write");
}
/* slave */ {
unsigned char buf[512]; int i, r;
struct termios ts;
usleep(1000);
if(tcgetattr(st, &ts)) err(1, "tcgetattr");
ts.c_lflag &= ~ICANON;
if(tcsetattr(st, TCSANOW, &ts)) err(1, "tcsetattr");
if((r = read(st, buf, sizeof buf)) < 0)
err(1, "read");
for(i = 0; i < r; i++)
if(isprint(buf[i])) putchar(buf[i]);
else printf("\\x%02x", buf[i]);
putchar('\n');
}
return 0;
}
This conversion is done by the line discipline driver when the data is written by the master, not when the slave reads the data. The relevant code is:
https://elixir.bootlin.com/linux/latest/source/drivers/tty/n_tty.c#L1344
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With