| /* |
| * 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 <fcntl.h> |
| #include <dirent.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 *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("can not 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 *file) |
| { |
| FILE *fp; |
| char *ret = NULL; |
| size_t len = 0; |
| |
| DBG(dbgprint("reading %s", file)); |
| |
| if (!(fp = fopen(file, "re"))) |
| return NULL; |
| if (getline(&ret, &len, fp) >= 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 *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 *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 characer device in `dev_t comparedev' variable. |
| */ |
| static |
| #ifdef __GNUC__ |
| __attribute__((__nonnull__,__malloc__,__hot__)) |
| #endif |
| char* scandev(DIR *dir, dev_t comparedev) |
| { |
| char *name = NULL; |
| struct dirent *dent; |
| int fd; |
| |
| DBG(dbgprint("scanning /dev for %u:%u", major(comparedev), minor(comparedev))); |
| |
| fd = dirfd(dir); |
| rewinddir(dir); |
| while ((dent = readdir(dir))) { |
| char path[PATH_MAX]; |
| 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 ((size_t)snprintf(path, sizeof(path), "/dev/%s", dent->d_name) >= sizeof(path)) |
| continue; |
| #ifdef USE_SULOGIN_EMERGENCY_MOUNT |
| if (emergency_flags & MNT_DEVTMPFS) |
| mknod(path, S_IFCHR|S_IRUSR|S_IWUSR, comparedev); |
| #endif |
| |
| name = canonicalize_path(path); |
| break; |
| } |
| |
| 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__((__nonnull__,__hot__)) |
| #endif |
| int append_console(struct list_head *consoles, const char *name) |
| { |
| struct console *restrict tail; |
| 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 = 0; |
| 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", "re"); |
| 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, |
| 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, 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 |
| 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 consolei 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; |
| LIST_HEAD(consoles); |
| struct list_head *p; |
| |
| 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); |
| |
| 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 |