diff --git a/Makefile.am b/Makefile.am index d7b2b45d..f4ba55cd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,6 @@ ACLOCAL_AMFLAGS = -I m4 sbin_PROGRAMS = finit initctl reboot -finit_SOURCES = finit.c api.c client.c conf.c exec.c helpers.c pid.c \ +finit_SOURCES = finit.c api.c client.c conf.c exec.c getty.c helpers.c pid.c \ sig.c svc.c service.c plugin.c tty.c cond.c cond-w.c sm.c pkginclude_HEADERS = cond.h finit.h helpers.h inetd.h plugin.h svc.h if INETD diff --git a/configure.ac b/configure.ac index d9fd8bf6..1ea9883a 100644 --- a/configure.ac +++ b/configure.ac @@ -122,14 +122,8 @@ AC_ARG_WITH(random-seed, AS_IF([test "x$enable_embedded" = "xyes"],[ AC_DEFINE(EMBEDDED_SYSTEM, 1, [Target an embedded system with BusyBox]) AC_DEFINE(SETUP_DEVFS, "/sbin/mdev -s", [Command to populate /dev]) - AC_DEFINE(GETTY, "/sbin/getty -L", [Command to start getty on login prompt]) - AC_DEFINE(GETTY_BUSYBOX, 1, [Use BusyBox getty call syntax]) - AC_DEFINE(BAUDRATE, 115200, [Default getty baudrate]) ],[ - AC_DEFINE(SETUP_DEVFS, "/sbin/udevd --daemon", [Command to populate /dev]) - AC_DEFINE(GETTY, "/sbin/getty -8", [Command to start getty on login prompt]) - AC_DEFINE(GETTY_AGETTY, 1, [Use standard agetty call syntax]) - AC_DEFINE(BAUDRATE, 38400, [Default getty baudrate])]) + AC_DEFINE(SETUP_DEVFS, "/sbin/udevd --daemon", [Command to populate /dev])]) AS_IF([test "x$enable_rw_rootfs" = "xyes"],[ AC_DEFINE(REMOUNT_ROOTFS, 1, [Remount / as rw at boot])]) diff --git a/doc/config.md b/doc/config.md index f053fd24..6a8faf24 100644 --- a/doc/config.md +++ b/doc/config.md @@ -120,33 +120,21 @@ Syntax: * `include ` Include another configuration file. Absolute path required. -* `tty [LVLS] ` +* `tty [LVLS] [BAUD] [TERM]` Start a getty on the given TTY device DEV, in the given runlevels. If - no tty setting is given in `finit.conf`, or if `/bin/sh` is given as - argument instead of a device path, a single shell is started on the - default console. Useful for really bare-bones systems. - - It is also possible to supply the full command line, with arguments, - to your getty. In this case finit will simply use that command. - - See `finit.h` for the `#define GETTY` that is called, along with the - default baud rate. + no tty setting is given in `finit.conf` no login is possible. Use the + `service` stanza to start a stand-alone shell for really bare bones + systems. - **Note:** BusyBox getty, used when Finit is built for embedded - systems, can take a `DEV` with *or* without `/dev/` prefix. Make - sure to call the configure script with `--enable-embedded` if this - is what you want. + Finit comes with a lightweight built-in getty, which after it has + opened a TTY and the user has input a username, calls `/bin/login` + like any other getty. -* `console ` + Use this, along with a matching `tty DEV` line, to mark this TTY as a + special "console" port with with `prctl()`. Useful in use-cases when + all logins except the console must be stopped, e.g. when performing a + flash upgrade on an embedded system. When running make install no default `/etc/finit.conf` will be installed since system requirements differ too much. Try out the diff --git a/exec.c b/exec.c index 7842c2a9..216c07ef 100644 --- a/exec.c +++ b/exec.c @@ -26,16 +26,8 @@ #include /* isdigit() */ #include -#include -#include #include -#include -#include -#include #include -#include -#include -#include #include #include @@ -220,67 +212,23 @@ int run_interactive(char *cmd, char *fmt, ...) return status; } -pid_t run_getty(char *cmd, char *args[], char *tty, int console) +int getty(char *tty, char *baud, char *term); + +pid_t run_getty(char *tty, char *speed, char *term, int console) { pid_t pid = fork(); if (!pid) { - int i, fd; - char c; - struct sigaction sa; - /* Reset signal handlers that were set by the parent process */ - for (i = 1; i < NSIG; i++) - DFLSIG(sa, i, 0); - - /* Detach from initial controlling TTY */ - vhangup(); - - close(STDERR_FILENO); - close(STDOUT_FILENO); - close(STDIN_FILENO); - - /* Attach TTY to console */ - fd = open(tty, O_RDWR); - if (fd != STDIN_FILENO) - exit(1); - - dup2(fd, STDIN_FILENO); - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); + sig_unblock(); + setsid(); if (console) prctl(PR_SET_NAME, "console", 0, 0, 0); else prctl(PR_SET_NAME, "finit-getty", 0, 0, 0); - sig_unblock(); - setsid(); - - if (ioctl(STDIN_FILENO, TIOCSCTTY, 1) < 0) - _pe("Failed TIOCSCTTY"); - - while (!fexist(SYNC_SHUTDOWN)) { - static const char msg[] = "\nPlease press Enter to activate this console."; - - if (fexist(SYNC_STOPPED)) { - sleep(1); - continue; - } - - (void)write(STDERR_FILENO, msg, sizeof(msg)); - while (read(STDIN_FILENO, &c, 1) == 1 && c != '\n') - continue; - - if (fexist(SYNC_STOPPED)) - continue; - - execv(cmd, args); - } - - close(fd); - vhangup(); - exit(0); + _exit(getty(tty, speed, term)); } return pid; diff --git a/getty.c b/getty.c new file mode 100644 index 00000000..298db4ef --- /dev/null +++ b/getty.c @@ -0,0 +1,319 @@ +/* Initialize and serve a login terminal + * + * Copyright (c) 2016 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "finit.h" +#include "helpers.h" +#include "sig.h" + +#ifndef _PATH_LOGIN +#define _PATH_LOGIN "/bin/login" +#endif + +#define print(s) (void)write(STDOUT_FILENO, s, strlen(s)) + + +/* + * Read one character from stdin. + */ +static int readch(char *tty) +{ + int st; + char ch1; + + st = read(STDIN_FILENO, &ch1, 1); + if (st == 0) { + print("\n"); + _exit(0); + } + + if (st < 0) + errx(1, "getty: %s: read error.", tty); + + return ch1 & 0xFF; +} + +static void stty(int fd, speed_t speed) +{ + struct termios term; + + tcdrain(fd); + if (tcgetattr(fd, &term)) + return; + + cfsetispeed(&term, speed); + cfsetospeed(&term, speed); + tcsetattr(fd, TCSAFLUSH, &term); + + tcflush(fd, TCIOFLUSH); + + /* Clear screen -- XXX: add option to disable */ + print("\033[r\033[H\033[J"); +} + + +/* + * Parse and display a line from /etc/issue + */ +static void do_parse(char *line, struct utsname *uts, char *tty) +{ + char *s, *s0; + + s0 = line; + for (s = line; *s != 0; s++) { + if (*s == '\\') { + (void)write(1, s0, s - s0); + s0 = s + 2; + switch (*++s) { + case 'l': + print(tty); + break; + case 'm': + print(uts->machine); + break; + case 'n': + print(uts->nodename); + break; +#ifdef _GNU_SOURCE + case 'o': + print(uts->domainname); + break; +#endif + case 'r': + print(uts->release); + break; + case 's': + print(uts->sysname); + break; + case 'v': + print(uts->version); + break; + case 0: + goto leave; + default: + s0 = s - 1; + } + } + } + +leave: + (void)write(1, s0, s - s0); +} + +/* + * Parse and display /etc/issue + */ +static void do_issue(char *tty) +{ + FILE *fp; + char buf[BUFSIZ] = "Welcome to \\s \\v \\n \\l\n\n"; + struct utsname uts; + + /* + * Get data about this machine. + */ + uname(&uts); + + print("\n"); + fp = fopen("/etc/issue", "r"); + if (fp) { + while (fgets(buf, sizeof(buf), fp)) + do_parse(buf, &uts, tty); + + fclose(fp); + } else { + do_parse(buf, &uts, tty); + } + + do_parse("\\n login: ", &uts, tty); +} + +/* + * Handle the process of a GETTY. + */ +static void do_getty(char *tty, char *name, size_t len) +{ + int ch; + char *np; + + /* + * Clean up tty name. + */ + if (!strncmp(tty, _PATH_DEV, strlen(_PATH_DEV))) + tty += 5; + + /* + * Display prompt. + */ + ch = ' '; + *name = '\0'; + while (ch != '\n') { + do_issue(tty); + + np = name; + while ((ch = readch(tty)) != '\n') { + if (np < name + len) + *np++ = ch; + } + + *np = '\0'; + if (*name == '\0') + ch = ' '; /* blank line typed! */ + } + + name[len - 1] = 0; +} + +/* Execute the login(1) command with the current + * username as its argument. It will reply to the + * calling user by typing "Password: "... + */ +static int do_login(char *name) +{ + struct stat st; + + execl(_PATH_LOGIN, _PATH_LOGIN, name, NULL); + + /* + * Failed to exec login, should be impossible. Try a starting a + * fallback shell instead. + * + * Note: Add /etc/securetty handling. + */ + warnx("Failed exec %s, attempting fallback to %s ...", _PATH_LOGIN, _PATH_BSHELL); + if (fstat(0, &st) == 0 && S_ISCHR(st.st_mode)) + execl(_PATH_BSHELL, _PATH_BSHELL, NULL); + + return 1; /* We shouldn't get here ... */ +} + +static speed_t do_parse_speed(char *baud) +{ + char *ptr; + size_t i; + unsigned long val; + struct { unsigned long val; speed_t speed; } v2s[] = { + { 0, B0 }, + { 50, B50 }, + { 75, B75 }, + { 110, B110 }, + { 134, B134 }, + { 150, B150 }, + { 200, B200 }, + { 300, B300 }, + { 600, B600 }, + { 1200, B1200 }, + { 1800, B1800 }, + { 2400, B2400 }, + { 4800, B4800 }, + { 9600, B9600 }, + { 19200, B19200 }, + { 38400, B38400 }, + { 57600, B57600 }, + { 115200, B115200 }, + { 230400, B230400 }, + { 460800, B460800 }, + { 500000, B500000 }, + { 576000, B576000 }, + { 921600, B921600 }, + { 1000000, B1000000 }, + { 1152000, B1152000 }, + { 1500000, B1500000 }, + { 2000000, B2000000 }, + { 2500000, B2500000 }, + { 3000000, B3000000 }, + { 3500000, B3500000 }, + { 4000000, B4000000 }, + }; + + errno = 0; + val = strtoul(baud, &ptr, 10); + if (errno || ptr == baud) + return B0; + + for (i = 0; i < sizeof(v2s) / sizeof(v2s[0]); i++) { + if (v2s[i].val == val) + return v2s[i].speed; + } + + return B0; +} + +int getty(char *tty, char *baud, char *UNUSED(term)) +{ + int fd; + char name[30]; + speed_t speed = B38400; + struct sigaction sa; + + if (baud) { + speed = do_parse_speed(baud); + if (speed == B0) { + FLOG_ERROR("TTY %s: Invalid speed %s", tty, baud); + return 1; + } + } + + /* Detach from initial controlling TTY */ + vhangup(); + + fd = open(tty, O_RDWR); + if (fd < 0) + err(1, "Failed opening %s", tty); + + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + + if (ioctl(STDIN_FILENO, TIOCSCTTY, 1) < 0) + warn("Failed TIOCSCTTY"); + + /* + * Don't let QUIT dump core. + */ + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = _exit; + sigaction(SIGQUIT, &sa, NULL); + + stty(fd, speed); +// do_utmp(); + + do_getty(tty, name, sizeof(name)); + return do_login(name); +} + +/** + * Local Variables: + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/helpers.h b/helpers.h index dc532946..0eae47c9 100644 --- a/helpers.h +++ b/helpers.h @@ -83,7 +83,7 @@ void set_hostname (char **hostname); int complete (char *cmd, int pid); int run (char *cmd); int run_interactive (char *cmd, char *fmt, ...); -pid_t run_getty (char *cmd, char *args[], char *tty, int console); +pid_t run_getty (char *tty, char *speed, char *term, int console); int run_parts (char *dir, char *cmd); #endif /* FINIT_HELPERS_H_ */ diff --git a/tty.c b/tty.c index e0092436..50617768 100644 --- a/tty.c +++ b/tty.c @@ -39,8 +39,8 @@ LIST_HEAD(, tty_node) tty_list = LIST_HEAD_INITIALIZER(); int tty_register(char *line) { tty_node_t *entry; - int insert = 0, baud = 0; - char *cmd, *dev; + int insert = 0; + char *cmd, *dev, *baud = NULL; char *runlevels = NULL, *term = NULL; if (!line) { @@ -66,7 +66,7 @@ int tty_register(char *line) cmd = strtok(NULL, " "); if (cmd) { - baud = strtonum(cmd, 50, 4000000, NULL); + baud = strdup(cmd); term = strtok(NULL, " "); } @@ -79,12 +79,10 @@ int tty_register(char *line) } entry->data.name = strdup(dev); - if (!baud) - baud = BAUDRATE; entry->data.baud = baud; entry->data.term = term ? strdup(term) : NULL; entry->data.runlevels = conf_parse_runlevels(runlevels); - _d("Registering tty %s at %d baud with term=%s on runlevels %s", dev, baud, term ?: "N/A", runlevels); + _d("Registering tty %s at %s baud with term=%s on runlevels %s", dev, baud ?: "NULL", term ?: "N/A", runlevels); if (insert) LIST_INSERT_HEAD(&tty_list, entry, link); @@ -127,40 +125,43 @@ tty_node_t *tty_find_by_pid(pid_t pid) return NULL; } -void tty_start(finit_tty_t *tty) +static char *canonicalize(char *tty) { - int i = 0, is_console = 0; - char *cmd, *arg; - char line[FILENAME_MAX]; - char *args[42]; + struct stat st; + static char path[80]; - if (tty->pid) + if (!tty) + return NULL; + + strlcpy(path, tty, sizeof(path)); + if (stat(path, &st)) { + snprintf(path, sizeof(path), "%s%s", _PATH_DEV, tty); + if (stat(path, &st)) + return NULL; + } + + if (!S_ISCHR(st.st_mode)) + return NULL; + + return path; +} + +void tty_start(finit_tty_t *fitty) +{ + int is_console = 0; + char *tty; + + if (fitty->pid) return; - if (console && !strcmp(tty->name, console)) + tty = canonicalize(fitty->name); + if (!tty) + return; + + if (console && !strcmp(tty, console)) is_console = 1; - if (!strncmp(tty->name, _PATH_DEV, strlen(_PATH_DEV))) { -#if defined(GETTY_AGETTY) - snprintf(line, sizeof(line), "%s %s %d %s", GETTY, tty->name, tty->baud, tty->term ?: ""); -#elif defined(GETTY_BUSYBOX) - snprintf(line, sizeof(line), "%s %d %s %s", GETTY, tty->baud, tty->name, tty->term ?: ""); -#else - strlcpy(line, FALLBACK_SHELL, sizeof(line)); - is_console = 1; -#endif - } else { - strlcpy(line, tty->name, sizeof(line)); - is_console = 1; - } - cmd = strtok(line, " "); - args[i++] = basename(cmd); - while ((arg = strtok(NULL, " "))) - args[i++] = arg; - args[i] = NULL; - - _d("Starting %s: %s on %s", is_console ? "console" : "TTY", cmd, tty->name); - tty->pid = run_getty(cmd, args, tty->name, is_console); + fitty->pid = run_getty(tty, fitty->baud, fitty->term, is_console); } void tty_stop(finit_tty_t *tty) diff --git a/tty.h b/tty.h index 15cff1b4..74232099 100644 --- a/tty.h +++ b/tty.h @@ -32,7 +32,7 @@ typedef struct { char *name; - int baud; + char *baud; char *term; int runlevels;