From d0bbbdcd6eefd249637f153f9d29b37c7f545e33 Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Tue, 4 Dec 2007 09:48:40 +0000 Subject: [PATCH] getty: don't accept ancient '#' and '@' as backspace/kill line, it only confuses people. (Alexander Griesser (LKH Villach)) various other cleanups. function old new delta getty_main 2526 2546 +20 static.baud_index 4 - -4 parse_speeds 91 - -91 ------------------------------------------------------------------------------ (add/remove: 0/2 grow/shrink: 1/0 up/down: 20/-95) Total: -75 bytes text data bss dec hex filename 773152 1086 9008 783246 bf38e busybox_old 773081 1086 9008 783175 bf347 busybox_unstripped --- docs/ctty.htm | 474 +++++++++++++++++++++++++++++++++++++++++++++ loginutils/getty.c | 392 ++++++++++++++++--------------------- 2 files changed, 639 insertions(+), 227 deletions(-) create mode 100644 docs/ctty.htm diff --git a/docs/ctty.htm b/docs/ctty.htm new file mode 100644 index 000000000..26d2c7956 --- /dev/null +++ b/docs/ctty.htm @@ -0,0 +1,474 @@ + + + + The Linux kernel: Processes + + +
+

10. Processes

+ +

Before looking at the Linux implementation, first a general Unix +description of threads, processes, process groups and sessions. +

A session contains a number of process groups, and a process group +contains a number of processes, and a process contains a number +of threads. +

A session can have a controlling tty. +At most one process group in a session can be a foreground process group. +An interrupt character typed on a tty ("Teletype", i.e., terminal) +causes a signal to be sent to all members of the foreground process group +in the session (if any) that has that tty as controlling tty. +

All these objects have numbers, and we have thread IDs, process IDs, +process group IDs and session IDs. +

+

10.1 Processes +

+ +

+

Creation

+ +

A new process is traditionally started using the fork() +system call: +

+
pid_t p;
+
+p = fork();
+if (p == (pid_t) -1)
+        /* ERROR */
+else if (p == 0)
+        /* CHILD */
+else
+        /* PARENT */
+
+
+

This creates a child as a duplicate of its parent. +Parent and child are identical in almost all respects. +In the code they are distinguished by the fact that the parent +learns the process ID of its child, while fork() +returns 0 in the child. (It can find the process ID of its +parent using the getppid() system call.) +

+

Termination

+ +

Normal termination is when the process does +

+
exit(n);
+
+
+ +or +
+
return n;
+
+
+ +from its main() procedure. It returns the single byte n +to its parent. +

Abnormal termination is usually caused by a signal. +

+

Collecting the exit code. Zombies

+ +

The parent does +

+
pid_t p;
+int status;
+
+p = wait(&status);
+
+
+ +and collects two bytes: +

+

+ + + +

A process that has terminated but has not yet been waited for +is a zombie. It need only store these two bytes: +exit code and reason for termination. +

On the other hand, if the parent dies first, init (process 1) +inherits the child and becomes its parent. +

+

Signals

+ +

+

Stopping

+ +

Some signals cause a process to stop: +SIGSTOP (stop!), +SIGTSTP (stop from tty: probably ^Z was typed), +SIGTTIN (tty input asked by background process), +SIGTTOU (tty output sent by background process, and this was +disallowed by stty tostop). +

Apart from ^Z there also is ^Y. The former stops the process +when it is typed, the latter stops it when it is read. +

Signals generated by typing the corresponding character on some tty +are sent to all processes that are in the foreground process group +of the session that has that tty as controlling tty. (Details below.) +

If a process is being traced, every signal will stop it. +

+

Continuing

+ +

SIGCONT: continue a stopped process. +

+

Terminating

+ +

SIGKILL (die! now!), +SIGTERM (please, go away), +SIGHUP (modem hangup), +SIGINT (^C), +SIGQUIT (^\), etc. +Many signals have as default action to kill the target. +(Sometimes with an additional core dump, when such is +allowed by rlimit.) +The signals SIGCHLD and SIGWINCH +are ignored by default. +All except SIGKILL and SIGSTOP can be +caught or ignored or blocked. +For details, see signal(7). +

+

10.2 Process groups +

+ +

Every process is member of a unique process group, +identified by its process group ID. +(When the process is created, it becomes a member of the process group +of its parent.) +By convention, the process group ID of a process group +equals the process ID of the first member of the process group, +called the process group leader. +A process finds the ID of its process group using the system call +getpgrp(), or, equivalently, getpgid(0). +One finds the process group ID of process p using +getpgid(p). +

One may use the command ps j to see PPID (parent process ID), +PID (process ID), PGID (process group ID) and SID (session ID) +of processes. With a shell that does not know about job control, +like ash, each of its children will be in the same session +and have the same process group as the shell. With a shell that knows +about job control, like bash, the processes of one pipeline. like +

