diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:42:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:42:50 +0000 |
commit | 8cb83eee5a58b1fad74c34094ce3afb9e430b5a4 (patch) | |
tree | a9b2e7baeca1be40eb734371e3c8b11b02294497 /login-utils/sulogin-consoles.c | |
parent | Initial commit. (diff) | |
download | util-linux-93fc1e48d951bf0d0c3e3d7ff6a2489f43f9c857.tar.xz util-linux-93fc1e48d951bf0d0c3e3d7ff6a2489f43f9c857.zip |
Adding upstream version 2.33.1.upstream/2.33.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'login-utils/sulogin-consoles.c')
-rw-r--r-- | login-utils/sulogin-consoles.c | 828 |
1 files changed, 828 insertions, 0 deletions
diff --git a/login-utils/sulogin-consoles.c b/login-utils/sulogin-consoles.c new file mode 100644 index 0000000..2c0eed3 --- /dev/null +++ b/login-utils/sulogin-consoles.c @@ -0,0 +1,828 @@ +/* + * consoles.c Routines to detect the system consoles + * + * Copyright (c) 2011 SuSE LINUX Products GmbH, All rights reserved. + * Copyright (C) 2012 Karel Zak <kzak@redhat.com> + * Copyright (C) 2012 Werner Fink <werner@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * Author: Werner Fink <werner@suse.de> + */ + +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#ifdef __linux__ +# include <sys/vt.h> +# include <sys/kd.h> +# include <linux/serial.h> +# include <linux/major.h> +#endif +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#ifdef USE_SULOGIN_EMERGENCY_MOUNT +# include <sys/mount.h> +# include <linux/fs.h> +# include <linux/magic.h> +# ifndef MNT_DETACH +# define MNT_DETACH 2 +# endif +#endif + +#include "c.h" +#include "canonicalize.h" +#include "sulogin-consoles.h" + +#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) +# ifndef typeof +# define typeof __typeof__ +# endif +# ifndef restrict +# define restrict __restrict__ +# endif +#endif + +#define alignof(type) ((sizeof(type)+(sizeof(void*)-1)) & ~(sizeof(void*)-1)) +#define strsize(string) (strlen((string))+1) + +static int consoles_debug; +#define DBG(x) do { \ + if (consoles_debug) { \ + fputs("consoles debug: ", stderr); \ + x; \ + } \ + } while (0) + +static inline void __attribute__ ((__format__ (__printf__, 1, 2))) +dbgprint(const char * const mesg, ...) +{ + va_list ap; + va_start(ap, mesg); + vfprintf(stderr, mesg, ap); + va_end(ap); + fputc('\n', stderr); +} + +#ifdef USE_SULOGIN_EMERGENCY_MOUNT +/* + * Make C library standard calls such like ttyname(3) work + * even if the system does not show any of the standard + * directories. + */ + +static uint32_t emergency_flags; +# define MNT_PROCFS 0x0001 +# define MNT_DEVTMPFS 0x0002 + +void emergency_do_umounts(void) +{ + if (emergency_flags & MNT_DEVTMPFS) + umount2("/dev", MNT_DETACH); + if (emergency_flags & MNT_PROCFS) + umount2("/proc", MNT_DETACH); +} + +void emergency_do_mounts(void) +{ + struct stat rt, xt; + + if (emergency_flags) { + emergency_flags = 0; + return; + } + + if (stat("/", &rt) != 0) { + warn("cannot get file status of root file system\n"); + return; + } + + if (stat("/proc", &xt) == 0 + && rt.st_dev == xt.st_dev + && mount("proc", "/proc", "proc", MS_RELATIME, NULL) == 0) + emergency_flags |= MNT_PROCFS; + + if (stat("/dev", &xt) == 0 + && rt.st_dev == xt.st_dev + && mount("devtmpfs", "/dev", "devtmpfs", + MS_RELATIME, "mode=0755,nr_inodes=0") == 0) { + + emergency_flags |= MNT_DEVTMPFS; + mknod("/dev/console", S_IFCHR|S_IRUSR|S_IWUSR, + makedev(TTYAUX_MAJOR, 1)); + + if (symlink("/proc/self/fd", "/dev/fd") == 0) { + ignore_result( symlink("fd/0", "/dev/stdin") ); + ignore_result( symlink("fd/1", "/dev/stdout") ); + ignore_result( symlink("fd/2", "/dev/stderr") ); + } + } +} + +#else /* !USE_SULOGIN_EMERGENCY_MOUNT */ + +void emergency_do_umounts(void) { } +void emergency_do_mounts(void) { } + +#endif /* USE_SULOGIN_EMERGENCY_MOUNT */ + +/* + * Read and allocate one line from file, + * the caller has to free the result + */ +static __attribute__((__nonnull__)) +char *oneline(const char * const file) +{ + FILE *fp; + char *ret = NULL; + size_t dummy = 0; + ssize_t len; + + DBG(dbgprint("reading %s", file)); + + if (!(fp = fopen(file, "r" UL_CLOEXECSTR))) + return NULL; + len = getline(&ret, &dummy, fp); + if (len >= 0) { + char *nl; + + if (len) + ret[len-1] = '\0'; + if ((nl = strchr(ret, '\n'))) + *nl = '\0'; + } + + fclose(fp); + return ret; +} + +#ifdef __linux__ +/* + * Read and determine active attribute for tty below + * /sys/class/tty, the caller has to free the result. + */ +static __attribute__((__malloc__)) +char *actattr(const char * const tty) +{ + char *ret, *path; + + if (!tty || !*tty) + return NULL; + if (asprintf(&path, "/sys/class/tty/%s/active", tty) < 0) + return NULL; + + ret = oneline(path); + free(path); + return ret; +} + +/* + * Read and determine device attribute for tty below + * /sys/class/tty. + */ +static +dev_t devattr(const char * const tty) +{ + dev_t dev = 0; + char *path, *value; + + if (!tty || !*tty) + return 0; + if (asprintf(&path, "/sys/class/tty/%s/dev", tty) < 0) + return 0; + + value = oneline(path); + if (value) { + unsigned int maj, min; + + if (sscanf(value, "%u:%u", &maj, &min) == 2) + dev = makedev(maj, min); + free(value); + } + + free(path); + return dev; +} +#endif /* __linux__ */ + +/* + * Search below /dev for the character device in `dev_t comparedev' variable. + * Note that realpath(3) is used here to avoid not existent devices due the + * strdup(3) used in our canonicalize_path()! + */ +static +#ifdef __GNUC__ +__attribute__((__nonnull__,__malloc__,__hot__)) +#endif +char* scandev(DIR *dir, const dev_t comparedev) +{ + char path[PATH_MAX]; + char *name = NULL; + const struct dirent *dent; + int len, fd; + + DBG(dbgprint("scanning /dev for %u:%u", major(comparedev), minor(comparedev))); + + /* + * Try udev links on character devices first. + */ + if ((len = snprintf(path, sizeof(path), + "/dev/char/%u:%u", major(comparedev), minor(comparedev))) > 0 && + (size_t)len < sizeof(path)) { + + name = realpath(path, NULL); + if (name) + goto out; + } + + fd = dirfd(dir); + rewinddir(dir); + while ((dent = readdir(dir))) { + struct stat st; + +#ifdef _DIRENT_HAVE_D_TYPE + if (dent->d_type != DT_UNKNOWN && dent->d_type != DT_CHR) + continue; +#endif + if (fstatat(fd, dent->d_name, &st, 0) < 0) + continue; + if (!S_ISCHR(st.st_mode)) + continue; + if (comparedev != st.st_rdev) + continue; + if ((len = snprintf(path, sizeof(path), "/dev/%s", dent->d_name)) < 0 || + (size_t)len >= sizeof(path)) + continue; + + name = realpath(path, NULL); + if (name) + goto out; + } + +#ifdef USE_SULOGIN_EMERGENCY_MOUNT + /* + * There was no /dev mounted hence and no device was found hence we create our own. + */ + if (!name && (emergency_flags & MNT_DEVTMPFS)) { + + if ((len = snprintf(path, sizeof(path), + "/dev/tmp-%u:%u", major(comparedev), minor(comparedev))) < 0 || + (size_t)len >= sizeof(path)) + goto out; + + if (mknod(path, S_IFCHR|S_IRUSR|S_IWUSR, comparedev) < 0 && errno != EEXIST) + goto out; + + name = realpath(path, NULL); + } +#endif +out: + return name; +} + +/* + * Default control characters for an unknown terminal line. + */ + +/* + * Allocate an aligned `struct console' memory area, + * initialize its default values, and append it to + * the global linked list. + */ +static +#ifdef __GNUC__ +__attribute__((__hot__)) +#endif +int append_console(struct list_head *consoles, const char * const name) +{ + struct console *restrict tail; + const struct console *last = NULL; + + DBG(dbgprint("appenging %s", name)); + + if (!list_empty(consoles)) + last = list_last_entry(consoles, struct console, entry); + + if (posix_memalign((void *) &tail, sizeof(void *), + alignof(struct console) + strsize(name)) != 0) + return -ENOMEM; + + INIT_LIST_HEAD(&tail->entry); + INIT_CHARDATA(&tail->cp); + + list_add_tail(&tail->entry, consoles); + tail->tty = ((char *) tail) + alignof(struct console); + strcpy(tail->tty, name); + + tail->file = (FILE*)0; + tail->flags = 0; + tail->fd = -1; + tail->id = last ? last->id + 1 : 0; + tail->pid = -1; + memset(&tail->tio, 0, sizeof(tail->tio)); + + return 0; +} + +#ifdef __linux__ +/* + * return codes: + * < 0 - fatal error (no mem or so... ) + * 0 - success + * 1 - recoverable error + * 2 - detection not available + */ +static int detect_consoles_from_proc(struct list_head *consoles) +{ + char fbuf[16 + 1]; + DIR *dir = NULL; + FILE *fc = NULL; + int maj, min, rc = 1, matches; + + DBG(dbgprint("trying /proc")); + + fc = fopen("/proc/consoles", "r" UL_CLOEXECSTR); + if (!fc) { + rc = 2; + goto done; + } + dir = opendir("/dev"); + if (!dir) + goto done; + + while ((matches = fscanf(fc, "%*s %*s (%16[^)]) %d:%d", fbuf, &maj, &min)) >= 1) { + char *name; + dev_t comparedev; + + if (matches != 3) + continue; + if (!strchr(fbuf, 'E')) + continue; + comparedev = makedev(maj, min); + name = scandev(dir, comparedev); + if (!name) + continue; + rc = append_console(consoles, name); + free(name); + if (rc < 0) + goto done; + } + + rc = list_empty(consoles) ? 1 : 0; +done: + if (dir) + closedir(dir); + if (fc) + fclose(fc); + DBG(dbgprint("[/proc rc=%d]", rc)); + return rc; +} + +/* + * return codes: + * < 0 - fatal error (no mem or so... ) + * 0 - success + * 1 - recoverable error + * 2 - detection not available + */ +static int detect_consoles_from_sysfs(struct list_head *consoles) +{ + char *attrib = NULL, *words, *token; + DIR *dir = NULL; + int rc = 1; + + DBG(dbgprint("trying /sys")); + + attrib = actattr("console"); + if (!attrib) { + rc = 2; + goto done; + } + + words = attrib; + + dir = opendir("/dev"); + if (!dir) + goto done; + + while ((token = strsep(&words, " \t\r\n"))) { + char *name; + dev_t comparedev; + + if (*token == '\0') + continue; + + comparedev = devattr(token); + if (comparedev == makedev(TTY_MAJOR, 0)) { + char *tmp = actattr(token); + if (!tmp) + continue; + comparedev = devattr(tmp); + free(tmp); + } + + name = scandev(dir, comparedev); + if (!name) + continue; + rc = append_console(consoles, name); + free(name); + if (rc < 0) + goto done; + } + + rc = list_empty(consoles) ? 1 : 0; +done: + free(attrib); + if (dir) + closedir(dir); + DBG(dbgprint("[/sys rc=%d]", rc)); + return rc; +} + + +static int detect_consoles_from_cmdline(struct list_head *consoles) +{ + char *cmdline, *words, *token; + dev_t comparedev; + DIR *dir = NULL; + int rc = 1, fd; + + DBG(dbgprint("trying kernel cmdline")); + + cmdline = oneline("/proc/cmdline"); + if (!cmdline) { + rc = 2; + goto done; + } + + words= cmdline; + dir = opendir("/dev"); + if (!dir) + goto done; + + while ((token = strsep(&words, " \t\r\n"))) { +#ifdef TIOCGDEV + unsigned int devnum; +#else + struct vt_stat vt; + struct stat st; +#endif + char *colon, *name; + + if (*token != 'c') + continue; + if (strncmp(token, "console=", 8) != 0) + continue; + token += 8; + + if (strcmp(token, "brl") == 0) + token += 4; + if ((colon = strchr(token, ','))) + *colon = '\0'; + + if (asprintf(&name, "/dev/%s", token) < 0) + continue; + if ((fd = open(name, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC)) < 0) { + free(name); + continue; + } + free(name); +#ifdef TIOCGDEV + if (ioctl (fd, TIOCGDEV, &devnum) < 0) { + close(fd); + continue; + } + comparedev = (dev_t) devnum; +#else + if (fstat(fd, &st) < 0) { + close(fd); + continue; + } + comparedev = st.st_rdev; + if (comparedev == makedev(TTY_MAJOR, 0)) { + if (ioctl(fd, VT_GETSTATE, &vt) < 0) { + close(fd); + continue; + } + comparedev = makedev(TTY_MAJOR, (int)vt.v_active); + } +#endif + close(fd); + + name = scandev(dir, comparedev); + if (!name) + continue; + rc = append_console(consoles, name); + free(name); + if (rc < 0) + goto done; + } + + rc = list_empty(consoles) ? 1 : 0; +done: + if (dir) + closedir(dir); + free(cmdline); + DBG(dbgprint("[kernel cmdline rc=%d]", rc)); + return rc; +} + +#ifdef TIOCGDEV +static int detect_consoles_from_tiocgdev(struct list_head *consoles, + const int fallback, + const char *device) +{ + unsigned int devnum; + char *name; + int rc = 1, fd = -1; + dev_t comparedev; + DIR *dir = NULL; + struct console *console; + + DBG(dbgprint("trying tiocgdev")); + + if (!device || !*device) + fd = dup(fallback); + else + fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC); + + if (fd < 0) + goto done; + if (ioctl (fd, TIOCGDEV, &devnum) < 0) + goto done; + + comparedev = (dev_t) devnum; + dir = opendir("/dev"); + if (!dir) + goto done; + + name = scandev(dir, comparedev); + closedir(dir); + + if (!name) { + name = (char *) (device && *device ? device : ttyname(fallback)); + if (!name) + name = "/dev/tty1"; + + name = strdup(name); + if (!name) { + rc = -ENOMEM; + goto done; + } + } + rc = append_console(consoles, name); + free(name); + if (rc < 0) + goto done; + if (list_empty(consoles)) { + rc = 1; + goto done; + } + console = list_last_entry(consoles, struct console, entry); + if (console && (!device || !*device)) + console->fd = fallback; +done: + if (fd >= 0) + close(fd); + DBG(dbgprint("[tiocgdev rc=%d]", rc)); + return rc; +} +#endif /* TIOCGDEV */ +#endif /* __linux__ */ + +/* + * Try to detect the real device(s) used for the system console + * /dev/console if but only if /dev/console is used. On Linux + * this can be more than one device, e.g. a serial line as well + * as a virtual console as well as a simple printer. + * + * Returns 1 if stdout and stderr should be reconnected and 0 + * otherwise or less than zero on error. + */ +int detect_consoles(const char *device, const int fallback, struct list_head *consoles) +{ + int fd, reconnect = 0, rc; + dev_t comparedev = 0; + + consoles_debug = getenv("CONSOLES_DEBUG") ? 1 : 0; + + if (!device || !*device) + fd = fallback >= 0 ? dup(fallback) : - 1; + else { + fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC); + reconnect = 1; + } + + DBG(dbgprint("detection started [device=%s, fallback=%d]", + device, fallback)); + + if (fd >= 0) { + DIR *dir; + char *name; + struct stat st; +#ifdef TIOCGDEV + unsigned int devnum; +#endif +#ifdef __GNU__ + /* + * The Hurd always gives st_rdev as 0, which causes this + * method to select the first terminal it finds. + */ + close(fd); + goto fallback; +#endif + DBG(dbgprint("trying device/fallback file descriptor")); + + if (fstat(fd, &st) < 0) { + close(fd); + goto fallback; + } + comparedev = st.st_rdev; + + if (reconnect && + (fstat(fallback, &st) < 0 || comparedev != st.st_rdev)) + dup2(fd, fallback); +#ifdef __linux__ + /* + * Check if the device detection for Linux system console should be used. + */ + if (comparedev == makedev(TTYAUX_MAJOR, 0)) { /* /dev/tty */ + close(fd); + device = "/dev/tty"; + goto fallback; + } + if (comparedev == makedev(TTYAUX_MAJOR, 1)) { /* /dev/console */ + close(fd); + goto console; + } + if (comparedev == makedev(TTYAUX_MAJOR, 2)) { /* /dev/ptmx */ + close(fd); + device = "/dev/tty"; + goto fallback; + } + if (comparedev == makedev(TTY_MAJOR, 0)) { /* /dev/tty0 */ + struct vt_stat vt; + if (ioctl(fd, VT_GETSTATE, &vt) < 0) { + close(fd); + goto fallback; + } + comparedev = makedev(TTY_MAJOR, (int)vt.v_active); + } +#endif +#ifdef TIOCGDEV + if (ioctl (fd, TIOCGDEV, &devnum) < 0) { + close(fd); + goto fallback; + } + comparedev = (dev_t)devnum; +#endif + close(fd); + dir = opendir("/dev"); + if (!dir) + goto fallback; + name = scandev(dir, comparedev); + closedir(dir); + + if (name) { + rc = append_console(consoles, name); + free(name); + if (rc < 0) + return rc; + } + if (list_empty(consoles)) + goto fallback; + + DBG(dbgprint("detection success [rc=%d]", reconnect)); + return reconnect; + } +#ifdef __linux__ +console: + /* + * Detection of devices used for Linux system console using + * the /proc/consoles API with kernel 2.6.38 and higher. + */ + rc = detect_consoles_from_proc(consoles); + if (rc == 0) + return reconnect; /* success */ + if (rc < 0) + return rc; /* fatal error */ + + /* + * Detection of devices used for Linux system console using + * the sysfs /sys/class/tty/ API with kernel 2.6.37 and higher. + */ + rc = detect_consoles_from_sysfs(consoles); + if (rc == 0) + return reconnect; /* success */ + if (rc < 0) + return rc; /* fatal error */ + + /* + * Detection of devices used for Linux system console using + * kernel parameter on the kernels command line. + */ + rc = detect_consoles_from_cmdline(consoles); + if (rc == 0) + return reconnect; /* success */ + if (rc < 0) + return rc; /* fatal error */ + + /* + * Detection of the device used for Linux system console using + * the ioctl TIOCGDEV if available (e.g. official 2.6.38). + */ +#ifdef TIOCGDEV + rc = detect_consoles_from_tiocgdev(consoles, fallback, device); + if (rc == 0) + return reconnect; /* success */ + if (rc < 0) + return rc; /* fatal error */ +#endif + if (!list_empty(consoles)) { + DBG(dbgprint("detection success [rc=%d]", reconnect)); + return reconnect; + } + +#endif /* __linux __ */ + +fallback: + if (fallback >= 0) { + const char *name; + char *n; + struct console *console; + + if (device && *device != '\0') + name = device; + else name = ttyname(fallback); + + if (!name) + name = "/dev/tty"; + + n = strdup(name); + if (!n) + return -ENOMEM; + rc = append_console(consoles, n); + free(n); + if (rc < 0) + return rc; + if (list_empty(consoles)) + return 1; + console = list_last_entry(consoles, struct console, entry); + if (console) + console->fd = fallback; + } + + DBG(dbgprint("detection done by fallback [rc=%d]", reconnect)); + return reconnect; +} + + +#ifdef TEST_PROGRAM +int main(int argc, char *argv[]) +{ + char *name = NULL; + int fd, re; + struct list_head *p, consoles; + + if (argc == 2) { + name = argv[1]; + fd = open(name, O_RDWR); + } else { + name = ttyname(STDIN_FILENO); + fd = STDIN_FILENO; + } + + if (!name) + errx(EXIT_FAILURE, "usage: %s [<tty>]\n", program_invocation_short_name); + + INIT_LIST_HEAD(&consoles); + re = detect_consoles(name, fd, &consoles); + + list_for_each(p, &consoles) { + struct console *c = list_entry(p, struct console, entry); + printf("%s: id=%d %s\n", c->tty, c->id, re ? "(reconnect) " : ""); + } + + return 0; +} +#endif |