summaryrefslogtreecommitdiffstats
path: root/login-utils/sulogin-consoles.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 02:42:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 02:42:50 +0000
commit8cb83eee5a58b1fad74c34094ce3afb9e430b5a4 (patch)
treea9b2e7baeca1be40eb734371e3c8b11b02294497 /login-utils/sulogin-consoles.c
parentInitial commit. (diff)
downloadutil-linux-upstream/2.33.1.tar.xz
util-linux-upstream/2.33.1.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.c828
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