+
% cat paper | ideal | pic | tbl | eqn | ditroff > out
+
+
+ +form a single process group. +

+

Creation

+ +

A process pid is put into the process group pgid by +

+
setpgid(pid, pgid);
+
+
+ +If pgid == pid or pgid == 0 then this creates +a new process group with process group leader pid. +Otherwise, this puts pid into the already existing +process group pgid. +A zero pid refers to the current process. +The call setpgrp() is equivalent to setpgid(0,0). +

+

Restrictions on setpgid()

+ +

The calling process must be pid itself, or its parent, +and the parent can only do this before pid has done +exec(), and only when both belong to the same session. +It is an error if process pid is a session leader +(and this call would change its pgid). +

+

Typical sequence

+ +

+

+
p = fork();
+if (p == (pid_t) -1) {
+        /* ERROR */
+} else if (p == 0) {    /* CHILD */
+        setpgid(0, pgid);
+        ...
+} else {                /* PARENT */
+        setpgid(p, pgid);
+        ...
+}
+
+
+ +This ensures that regardless of whether parent or child is scheduled +first, the process group setting is as expected by both. +

+

Signalling and waiting

+ +

One can signal all members of a process group: +

+
killpg(pgrp, sig);
+
+
+

One can wait for children in ones own process group: +

+
waitpid(0, &status, ...);
+
+
+ +or in a specified process group: +
+
waitpid(-pgrp, &status, ...);
+
+
+

+

Foreground process group

+ +

Among the process groups in a session at most one can be +the foreground process group of that session. +The tty input and tty signals (signals generated by ^C, ^Z, etc.) +go to processes in this foreground process group. +

A process can determine the foreground process group in its session +using tcgetpgrp(fd), where fd refers to its +controlling tty. If there is none, this returns a random value +larger than 1 that is not a process group ID. +

A process can set the foreground process group in its session +using tcsetpgrp(fd,pgrp), where fd refers to its +controlling tty, and pgrp is a process group in the +its session, and this session still is associated to the controlling +tty of the calling process. +

How does one get fd? By definition, /dev/tty +refers to the controlling tty, entirely independent of redirects +of standard input and output. (There is also the function +ctermid() to get the name of the controlling terminal. +On a POSIX standard system it will return /dev/tty.) +Opening the name of the +controlling tty gives a file descriptor fd. +

+

Background process groups

+ +

