feat(termios): terminal attributes
This commit is contained in:
@@ -0,0 +1,366 @@
|
|||||||
|
#ifndef VLIBC_TERMIOS_H
|
||||||
|
#define VLIBC_TERMIOS_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <termios.h>.
|
||||||
|
*
|
||||||
|
* General terminal interface (POSIX.1-2008 base): the termios structure and
|
||||||
|
* the tcgetattr/tcsetattr/tcsendbreak/tcdrain/tcflush/tcflow query-and-set
|
||||||
|
* functions plus the cfgetispeed/cfgetospeed/cfsetispeed/cfsetospeed baud
|
||||||
|
* helpers, over the kernel's TCGETS/TCSETS ioctls (SYS_ioctl).
|
||||||
|
*
|
||||||
|
* Level 1 (onlyposix): tcgetattr, tcsetattr, tcsendbreak, tcdrain,
|
||||||
|
* tcflush, tcflow, cfgetispeed, cfgetospeed,
|
||||||
|
* cfsetispeed, cfsetospeed (POSIX base).
|
||||||
|
* Level 2 (muslmimic): cfmakeraw, cfsetspeed (BSD/glibc extensions),
|
||||||
|
* tcgetsid (XSI [CX] — glibc declares it only under
|
||||||
|
* __USE_XOPEN2K8, not base __USE_POSIX).
|
||||||
|
*
|
||||||
|
* struct termios is a byte-for-byte copy of the x86_64 kernel termios
|
||||||
|
* (asm-generic/termbits.h), pinned by the static assertions below. The
|
||||||
|
* kernel TCGETS/TCSETS ioctls copy exactly sizeof(struct termios) bytes of
|
||||||
|
* this layout (verified empirically on this host: TCGETS writes 36 bytes);
|
||||||
|
* any divergence would silently corrupt c_cc. Unlike glibc's 60-byte
|
||||||
|
* userspace struct (NCCS 32 + trailing ispeed/ospeed members), the speed
|
||||||
|
* is carried only in the c_cflag CBAUD/CIBAUD bit fields, exactly as the
|
||||||
|
* kernel encodes it — so the ioctl copies the whole structure both ways
|
||||||
|
* with nothing left stale.
|
||||||
|
*
|
||||||
|
* The baud-rate constants B0..B4000000 are the KERNEL CBAUD encodings
|
||||||
|
* (B9600 is 0x0d, B38400 is 0x0f, B57600 is 0x1001, ...), NOT glibc's
|
||||||
|
* real-numbers-in-speed_t scheme. cfgetospeed()/cfgetispeed() return these
|
||||||
|
* constants and cfsetospeed()/cfsetispeed()/cfsetspeed() accept them, so
|
||||||
|
* a speed round-trips through the c_cflag bits untouched. This is the same
|
||||||
|
* choice musl makes.
|
||||||
|
*
|
||||||
|
* All flag, control-character, baud, and action constants below are Linux
|
||||||
|
* x86_64 kernel-UAPI facts transcribed from asm-generic/termbits.h and
|
||||||
|
* asm-generic/termbits-common.h. The ioctl request numbers (TCGETS,
|
||||||
|
* TCSETS, TCSBRK, TCXONC, TCFLSH, TIOCGSID) are Linux-only and stay
|
||||||
|
* private to src/termios/termios.c, matching the sys/ioctl.h-less layout
|
||||||
|
* of the tree today (they belong to a future <sys/ioctl.h>).
|
||||||
|
*
|
||||||
|
* No declaration carries an intent attribute: tcgetattr/tcsetattr &c
|
||||||
|
* perform I/O, and the cf* speed helpers read or write the caller's
|
||||||
|
* termios through a pointer that may alias arbitrary memory — const/pure
|
||||||
|
* would be unsound (same rationale as <fcntl.h>).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ---- Basic types ---- */
|
||||||
|
|
||||||
|
typedef unsigned char cc_t; /* control-character value */
|
||||||
|
typedef unsigned int speed_t; /* baud rate (a B* constant) */
|
||||||
|
typedef unsigned int tcflag_t; /* mode-flag bit mask */
|
||||||
|
|
||||||
|
/* ---- struct termios (x86_64 kernel layout) ---- */
|
||||||
|
|
||||||
|
#define NCCS 19 /* control-character slots (kernel UAPI) */
|
||||||
|
|
||||||
|
struct termios
|
||||||
|
{
|
||||||
|
tcflag_t c_iflag; /* 0: input mode flags */
|
||||||
|
tcflag_t c_oflag; /* 4: output mode flags */
|
||||||
|
tcflag_t c_cflag; /* 8: control mode flags */
|
||||||
|
tcflag_t c_lflag; /* 12: local mode flags */
|
||||||
|
cc_t c_line; /* 16: line discipline */
|
||||||
|
cc_t c_cc[NCCS]; /* 17: control characters */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Pin the kernel layout (TCGETS/TCSETS copy this exact size and shape). */
|
||||||
|
_Static_assert(sizeof(struct termios) == 36, "struct termios must match the x86_64 kernel layout");
|
||||||
|
_Static_assert(offsetof(struct termios, c_lflag) == 12, "c_lflag must sit at offset 12");
|
||||||
|
_Static_assert(offsetof(struct termios, c_line) == 16, "c_line must sit at offset 16");
|
||||||
|
_Static_assert(offsetof(struct termios, c_cc) == 17, "c_cc must sit at offset 17");
|
||||||
|
|
||||||
|
/* ---- Control characters (indices into c_cc) ---- */
|
||||||
|
|
||||||
|
#define VINTR 0 /* interrupt character */
|
||||||
|
#define VQUIT 1 /* quit character */
|
||||||
|
#define VERASE 2 /* erase character */
|
||||||
|
#define VKILL 3 /* kill character */
|
||||||
|
#define VEOF 4 /* end-of-file character */
|
||||||
|
#define VTIME 5 /* timeout for non-canonical read */
|
||||||
|
#define VMIN 6 /* min characters for non-canonical read */
|
||||||
|
#define VSWTC 7 /* switch character (System V) */
|
||||||
|
#define VSTART 8 /* start character */
|
||||||
|
#define VSTOP 9 /* stop character */
|
||||||
|
#define VSUSP 10 /* suspend character */
|
||||||
|
#define VEOL 11 /* alternate end-of-line character */
|
||||||
|
#define VREPRINT 12 /* reprint character */
|
||||||
|
#define VDISCARD 13 /* discard character */
|
||||||
|
#define VWERASE 14 /* word-erase character */
|
||||||
|
#define VLNEXT 15 /* literal-next character */
|
||||||
|
#define VEOL2 16 /* alternate end-of-line character */
|
||||||
|
|
||||||
|
/* ---- c_iflag: input mode flags ---- */
|
||||||
|
|
||||||
|
#define IGNBRK 0x0001 /* ignore break condition */
|
||||||
|
#define BRKINT 0x0002 /* signal interrupt on break */
|
||||||
|
#define IGNPAR 0x0004 /* ignore characters with parity errors */
|
||||||
|
#define PARMRK 0x0008 /* mark parity and framing errors */
|
||||||
|
#define INPCK 0x0010 /* enable input parity check */
|
||||||
|
#define ISTRIP 0x0020 /* strip 8th bit off characters */
|
||||||
|
#define INLCR 0x0040 /* map NL to CR on input */
|
||||||
|
#define IGNCR 0x0080 /* ignore CR */
|
||||||
|
#define ICRNL 0x0100 /* map CR to NL on input */
|
||||||
|
#define IUCLC 0x0200 /* map uppercase to lowercase on input */
|
||||||
|
#define IXON 0x0400 /* enable start/stop output control */
|
||||||
|
#define IXANY 0x0800 /* any character will restart after stop */
|
||||||
|
#define IXOFF 0x1000 /* enable start/stop input control */
|
||||||
|
#define IMAXBEL 0x2000 /* ring bell on input queue full */
|
||||||
|
#define IUTF8 0x4000 /* input is UTF-8 */
|
||||||
|
|
||||||
|
/* ---- c_oflag: output mode flags ---- */
|
||||||
|
|
||||||
|
#define OPOST 0x0001 /* perform output processing */
|
||||||
|
#define OLCUC 0x0002 /* map lowercase to uppercase on output */
|
||||||
|
#define ONLCR 0x0004 /* map NL to CR-NL on output */
|
||||||
|
#define OCRNL 0x0008 /* map CR to NL on output */
|
||||||
|
#define ONOCR 0x0010 /* no CR output at column 0 */
|
||||||
|
#define ONLRET 0x0020 /* NL performs CR function on output */
|
||||||
|
#define OFILL 0x0040 /* use fill characters for delay */
|
||||||
|
#define OFDEL 0x0080 /* fill is DEL, else NUL */
|
||||||
|
#define NLDLY 0x0100 /* newline delay mask */
|
||||||
|
#define NL0 0x0000
|
||||||
|
#define NL1 0x0100
|
||||||
|
#define CRDLY 0x0600 /* carriage-return delay mask */
|
||||||
|
#define CR0 0x0000
|
||||||
|
#define CR1 0x0200
|
||||||
|
#define CR2 0x0400
|
||||||
|
#define CR3 0x0600
|
||||||
|
#define TABDLY 0x1800 /* horizontal-tab delay mask */
|
||||||
|
#define TAB0 0x0000
|
||||||
|
#define TAB1 0x0800
|
||||||
|
#define TAB2 0x1000
|
||||||
|
#define TAB3 0x1800
|
||||||
|
#define XTABS 0x1800 /* TAB3: expand tabs to spaces */
|
||||||
|
#define BSDLY 0x2000 /* backspace delay mask */
|
||||||
|
#define BS0 0x0000
|
||||||
|
#define BS1 0x2000
|
||||||
|
#define VTDLY 0x4000 /* vertical-tab delay mask */
|
||||||
|
#define VT0 0x0000
|
||||||
|
#define VT1 0x4000
|
||||||
|
#define FFDLY 0x8000 /* form-feed delay mask */
|
||||||
|
#define FF0 0x0000
|
||||||
|
#define FF1 0x8000
|
||||||
|
|
||||||
|
/* ---- c_cflag: control mode flags ---- */
|
||||||
|
|
||||||
|
#define CBAUD 0x100f /* baud-rate mask (includes CBAUDEX) */
|
||||||
|
#define CSIZE 0x0030 /* character-size mask */
|
||||||
|
#define CS5 0x0000 /* 5 bits */
|
||||||
|
#define CS6 0x0010 /* 6 bits */
|
||||||
|
#define CS7 0x0020 /* 7 bits */
|
||||||
|
#define CS8 0x0030 /* 8 bits */
|
||||||
|
#define CSTOPB 0x0040 /* two stop bits, else one */
|
||||||
|
#define CREAD 0x0080 /* enable receiver */
|
||||||
|
#define PARENB 0x0100 /* enable parity */
|
||||||
|
#define PARODD 0x0200 /* odd parity, else even */
|
||||||
|
#define HUPCL 0x0400 /* hang up on last close */
|
||||||
|
#define CLOCAL 0x0800 /* ignore modem status lines */
|
||||||
|
#define CBAUDEX 0x1000 /* extended baud-rate mask (above B38400) */
|
||||||
|
#define CRTSCTS 0x80000000 /* enable RTS/CTS flow control */
|
||||||
|
|
||||||
|
/* ---- c_lflag: local mode flags ---- */
|
||||||
|
|
||||||
|
#define ISIG 0x0001 /* enable signals */
|
||||||
|
#define ICANON 0x0002 /* canonical mode (line-buffered) */
|
||||||
|
#define XCASE 0x0004 /* canonical uppercase/lowercase presentation */
|
||||||
|
#define ECHO 0x0008 /* enable echo */
|
||||||
|
#define ECHOE 0x0010 /* echo erase as backspace */
|
||||||
|
#define ECHOK 0x0020 /* echo KILL */
|
||||||
|
#define ECHONL 0x0040 /* echo NL */
|
||||||
|
#define NOFLSH 0x0080 /* disable flush after interrupt/quit/suspend */
|
||||||
|
#define TOSTOP 0x0100 /* send SIGTTOU on background output */
|
||||||
|
#define ECHOCTL 0x0200 /* echo control characters as ^X */
|
||||||
|
#define ECHOPRT 0x0400 /* echo erase as backspace-print-backspace */
|
||||||
|
#define ECHOKE 0x0800 /* BSD: erase entire line on KILL */
|
||||||
|
#define FLUSHO 0x1000 /* output is being flushed */
|
||||||
|
#define PENDIN 0x4000 /* reprint pending input at next read */
|
||||||
|
#define IEXTEN 0x8000 /* enable implementation-defined input processing */
|
||||||
|
#define EXTPROC 0x10000 /* external processing */
|
||||||
|
|
||||||
|
/* ---- Baud rates (kernel CBAUD encodings — see the header comment) ---- */
|
||||||
|
|
||||||
|
#define B0 0x00000000 /* hang up */
|
||||||
|
#define B50 0x00000001
|
||||||
|
#define B75 0x00000002
|
||||||
|
#define B110 0x00000003
|
||||||
|
#define B134 0x00000004
|
||||||
|
#define B150 0x00000005
|
||||||
|
#define B200 0x00000006
|
||||||
|
#define B300 0x00000007
|
||||||
|
#define B600 0x00000008
|
||||||
|
#define B1200 0x00000009
|
||||||
|
#define B1800 0x0000000a
|
||||||
|
#define B2400 0x0000000b
|
||||||
|
#define B4800 0x0000000c
|
||||||
|
#define B9600 0x0000000d
|
||||||
|
#define B19200 0x0000000e
|
||||||
|
#define B38400 0x0000000f
|
||||||
|
#define B57600 0x00001001 /* CBAUDEX | 1 */
|
||||||
|
#define B115200 0x00001002
|
||||||
|
#define B230400 0x00001003
|
||||||
|
#define B460800 0x00001004
|
||||||
|
#define B500000 0x00001005
|
||||||
|
#define B576000 0x00001006
|
||||||
|
#define B921600 0x00001007
|
||||||
|
#define B1000000 0x00001008
|
||||||
|
#define B1152000 0x00001009
|
||||||
|
#define B1500000 0x0000100a
|
||||||
|
#define B2000000 0x0000100b
|
||||||
|
#define B2500000 0x0000100c
|
||||||
|
#define B3000000 0x0000100d
|
||||||
|
#define B3500000 0x0000100e
|
||||||
|
#define B4000000 0x0000100f
|
||||||
|
|
||||||
|
/* ---- tcsetattr optional_actions ---- */
|
||||||
|
|
||||||
|
#define TCSANOW 0 /* change immediately */
|
||||||
|
#define TCSADRAIN 1 /* change after output drains */
|
||||||
|
#define TCSAFLUSH 2 /* drain output and discard input */
|
||||||
|
|
||||||
|
/* ---- tcflush queue_selector ---- */
|
||||||
|
|
||||||
|
#define TCIFLUSH 0 /* discard data received but not yet read */
|
||||||
|
#define TCOFLUSH 1 /* discard data written but not yet transmitted */
|
||||||
|
#define TCIOFLUSH 2 /* discard both */
|
||||||
|
|
||||||
|
/* ---- tcflow action ---- */
|
||||||
|
|
||||||
|
#define TCOOFF 0 /* suspend output */
|
||||||
|
#define TCOON 1 /* restart suspended output */
|
||||||
|
#define TCIOFF 2 /* transmit a STOP character */
|
||||||
|
#define TCION 3 /* transmit a START character */
|
||||||
|
|
||||||
|
/* ---- Level 1 (POSIX base) ---- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store the terminal attributes of the terminal open on fildes into
|
||||||
|
* *termios_p. Return 0 on success, or -1 with errno set (ENOTTY when
|
||||||
|
* fildes is not a terminal, EBADF on a bad descriptor).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
tcgetattr(int fildes, struct termios *termios_p);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the terminal attributes of the terminal open on fildes from
|
||||||
|
* *termios_p, applying optional_actions (TCSANOW apply immediately,
|
||||||
|
* TCSADRAIN wait for output to drain, TCSAFLUSH drain and then discard
|
||||||
|
* unread input). Return 0 on success, or -1 with errno set (EINVAL for an
|
||||||
|
* invalid optional_actions, ENOTTY/EBADF as for tcgetattr).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
tcsetattr(int fildes, int optional_actions, const struct termios *termios_p);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transmit a break condition on the terminal open on fildes. The duration
|
||||||
|
* argument is ignored (POSIX leaves a nonzero duration
|
||||||
|
* implementation-defined). Return 0 on success or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
tcsendbreak(int fildes, int duration);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait until all output written to the terminal open on fildes has been
|
||||||
|
* transmitted. Return 0 on success or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
tcdrain(int fildes);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Discard data written but not yet transmitted (TCOFLUSH), data received
|
||||||
|
* but not yet read (TCIFLUSH), or both (TCIOFLUSH) on the terminal open on
|
||||||
|
* fildes. Return 0 on success or -1 with errno set (EINVAL for an invalid
|
||||||
|
* queue_selector).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
tcflush(int fildes, int queue_selector);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Suspend or restart transmission (TCOOFF/TCOON) or transmit a STOP/START
|
||||||
|
* character (TCIOFF/TCION) on the terminal open on fildes. Return 0 on
|
||||||
|
* success or -1 with errno set (EINVAL for an invalid action).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
tcflow(int fildes, int action);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the input baud rate stored in *termios_p (a B* constant), as
|
||||||
|
* encoded in the c_cflag CIBAUD field, or B0 when the input rate is not
|
||||||
|
* separately stored (the kernel then treats it as equal to the output
|
||||||
|
* rate, and cfsetispeed with B0 selects exactly that).
|
||||||
|
*/
|
||||||
|
speed_t
|
||||||
|
cfgetispeed(const struct termios *termios_p);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the output baud rate stored in *termios_p (a B* constant), as
|
||||||
|
* encoded in the c_cflag CBAUD field.
|
||||||
|
*/
|
||||||
|
speed_t
|
||||||
|
cfgetospeed(const struct termios *termios_p);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store speed as the input baud rate of *termios_p. Accept any value that
|
||||||
|
* fits the CBAUD encoding (B0 means "input rate follows the output rate");
|
||||||
|
* an out-of-range speed yields -1 with errno EINVAL.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
cfsetispeed(struct termios *termios_p, speed_t speed);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store speed as the output baud rate of *termios_p. Accept any value that
|
||||||
|
* fits the CBAUD encoding (see cfsetispeed); an out-of-range speed yields
|
||||||
|
* -1 with errno EINVAL.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
cfsetospeed(struct termios *termios_p, speed_t speed);
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* Level 2 (muslmimic): BSD/glibc extensions and XSI [CX] additions. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set *termios_p to the "raw" mode of the old Version-7 terminal driver:
|
||||||
|
* input available character by character, echoing and all special
|
||||||
|
* processing of input and output characters disabled (clear IGNBRK BRKINT
|
||||||
|
* PARMRK ISTRIP INLCR IGNCR ICRNL IXON, clear OPOST, clear ECHO ECHONL
|
||||||
|
* ICANON ISIG IEXTEN, select CS8 and no parity). Does not report errors.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
cfmakeraw(struct termios *termios_p);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store speed as both the input and the output baud rate of *termios_p
|
||||||
|
* (cfsetospeed then cfsetispeed with the same value). Return 0 on success
|
||||||
|
* or -1 with errno EINVAL for an out-of-range speed.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
cfsetspeed(struct termios *termios_p, speed_t speed);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the session id of the session that has the terminal open on
|
||||||
|
* fildes as its controlling terminal, or -1 with errno set when fildes is
|
||||||
|
* not a controlling terminal (ENOTTY).
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
tcgetsid(int fildes);
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_TERMIOS_H */
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <termios.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — terminal attributes (todo 29).
|
||||||
|
*
|
||||||
|
* The ten POSIX base functions plus the three level-2 extras, all thin
|
||||||
|
* pass-throughs to the kernel tty ioctls over SYS_ioctl. struct termios
|
||||||
|
* (include/termios.h) is byte-identical to the x86_64 kernel termios, so
|
||||||
|
* the ioctl argument pointer is passed straight through: the kernel copies
|
||||||
|
* exactly sizeof(struct termios) bytes in each direction.
|
||||||
|
*
|
||||||
|
* The ioctl request numbers are Linux-only kernel-UAPI facts
|
||||||
|
* (asm-generic/ioctls.h), kept private here — they are the future
|
||||||
|
* <sys/ioctl.h>'s names, not <termios.h>'s (POSIX exposes only the
|
||||||
|
* actions/queues/actions constants, which the header defines).
|
||||||
|
*
|
||||||
|
* The cf* speed helpers never touch the kernel: they only re-encode the
|
||||||
|
* caller's c_cflag CBAUD/CIBAUD bit fields, which the kernel's termios
|
||||||
|
* layer itself uses (a later tcsetattr carries the bits verbatim). The
|
||||||
|
* CIBAUD field (input speed) is shifted IBSHIFT = 16 bits above CBAUD and
|
||||||
|
* is not part of any public constant set, so it is transcribed locally.
|
||||||
|
*
|
||||||
|
* Return-value convention follows syscall_ret for every I/O function
|
||||||
|
* (0 or positive on success, -1 with errno on failure).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Kernel tty ioctls (asm-generic/ioctls.h on x86_64). */
|
||||||
|
#define VLIBC_TERMIOS_TCGETS 0x5401
|
||||||
|
#define VLIBC_TERMIOS_TCSETS 0x5402
|
||||||
|
#define VLIBC_TERMIOS_TCSETSW 0x5403
|
||||||
|
#define VLIBC_TERMIOS_TCSETSF 0x5404
|
||||||
|
#define VLIBC_TERMIOS_TCSBRK 0x5409
|
||||||
|
#define VLIBC_TERMIOS_TCXONC 0x540a
|
||||||
|
#define VLIBC_TERMIOS_TCFLSH 0x540b
|
||||||
|
#define VLIBC_TERMIOS_TIOCGSID 0x5429
|
||||||
|
|
||||||
|
/* Kernel input-baud encoding (asm-generic/termbits.h): CIBAUD = CBAUD << 16. */
|
||||||
|
#define VLIBC_TERMIOS_CIBAUD 0x100f0000
|
||||||
|
#define VLIBC_TERMIOS_IBSHIFT 16
|
||||||
|
|
||||||
|
/*
|
||||||
|
* tcgetattr: read the terminal attributes of the terminal open on fildes.
|
||||||
|
* The kernel returns -ENOTTY when fildes is not a terminal; syscall_ret
|
||||||
|
* converts that to -1 with errno ENOTTY.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
tcgetattr(int fildes, struct termios *termios_p)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall3(SYS_ioctl, fildes, VLIBC_TERMIOS_TCGETS, (long)termios_p));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* tcsetattr: write the terminal attributes. optional_actions selects the
|
||||||
|
* ioctl: TCSANOW applies immediately (TCSETS), TCSADRAIN waits for output
|
||||||
|
* to drain (TCSETSW), TCSAFLUSH additionally discards unread input
|
||||||
|
* (TCSETSF). Anything else is EINVAL before the kernel is consulted.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
tcsetattr(int fildes, int optional_actions, // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
const struct termios *termios_p)
|
||||||
|
{
|
||||||
|
long request;
|
||||||
|
|
||||||
|
if (optional_actions == TCSANOW)
|
||||||
|
{
|
||||||
|
request = VLIBC_TERMIOS_TCSETS;
|
||||||
|
}
|
||||||
|
else if (optional_actions == TCSADRAIN)
|
||||||
|
{
|
||||||
|
request = VLIBC_TERMIOS_TCSETSW;
|
||||||
|
}
|
||||||
|
else if (optional_actions == TCSAFLUSH)
|
||||||
|
{
|
||||||
|
request = VLIBC_TERMIOS_TCSETSF;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return syscall_ret(__syscall3(SYS_ioctl, fildes, request, (long)termios_p));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* tcsendbreak: transmit a break condition. The kernel sends a break of
|
||||||
|
* implementation-defined duration; POSIX leaves the meaning of a nonzero
|
||||||
|
* duration to the implementation, so the argument is ignored.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
tcsendbreak(int fildes, int duration) // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
{
|
||||||
|
(void)duration;
|
||||||
|
return syscall_ret(__syscall3(SYS_ioctl, fildes, VLIBC_TERMIOS_TCSBRK, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* tcdrain: wait until all output written to the terminal has been
|
||||||
|
* transmitted. The kernel implements this as the TCSBRK ioctl with
|
||||||
|
* argument 1 (as opposed to tcsendbreak's argument 0).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
tcdrain(int fildes)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall3(SYS_ioctl, fildes, VLIBC_TERMIOS_TCSBRK, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* tcflush: discard pending input, output, or both per queue_selector.
|
||||||
|
* The kernel validates the selector (EINVAL on anything but TCIFLUSH/
|
||||||
|
* TCOFLUSH/TCIOFLUSH).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
tcflush(int fildes, int queue_selector) // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall3(SYS_ioctl, fildes, VLIBC_TERMIOS_TCFLSH, queue_selector));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* tcflow: suspend/restart transmission or send a STOP/START character per
|
||||||
|
* action. The kernel validates the action (EINVAL on anything but
|
||||||
|
* TCOOFF/TCOON/TCIOFF/TCION).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
tcflow(int fildes, int action) // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall3(SYS_ioctl, fildes, VLIBC_TERMIOS_TCXONC, action));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* cfgetispeed: the input baud rate as stored in *termios_p. The kernel
|
||||||
|
* encodes it in the CIBAUD field (c_cflag bits IBSHIFT..); when that field
|
||||||
|
* is clear the input rate is defined to follow the output rate and the
|
||||||
|
* stored value is B0.
|
||||||
|
*/
|
||||||
|
speed_t
|
||||||
|
cfgetispeed(const struct termios *termios_p)
|
||||||
|
{
|
||||||
|
return (speed_t)((termios_p->c_cflag & VLIBC_TERMIOS_CIBAUD) >> VLIBC_TERMIOS_IBSHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* cfgetospeed: the output baud rate as stored in *termios_p (the CBAUD
|
||||||
|
* field, which already spans the CBAUDEX extension bits).
|
||||||
|
*/
|
||||||
|
speed_t
|
||||||
|
cfgetospeed(const struct termios *termios_p)
|
||||||
|
{
|
||||||
|
return termios_p->c_cflag & CBAUD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* cfsetispeed: store speed in the CIBAUD field of *termios_p. Only values
|
||||||
|
* representable in the CBAUD encoding are valid (B0 clears the field,
|
||||||
|
* meaning "input rate follows the output rate").
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
cfsetispeed(struct termios *termios_p, speed_t speed)
|
||||||
|
{
|
||||||
|
if ((speed & ~CBAUD) != 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
termios_p->c_cflag &= ~VLIBC_TERMIOS_CIBAUD;
|
||||||
|
termios_p->c_cflag |= (speed & CBAUD) << VLIBC_TERMIOS_IBSHIFT;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* cfsetospeed: store speed in the CBAUD field of *termios_p. Only values
|
||||||
|
* representable in the CBAUD encoding (B0..B38400, B57600..B4000000) are
|
||||||
|
* valid.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
cfsetospeed(struct termios *termios_p, speed_t speed)
|
||||||
|
{
|
||||||
|
if ((speed & ~CBAUD) != 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
termios_p->c_cflag &= ~CBAUD;
|
||||||
|
termios_p->c_cflag |= speed & CBAUD;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* Level 2 (muslmimic): BSD/glibc extensions and XSI [CX] additions. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* cfmakeraw: switch *termios_p to raw mode. Only the termios flags are
|
||||||
|
* touched — speeds, character size beyond CS8, and the c_cc array are the
|
||||||
|
* caller's business (POSIX does not define the function, so its exact
|
||||||
|
* effect is a glibc-compatible choice).
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
cfmakeraw(struct termios *termios_p)
|
||||||
|
{
|
||||||
|
termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
|
||||||
|
termios_p->c_oflag &= ~OPOST;
|
||||||
|
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
||||||
|
termios_p->c_cflag &= ~(CSIZE | PARENB);
|
||||||
|
termios_p->c_cflag |= CS8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* cfsetspeed: set both the input and the output baud rate to speed. Both
|
||||||
|
* fields are written explicitly (cfsetospeed then cfsetispeed), so the
|
||||||
|
* structure round-trips cfgetospeed/cfgetispeed and the kernel decodes the
|
||||||
|
* same rate on both sides.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
cfsetspeed(struct termios *termios_p, speed_t speed)
|
||||||
|
{
|
||||||
|
if (cfsetospeed(termios_p, speed) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return cfsetispeed(termios_p, speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* tcgetsid: the session id of the session whose controlling terminal is
|
||||||
|
* the terminal open on fildes. The kernel fills an int via the TIOCGSID ioctl
|
||||||
|
* and fails with -ENOTTY when the terminal has no session.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
tcgetsid(int fildes)
|
||||||
|
{
|
||||||
|
int sid = 0; /* written by TIOCGSID; zeroed for the static analyzers */
|
||||||
|
|
||||||
|
if (syscall_ret(__syscall3(SYS_ioctl, fildes, VLIBC_TERMIOS_TIOCGSID, (long)&sid)) < 0)
|
||||||
|
{
|
||||||
|
return (pid_t)-1;
|
||||||
|
}
|
||||||
|
return (pid_t)sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,388 @@
|
|||||||
|
/*
|
||||||
|
* 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 <stddef.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user