]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
agetty: split out terminal functions to tty.c
authorKarel Zak <kzak@redhat.com>
Thu, 14 May 2026 08:57:21 +0000 (10:57 +0200)
committerKarel Zak <kzak@redhat.com>
Thu, 14 May 2026 11:14:57 +0000 (13:14 +0200)
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 <kzak@redhat.com>
agetty-cmd/agetty.c
agetty-cmd/agetty.h
agetty-cmd/tty.c

index 7dc24e905ea3edb2ba449dcebf9a51cb94d2d9c1..d78975ca7f833dd129f543bbd0aca23b25c36004 100644 (file)
@@ -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[=<mode>]*/
-       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:
-        *
-        * <junk><number><junk>
-        *
-        * 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.
index 034684cd00bf58c6af92b5eaddb6569281617b2e..6c5e8cf38c0183f2f2c6f58d49a49f6ffaa1e343 100644 (file)
@@ -4,8 +4,11 @@
 #include <stddef.h>
 #include <stdio.h>
 #include <termios.h>
+#include <unistd.h>
 #include <utmpx.h>
 
+#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 */
index b176ff88997084b645f59c41b954bc31e887e0ce..9826c2bb7429f1a27144c84120e6061f8a69244a 100644 (file)
@@ -1,9 +1,40 @@
+#include <ctype.h>
 #include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <locale.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
 #include <termios.h>
+#include <unistd.h>
 
+#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 <sys/kd.h>
+#endif
+
+#if defined(__FreeBSD_kernel__)
+# include <pty.h>
+# ifdef HAVE_UTMP_H
+#  include <utmp.h>
+# endif
+# ifdef HAVE_LIBUTIL_H
+#  include <libutil.h>
+# 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[=<mode>]*/
+       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:
+        *
+        * <junk><number><junk>
+        *
+        * 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);
+}