All process groups in a session that are not foreground +process group are background process groups. +Since the user at the keyboard is interacting with foreground +processes, background processes should stay away from it. +When a background process reads from the terminal it gets +a SIGTTIN signal. Normally, that will stop it, the job control shell +notices and tells the user, who can say fg to continue +this background process as a foreground process, and then this +process can read from the terminal. But if the background process +ignores or blocks the SIGTTIN signal, or if its process group +is orphaned (see below), then the read() returns an EIO error, +and no signal is sent. (Indeed, the idea is to tell the process +that reading from the terminal is not allowed right now. +If it wouldn't see the signal, then it will see the error return.) +

When a background process writes to the terminal, it may get +a SIGTTOU signal. May: namely, when the flag that this must happen +is set (it is off by default). One can set the flag by +

+
% stty tostop
+
+
+ +and clear it again by +
+
% stty -tostop
+
+
+ +and inspect it by +
+
% stty -a
+
+
+ +Again, if TOSTOP is set but the background process ignores or blocks +the SIGTTOU signal, or if its process group is orphaned (see below), +then the write() returns an EIO error, and no signal is sent. +

+

Orphaned process groups

+ +

The process group leader is the first member of the process group. +It may terminate before the others, and then the process group is +without leader. +

A process group is called orphaned when the +parent of every member is either in the process group +or outside the session. +In particular, the process group of the session leader +is always orphaned. +

If termination of a process causes a process group to become +orphaned, and some member is stopped, then all are sent first SIGHUP +and then SIGCONT. +

The idea is that perhaps the parent of the process group leader +is a job control shell. (In the same session but a different +process group.) As long as this parent is alive, it can +handle the stopping and starting of members in the process group. +When it dies, there may be nobody to continue stopped processes. +Therefore, these stopped processes are sent SIGHUP, so that they +die unless they catch or ignore it, and then SIGCONT to continue them. +

Note that the process group of the session leader is already +orphaned, so no signals are sent when the session leader dies. +

Note also that a process group can become orphaned in two ways +by termination of a process: either it was a parent and not itself +in the process group, or it was the last element of the process group +with a parent outside but in the same session. +Furthermore, that a process group can become orphaned +other than by termination of a process, namely when some +member is moved to a different process group. +

+

10.3 Sessions +

+ +

Every process group is in a unique session. +(When the process is created, it becomes a member of the session +of its parent.) +By convention, the session ID of a session +equals the process ID of the first member of the session, +called the session leader. +A process finds the ID of its session using the system call +getsid(). +

Every session may have a controlling tty, +that then also is called the controlling tty of each of +its member processes. +A file descriptor for the controlling tty is obtained by +opening /dev/tty. (And when that fails, there was no +controlling tty.) Given a file descriptor for the controlling tty, +one may obtain the SID using tcgetsid(fd). +

A session is often set up by a login process. The terminal +on which one is logged in then becomes the controlling tty +of the session. All processes that are descendants of the +login process will in general be members of the session. +

+

Creation

+ +

A new session is created by +

+
pid = setsid();
+
+
+ +This is allowed only when the current process is not a process group leader. +In order to be sure of that we fork first: +
+
p = fork();
+if (p) exit(0);
+pid = setsid();
+
+
+ +The result is that the current process (with process ID pid) +becomes session leader of a new session with session ID pid. +Moreover, it becomes process group leader of a new process group. +Both session and process group contain only the single process pid. +Furthermore, this process has no controlling tty. +

The restriction that the current process must not be a process group leader +is needed: otherwise its PID serves as PGID of some existing process group +and cannot be used as the PGID of a new process group. +

+

Getting a controlling tty

+ +

How does one get a controlling terminal? Nobody knows, +this is a great mystery. +

The System V approach is that the first tty opened by the process +becomes its controlling tty. +

The BSD approach is that one has to explicitly call +

+
ioctl(fd, TIOCSCTTY, ...);
+
+
+ +to get a controlling tty. +

Linux tries to be compatible with both, as always, and this +results in a very obscure complex of conditions. Roughly: +

The TIOCSCTTY ioctl will give us a controlling tty, +provided that (i) the current process is a session leader, +and (ii) it does not yet have a controlling tty, and +(iii) maybe the tty should not already control some other session; +if it does it is an error if we aren't root, or we steal the tty +if we are all-powerful. +

Opening some terminal will give us a controlling tty, +provided that (i) the current process is a session leader, and +(ii) it does not yet have a controlling tty, and +(iii) the tty does not already control some other session, and +(iv) the open did not have the O_NOCTTY flag, and +(v) the tty is not the foreground VT, and +(vi) the tty is not the console, and +(vii) maybe the tty should not be master or slave pty. +

+

Getting rid of a controlling tty

+ +

If a process wants to continue as a daemon, it must detach itself +from its controlling tty. Above we saw that setsid() +will remove the controlling tty. Also the ioctl TIOCNOTTY does this. +Moreover, in order not to get a controlling tty again as soon as it +opens a tty, the process has to fork once more, to assure that it +is not a session leader. Typical code fragment: +

+

        if ((fork()) != 0)
+                exit(0);
+        setsid();
+        if ((fork()) != 0)
+                exit(0);
+
+

See also daemon(3). +

+

Disconnect

+ +

If the terminal goes away by modem hangup, and the line was not local, +then a SIGHUP is sent to the session leader. +Any further reads from the gone terminal return EOF. +(Or possibly -1 with errno set to EIO.) +

If the terminal is the slave side of a pseudotty, and the master side +is closed (for the last time), then a SIGHUP is sent to the foreground +process group of the slave side. +

When the session leader dies, a SIGHUP is sent to all processes +in the foreground process group. Moreover, the terminal stops being +the controlling terminal of this session (so that it can become +the controlling terminal of another session). +

Thus, if the terminal goes away and the session leader is +a job control shell, then it can handle things for its descendants, +e.g. by sending them again a SIGHUP. +If on the other hand the session leader is an innocent process +that does not catch SIGHUP, it will die, and all foreground processes +get a SIGHUP. +

+

10.4 Threads +

+ +

A process can have several threads. New threads (with the same PID +as the parent thread) are started using the clone system +call using the CLONE_THREAD flag. Threads are distinguished +by a thread ID (TID). An ordinary process has a single thread +with TID equal to PID. The system call gettid() returns the +TID. The system call tkill() sends a signal to a single thread. +

Example: a process with two threads. Both only print PID and TID and exit. +(Linux 2.4.19 or later.) +

% cat << EOF > gettid-demo.c
+#include <unistd.h>
+#include <sys/types.h>
+#define CLONE_SIGHAND   0x00000800
+#define CLONE_THREAD    0x00010000
+#include <linux/unistd.h>
+#include <errno.h>
+_syscall0(pid_t,gettid)
+
+int thread(void *p) {
+        printf("thread: %d %d\n", gettid(), getpid());
+}
+
+main() {
+        unsigned char stack[4096];
+        int i;
+
+        i = clone(thread, stack+2048, CLONE_THREAD | CLONE_SIGHAND, NULL);
+        if (i == -1)
+                perror("clone");
+        else
+                printf("clone returns %d\n", i);
+        printf("parent: %d %d\n", gettid(), getpid());
+}
+EOF
+% cc -o gettid-demo gettid-demo.c
+% ./gettid-demo
+clone returns 21826
+parent: 21825 21825
+thread: 21826 21825
+%
+
+

+

+


+ + diff --git a/loginutils/getty.c b/loginutils/getty.c index d32d18935..590a05de8 100644 --- a/loginutils/getty.c +++ b/loginutils/getty.c @@ -33,7 +33,6 @@ #include #if ENABLE_FEATURE_WTMP extern void updwtmp(const char *filename, const struct utmp *ut); -static void update_utmp(const char *line); #endif #endif /* LOGIN_PROCESS */ @@ -47,6 +46,7 @@ static void update_utmp(const char *line); /* I doubt there are systems which still need this */ #undef HANDLE_ALLCAPS +#undef ANCIENT_BS_KILL_CHARS #define _PATH_LOGIN "/bin/login" @@ -76,36 +76,20 @@ static void update_utmp(const char *line); * When multiple baud rates are specified on the command line, the first one * we will try is the first one specified. */ -#define FIRST_SPEED 0 - -/* Storage for command-line options. */ - #define MAX_SPEED 10 /* max. nr. of baud rates */ +/* Storage for command-line options. */ struct options { int flags; /* toggle switches, see below */ unsigned timeout; /* time-out period */ - const char *login; /* login program */ - const char *tty; /* name of tty */ - const char *initstring; /* modem init string */ - const char *issue; /* alternative issue file */ + const char *login; /* login program */ + const char *tty; /* name of tty */ + const char *initstring; /* modem init string */ + const char *issue; /* alternative issue file */ int numspeed; /* number of baud rates to try */ int speeds[MAX_SPEED]; /* baud rates to be tried */ }; -static const char opt_string[] ALIGN1 = "I:LH:f:hil:mt:wn"; -#define F_INITSTRING (1<<0) /* initstring is set */ -#define F_LOCAL (1<<1) /* force local */ -#define F_FAKEHOST (1<<2) /* force fakehost */ -#define F_CUSTISSUE (1<<3) /* give alternative issue file */ -#define F_RTSCTS (1<<4) /* enable RTS/CTS flow control */ -#define F_ISSUE (1<<5) /* display /etc/issue */ -#define F_LOGIN (1<<6) /* non-default login program */ -#define F_PARSE (1<<7) /* process modem status messages */ -#define F_TIMEOUT (1<<8) /* time out */ -#define F_WAITCRLF (1<<9) /* wait for CR or LF */ -#define F_NOPROMPT (1<<10) /* don't ask for login name! */ - /* Storage for things detected while the login name was read. */ struct chardata { unsigned char erase; /* erase character */ @@ -117,6 +101,7 @@ struct chardata { #endif }; + /* Initial values for the above. */ static const struct chardata init_chardata = { DEF_ERASE, /* default erase character */ @@ -128,12 +113,25 @@ static const struct chardata init_chardata = { #endif }; -/* The following is used for understandable diagnostics. */ +static const char opt_string[] ALIGN1 = "I:LH:f:hil:mt:wn"; +#define F_INITSTRING (1 << 0) /* -I initstring is set */ +#define F_LOCAL (1 << 1) /* -L force local */ +#define F_FAKEHOST (1 << 2) /* -H force fakehost */ +#define F_CUSTISSUE (1 << 3) /* -f give alternative issue file */ +#define F_RTSCTS (1 << 4) /* -h enable RTS/CTS flow control */ +#define F_ISSUE (1 << 5) /* -i display /etc/issue */ +#define F_LOGIN (1 << 6) /* -l non-default login program */ +#define F_PARSE (1 << 7) /* -m process modem status messages */ +#define F_TIMEOUT (1 << 8) /* -t time out */ +#define F_WAITCRLF (1 << 9) /* -w wait for CR or LF */ +#define F_NOPROMPT (1 << 10) /* -n don't ask for login name! */ + /* Fake hostname for ut_host specified on command line. */ static char *fakehost = NULL; +#define line_buf bb_common_bufsiz1 -/* ... */ +/* The following is used for understandable diagnostics. */ #ifdef DEBUGGING #define debug(s) fprintf(dbf,s); fflush(dbf) #define DEBUGTERM "/dev/ttyp0" @@ -158,14 +156,14 @@ static int bcode(const char *s) return 0; } - /* parse_speeds - parse alternate baud rates */ static void parse_speeds(struct options *op, char *arg) { char *cp; + /* NB: at least one iteration is always done */ debug("entered parse_speeds\n"); - for (cp = strtok(arg, ","); cp != 0; cp = strtok((char *) 0, ",")) { + while ((cp = strsep(&arg, ",")) != NULL) { op->speeds[op->numspeed] = bcode(cp); if (op->speeds[op->numspeed] <= 0) bb_error_msg_and_die("bad speed: %s", cp); @@ -173,18 +171,19 @@ static void parse_speeds(struct options *op, char *arg) if (op->numspeed > MAX_SPEED) bb_error_msg_and_die("too many alternate speeds"); } - debug("exiting parsespeeds\n"); + debug("exiting parse_speeds\n"); } - /* parse_args - parse command-line arguments */ -static void parse_args(int argc, char **argv, struct options *op) +static void parse_args(char **argv, struct options *op) { char *ts; + opt_complementary = "-2"; /* at least 2 args */ op->flags = getopt32(argv, opt_string, &(op->initstring), &fakehost, &(op->issue), &(op->login), &ts); + argv += optind; if (op->flags & F_INITSTRING) { const char *p = op->initstring; char *q; @@ -202,45 +201,40 @@ static void parse_args(int argc, char **argv, struct options *op) } *q = '\0'; } - op->flags ^= F_ISSUE; /* revert flag show /etc/issue */ + op->flags ^= F_ISSUE; /* invert flag show /etc/issue */ if (op->flags & F_TIMEOUT) { - op->timeout = xatoul_range(ts, 1, INT_MAX); + op->timeout = xatoi_u(ts); } - argv += optind; - argc -= optind; - debug("after getopt loop\n"); - if (argc < 2) /* check parameter count */ - bb_show_usage(); + debug("after getopt\n"); /* we loosen up a bit and accept both "baudrate tty" and "tty baudrate" */ + op->tty = argv[0]; /* tty name */ + ts = argv[1]; /* baud rate(s) */ if (isdigit(argv[0][0])) { /* a number first, assume it's a speed (BSD style) */ - parse_speeds(op, argv[0]); /* baud rate(s) */ - op->tty = argv[1]; /* tty name */ - } else { - op->tty = argv[0]; /* tty name */ - parse_speeds(op, argv[1]); /* baud rate(s) */ + op->tty = ts; /* tty name is in argv[1] */ + ts = argv[0]; /* baud rate(s) */ } + parse_speeds(op, ts); if (argv[2]) setenv("TERM", argv[2], 1); - debug("exiting parseargs\n"); + debug("exiting parse_args\n"); } /* open_tty - set up tty as standard { input, output, error } */ -static void open_tty(const char *tty, struct termios *tp, int local) +static void open_tty(const char *tty) { - int chdir_to_root = 0; - /* Set up new standard input, unless we are given an already opened port. */ if (NOT_LONE_DASH(tty)) { struct stat st; + int cur_dir_fd; int fd; /* Sanity checks... */ + cur_dir_fd = xopen(".", O_DIRECTORY | O_NONBLOCK); xchdir("/dev"); - chdir_to_root = 1; xstat(tty, &st); if ((st.st_mode & S_IFMT) != S_IFCHR) bb_error_msg_and_die("%s: not a character device", tty); @@ -248,9 +242,23 @@ static void open_tty(const char *tty, struct termios *tp, int local) /* Open the tty as standard input. */ debug("open(2)\n"); fd = xopen(tty, O_RDWR | O_NONBLOCK); + + /* Restore current directory */ + fchdir(cur_dir_fd); + + /* Open the tty as standard input, continued */ xdup2(fd, 0); + /* fd is >= cur_dir_fd, and cur_dir_fd gets closed too here: */ while (fd > 2) close(fd--); + + /* Set proper protections and ownership. Mode 0622 + * is suitable for SYSV < 4 because /bin/login does not change + * protections. SunOS 4 login will change the protections to 0620 + * (write access for group tty) after the login has succeeded. + */ + fchown(0, 0, 0); /* 0:0 */ + fchmod(0, 0622); /* crw--w--w- */ } else { /* * Standard input should already be connected to an open port. Make @@ -259,71 +267,6 @@ static void open_tty(const char *tty, struct termios *tp, int local) if ((fcntl(0, F_GETFL) & O_RDWR) != O_RDWR) bb_error_msg_and_die("stdin is not open for read/write"); } - - /* Replace current standard output/error fd's with new ones */ - debug("duping\n"); - xdup2(0, 1); - xdup2(0, 2); - - /* - * 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. - */ - ioctl_or_perror_and_die(0, TCGETS, tp, "%s: TCGETS", tty); - - /* - * It seems to be a terminal. Set proper protections and ownership. Mode - * 0622 is suitable for SYSV <4 because /bin/login does not change - * protections. SunOS 4 login will change the protections to 0620 (write - * access for group tty) after the login has succeeded. - */ - -#ifdef DEBIAN -#warning Debian /dev/vcs[a]NN hack is deprecated and will be removed - { - /* tty to root.dialout 660 */ - struct group *gr; - int id; - - gr = getgrnam("dialout"); - id = gr ? gr->gr_gid : 0; - chown(tty, 0, id); - chmod(tty, 0660); - - /* vcs,vcsa to root.sys 600 */ - if (!strncmp(tty, "tty", 3) && isdigit(tty[3])) { - char *vcs, *vcsa; - - vcs = xstrdup(tty); - vcsa = xmalloc(strlen(tty) + 2); - strcpy(vcs, "vcs"); - strcpy(vcs + 3, tty + 3); - strcpy(vcsa, "vcsa"); - strcpy(vcsa + 4, tty + 3); - - gr = getgrnam("sys"); - id = gr ? gr->gr_gid : 0; - chown(vcs, 0, id); - chmod(vcs, 0600); - chown(vcsa, 0, id); - chmod(vcs, 0600); - - free(vcs); - free(vcsa); - } - } -#else - if (NOT_LONE_DASH(tty)) { - chown(tty, 0, 0); /* 0:0 */ - chmod(tty, 0622); /* crw--w--w- */ - } -#endif - if (chdir_to_root) - xchdir("/"); } /* termios_init - initialize termios settings */ @@ -351,17 +294,13 @@ static void termios_init(struct termios *tp, int speed, struct options *op) tp->c_cc[VTIME] = 0; /* Optionally enable hardware flow control */ - -#ifdef CRTSCTS +#ifdef CRTSCTS if (op->flags & F_RTSCTS) tp->c_cflag |= CRTSCTS; #endif ioctl(0, TCSETS, tp); - /* go to blocking input even in local mode */ - ndelay_off(0); - debug("term_io 2\n"); } @@ -393,26 +332,24 @@ static void auto_baud(char *buf, unsigned size_buf, struct termios *tp) * Use 7-bit characters, don't block if input queue is empty. Errors will * be dealt with later on. */ - iflag = tp->c_iflag; tp->c_iflag |= ISTRIP; /* enable 8th-bit stripping */ vmin = tp->c_cc[VMIN]; - tp->c_cc[VMIN] = 0; /* don't block if queue empty */ + tp->c_cc[VMIN] = 0; /* don't block if queue empty */ ioctl(0, TCSETS, 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); - nread = read(0, buf, size_buf - 1); + nread = safe_read(0, buf, size_buf - 1); if (nread > 0) { buf[nread] = '\0'; for (bp = buf; bp < buf + nread; bp++) { - if (isascii(*bp) && isdigit(*bp)) { + if (isdigit(*bp)) { speed = bcode(bp); - if (speed) { + if (speed > 0) { tp->c_cflag &= ~CBAUD; tp->c_cflag |= speed; } @@ -420,25 +357,13 @@ static void auto_baud(char *buf, unsigned size_buf, struct termios *tp) } } } - /* Restore terminal settings. Errors will be dealt with later on. */ + /* Restore terminal settings. Errors will be dealt with later on. */ tp->c_iflag = iflag; tp->c_cc[VMIN] = vmin; ioctl(0, TCSETS, tp); } -/* next_speed - select next baud rate */ -static void next_speed(struct termios *tp, struct options *op) -{ - static int baud_index = FIRST_SPEED; /* current speed index */ - - baud_index = (baud_index + 1) % op->numspeed; - tp->c_cflag &= ~CBAUD; - tp->c_cflag |= op->speeds[baud_index]; - ioctl(0, TCSETS, tp); -} - - /* do_prompt - show login prompt, optionally preceded by /etc/issue contents */ static void do_prompt(struct options *op, struct termios *tp) { @@ -449,9 +374,9 @@ static void do_prompt(struct options *op, struct termios *tp) } #ifdef HANDLE_ALLCAPS -/* caps_lock - string contains upper case without lower case */ +/* all_is_upcase - string contains upper case without lower case */ /* returns 1 if true, 0 if false */ -static int caps_lock(const char *s) +static int all_is_upcase(const char *s) { while (*s) if (islower(*s++)) @@ -460,8 +385,8 @@ static int caps_lock(const char *s) } #endif -/* get_logname - get user name, establish parity, speed, erase, kill, eol */ -/* return NULL on failure, logname on success */ +/* get_logname - get user name, establish parity, speed, erase, kill, eol; + * return NULL on BREAK, logname on success */ static char *get_logname(char *logname, unsigned size_logname, struct options *op, struct chardata *cp, struct termios *tp) { @@ -477,26 +402,19 @@ static char *get_logname(char *logname, unsigned size_logname, "\210\240\210", /* no parity */ }; - /* Initialize kill, erase, parity etc. (also after switching speeds). */ - - *cp = init_chardata; + /* NB: *cp is pre-initialized with init_chardata */ /* Flush pending input (esp. after parsing or switching the baud rate). */ - sleep(1); ioctl(0, TCFLSH, TCIFLUSH); /* Prompt for and read a login name. */ - logname[0] = '\0'; while (!logname[0]) { - /* Write issue file and prompt, with "parity" bit == 0. */ - do_prompt(op, tp); /* Read name, watch for break, parity, erase, kill, end-of-line. */ - bp = logname; cp->eol = '\0'; while (cp->eol == '\0') { @@ -508,7 +426,8 @@ static char *get_logname(char *logname, unsigned size_logname, bb_perror_msg_and_die("%s: read", op->tty); } - /* Do BREAK handling elsewhere. */ + /* BREAK. If we have speeds to try, + * return NULL (will switch speeds and return here) */ if (c == '\0' && op->numspeed > 1) return NULL; @@ -535,18 +454,22 @@ static char *get_logname(char *logname, unsigned size_logname, break; case BS: case DEL: +#ifdef ANCIENT_BS_KILL_CHARS case '#': +#endif cp->erase = ascval; /* set erase character */ if (bp > logname) { - write(1, erase[cp->parity], 3); + full_write(1, erase[cp->parity], 3); bp--; } break; case CTL('U'): +#ifdef ANCIENT_BS_KILL_CHARS case '@': +#endif cp->kill = ascval; /* set kill character */ while (bp > logname) { - write(1, erase[cp->parity], 3); + full_write(1, erase[cp->parity], 3); bp--; } break; @@ -558,7 +481,7 @@ static char *get_logname(char *logname, unsigned size_logname, } else if (bp - logname >= size_logname - 1) { bb_error_msg_and_die("%s: input overrun", op->tty); } else { - write(1, &c, 1); /* echo the character */ + full_write(1, &c, 1); /* echo the character */ *bp++ = ascval; /* and store it */ } break; @@ -568,7 +491,7 @@ static char *get_logname(char *logname, unsigned size_logname, /* Handle names with upper case and no lower case. */ #ifdef HANDLE_ALLCAPS - cp->capslock = caps_lock(logname); + cp->capslock = all_is_upcase(logname); if (cp->capslock) { for (bp = logname; *bp; bp++) if (isupper(*bp)) @@ -641,7 +564,6 @@ static void termios_final(struct options *op, struct termios *tp, struct chardat ioctl_or_perror_and_die(0, TCSETS, tp, "%s: TCSETS", op->tty); } - #ifdef SYSV_STYLE #if ENABLE_FEATURE_UTMP /* update_utmp - update our utmp entry */ @@ -666,17 +588,16 @@ static void update_utmp(const char *line) utmpname(_PATH_UTMP); setutent(); while ((utp = getutent()) - && !(utp->ut_type == INIT_PROCESS && utp->ut_pid == mypid)) - /* nothing */; + && !(utp->ut_type == INIT_PROCESS && utp->ut_pid == mypid) + ) { + continue; + } - if (utp) { + /* some inits don't initialize utmp... */ + memset(&ut, 0, sizeof(ut)); + safe_strncpy(ut.ut_id, line + 3, sizeof(ut.ut_id)); + if (utp) memcpy(&ut, utp, sizeof(ut)); - } else { - /* some inits don't initialize utmp... */ - memset(&ut, 0, sizeof(ut)); - safe_strncpy(ut.ut_id, line + 3, sizeof(ut.ut_id)); - } - /* endutent(); */ strcpy(ut.ut_user, "LOGIN"); safe_strncpy(ut.ut_line, line, sizeof(ut.ut_line)); @@ -700,42 +621,43 @@ static void update_utmp(const char *line) #endif /* CONFIG_FEATURE_UTMP */ #endif /* SYSV_STYLE */ - int getty_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int getty_main(int argc, char **argv) { - int nullfd; - char *logname = NULL; /* login name, given to /bin/login */ + int n; + char *logname; /* login name, given to /bin/login */ /* Merging these into "struct local" may _seem_ to reduce * parameter passing, but today's gcc will inline * statics which are called once anyway, so don't do that */ struct chardata chardata; /* set by get_logname() */ - struct termios termios; /* terminal mode bits */ - struct options options = { - 0, /* show /etc/issue (SYSV_STYLE) */ - 0, /* no timeout */ - _PATH_LOGIN, /* default login program */ - "tty1", /* default tty line */ - "", /* modem init string */ + struct termios termios; /* terminal mode bits */ + struct options options; + + memset(&options, 0, sizeof(options)); + options.login = _PATH_LOGIN; /* default login program */ + options.tty = "tty1"; /* default tty line */ + options.initstring = ""; /* modem init string */ #ifdef ISSUE - ISSUE, /* default issue file */ -#else - NULL, + options.issue = ISSUE; /* default issue file */ #endif - 0, /* no baud rates known yet */ - }; /* Already too late because of theoretical * possibility of getty --help somehow triggered * inadvertently before we reach this. Oh well. */ logmode = LOGMODE_NONE; + + /* Create new session, lose controlling tty, if any */ + /* docs/ctty.htm says: + * "This is allowed only when the current process + * is not a process group leader" - is this a problem? */ setsid(); - nullfd = xopen(bb_dev_null, O_RDWR); - /* dup2(nullfd, 0); - no, because of possible "getty - 9600" */ - /* open_tty() will take care of fd# 0 anyway */ - dup2(nullfd, 1); - dup2(nullfd, 2); - while (nullfd > 2) close(nullfd--); + + n = xopen(bb_dev_null, O_RDWR); + /* dup2(n, 0); - no, because of possible "getty - 9600" */ + dup2(n, 1); + dup2(n, 2); + while (n > 2) + close(n--); /* We want special flavor of error_msg_and_die */ die_sleep = 10; msg_eol = "\r\n"; @@ -744,69 +666,74 @@ int getty_main(int argc, char **argv) #ifdef DEBUGGING dbf = xfopen(DEBUGTERM, "w"); - - { - int i; - - for (i = 1; i < argc; i++) { - debug(argv[i]); - debug("\n"); - } + for (n = 1; n < argc; n++) { + debug(argv[n]); + debug("\n"); } #endif /* Parse command-line arguments. */ - parse_args(argc, argv, &options); + parse_args(argv, &options); + + debug("calling open_tty\n"); + /* Open the tty as standard input, if it is not "-" */ + open_tty(options.tty); + + debug("duping\n"); + ndelay_off(0); + xdup2(0, 1); + xdup2(0, 2); + + /* + * 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. + */ + ioctl_or_perror_and_die(0, TCGETS, &termios, "%s: TCGETS", options.tty); #ifdef SYSV_STYLE #if ENABLE_FEATURE_UTMP - /* Update the utmp file. */ + /* Update the utmp file */ update_utmp(options.tty); #endif #endif - debug("calling open_tty\n"); - /* Open the tty as standard { input, output, error }. */ - open_tty(options.tty, &termios, options.flags & F_LOCAL); - #ifdef __linux__ - { - int iv; - - iv = getpid(); - ioctl(0, TIOCSPGRP, &iv); - } + /* Make ourself a foreground process group within our session */ + tcsetpgrp(0, getpid()); + // /* Forcibly make fd 0 our controlling tty, even if another session + // * has it as a ctty. (Another session loses ctty). */ + // ioctl(0, TIOCSCTTY, (void*)1); #endif + /* Initialize the termios settings (raw mode, eight-bit, blocking i/o). */ debug("calling termios_init\n"); - termios_init(&termios, options.speeds[FIRST_SPEED], &options); + termios_init(&termios, options.speeds[0], &options); - /* write the modem init string and DON'T flush the buffers */ + /* Write the modem init string and DON'T flush the buffers */ if (options.flags & F_INITSTRING) { debug("writing init string\n"); - write(1, options.initstring, strlen(options.initstring)); - } - - if (!(options.flags & F_LOCAL)) { - /* go to blocking write mode unless -L is specified */ - ndelay_off(1); + full_write(1, options.initstring, strlen(options.initstring)); } - /* Optionally detect the baud rate from the modem status message. */ + /* Optionally detect the baud rate from the modem status message */ debug("before autobaud\n"); if (options.flags & F_PARSE) - auto_baud(bb_common_bufsiz1, sizeof(bb_common_bufsiz1), &termios); + auto_baud(line_buf, sizeof(line_buf), &termios); - /* Set the optional timer. */ + /* Set the optional timer */ if (options.timeout) alarm(options.timeout); - /* optionally wait for CR or LF before writing /etc/issue */ + /* Optionally wait for CR or LF before writing /etc/issue */ if (options.flags & F_WAITCRLF) { char ch; debug("waiting for cr-lf\n"); - while (read(0, &ch, 1) == 1) { + while (safe_read(0, &ch, 1) == 1) { ch &= 0x7f; /* strip "parity bit" */ #ifdef DEBUGGING fprintf(dbf, "read %c\n", ch); @@ -816,31 +743,42 @@ int getty_main(int argc, char **argv) } } + logname = NULL; chardata = init_chardata; if (!(options.flags & F_NOPROMPT)) { - /* Read the login name. */ - debug("reading login name\n"); - logname = get_logname(bb_common_bufsiz1, sizeof(bb_common_bufsiz1), + /* NB:termios_init already set line speed + * to options.speeds[0] */ + int baud_index = 0; + + while (1) { + /* Read the login name. */ + debug("reading login name\n"); + logname = get_logname(line_buf, sizeof(line_buf), &options, &chardata, &termios); - while (logname == NULL) - next_speed(&termios, &options); + if (logname) + break; + /* we are here only if options.numspeed > 1 */ + baud_index = (baud_index + 1) % options.numspeed; + termios.c_cflag &= ~CBAUD; + termios.c_cflag |= options.speeds[baud_index]; + ioctl(0, TCSETS, &termios); + } } /* Disable timer. */ - if (options.timeout) alarm(0); /* Finalize the termios settings. */ - termios_final(&options, &termios, &chardata); /* Now the newline character should be properly written. */ - - write(1, "\n", 1); + full_write(1, "\n", 1); /* Let the login program take care of password validation. */ - - execl(options.login, options.login, "--", logname, (char *) 0); + /* We use PATH because we trust that root doesn't set "bad" PATH, + * and getty is not suid-root applet. */ + /* Hmm... with -n, logname == NULL! Is it ok? */ + BB_EXECLP(options.login, options.login, "--", logname, NULL); bb_error_msg_and_die("%s: can't exec %s", options.tty, options.login); } -- 2.25.1