From: Karel Zak Date: Thu, 14 May 2026 08:57:21 +0000 (+0200) Subject: agetty: split out terminal functions to tty.c X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=2431927ff5844d6cc45beb5a7fada48e8e5c470c;p=thirdparty%2Futil-linux.git agetty: split out terminal functions to tty.c Move terminal initialization and management functions to tty.c: agetty_open_tty(), agetty_termio_clear(), agetty_termio_init(), agetty_termio_final(), agetty_reset_vc(), agetty_auto_baud(), agetty_next_speed() and agetty_erase_char(). Move CLOCAL_MODE_* enum, FIRST_SPEED and debug() macro to agetty.h as they are now needed by multiple source files. Signed-off-by: Karel Zak --- diff --git a/agetty-cmd/agetty.c b/agetty-cmd/agetty.c index 7dc24e905..d78975ca7 100644 --- a/agetty-cmd/agetty.c +++ b/agetty-cmd/agetty.c @@ -148,30 +148,17 @@ struct issue { do_tcrestore : 1; }; -enum { - CLOCAL_MODE_AUTO = 0, - CLOCAL_MODE_ALWAYS, - CLOCAL_MODE_NEVER -}; - #define serial_tty_option(opt, flag) \ (((opt)->flags & (F_VCONSOLE|(flag))) == (flag)) static void init_special_char(char* arg, struct agetty_options *op); static void parse_args(int argc, char **argv, struct agetty_options *op); static void parse_speeds(struct agetty_options *op, char *arg); -static void open_tty(const char *tty, struct termios *tp, struct agetty_options *op); -static void termio_init(struct agetty_options *op, struct termios *tp); -static void reset_vc(const struct agetty_options *op, struct termios *tp, int canon); -static void auto_baud(struct termios *tp); static void output_special_char (struct issue *ie, unsigned char c, struct agetty_options *op, struct termios *tp, FILE *fp); static void do_prompt(struct issue *ie, struct agetty_options *op, struct termios *tp); -static void next_speed(struct agetty_options *op, struct termios *tp); static char *get_logname(struct issue *ie, struct agetty_options *op, struct termios *tp, struct chardata *cp); -static void termio_final(struct agetty_options *op, - struct termios *tp, struct chardata *cp); static int caps_lock(char *s); static void usage(void) __attribute__((__noreturn__)); #ifdef KDGKBLED @@ -193,10 +180,7 @@ static char *fakehost; # ifndef DEBUG_OUTPUT # define DEBUG_OUTPUT "/dev/tty10" # endif -# define debug(s) do { fprintf(dbf,s); fflush(dbf); } while (0) FILE *dbf; -#else -# define debug(s) do { ; } while (0) #endif int main(int argc, char **argv) @@ -264,7 +248,7 @@ int main(int argc, char **argv) debug("calling open_tty\n"); /* Open the tty as standard { input, output, error }. */ - open_tty(options.tty, &termios, &options); + agetty_open_tty(options.tty, &termios, &options); /* Unmask SIGHUP if inherited */ sigemptyset(&set); @@ -282,7 +266,7 @@ int main(int argc, char **argv) /* Initialize the termios settings (raw mode, eight-bit, blocking i/o). */ debug("calling termio_init\n"); - termio_init(&options, &termios); + agetty_termio_init(&options, &termios); /* Write the modem init string and DO NOT flush the buffers. */ if (options.flags & F_INITSTRING && @@ -302,7 +286,7 @@ int main(int argc, char **argv) /* Optionally detect the baud rate from the modem status message. */ debug("before autobaud\n"); if (serial_tty_option(&options, F_PARSE)) - auto_baud(&termios); + agetty_auto_baud(&termios); /* Set the optional timer. */ if (options.timeout) @@ -353,7 +337,7 @@ int main(int argc, char **argv) while ((username = get_logname(&issue, &options, &termios, &chardata)) == NULL) if ((options.flags & F_VCONSOLE) == 0 && options.numspeed) - next_speed(&options, &termios); + agetty_next_speed(&options, &termios); } } @@ -363,9 +347,9 @@ int main(int argc, char **argv) /* Finalize the termios settings. */ if ((options.flags & F_VCONSOLE) == 0) - termio_final(&options, &termios, &chardata); + agetty_termio_final(&options, &termios, &chardata); else - reset_vc(&options, &termios, 1); + agetty_reset_vc(&options, &termios, 1); /* Now the newline character should be properly written. */ write_all(STDOUT_FILENO, "\r\n", 2); @@ -823,440 +807,6 @@ static void parse_speeds(struct agetty_options *op, char *arg) } -/* Set up tty as stdin, stdout & stderr. */ -static void open_tty(const char *tty, struct termios *tp, struct agetty_options *op) -{ - const pid_t pid = getpid(); - int closed = 0; -#ifndef KDGKBMODE - int serial; -#endif - - /* Set up new standard input, unless we are given an already opened port. */ - - if (!op->tty_is_stdin) { - char buf[PATH_MAX+1]; - struct group *gr = NULL; - struct stat st; - int fd, len; - pid_t tid; - gid_t gid = 0; - - /* Use tty group if available */ - if ((gr = getgrnam("tty"))) - gid = gr->gr_gid; - - len = snprintf(buf, sizeof(buf), "/dev/%s", tty); - if (len < 0 || (size_t)len >= sizeof(buf)) - agetty_log_err(_("/dev/%s: cannot open as standard input: %m"), tty); - - /* Open the tty as standard input. */ - if ((fd = open(buf, O_RDWR|O_NOCTTY|O_NONBLOCK, 0)) < 0) - agetty_log_err(_("/dev/%s: cannot open as standard input: %m"), tty); - - /* - * There is always a race between this reset and the call to - * vhangup() that s.o. can use to get access to your tty. - * Linux login(1) will change tty permissions. Use root owner and group - * with permission -rw------- for the period between getty and login. - */ - if (fchown(fd, 0, gid) || fchmod(fd, (gid ? 0620 : 0600))) { - if (errno == EROFS) - agetty_log_warn("%s: %m", buf); - else - agetty_log_err("%s: %m", buf); - } - - /* Sanity checks... */ - if (fstat(fd, &st) < 0) - agetty_log_err("%s: %m", buf); - if ((st.st_mode & S_IFMT) != S_IFCHR) - agetty_log_err(_("/dev/%s: not a character device"), tty); - if (!isatty(fd)) - agetty_log_err(_("/dev/%s: not a tty"), tty); - - if (((tid = tcgetsid(fd)) < 0) || (pid != tid)) { - if (ioctl(fd, TIOCSCTTY, 1) == -1) - agetty_log_warn(_("/dev/%s: cannot get controlling tty: %m"), tty); - } - - close(STDIN_FILENO); - errno = 0; - - if (op->flags & F_HANGUP) { - - if (ioctl(fd, TIOCNOTTY)) - debug("TIOCNOTTY ioctl failed\n"); - - /* - * Let's close all file descriptors before vhangup - * https://lkml.org/lkml/2012/6/5/145 - */ - close(fd); - close(STDOUT_FILENO); - close(STDERR_FILENO); - errno = 0; - closed = 1; - - if (vhangup()) - agetty_log_err(_("/dev/%s: vhangup() failed: %m"), tty); - } else - close(fd); - - debug("open(2)\n"); - if (open(buf, O_RDWR|O_NOCTTY|O_NONBLOCK, 0) != 0) - agetty_log_err(_("/dev/%s: cannot open as standard input: %m"), tty); - - if (((tid = tcgetsid(STDIN_FILENO)) < 0) || (pid != tid)) { - if (ioctl(STDIN_FILENO, TIOCSCTTY, 1) == -1) - agetty_log_warn(_("/dev/%s: cannot get controlling tty: %m"), tty); - } - - } else { - - /* - * Standard input should already be connected to an open port. Make - * sure it is open for read/write. - */ - - if ((fcntl(STDIN_FILENO, F_GETFL, 0) & O_RDWR) != O_RDWR) - agetty_log_err(_("%s: not open for read/write"), tty); - - } - - if (tcsetpgrp(STDIN_FILENO, pid)) - agetty_log_warn(_("/dev/%s: cannot set process group: %m"), tty); - - /* Get rid of the present outputs. */ - if (!closed) { - close(STDOUT_FILENO); - close(STDERR_FILENO); - errno = 0; - } - - /* Set up standard output and standard error file descriptors. */ - debug("duping\n"); - - /* set up stdout and stderr */ - if (dup(STDIN_FILENO) != 1 || dup(STDIN_FILENO) != 2) - agetty_log_err(_("%s: dup problem: %m"), tty); - - /* make stdio unbuffered for slow modem lines */ - setvbuf(stdout, NULL, _IONBF, 0); - - /* - * The following ioctl will fail if stdin is not a tty, but also when - * there is noise on the modem control lines. In the latter case, the - * common course of action is (1) fix your cables (2) give the modem - * more time to properly reset after hanging up. - * - * SunOS users can achieve (2) by patching the SunOS kernel variable - * "zsadtrlow" to a larger value; 5 seconds seems to be a good value. - * http://www.sunmanagers.org/archives/1993/0574.html - */ - memset(tp, 0, sizeof(struct termios)); - if (tcgetattr(STDIN_FILENO, tp) < 0) - agetty_log_err(_("%s: failed to get terminal attributes: %m"), tty); - -#if defined(__FreeBSD_kernel__) - login_tty (0); -#endif - - /* - * Detect if this is a virtual console or serial/modem line. - * In case of a virtual console the ioctl KDGKBMODE succeeds - * whereas on other lines it will fails. - */ -#ifdef KDGKBMODE - if (ioctl(STDIN_FILENO, KDGKBMODE, &op->kbmode) == 0) -#else - if (ioctl(STDIN_FILENO, TIOCMGET, &serial) < 0 && (errno == EINVAL)) -#endif - { - op->flags |= F_VCONSOLE; - } else { -#ifdef K_RAW - op->kbmode = K_RAW; -#endif - } - - if (!op->term) - op->term = get_terminal_default_type(op->tty, !(op->flags & F_VCONSOLE)); - if (!op->term) - agetty_log_err(_("failed to allocate memory: %m")); - - if (setenv("TERM", op->term, 1) != 0) - agetty_log_err(_("failed to set the %s environment variable"), "TERM"); -} - -/* Initialize termios settings. */ -static void termio_clear(int fd) -{ - /* - * Do not write a full reset (ESC c) because this destroys - * the unicode mode again if the terminal was in unicode - * mode. Also it clears the CONSOLE_MAGIC features which - * are required for some languages/console-fonts. - * Just put the cursor to the home position (ESC [ H), - * erase everything below the cursor (ESC [ J), and set the - * scrolling region to the full window (ESC [ r) - */ - write_all(fd, "\033[r\033[H\033[J", 9); -} - -/* Initialize termios settings. */ -static void termio_init(struct agetty_options *op, struct termios *tp) -{ - speed_t ispeed, ospeed; - struct winsize ws; -#ifdef USE_PLYMOUTH_SUPPORT - struct termios lock; - int i = (plymouth_command(MAGIC_PING) == 0) ? PLYMOUTH_TERMIOS_FLAGS_DELAY : 0; - if (i) - plymouth_command(MAGIC_QUIT); - while (i-- > 0) { - /* - * Even with TTYReset=no it seems with systemd or plymouth - * the termios flags become changed from under the first - * agetty on a serial system console as the flags are locked. - */ - memset(&lock, 0, sizeof(struct termios)); - if (ioctl(STDIN_FILENO, TIOCGLCKTRMIOS, &lock) < 0) - break; - if (!lock.c_iflag && !lock.c_oflag && !lock.c_cflag && !lock.c_lflag) - break; - debug("termios locked\n"); - sleep(1); - } - memset(&lock, 0, sizeof(struct termios)); - ioctl(STDIN_FILENO, TIOCSLCKTRMIOS, &lock); -#endif - - if (op->flags & F_VCONSOLE) { -#if defined(IUTF8) && defined(KDGKBMODE) - switch(op->kbmode) { - case K_UNICODE: - setlocale(LC_CTYPE, "C.UTF-8"); - op->flags |= F_UTF8; - break; - case K_RAW: - case K_MEDIUMRAW: - case K_XLATE: - default: - setlocale(LC_CTYPE, "POSIX"); - op->flags &= ~F_UTF8; - break; - } -#else - setlocale(LC_CTYPE, "POSIX"); - op->flags &= ~F_UTF8; -#endif - reset_vc(op, tp, 0); - - if ((tp->c_cflag & (CS8|PARODD|PARENB)) == CS8) - op->flags |= F_EIGHTBITS; - - if ((op->flags & F_NOCLEAR) == 0) - termio_clear(STDOUT_FILENO); - return; - } - - /* - * Serial line - */ - - if (op->flags & F_KEEPSPEED || !op->numspeed) { - /* Save the original setting. */ - ispeed = cfgetispeed(tp); - ospeed = cfgetospeed(tp); - - /* Save also the original speed to array of the speeds to make - * it possible to return the original after unexpected BREAKs. - */ - if (op->numspeed) - op->speeds[op->numspeed++] = ispeed ? ispeed : - ospeed ? ospeed : - TTYDEF_SPEED; - if (!ispeed) - ispeed = TTYDEF_SPEED; - if (!ospeed) - ospeed = TTYDEF_SPEED; - } else { - ospeed = ispeed = op->speeds[FIRST_SPEED]; - } - - /* - * Initial termios settings: 8-bit characters, raw-mode, blocking i/o. - * Special characters are set after we have read the login name; all - * reads will be done in raw mode anyway. Errors will be dealt with - * later on. - */ - - /* The default is set c_iflag in termio_final() according to chardata. - * Unfortunately, the chardata are not set according to the serial line - * if --autolog is enabled. In this case we do not read from the line - * at all. The best what we can do in this case is to keep c_iflag - * unmodified for --autolog. - */ - if (!op->autolog) { -#ifdef IUTF8 - tp->c_iflag = tp->c_iflag & IUTF8; - if (tp->c_iflag & IUTF8) - op->flags |= F_UTF8; -#else - tp->c_iflag = 0; -#endif - } - - tp->c_lflag = 0; - tp->c_oflag &= OPOST | ONLCR; - - if ((op->flags & F_KEEPCFLAGS) == 0) - tp->c_cflag = CS8 | HUPCL | CREAD | (tp->c_cflag & CLOCAL); - - /* - * Note that the speed is stored in the c_cflag termios field, so we have - * set the speed always when the cflag is reset. - */ - cfsetispeed(tp, ispeed); - cfsetospeed(tp, ospeed); - - /* The default is to follow setting from kernel, but it's possible - * to explicitly remove/add CLOCAL flag by -L[=]*/ - switch (op->clocal) { - case CLOCAL_MODE_ALWAYS: - tp->c_cflag |= CLOCAL; /* -L or -L=always */ - break; - case CLOCAL_MODE_NEVER: - tp->c_cflag &= ~CLOCAL; /* -L=never */ - break; - case CLOCAL_MODE_AUTO: /* -L=auto */ - break; - } - -#ifdef HAVE_STRUCT_TERMIOS_C_LINE - tp->c_line = 0; -#endif - tp->c_cc[VMIN] = 1; - tp->c_cc[VTIME] = 0; - - /* Check for terminal size and if not found set default */ - if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0) { - if (ws.ws_row == 0) - ws.ws_row = 24; - if (ws.ws_col == 0) - ws.ws_col = 80; - if (ioctl(STDIN_FILENO, TIOCSWINSZ, &ws)) - debug("TIOCSWINSZ ioctl failed\n"); - } - - /* Optionally enable hardware flow control. */ -#ifdef CRTSCTS - if (op->flags & F_RTSCTS) - tp->c_cflag |= CRTSCTS; -#endif - /* Flush input and output queues, important for modems! */ - tcflush(STDIN_FILENO, TCIOFLUSH); - - if (tcsetattr(STDIN_FILENO, TCSANOW, tp)) - agetty_log_warn(_("setting terminal attributes failed: %m")); - - /* Go to blocking input even in local mode. */ - fcntl(STDIN_FILENO, F_SETFL, - fcntl(STDIN_FILENO, F_GETFL, 0) & ~O_NONBLOCK); - - debug("term_io 2\n"); -} - -/* Reset virtual console on stdin to its defaults */ -static void reset_vc(const struct agetty_options *op, struct termios *tp, int canon) -{ - int fl = 0; - - fl |= (op->flags & F_KEEPCFLAGS) == 0 ? 0 : UL_TTY_KEEPCFLAGS; - fl |= (op->flags & F_UTF8) == 0 ? 0 : UL_TTY_UTF8; - - reset_virtual_console(tp, fl); - -#ifdef AGETTY_RELOAD - /* - * Discard all the flags that makes the line go canonical with echoing. - * We need to know when the user starts typing. - */ - if (canon == 0) - tp->c_lflag = 0; -#endif - - if (tcsetattr(STDIN_FILENO, TCSADRAIN, tp)) - agetty_log_warn(_("setting terminal attributes failed: %m")); - - /* Go to blocking input even in local mode. */ - fcntl(STDIN_FILENO, F_SETFL, - fcntl(STDIN_FILENO, F_GETFL, 0) & ~O_NONBLOCK); -} - -/* Extract baud rate from modem status message. */ -static void auto_baud(struct termios *tp) -{ - speed_t speed; - int vmin; - unsigned iflag; - char buf[BUFSIZ]; - char *bp; - int nread; - - /* - * This works only if the modem produces its status code AFTER raising - * the DCD line, and if the computer is fast enough to set the proper - * baud rate before the message has gone by. We expect a message of the - * following format: - * - * - * - * The number is interpreted as the baud rate of the incoming call. If the - * modem does not tell us the baud rate within one second, we will keep - * using the current baud rate. It is advisable to enable BREAK - * processing (comma-separated list of baud rates) if the processing of - * modem status messages is enabled. - */ - - /* - * Use 7-bit characters, don't block if input queue is empty. Errors will - * be dealt with later on. - */ - iflag = tp->c_iflag; - /* Enable 8th-bit stripping. */ - tp->c_iflag |= ISTRIP; - vmin = tp->c_cc[VMIN]; - /* Do not block when queue is empty. */ - tp->c_cc[VMIN] = 0; - tcsetattr(STDIN_FILENO, TCSANOW, tp); - - /* - * Wait for a while, then read everything the modem has said so far and - * try to extract the speed of the dial-in call. - */ - sleep(1); - if ((nread = read(STDIN_FILENO, buf, sizeof(buf) - 1)) > 0) { - buf[nread] = '\0'; - for (bp = buf; bp < buf + nread; bp++) - if (c_isascii(*bp) && isdigit(*bp)) { - if ((speed = agetty_bcode(bp))) { - cfsetispeed(tp, speed); - cfsetospeed(tp, speed); - } - break; - } - } - - /* Restore terminal settings. Errors will be dealt with later on. */ - tp->c_iflag = iflag; - tp->c_cc[VMIN] = vmin; - tcsetattr(STDIN_FILENO, TCSANOW, tp); -} - - - static char *read_os_release(struct agetty_options *op, const char *varname) { int fd = -1; @@ -1759,7 +1309,7 @@ again: if (issue_is_changed(ie)) { if ((op->flags & F_VCONSOLE) && (op->flags & F_NOCLEAR) == 0) - termio_clear(STDOUT_FILENO); + agetty_termio_clear(STDOUT_FILENO); { /* TODO: Close to set netlink_groups again using pass 1 */ /* if (ie->nl.fd >= 0) ul_nl_close(&(ie->nl)); @@ -1840,38 +1390,7 @@ again: } /* Select next baud rate. */ -static void next_speed(struct agetty_options *op, struct termios *tp) -{ - static int baud_index = -1; - if (baud_index == -1) - /* - * If the F_KEEPSPEED flags is set then the FIRST_SPEED is not - * tested yet (see termio_init()). - */ - baud_index = - (op->flags & F_KEEPSPEED) ? FIRST_SPEED : 1 % op->numspeed; - else - baud_index = (baud_index + 1) % op->numspeed; - - cfsetispeed(tp, op->speeds[baud_index]); - cfsetospeed(tp, op->speeds[baud_index]); - tcsetattr(STDIN_FILENO, TCSANOW, tp); -} - -/* Erase visual characters for one stored character */ -static void erase_char(int visual_count, struct chardata *cp) -{ - static const char *const erase[] = { /* backspace-space-backspace */ - "\010\040\010", /* space parity */ - "\010\040\010", /* odd parity */ - "\210\240\210", /* even parity */ - "\210\240\210", /* no parity */ - }; - int i; - for (i = 0; i < visual_count; i++) - write_all(1, erase[cp->parity], 3); -} /* Get user name, establish parity, speed, erase, kill & eol. */ static char *get_logname(struct issue *ie, struct agetty_options *op, struct termios *tp, struct chardata *cp) @@ -1924,7 +1443,7 @@ static char *get_logname(struct issue *ie, struct agetty_options *op, struct ter tcflush(STDIN_FILENO, TCIFLUSH); if ((op->flags & F_VCONSOLE) && (op->flags & F_NOCLEAR) == 0) - termio_clear(STDOUT_FILENO); + agetty_termio_clear(STDOUT_FILENO); bp = logname; visual_bp = visual_widths; *bp = '\0'; @@ -2004,7 +1523,7 @@ static char *get_logname(struct issue *ie, struct agetty_options *op, struct ter cp->erase = ascval; /* set erase character */ if (bp > logname) { if ((tp->c_lflag & ECHO) == 0) - erase_char(*(visual_bp - 1), cp); + agetty_erase_char(*(visual_bp - 1), cp); bp--; visual_bp--; } @@ -2018,7 +1537,7 @@ static char *get_logname(struct issue *ie, struct agetty_options *op, struct ter break; while (bp > logname) { if ((tp->c_lflag & ECHO) == 0) - erase_char(*(visual_bp - 1), cp); + agetty_erase_char(*(visual_bp - 1), cp); bp--; visual_bp--; } @@ -2099,77 +1618,6 @@ static char *get_logname(struct issue *ie, struct agetty_options *op, struct ter return logname; } -/* Set the final tty mode bits. */ -static void termio_final(struct agetty_options *op, struct termios *tp, struct chardata *cp) -{ - /* General terminal-independent stuff. */ - - /* 2-way flow control */ - tp->c_iflag |= IXON | IXOFF; - tp->c_lflag |= ICANON | ISIG | ECHO | ECHOE | ECHOK | ECHOKE; - /* no longer| ECHOCTL | ECHOPRT */ - tp->c_oflag |= OPOST; - /* tp->c_cflag = 0; */ - tp->c_cc[VINTR] = DEF_INTR; - tp->c_cc[VQUIT] = DEF_QUIT; - tp->c_cc[VEOF] = DEF_EOF; - tp->c_cc[VEOL] = DEF_EOL; -#ifdef __linux__ - tp->c_cc[VSWTC] = DEF_SWITCH; -#elif defined(VSWTCH) - tp->c_cc[VSWTCH] = DEF_SWITCH; -#endif /* __linux__ */ - - /* Account for special characters seen in input. */ - if (cp->eol == CR) { - tp->c_iflag |= ICRNL; - tp->c_oflag |= ONLCR; - } - tp->c_cc[VERASE] = cp->erase; - tp->c_cc[VKILL] = cp->kill; - - /* Account for the presence or absence of parity bits in input. */ - switch (cp->parity) { - case 0: - /* space (always 0) parity */ - break; - case 1: - /* odd parity */ - tp->c_cflag |= PARODD; - FALLTHROUGH; - case 2: - /* even parity */ - tp->c_cflag |= PARENB; - tp->c_iflag |= INPCK | ISTRIP; - FALLTHROUGH; - case (1 | 2): - /* no parity bit */ - tp->c_cflag &= ~CSIZE; - tp->c_cflag |= CS7; - break; - } - /* Account for upper case without lower case. */ - if (cp->capslock) { -#ifdef IUCLC - tp->c_iflag |= IUCLC; -#endif -#ifdef XCASE - tp->c_lflag |= XCASE; -#endif -#ifdef OLCUC - tp->c_oflag |= OLCUC; -#endif - } - /* Optionally enable hardware flow control. */ -#ifdef CRTSCTS - if (op->flags & F_RTSCTS) - tp->c_cflag |= CRTSCTS; -#endif - - /* Finally, make the new settings effective. */ - if (tcsetattr(STDIN_FILENO, TCSANOW, tp) < 0) - agetty_log_err(_("%s: failed to set terminal attributes: %m"), op->tty); -} /* * String contains upper case without lower case. diff --git a/agetty-cmd/agetty.h b/agetty-cmd/agetty.h index 034684cd0..6c5e8cf38 100644 --- a/agetty-cmd/agetty.h +++ b/agetty-cmd/agetty.h @@ -4,8 +4,11 @@ #include #include #include +#include #include +#include "ttyutils.h" + /* * Some heuristics to find out what environment we are in: if it is not * System V, assume it is SunOS 4. The LOGIN_PROCESS is defined in System V @@ -78,10 +81,32 @@ extern char *agetty_xgethostname(void); extern char *agetty_xgetdomainname(void); extern void agetty_update_utmp(struct agetty_options *op, const char *fakehost); +enum { + CLOCAL_MODE_AUTO = 0, + CLOCAL_MODE_ALWAYS, + CLOCAL_MODE_NEVER +}; + #define FIRST_SPEED 0 +#ifdef DEBUGGING +# define debug(s) do { fprintf(dbf,s); fflush(dbf); } while (0) +extern FILE *dbf; +#else +# define debug(s) do { ; } while (0) +#endif + extern speed_t agetty_bcode(char *s); extern void agetty_list_speeds(void); extern void agetty_fprint_speed(FILE *out, speed_t speed); +extern void agetty_termio_clear(int fd); +extern void agetty_reset_vc(const struct agetty_options *op, struct termios *tp, int canon); +extern void agetty_open_tty(const char *tty, struct termios *tp, struct agetty_options *op); +extern void agetty_termio_init(struct agetty_options *op, struct termios *tp); +extern void agetty_termio_final(struct agetty_options *op, struct termios *tp, struct chardata *cp); +extern void agetty_auto_baud(struct termios *tp); +extern void agetty_next_speed(struct agetty_options *op, struct termios *tp); +extern void agetty_erase_char(int visual_count, struct chardata *cp); + #endif /* UTIL_LINUX_AGETTY_H */ diff --git a/agetty-cmd/tty.c b/agetty-cmd/tty.c index b176ff889..9826c2bb7 100644 --- a/agetty-cmd/tty.c +++ b/agetty-cmd/tty.c @@ -1,9 +1,40 @@ +#include #include +#include +#include +#include #include #include +#include +#include +#include #include +#include +#include "all-io.h" #include "agetty.h" +#include "c.h" +#include "cctype.h" +#include "nls.h" +#include "ttyutils.h" + +#ifdef USE_PLYMOUTH_SUPPORT +# include "plymouth-ctrl.h" +#endif + +#ifdef __linux__ +# include +#endif + +#if defined(__FreeBSD_kernel__) +# include +# ifdef HAVE_UTMP_H +# include +# endif +# ifdef HAVE_LIBUTIL_H +# include +# endif +#endif struct Speedtab { long speed; @@ -119,3 +150,534 @@ void agetty_fprint_speed(FILE *out, speed_t speed) } } } + +void agetty_termio_clear(int fd) +{ + /* + * Do not write a full reset (ESC c) because this destroys + * the unicode mode again if the terminal was in unicode + * mode. Also it clears the CONSOLE_MAGIC features which + * are required for some languages/console-fonts. + * Just put the cursor to the home position (ESC [ H), + * erase everything below the cursor (ESC [ J), and set the + * scrolling region to the full window (ESC [ r) + */ + write_all(fd, "\033[r\033[H\033[J", 9); +} + +void agetty_reset_vc(const struct agetty_options *op, struct termios *tp, int canon) +{ + int fl = 0; + + fl |= (op->flags & F_KEEPCFLAGS) == 0 ? 0 : UL_TTY_KEEPCFLAGS; + fl |= (op->flags & F_UTF8) == 0 ? 0 : UL_TTY_UTF8; + + reset_virtual_console(tp, fl); + +#ifdef AGETTY_RELOAD + /* + * Discard all the flags that makes the line go canonical with echoing. + * We need to know when the user starts typing. + */ + if (canon == 0) + tp->c_lflag = 0; +#endif + + if (tcsetattr(STDIN_FILENO, TCSADRAIN, tp)) + agetty_log_warn(_("setting terminal attributes failed: %m")); + + /* Go to blocking input even in local mode. */ + fcntl(STDIN_FILENO, F_SETFL, + fcntl(STDIN_FILENO, F_GETFL, 0) & ~O_NONBLOCK); +} + +void agetty_open_tty(const char *tty, struct termios *tp, struct agetty_options *op) +{ + const pid_t pid = getpid(); + int closed = 0; +#ifndef KDGKBMODE + int serial; +#endif + + /* Set up new standard input, unless we are given an already opened port. */ + + if (!op->tty_is_stdin) { + char buf[PATH_MAX+1]; + struct group *gr = NULL; + struct stat st; + int fd, len; + pid_t tid; + gid_t gid = 0; + + /* Use tty group if available */ + if ((gr = getgrnam("tty"))) + gid = gr->gr_gid; + + len = snprintf(buf, sizeof(buf), "/dev/%s", tty); + if (len < 0 || (size_t)len >= sizeof(buf)) + agetty_log_err(_("/dev/%s: cannot open as standard input: %m"), tty); + + /* Open the tty as standard input. */ + if ((fd = open(buf, O_RDWR|O_NOCTTY|O_NONBLOCK, 0)) < 0) + agetty_log_err(_("/dev/%s: cannot open as standard input: %m"), tty); + + /* + * There is always a race between this reset and the call to + * vhangup() that s.o. can use to get access to your tty. + * Linux login(1) will change tty permissions. Use root owner and group + * with permission -rw------- for the period between getty and login. + */ + if (fchown(fd, 0, gid) || fchmod(fd, (gid ? 0620 : 0600))) { + if (errno == EROFS) + agetty_log_warn("%s: %m", buf); + else + agetty_log_err("%s: %m", buf); + } + + /* Sanity checks... */ + if (fstat(fd, &st) < 0) + agetty_log_err("%s: %m", buf); + if ((st.st_mode & S_IFMT) != S_IFCHR) + agetty_log_err(_("/dev/%s: not a character device"), tty); + if (!isatty(fd)) + agetty_log_err(_("/dev/%s: not a tty"), tty); + + if (((tid = tcgetsid(fd)) < 0) || (pid != tid)) { + if (ioctl(fd, TIOCSCTTY, 1) == -1) + agetty_log_warn(_("/dev/%s: cannot get controlling tty: %m"), tty); + } + + close(STDIN_FILENO); + errno = 0; + + if (op->flags & F_HANGUP) { + + if (ioctl(fd, TIOCNOTTY)) + debug("TIOCNOTTY ioctl failed\n"); + + /* + * Let's close all file descriptors before vhangup + * https://lkml.org/lkml/2012/6/5/145 + */ + close(fd); + close(STDOUT_FILENO); + close(STDERR_FILENO); + errno = 0; + closed = 1; + + if (vhangup()) + agetty_log_err(_("/dev/%s: vhangup() failed: %m"), tty); + } else + close(fd); + + debug("open(2)\n"); + if (open(buf, O_RDWR|O_NOCTTY|O_NONBLOCK, 0) != 0) + agetty_log_err(_("/dev/%s: cannot open as standard input: %m"), tty); + + if (((tid = tcgetsid(STDIN_FILENO)) < 0) || (pid != tid)) { + if (ioctl(STDIN_FILENO, TIOCSCTTY, 1) == -1) + agetty_log_warn(_("/dev/%s: cannot get controlling tty: %m"), tty); + } + + } else { + + /* + * Standard input should already be connected to an open port. Make + * sure it is open for read/write. + */ + + if ((fcntl(STDIN_FILENO, F_GETFL, 0) & O_RDWR) != O_RDWR) + agetty_log_err(_("%s: not open for read/write"), tty); + + } + + if (tcsetpgrp(STDIN_FILENO, pid)) + agetty_log_warn(_("/dev/%s: cannot set process group: %m"), tty); + + /* Get rid of the present outputs. */ + if (!closed) { + close(STDOUT_FILENO); + close(STDERR_FILENO); + errno = 0; + } + + /* Set up standard output and standard error file descriptors. */ + debug("duping\n"); + + /* set up stdout and stderr */ + if (dup(STDIN_FILENO) != 1 || dup(STDIN_FILENO) != 2) + agetty_log_err(_("%s: dup problem: %m"), tty); + + /* make stdio unbuffered for slow modem lines */ + setvbuf(stdout, NULL, _IONBF, 0); + + /* + * The following ioctl will fail if stdin is not a tty, but also when + * there is noise on the modem control lines. In the latter case, the + * common course of action is (1) fix your cables (2) give the modem + * more time to properly reset after hanging up. + * + * SunOS users can achieve (2) by patching the SunOS kernel variable + * "zsadtrlow" to a larger value; 5 seconds seems to be a good value. + * http://www.sunmanagers.org/archives/1993/0574.html + */ + memset(tp, 0, sizeof(struct termios)); + if (tcgetattr(STDIN_FILENO, tp) < 0) + agetty_log_err(_("%s: failed to get terminal attributes: %m"), tty); + +#if defined(__FreeBSD_kernel__) + login_tty (0); +#endif + + /* + * Detect if this is a virtual console or serial/modem line. + * In case of a virtual console the ioctl KDGKBMODE succeeds + * whereas on other lines it will fails. + */ +#ifdef KDGKBMODE + if (ioctl(STDIN_FILENO, KDGKBMODE, &op->kbmode) == 0) +#else + if (ioctl(STDIN_FILENO, TIOCMGET, &serial) < 0 && (errno == EINVAL)) +#endif + { + op->flags |= F_VCONSOLE; + } else { +#ifdef K_RAW + op->kbmode = K_RAW; +#endif + } + + if (!op->term) + op->term = get_terminal_default_type(op->tty, !(op->flags & F_VCONSOLE)); + if (!op->term) + agetty_log_err(_("failed to allocate memory: %m")); + + if (setenv("TERM", op->term, 1) != 0) + agetty_log_err(_("failed to set the %s environment variable"), "TERM"); +} + +void agetty_termio_init(struct agetty_options *op, struct termios *tp) +{ + speed_t ispeed, ospeed; + struct winsize ws; +#ifdef USE_PLYMOUTH_SUPPORT + struct termios lock; + int i = (plymouth_command(MAGIC_PING) == 0) ? PLYMOUTH_TERMIOS_FLAGS_DELAY : 0; + if (i) + plymouth_command(MAGIC_QUIT); + while (i-- > 0) { + /* + * Even with TTYReset=no it seems with systemd or plymouth + * the termios flags become changed from under the first + * agetty on a serial system console as the flags are locked. + */ + memset(&lock, 0, sizeof(struct termios)); + if (ioctl(STDIN_FILENO, TIOCGLCKTRMIOS, &lock) < 0) + break; + if (!lock.c_iflag && !lock.c_oflag && !lock.c_cflag && !lock.c_lflag) + break; + debug("termios locked\n"); + sleep(1); + } + memset(&lock, 0, sizeof(struct termios)); + ioctl(STDIN_FILENO, TIOCSLCKTRMIOS, &lock); +#endif + + if (op->flags & F_VCONSOLE) { +#if defined(IUTF8) && defined(KDGKBMODE) + switch(op->kbmode) { + case K_UNICODE: + setlocale(LC_CTYPE, "C.UTF-8"); + op->flags |= F_UTF8; + break; + case K_RAW: + case K_MEDIUMRAW: + case K_XLATE: + default: + setlocale(LC_CTYPE, "POSIX"); + op->flags &= ~F_UTF8; + break; + } +#else + setlocale(LC_CTYPE, "POSIX"); + op->flags &= ~F_UTF8; +#endif + agetty_reset_vc(op, tp, 0); + + if ((tp->c_cflag & (CS8|PARODD|PARENB)) == CS8) + op->flags |= F_EIGHTBITS; + + if ((op->flags & F_NOCLEAR) == 0) + agetty_termio_clear(STDOUT_FILENO); + return; + } + + /* + * Serial line + */ + + if (op->flags & F_KEEPSPEED || !op->numspeed) { + /* Save the original setting. */ + ispeed = cfgetispeed(tp); + ospeed = cfgetospeed(tp); + + /* Save also the original speed to array of the speeds to make + * it possible to return the original after unexpected BREAKs. + */ + if (op->numspeed) + op->speeds[op->numspeed++] = ispeed ? ispeed : + ospeed ? ospeed : + TTYDEF_SPEED; + if (!ispeed) + ispeed = TTYDEF_SPEED; + if (!ospeed) + ospeed = TTYDEF_SPEED; + } else { + ospeed = ispeed = op->speeds[FIRST_SPEED]; + } + + /* + * Initial termios settings: 8-bit characters, raw-mode, blocking i/o. + * Special characters are set after we have read the login name; all + * reads will be done in raw mode anyway. Errors will be dealt with + * later on. + */ + + /* The default is set c_iflag in agetty_termio_final() according to + * chardata. Unfortunately, the chardata are not set according to the + * serial line if --autolog is enabled. In this case we do not read + * from the line at all. The best what we can do in this case is to + * keep c_iflag unmodified for --autolog. + */ + if (!op->autolog) { +#ifdef IUTF8 + tp->c_iflag = tp->c_iflag & IUTF8; + if (tp->c_iflag & IUTF8) + op->flags |= F_UTF8; +#else + tp->c_iflag = 0; +#endif + } + + tp->c_lflag = 0; + tp->c_oflag &= OPOST | ONLCR; + + if ((op->flags & F_KEEPCFLAGS) == 0) + tp->c_cflag = CS8 | HUPCL | CREAD | (tp->c_cflag & CLOCAL); + + /* + * Note that the speed is stored in the c_cflag termios field, so we have + * set the speed always when the cflag is reset. + */ + cfsetispeed(tp, ispeed); + cfsetospeed(tp, ospeed); + + /* The default is to follow setting from kernel, but it's possible + * to explicitly remove/add CLOCAL flag by -L[=]*/ + switch (op->clocal) { + case CLOCAL_MODE_ALWAYS: + tp->c_cflag |= CLOCAL; /* -L or -L=always */ + break; + case CLOCAL_MODE_NEVER: + tp->c_cflag &= ~CLOCAL; /* -L=never */ + break; + case CLOCAL_MODE_AUTO: /* -L=auto */ + break; + } + +#ifdef HAVE_STRUCT_TERMIOS_C_LINE + tp->c_line = 0; +#endif + tp->c_cc[VMIN] = 1; + tp->c_cc[VTIME] = 0; + + /* Check for terminal size and if not found set default */ + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0) { + if (ws.ws_row == 0) + ws.ws_row = 24; + if (ws.ws_col == 0) + ws.ws_col = 80; + if (ioctl(STDIN_FILENO, TIOCSWINSZ, &ws)) + debug("TIOCSWINSZ ioctl failed\n"); + } + + /* Optionally enable hardware flow control. */ +#ifdef CRTSCTS + if (op->flags & F_RTSCTS) + tp->c_cflag |= CRTSCTS; +#endif + /* Flush input and output queues, important for modems! */ + tcflush(STDIN_FILENO, TCIOFLUSH); + + if (tcsetattr(STDIN_FILENO, TCSANOW, tp)) + agetty_log_warn(_("setting terminal attributes failed: %m")); + + /* Go to blocking input even in local mode. */ + fcntl(STDIN_FILENO, F_SETFL, + fcntl(STDIN_FILENO, F_GETFL, 0) & ~O_NONBLOCK); + + debug("term_io 2\n"); +} + +void agetty_termio_final(struct agetty_options *op, struct termios *tp, + struct chardata *cp) +{ + /* General terminal-independent stuff. */ + + /* 2-way flow control */ + tp->c_iflag |= IXON | IXOFF; + tp->c_lflag |= ICANON | ISIG | ECHO | ECHOE | ECHOK | ECHOKE; + /* no longer| ECHOCTL | ECHOPRT */ + tp->c_oflag |= OPOST; + /* tp->c_cflag = 0; */ + tp->c_cc[VINTR] = DEF_INTR; + tp->c_cc[VQUIT] = DEF_QUIT; + tp->c_cc[VEOF] = DEF_EOF; + tp->c_cc[VEOL] = DEF_EOL; +#ifdef __linux__ + tp->c_cc[VSWTC] = DEF_SWITCH; +#elif defined(VSWTCH) + tp->c_cc[VSWTCH] = DEF_SWITCH; +#endif /* __linux__ */ + + /* Account for special characters seen in input. */ + if (cp->eol == CR) { + tp->c_iflag |= ICRNL; + tp->c_oflag |= ONLCR; + } + tp->c_cc[VERASE] = cp->erase; + tp->c_cc[VKILL] = cp->kill; + + /* Account for the presence or absence of parity bits in input. */ + switch (cp->parity) { + case 0: + /* space (always 0) parity */ + break; + case 1: + /* odd parity */ + tp->c_cflag |= PARODD; + FALLTHROUGH; + case 2: + /* even parity */ + tp->c_cflag |= PARENB; + tp->c_iflag |= INPCK | ISTRIP; + FALLTHROUGH; + case (1 | 2): + /* no parity bit */ + tp->c_cflag &= ~CSIZE; + tp->c_cflag |= CS7; + break; + } + /* Account for upper case without lower case. */ + if (cp->capslock) { +#ifdef IUCLC + tp->c_iflag |= IUCLC; +#endif +#ifdef XCASE + tp->c_lflag |= XCASE; +#endif +#ifdef OLCUC + tp->c_oflag |= OLCUC; +#endif + } + /* Optionally enable hardware flow control. */ +#ifdef CRTSCTS + if (op->flags & F_RTSCTS) + tp->c_cflag |= CRTSCTS; +#endif + + /* Finally, make the new settings effective. */ + if (tcsetattr(STDIN_FILENO, TCSANOW, tp) < 0) + agetty_log_err(_("%s: failed to set terminal attributes: %m"), op->tty); +} + +void agetty_auto_baud(struct termios *tp) +{ + speed_t speed; + int vmin; + unsigned iflag; + char buf[BUFSIZ]; + char *bp; + int nread; + + /* + * This works only if the modem produces its status code AFTER raising + * the DCD line, and if the computer is fast enough to set the proper + * baud rate before the message has gone by. We expect a message of the + * following format: + * + * + * + * The number is interpreted as the baud rate of the incoming call. If the + * modem does not tell us the baud rate within one second, we will keep + * using the current baud rate. It is advisable to enable BREAK + * processing (comma-separated list of baud rates) if the processing of + * modem status messages is enabled. + */ + + /* + * Use 7-bit characters, don't block if input queue is empty. Errors will + * be dealt with later on. + */ + iflag = tp->c_iflag; + /* Enable 8th-bit stripping. */ + tp->c_iflag |= ISTRIP; + vmin = tp->c_cc[VMIN]; + /* Do not block when queue is empty. */ + tp->c_cc[VMIN] = 0; + tcsetattr(STDIN_FILENO, TCSANOW, tp); + + /* + * Wait for a while, then read everything the modem has said so far and + * try to extract the speed of the dial-in call. + */ + sleep(1); + if ((nread = read(STDIN_FILENO, buf, sizeof(buf) - 1)) > 0) { + buf[nread] = '\0'; + for (bp = buf; bp < buf + nread; bp++) + if (c_isascii(*bp) && isdigit(*bp)) { + if ((speed = agetty_bcode(bp))) { + cfsetispeed(tp, speed); + cfsetospeed(tp, speed); + } + break; + } + } + + /* Restore terminal settings. Errors will be dealt with later on. */ + tp->c_iflag = iflag; + tp->c_cc[VMIN] = vmin; + tcsetattr(STDIN_FILENO, TCSANOW, tp); +} + +void agetty_next_speed(struct agetty_options *op, struct termios *tp) +{ + static int baud_index = -1; + + if (baud_index == -1) + /* + * If the F_KEEPSPEED flags is set then the FIRST_SPEED is not + * tested yet (see agetty_termio_init()). + */ + baud_index = + (op->flags & F_KEEPSPEED) ? FIRST_SPEED : 1 % op->numspeed; + else + baud_index = (baud_index + 1) % op->numspeed; + + cfsetispeed(tp, op->speeds[baud_index]); + cfsetospeed(tp, op->speeds[baud_index]); + tcsetattr(STDIN_FILENO, TCSANOW, tp); +} + +void agetty_erase_char(int visual_count, struct chardata *cp) +{ + static const char *const erase[] = { /* backspace-space-backspace */ + "\010\040\010", /* space parity */ + "\010\040\010", /* odd parity */ + "\210\240\210", /* even parity */ + "\210\240\210", /* no parity */ + }; + int i; + for (i = 0; i < visual_count; i++) + write_all(1, erase[cp->parity], 3); +}