18.11. Noncanonical ModeNoncanonical mode is specified by turning off the ICANON flag in the c_lflag field of the termios structure. In noncanonical mode, the input data is not assembled into lines. The following special characters (Section 18.3) are not processed: ERASE, KILL, EOF, NL, EOL, EOL2, CR, REPRINT, STATUS, and WERASE. As we said, canonical mode is easy: the system returns up to one line at a time. But with noncanonical mode, how does the system know when to return data to us? If it returned one byte at a time, overhead would be excessive. (Recall Figure 3.5, which showed the overhead in reading one byte at a time. Each time we doubled the amount of data returned, we halved the system call overhead.) The system can't always return multiple bytes at a time, since sometimes we don't know how much data to read until we start reading it. The solution is to tell the system to return when either a specified amount of data has been read or after a given amount of time has passed. This technique uses two variables in the c_cc array in the termios structure: MIN and TIME. These two elements of the array are indexed by the names VMIN and VTIME. MIN specifies the minimum number of bytes before a read returns. TIME specifies the number of tenths of a second to wait for data to arrive. There are four cases. Case A: MIN > 0, TIME > 0
Case B: MIN > 0, TIME == 0
Case D: MIN == 0, TIME == 0
Realize in all these cases that MIN is only a minimum. If the program requests more than MIN bytes of data, it's possible to receive up to the requested amount. This also applies to cases C and D, in which MIN is 0. Figure 18.19 summarizes the four cases for noncanonical input. In this figure, nbytes is the third argument to read (the maximum number of bytes to return). Figure 18.19. Four cases for noncanonical input
ExampleThe program in Figure 18.20 defines the tty_cbreak and tty_raw functions that set the terminal in cbreak mode and raw mode. (The terms cbreak and raw come from the Version 7 terminal driver.) We can reset the terminal to its original state (the state before either of these functions was called) by calling the function tty_reset. If we've called tty_cbreak, we need to call tty_reset before calling tty_raw. The same goes for calling tty_cbreak after calling tty_raw. This improves the chances that the terminal will be left in a usable state if we encounter any errors. Two additional functions are also provided: tty_atexit can be established as an exit handler to ensure that the terminal mode is reset by exit, and tty_termios returns a pointer to the original canonical mode termios structure. Our definition of cbreak mode is the following:
We define raw mode as follows:
The program in Figure 18.21 tests raw and cbreak modes. Running the program in Figure 18.21, we can see what happens with these two terminal modes: $ ./a.out Enter raw mode characters, terminate with DELETE 4 33 133 61 70 176 type DELETE Enter cbreak mode characters, terminate with SIGINT 1 type Control-A 10 type backspace signal caught type interrupt key In raw mode, the characters entered were Control-D (04) and the special function key F7. On the terminal being used, this function key generated five characters: ESC (033), [ (0133), 1 (061), 8 (070), and ~ (0176). Note that with the output processing turned off in raw mode (~OPOST), we do not get a carriage return output after each character. Also note that special-character processing is disabled in cbreak mode (so, for example, Control-D, the end-of-file character, and backspace aren't handled specially), whereas the terminal-generated signals are still processed. Figure 18.20. Set terminal mode to cbreak or raw#include "apue.h" #include <termios.h> #include <errno.h> static struct termios save_termios; static int ttysavefd = -1; static enum { RESET, RAW, CBREAK } ttystate = RESET; int tty_cbreak(int fd) /* put terminal into a cbreak mode */ { int err; struct termios buf; if (ttystate != RESET) { errno = EINVAL; return(-1); } if (tcgetattr(fd, &buf) < 0) return(-1); save_termios = buf; /* structure copy */ /* * Echo off, canonical mode off. */ buf.c_lflag &= ~(ECHO | ICANON); /* * Case B: 1 byte at a time, no timer. */ buf.c_cc[VMIN] = 1; buf.c_cc[VTIME] = 0; if (tcsetattr(fd, TCSAFLUSH, &buf) < 0) return(-1); /* * Verify that the changes stuck. tcsetattr can return 0 on * partial success. */ if (tcgetattr(fd, &buf) < 0) { err = errno; tcsetattr(fd, TCSAFLUSH, &save_termios); errno = err; return(-1); } if ((buf.c_lflag & (ECHO | ICANON)) || buf.c_cc[VMIN] != 1 || buf.c_cc[VTIME] != 0) { /* * Only some of the changes were made. Restore the * original settings. */ tcsetattr(fd, TCSAFLUSH, &save_termios); errno = EINVAL; return(-1); } ttystate = CBREAK; ttysavefd = fd; return(0); } int tty_raw(int fd) /* put terminal into a raw mode */ { int err; struct termios buf; if (ttystate != RESET) { errno = EINVAL; return(-1); } if (tcgetattr(fd, &buf) < 0) return(-1); save_termios = buf; /* structure copy */ /* * Echo off, canonical mode off, extended input * processing off, signal chars off. */ buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); /* * No SIGINT on BREAK, CR-to-NL off, input parity * check off, don't strip 8th bit on input, output * flow control off. */ buf.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); /* * Clear size bits, parity checking off. */ buf.c_cflag &= ~(CSIZE | PARENB); /* * Set 8 bits/char. */ buf.c_cflag |= CS8; /* * Output processing off. */ buf.c_oflag &= ~(OPOST); /* * Case B: 1 byte at a time, no timer. */ buf.c_cc[VMIN] = 1; buf.c_cc[VTIME] = 0; if (tcsetattr(fd, TCSAFLUSH, &buf) < 0) return(-1); /* * Verify that the changes stuck. tcsetattr can return 0 on * partial success. */ if (tcgetattr(fd, &buf) < 0) { err = errno; tcsetattr(fd, TCSAFLUSH, &save_termios); errno = err; return(-1); } if ((buf.c_lflag & (ECHO | ICANON | IEXTEN | ISIG)) || (buf.c_iflag & (BRKINT | ICRNL | INPCK | ISTRIP | IXON)) || (buf.c_cflag & (CSIZE | PARENB | CS8)) != CS8 || (buf.c_oflag & OPOST) || buf.c_cc[VMIN] != 1 || buf.c_cc[VTIME] != 0) { /* * Only some of the changes were made. Restore the * original settings. */ tcsetattr(fd, TCSAFLUSH, &save_termios); errno = EINVAL; return(-1); } ttystate = RAW; ttysavefd = fd; return(0); } int tty_reset(int fd) /* restore terminal's mode */ { if (ttystate == RESET) return(0); if (tcsetattr(fd, TCSAFLUSH, &save_termios) < 0) return(-1); ttystate = RESET; return(0); } void tty_atexit(void) /* can be set up by atexit(tty_atexit) */ { if (ttysavefd >= 0) tty_reset(ttysavefd); } struct termios * tty_termios(void) /* let caller see original tty state */ { return(&save_termios); } Figure 18.21. Test raw and cbreak terminal modes#include "apue.h" static void sig_catch(int signo) { printf("signal caught\n"); tty_reset(STDIN_FILENO); exit(0); } int main(void) { int i; char c; if (signal(SIGINT, sig_catch) == SIG_ERR) /* catch signals */ err_sys("signal(SIGINT) error"); if (signal(SIGQUIT, sig_catch) == SIG_ERR) err_sys("signal(SIGQUIT) error"); if (signal(SIGTERM, sig_catch) == SIG_ERR) err_sys("signal(SIGTERM) error"); if (tty_raw(STDIN_FILENO) < 0) err_sys("tty_raw error"); printf("Enter raw mode characters, terminate with DELETE\n"); while ((i = read(STDIN_FILENO, &c, 1)) == 1) { if ((c &= 255) == 0177) /* 0177 = ASCII DELETE */ break; printf("%o\n", c); } if (tty_reset(STDIN_FILENO) < 0) err_sys("tty_reset error"); if (i <= 0) err_sys("read error"); if (tty_cbreak(STDIN_FILENO) < 0) err_sys("tty_cbreak error"); printf("\nEnter cbreak mode characters, terminate with SIGINT\n"); while ((i = read(STDIN_FILENO, &c, 1)) == 1) { c &= 255; printf("%o\n", c); } if (tty_reset(STDIN_FILENO) < 0) err_sys("tty_reset error"); if (i <= 0) err_sys("read error"); exit(0); } |