diff options
author | Karel Zak | 2012-12-23 22:14:21 +0100 |
---|---|---|
committer | Karel Zak | 2012-12-23 22:14:21 +0100 |
commit | a73f59fa03ad28d001cc09d185adf865d6fad8bd (patch) | |
tree | 20dd9c6df33b076ba85ea896455641b78af940b8 /login-utils | |
parent | mkfs.minix: fsck:minix: fix compiler warnings (diff) | |
download | kernel-qcow2-util-linux-a73f59fa03ad28d001cc09d185adf865d6fad8bd.tar.gz kernel-qcow2-util-linux-a73f59fa03ad28d001cc09d185adf865d6fad8bd.tar.xz kernel-qcow2-util-linux-a73f59fa03ad28d001cc09d185adf865d6fad8bd.zip |
sulogin: remove consoles.c from libcommon
- move struct chardata to include/ttyutils.h
- move console.{h,c} to login-utils/sulogin-* (it's sulogin specific)
- fix sulogin and agetty includes
Signed-off-by: Karel Zak <kzak@redhat.com>
Diffstat (limited to 'login-utils')
-rw-r--r-- | login-utils/Makemodule.am | 11 | ||||
-rw-r--r-- | login-utils/sulogin-consoles.c | 781 | ||||
-rw-r--r-- | login-utils/sulogin-consoles.h | 47 | ||||
-rw-r--r-- | login-utils/sulogin.c | 2 |
4 files changed, 839 insertions, 2 deletions
diff --git a/login-utils/Makemodule.am b/login-utils/Makemodule.am index 9edb9f497..aef81771c 100644 --- a/login-utils/Makemodule.am +++ b/login-utils/Makemodule.am @@ -5,18 +5,27 @@ dist_man_MANS += login-utils/last.1 last_SOURCES = login-utils/last.c endif + if BUILD_SULOGIN sbin_PROGRAMS += sulogin dist_man_MANS += login-utils/sulogin.8 sulogin_SOURCES = \ - login-utils/sulogin.c + login-utils/sulogin.c \ + login-utils/sulogin-consoles.c \ + login-utils/sulogin-consoles.h sulogin_LDADD = $(LDADD) libcommon.la + if HAVE_LIBCRYPT sulogin_LDADD += -lcrypt endif if HAVE_SELINUX sulogin_LDADD += -lselinux endif + +check_PROGRAMS += test_consoles +test_consoles_SOURCES = login-utils/sulogin-consoles.c +test_consoles_CFLAGS = -DTEST_PROGRAM +test_consoles_LDADD = libcommon.la endif # BUILD_SULOGIN diff --git a/login-utils/sulogin-consoles.c b/login-utils/sulogin-consoles.c new file mode 100644 index 000000000..3729be448 --- /dev/null +++ b/login-utils/sulogin-consoles.c @@ -0,0 +1,781 @@ +/* + * 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> +#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> +# include <linux/major.h> +# ifndef MNT_DETACH +# define MNT_DETACH 2 +# endif +#endif + +#include "c.h" +#include "canonicalize.h" +#include "sulogin-consoles.h" + +#ifdef __linux__ +# include <linux/major.h> +#endif + +#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 + +static __attribute__((__destructor__)) +void emergency_do_umounts(void) +{ + if (emergency_flags & MNT_DEVTMPFS) + umount2("/dev", MNT_DETACH); + if (emergency_flags & MNT_PROCFS) + umount2("/proc", MNT_DETACH); +} + +static __attribute__((__constructor__)) +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") ); + } + } +} +#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; + 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) +{ + static const struct chardata initcp = { + .erase = CERASE, + .kill = CKILL, + .eol = CTRL('r'), + .parity = 0 + }; + 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); + + 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)); + memcpy(&tail->cp, &initcp, sizeof(struct chardata)); + + 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; + + DBG(dbgprint("trying /proc")); + + fc = fopen("/proc/consoles", "re"); + if (!fc) { + rc = 2; + goto done; + } + dir = opendir("/dev"); + if (!dir) + goto done; + + while (fscanf(fc, "%*s %*s (%16[^)]) %d:%d", fbuf, &maj, &min) == 3) { + char *name; + dev_t comparedev; + + 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; +} + +static int detect_consoles_from_tiocgdev(struct list_head *consoles, + int fallback, + const char *device) +{ +#ifdef TIOCGDEV + 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 + return 2; +} +#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 = dup(fallback); + 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). + */ + rc = detect_consoles_from_tiocgdev(consoles, fallback, device); + if (rc == 0) + return reconnect; /* success */ + if (rc < 0) + return rc; /* fatal error */ + + if (!list_empty(consoles)) { + DBG(dbgprint("detection success [rc=%d]", reconnect)); + return reconnect; + } + +#endif /* __linux __ */ + +fallback: + if (fallback >= 0) { + const char *name; + struct console *console; + + if (device && *device != '\0') + name = device; + else name = ttyname(fallback); + + if (!name) + name = "/dev/tty"; + + rc = append_console(consoles, strdup(name)); + 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 diff --git a/login-utils/sulogin-consoles.h b/login-utils/sulogin-consoles.h new file mode 100644 index 000000000..c713bad1d --- /dev/null +++ b/login-utils/sulogin-consoles.h @@ -0,0 +1,47 @@ +/* + * consoles.h Header file for routines to detect the system consoles + * + * Copyright (c) 2011 SuSE LINUX Products GmbH, All rights reserved. + * 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 <sys/types.h> +#include <stdint.h> +#include <stdio.h> +#include <termios.h> +#include <list.h> + +#include "ttyutils.h" + +struct console { + struct list_head entry; + char *tty; + FILE *file; + uint32_t flags; + int fd, id; +#define CON_SERIAL 0x0001 +#define CON_NOTTY 0x0002 + pid_t pid; + struct chardata cp; + struct termios tio; +}; + +extern int detect_consoles(const char *device, int fallback, + struct list_head *consoles); diff --git a/login-utils/sulogin.c b/login-utils/sulogin.c index 8416e3e0a..1a47e740f 100644 --- a/login-utils/sulogin.c +++ b/login-utils/sulogin.c @@ -55,7 +55,7 @@ #include "pathnames.h" #include "strutils.h" #include "ttyutils.h" -#include "consoles.h" +#include "sulogin-consoles.h" #define CONMAX 16 #define BS CTRL('h') |