/* * vlibc — terminal attributes test (todo 29). * * Exercises include/termios.h + src/termios/termios.c end to end over a * pty master opened with the raw SYS_openat syscall (deliberately not the * open() wrapper — the termios functions are what is under test). When * /dev/ptmx cannot be opened the default run prints SKIP and exits 0 * (guarded scenario, house pattern). * * Default-mode scenarios (in run order, all on the pty master): * * 1. tcgetattr returns 0 with sane defaults: c_cflag carries CREAD and a * nonzero CSIZE, c_lflag carries ICANON|ECHO. * 2. tcsetattr(TCSANOW) with ICANON cleared and c_cc[VMIN]/c_cc[VTIME] * set, then tcgetattr reflects the cleared ICANON and the VMIN/VTIME * values (proves the c_cc[NCCS] layout is not corrupted across the * kernel copy, and that indices 5/6 are the right slots). * 3. The blocking/flush/flow/break pass-throughs succeed: tcdrain, * tcflush for all three selectors, tcflow TCOOFF/TCOON, tcsendbreak, * and tcsetattr with the TCSADRAIN/TCSAFLUSH actions. * 4. (L1 speed helpers, in-memory) cfsetospeed(B9600)/cfgetospeed and * cfsetispeed(B9600)/cfgetispeed round-trip through the c_cflag bit * fields, and an extended rate (B115200, CBAUDEX region) round-trips * cfsetospeed/cfgetospeed as well. * * Level-2 scenarios (#if VLIBC_LEVEL_GE(2)): * * 5. cfmakeraw clears ICANON and ECHO, drops parity, selects CS8; after * tcsetattr + tcgetattr the cleared ICANON/ECHO are reflected. * 6. cfsetspeed(B9600) stores B9600 for both the input and the output * rate. * * The -f mode runs the failure scenarios and exits via raw SYS_exit_group * (house pattern): every negative makes the LIBRARY write errno * (syscall_ret -> %fs:0+8, glibc's private dtv slot under a host-linked * binary), so no host cleanup may run afterwards. The test itself NEVER * reads errno; every negative is asserted on the return value only: * tcsetattr(-1, ...) -> -1 (EBADF), tcgetattr on /dev/null -> -1 (ENOTTY, * the kernel pass-through), tcflush/tcflow with a bad selector/action on a * real pty -> -1, tcsetattr with an invalid action -> -1 (EINVAL, checked * before the kernel), an out-of-range speed to the cfset* helpers -> -1, * and tcgetsid(-1) -> -1 (level 2). * * All diagnostics go through raw SYS_write (no stdio): under -Iinclude the * vlibc public headers shadow GCC's internal ones, so a host header would * not compile. Only vlibc headers are included. */ #include #include "../include/fcntl.h" #include "../include/termios.h" #include "../src/internal/syscall.h" static int failures; /* Write a NUL-terminated string to fd via the raw syscall layer. The * optimize attribute keeps GCC from lowering the length loop into a * strlen call, which would leave a vlibc-owned symbol undefined in this * host-linked standalone binary (house idiom, see src/string). */ static __attribute__((optimize("no-tree-loop-distribute-patterns"))) void say(int fd, const char *s) { long n = 0; while (s[n] != '\0') { n++; } __syscall3(SYS_write, fd, (long)s, n); } /* Write v in decimal to fd. */ static void say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters) { char buf[24]; int i = (int)sizeof(buf); buf[--i] = '\0'; do { buf[--i] = (char)('0' + (v % 10)); v /= 10; } while (v != 0); __syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i)); } static void check(int cond, const char *what) { if (cond) { say(1, "PASS: "); say(1, what); say(1, "\n"); } else { say(2, "FAIL: "); say(2, what); say(2, "\n"); failures++; } } /* Open a pty master via the raw syscall (no open() wrapper, no errno * write): returns the fd, or -1 when /dev/ptmx is unavailable. */ static int open_ptmx(void) { long r = __syscall3(SYS_openat, AT_FDCWD, (long)"/dev/ptmx", O_RDWR | O_NOCTTY); if (r < 0) { return -1; } return (int)r; } /* 1. tcgetattr on a fresh pty master: 0 and sane defaults. */ static void defaults_scenario(int fd) { struct termios t; if (tcgetattr(fd, &t) != 0) { check(0, "tcgetattr on a pty master returns 0"); check(0, "the defaults carry CREAD/CSIZE and ICANON|ECHO"); return; } check(1, "tcgetattr on a pty master returns 0"); check((t.c_cflag & CREAD) != 0 && (t.c_cflag & CSIZE) != 0, "the default c_cflag carries CREAD and a CSIZE"); check((t.c_lflag & ICANON) != 0 && (t.c_lflag & ECHO) != 0, "the default c_lflag carries ICANON and ECHO"); } /* 2. tcsetattr with ICANON cleared + VMIN/VTIME set, reflected by * tcgetattr (c_cc[NCCS] layout survives the kernel copy intact). */ static void attr_roundtrip_scenario(int fd) { struct termios t; struct termios u; if (tcgetattr(fd, &t) != 0) { check(0, "tcsetattr(ICANON off) round trip"); check(0, "tcgetattr reflects the cleared ICANON"); check(0, "c_cc[VMIN]/c_cc[VTIME] survive the round trip"); return; } t.c_lflag &= ~ICANON; t.c_cc[VMIN] = 1; t.c_cc[VTIME] = 0; if (tcsetattr(fd, TCSANOW, &t) != 0) { check(0, "tcsetattr(ICANON off) round trip"); check(0, "tcgetattr reflects the cleared ICANON"); check(0, "c_cc[VMIN]/c_cc[VTIME] survive the round trip"); return; } check(1, "tcsetattr(TCSANOW) with ICANON cleared returns 0"); if (tcgetattr(fd, &u) != 0) { check(0, "tcgetattr reflects the cleared ICANON"); check(0, "c_cc[VMIN]/c_cc[VTIME] survive the round trip"); return; } check((u.c_lflag & ICANON) == 0, "tcgetattr reflects the cleared ICANON"); check(u.c_cc[VMIN] == 1 && u.c_cc[VTIME] == 0, "c_cc[VMIN]/c_cc[VTIME] survive the round trip"); /* Restore canonical mode for any later scenario. */ u.c_lflag |= ICANON; (void)tcsetattr(fd, TCSANOW, &u); } /* 3. The blocking/flush/flow/break pass-throughs and the other two * tcsetattr actions all succeed on a pty. */ static void flow_scenario(int fd) { struct termios t; if (tcgetattr(fd, &t) != 0) { check(0, "tcdrain on a pty"); check(0, "tcflush for all three selectors on a pty"); check(0, "tcflow TCOOFF/TCOON on a pty"); check(0, "tcsendbreak on a pty"); check(0, "tcsetattr TCSADRAIN/TCSAFLUSH on a pty"); return; } check(tcdrain(fd) == 0, "tcdrain on a pty returns 0"); check(tcflush(fd, TCIFLUSH) == 0 && tcflush(fd, TCOFLUSH) == 0 && tcflush(fd, TCIOFLUSH) == 0, "tcflush for all three selectors returns 0"); check(tcflow(fd, TCOOFF) == 0 && tcflow(fd, TCOON) == 0, "tcflow TCOOFF/TCOON returns 0"); check(tcsendbreak(fd, 0) == 0, "tcsendbreak on a pty returns 0"); check(tcsetattr(fd, TCSADRAIN, &t) == 0 && tcsetattr(fd, TCSAFLUSH, &t) == 0, "tcsetattr TCSADRAIN/TCSAFLUSH returns 0"); } /* 4. (L1) In-memory speed round trips through the c_cflag bit fields. */ static void speed_scenario(int fd) { struct termios t; if (tcgetattr(fd, &t) != 0) { check(0, "cfsetospeed/cfgetospeed B9600 round trip"); check(0, "cfsetispeed/cfgetispeed B9600 round trip"); check(0, "the extended B115200 rate round trip"); return; } check(cfsetospeed(&t, B9600) == 0 && cfgetospeed(&t) == B9600, "cfsetospeed(B9600)/cfgetospeed round trip"); check(cfsetispeed(&t, B9600) == 0 && cfgetispeed(&t) == B9600, "cfsetispeed(B9600)/cfgetispeed round trip"); check(cfsetospeed(&t, B115200) == 0 && cfgetospeed(&t) == B115200, "the extended B115200 rate round trips through CBAUDEX"); } #if VLIBC_LEVEL_GE(2) /* 5+6. cfmakeraw clears the canonical/echo bits (reflected by tcgetattr * after a tcsetattr) and cfsetspeed stores both rates. */ static void raw_scenario(int fd) { struct termios t; struct termios u; if (tcgetattr(fd, &t) != 0) { check(0, "cfmakeraw clears ICANON and ECHO"); check(0, "cfmakeraw selects CS8 with no parity"); check(0, "tcgetattr reflects the cfmakeraw state"); check(0, "cfsetspeed(B9600) stores both rates"); return; } cfmakeraw(&t); check((t.c_lflag & (ICANON | ECHO)) == 0, "cfmakeraw clears ICANON and ECHO"); check((t.c_cflag & PARENB) == 0 && (t.c_cflag & CSIZE) == CS8, "cfmakeraw selects CS8 with no parity"); if (tcsetattr(fd, TCSANOW, &t) == 0 && tcgetattr(fd, &u) == 0) { check((u.c_lflag & (ICANON | ECHO)) == 0, "tcgetattr reflects the cfmakeraw-cleared ICANON/ECHO"); /* Restore canonical mode for any later scenario. */ u.c_lflag |= ICANON | ECHO; (void)tcsetattr(fd, TCSANOW, &u); } else { check(0, "tcgetattr reflects the cfmakeraw-cleared ICANON/ECHO"); } if (tcgetattr(fd, &t) != 0) { check(0, "cfsetspeed(B9600) stores both rates"); return; } check(cfsetspeed(&t, B9600) == 0 && cfgetospeed(&t) == B9600 && cfgetispeed(&t) == B9600, "cfsetspeed(B9600) stores both the input and the output rate"); } #endif /* VLIBC_LEVEL_GE(2) */ /* * Failure scenarios (-f): every assertion is on the return value only, and * the process exits through raw SYS_exit_group because the library writes * errno on these paths (host-TCB hazard). A fresh pty master supplies the * real-terminal fd for the kernel-side EINVAL checks; /dev/null supplies * the ENOTTY case. */ static int failure_scenarios(void) { struct termios t; int fd_pty; int fd_null; int rc = 0; fd_pty = open_ptmx(); if (fd_pty < 0) { say(2, "FAIL: failure scenarios need a pty master\n"); rc = 1; } fd_null = (int)__syscall3(SYS_openat, AT_FDCWD, (long)"/dev/null", O_RDONLY); #define FAIL_ONE(cond, msg) \ do \ { \ if (cond) \ { \ say(1, "PASS: "); \ say(1, msg); \ say(1, "\n"); \ } \ else \ { \ say(2, "FAIL: "); \ say(2, msg); \ say(2, "\n"); \ rc = 1; \ } \ } while (0) FAIL_ONE(tcgetattr(-1, &t) == -1, "tcgetattr(-1) -> -1"); FAIL_ONE(tcsetattr(-1, TCSANOW, &t) == -1, "tcsetattr(-1, TCSANOW) -> -1"); FAIL_ONE(tcsetattr(fd_pty >= 0 ? fd_pty : -1, 99, &t) == -1, "tcsetattr with an invalid action -> -1"); FAIL_ONE(tcdrain(-1) == -1, "tcdrain(-1) -> -1"); FAIL_ONE(tcflush(-1, TCIFLUSH) == -1, "tcflush(-1) -> -1"); FAIL_ONE(tcflow(-1, TCOOFF) == -1, "tcflow(-1) -> -1"); FAIL_ONE(tcsendbreak(-1, 0) == -1, "tcsendbreak(-1) -> -1"); if (fd_null >= 0) { FAIL_ONE(tcgetattr(fd_null, &t) == -1, "tcgetattr on /dev/null -> -1 (ENOTTY)"); (void)__syscall1(SYS_close, fd_null); } if (fd_pty >= 0) { FAIL_ONE(tcflush(fd_pty, 9) == -1, "tcflush with a bad selector -> -1 (EINVAL)"); FAIL_ONE(tcflow(fd_pty, 9) == -1, "tcflow with a bad action -> -1 (EINVAL)"); (void)__syscall1(SYS_close, fd_pty); } FAIL_ONE(cfsetospeed(&t, (speed_t)0x1010) == -1, "cfsetospeed with an out-of-range speed -> -1"); FAIL_ONE(cfsetispeed(&t, (speed_t)0x1010) == -1, "cfsetispeed with an out-of-range speed -> -1"); #if VLIBC_LEVEL_GE(2) FAIL_ONE(cfsetspeed(&t, (speed_t)0x1010) == -1, "cfsetspeed with an out-of-range speed -> -1"); FAIL_ONE(tcgetsid(-1) == -1, "tcgetsid(-1) -> -1"); #endif #undef FAIL_ONE return rc; } int main(int argc, char **argv) { if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f') { /* * The failure scenarios write errno inside the library; under the * host libc that slot is glibc's private TLS state, so leave via * the raw syscall without running host cleanup. */ int rc = failure_scenarios(); __syscall1(SYS_exit_group, rc); return rc; /* not reached */ } int fd = open_ptmx(); if (fd < 0) { say(1, "SKIP: /dev/ptmx is unavailable; the termios scenarios are skipped\n"); return 0; } defaults_scenario(fd); attr_roundtrip_scenario(fd); flow_scenario(fd); speed_scenario(fd); #if VLIBC_LEVEL_GE(2) raw_scenario(fd); #endif (void)__syscall1(SYS_close, fd); if (failures > 0) { say(2, "FAILED ("); say_dec(2, (unsigned long)failures); say(2, " check(s))\n"); return 1; } say(1, "all termios tests passed\n"); return 0; }