2 * consoles.c Routines to detect the system consoles
4 * Copyright (c) 2011 SuSE LINUX Products GmbH, All rights reserved.
5 * Copyright (C) 2012 Karel Zak <kzak@redhat.com>
6 * Copyright (C) 2012 Werner Fink <werner@suse.de>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2, or (at your option)
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program (see the file COPYING); if not, write to the
20 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
23 * Author: Werner Fink <werner@suse.de>
30 #include <sys/types.h>
32 #include <sys/ioctl.h>
36 # include <linux/serial.h>
37 # include <linux/major.h>
44 #ifdef USE_SULOGIN_EMERGENCY_MOUNT
45 # include <sys/mount.h>
46 # include <linux/fs.h>
47 # include <linux/magic.h>
54 #include "canonicalize.h"
55 #include "sulogin-consoles.h"
57 #if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)
59 # define typeof __typeof__
62 # define restrict __restrict__
66 #define alignof(type) ((sizeof(type)+(sizeof(void*)-1)) & ~(sizeof(void*)-1))
67 #define strsize(string) (strlen((string))+1)
69 static int consoles_debug
;
71 if (consoles_debug) { \
72 fputs("consoles debug: ", stderr); \
77 static inline void __attribute__ ((__format__ (__printf__
, 1, 2)))
78 dbgprint(const char *mesg
, ...)
82 vfprintf(stderr
, mesg
, ap
);
87 #ifdef USE_SULOGIN_EMERGENCY_MOUNT
89 * Make C library standard calls such like ttyname(3) work
90 * even if the system does not show any of the standard
94 static uint32_t emergency_flags
;
95 # define MNT_PROCFS 0x0001
96 # define MNT_DEVTMPFS 0x0002
98 void emergency_do_umounts(void)
100 if (emergency_flags
& MNT_DEVTMPFS
)
101 umount2("/dev", MNT_DETACH
);
102 if (emergency_flags
& MNT_PROCFS
)
103 umount2("/proc", MNT_DETACH
);
106 void emergency_do_mounts(void)
110 if (emergency_flags
) {
115 if (stat("/", &rt
) != 0) {
116 warn("can not get file status of root file system\n");
120 if (stat("/proc", &xt
) == 0
121 && rt
.st_dev
== xt
.st_dev
122 && mount("proc", "/proc", "proc", MS_RELATIME
, NULL
) == 0)
123 emergency_flags
|= MNT_PROCFS
;
125 if (stat("/dev", &xt
) == 0
126 && rt
.st_dev
== xt
.st_dev
127 && mount("devtmpfs", "/dev", "devtmpfs",
128 MS_RELATIME
, "mode=0755,nr_inodes=0") == 0) {
130 emergency_flags
|= MNT_DEVTMPFS
;
131 mknod("/dev/console", S_IFCHR
|S_IRUSR
|S_IWUSR
,
132 makedev(TTYAUX_MAJOR
, 1));
134 if (symlink("/proc/self/fd", "/dev/fd") == 0) {
135 ignore_result( symlink("fd/0", "/dev/stdin") );
136 ignore_result( symlink("fd/1", "/dev/stdout") );
137 ignore_result( symlink("fd/2", "/dev/stderr") );
142 #else /* !USE_SULOGIN_EMERGENCY_MOUNT */
144 void emergency_do_umounts(void) { }
145 void emergency_do_mounts(void) { }
147 #endif /* USE_SULOGIN_EMERGENCY_MOUNT */
150 * Read and allocate one line from file,
151 * the caller has to free the result
153 static __attribute__((__nonnull__
))
154 char *oneline(const char *file
)
161 DBG(dbgprint("reading %s", file
));
163 if (!(fp
= fopen(file
, "r" UL_CLOEXECSTR
)))
165 len
= getline(&ret
, &dummy
, fp
);
171 if ((nl
= strchr(ret
, '\n')))
181 * Read and determine active attribute for tty below
182 * /sys/class/tty, the caller has to free the result.
184 static __attribute__((__malloc__
))
185 char *actattr(const char *tty
)
191 if (asprintf(&path
, "/sys/class/tty/%s/active", tty
) < 0)
200 * Read and determine device attribute for tty below
204 dev_t
devattr(const char *tty
)
211 if (asprintf(&path
, "/sys/class/tty/%s/dev", tty
) < 0)
214 value
= oneline(path
);
216 unsigned int maj
, min
;
218 if (sscanf(value
, "%u:%u", &maj
, &min
) == 2)
219 dev
= makedev(maj
, min
);
226 #endif /* __linux__ */
229 * Search below /dev for the character device in `dev_t comparedev' variable.
230 * Note that realpath(3) is used here to avoid not existent devices due the
231 * strdup(3) used in our canonicalize_path()!
235 __attribute__((__nonnull__
,__malloc__
,__hot__
))
237 char* scandev(DIR *dir
, dev_t comparedev
)
244 DBG(dbgprint("scanning /dev for %u:%u", major(comparedev
), minor(comparedev
)));
247 * Try udev links on character devices first.
249 if ((len
= snprintf(path
, sizeof(path
),
250 "/dev/char/%u:%u", major(comparedev
), minor(comparedev
))) > 0 &&
251 (size_t)len
< sizeof(path
)) {
253 name
= realpath(path
, NULL
);
260 while ((dent
= readdir(dir
))) {
263 #ifdef _DIRENT_HAVE_D_TYPE
264 if (dent
->d_type
!= DT_UNKNOWN
&& dent
->d_type
!= DT_CHR
)
267 if (fstatat(fd
, dent
->d_name
, &st
, 0) < 0)
269 if (!S_ISCHR(st
.st_mode
))
271 if (comparedev
!= st
.st_rdev
)
273 if ((len
= snprintf(path
, sizeof(path
), "/dev/%s", dent
->d_name
)) < 0 ||
274 (size_t)len
>= sizeof(path
))
277 name
= realpath(path
, NULL
);
282 #ifdef USE_SULOGIN_EMERGENCY_MOUNT
284 * There was no /dev mounted hence and no device was found hence we create our own.
286 if (!name
&& (emergency_flags
& MNT_DEVTMPFS
)) {
288 if ((len
= snprintf(path
, sizeof(path
),
289 "/dev/tmp-%u:%u", major(comparedev
), minor(comparedev
))) < 0 ||
290 (size_t)len
>= sizeof(path
))
293 if (mknod(path
, S_IFCHR
|S_IRUSR
|S_IWUSR
, comparedev
) < 0 && errno
!= EEXIST
)
296 name
= realpath(path
, NULL
);
304 * Default control characters for an unknown terminal line.
308 * Allocate an aligned `struct console' memory area,
309 * initialize its default values, and append it to
310 * the global linked list.
314 __attribute__((__hot__
))
316 int append_console(struct list_head
*consoles
, const char *name
)
318 struct console
*restrict tail
;
319 struct console
*last
= NULL
;
321 DBG(dbgprint("appenging %s", name
));
323 if (!list_empty(consoles
))
324 last
= list_last_entry(consoles
, struct console
, entry
);
326 if (posix_memalign((void *) &tail
, sizeof(void *),
327 alignof(struct console
) + strsize(name
)) != 0)
330 INIT_LIST_HEAD(&tail
->entry
);
331 INIT_CHARDATA(&tail
->cp
);
333 list_add_tail(&tail
->entry
, consoles
);
334 tail
->tty
= ((char *) tail
) + alignof(struct console
);
335 strcpy(tail
->tty
, name
);
337 tail
->file
= (FILE*)0;
340 tail
->id
= last
? last
->id
+ 1 : 0;
342 memset(&tail
->tio
, 0, sizeof(tail
->tio
));
350 * < 0 - fatal error (no mem or so... )
352 * 1 - recoverable error
353 * 2 - detection not available
355 static int detect_consoles_from_proc(struct list_head
*consoles
)
360 int maj
, min
, rc
= 1, matches
;
362 DBG(dbgprint("trying /proc"));
364 fc
= fopen("/proc/consoles", "r" UL_CLOEXECSTR
);
369 dir
= opendir("/dev");
373 while ((matches
= fscanf(fc
, "%*s %*s (%16[^)]) %d:%d", fbuf
, &maj
, &min
)) >= 1) {
379 if (!strchr(fbuf
, 'E'))
381 comparedev
= makedev(maj
, min
);
382 name
= scandev(dir
, comparedev
);
385 rc
= append_console(consoles
, name
);
391 rc
= list_empty(consoles
) ? 1 : 0;
397 DBG(dbgprint("[/proc rc=%d]", rc
));
403 * < 0 - fatal error (no mem or so... )
405 * 1 - recoverable error
406 * 2 - detection not available
408 static int detect_consoles_from_sysfs(struct list_head
*consoles
)
410 char *attrib
= NULL
, *words
, *token
;
414 DBG(dbgprint("trying /sys"));
416 attrib
= actattr("console");
424 dir
= opendir("/dev");
428 while ((token
= strsep(&words
, " \t\r\n"))) {
435 comparedev
= devattr(token
);
436 if (comparedev
== makedev(TTY_MAJOR
, 0)) {
437 char *tmp
= actattr(token
);
440 comparedev
= devattr(tmp
);
444 name
= scandev(dir
, comparedev
);
447 rc
= append_console(consoles
, name
);
453 rc
= list_empty(consoles
) ? 1 : 0;
458 DBG(dbgprint("[/sys rc=%d]", rc
));
463 static int detect_consoles_from_cmdline(struct list_head
*consoles
)
465 char *cmdline
, *words
, *token
;
470 DBG(dbgprint("trying kernel cmdline"));
472 cmdline
= oneline("/proc/cmdline");
479 dir
= opendir("/dev");
483 while ((token
= strsep(&words
, " \t\r\n"))) {
494 if (strncmp(token
, "console=", 8) != 0)
498 if (strcmp(token
, "brl") == 0)
500 if ((colon
= strchr(token
, ',')))
503 if (asprintf(&name
, "/dev/%s", token
) < 0)
505 if ((fd
= open(name
, O_RDWR
|O_NONBLOCK
|O_NOCTTY
|O_CLOEXEC
)) < 0) {
511 if (ioctl (fd
, TIOCGDEV
, &devnum
) < 0) {
515 comparedev
= (dev_t
) devnum
;
517 if (fstat(fd
, &st
) < 0) {
521 comparedev
= st
.st_rdev
;
522 if (comparedev
== makedev(TTY_MAJOR
, 0)) {
523 if (ioctl(fd
, VT_GETSTATE
, &vt
) < 0) {
527 comparedev
= makedev(TTY_MAJOR
, (int)vt
.v_active
);
532 name
= scandev(dir
, comparedev
);
535 rc
= append_console(consoles
, name
);
541 rc
= list_empty(consoles
) ? 1 : 0;
546 DBG(dbgprint("[kernel cmdline rc=%d]", rc
));
551 static int detect_consoles_from_tiocgdev(struct list_head
*consoles
,
560 struct console
*console
;
562 DBG(dbgprint("trying tiocgdev"));
564 if (!device
|| !*device
)
567 fd
= open(device
, O_RDWR
|O_NONBLOCK
|O_NOCTTY
|O_CLOEXEC
);
571 if (ioctl (fd
, TIOCGDEV
, &devnum
) < 0)
574 comparedev
= (dev_t
) devnum
;
575 dir
= opendir("/dev");
579 name
= scandev(dir
, comparedev
);
583 name
= (char *) (device
&& *device
? device
: ttyname(fallback
));
593 rc
= append_console(consoles
, name
);
597 if (list_empty(consoles
)) {
601 console
= list_last_entry(consoles
, struct console
, entry
);
602 if (console
&& (!device
|| !*device
))
603 console
->fd
= fallback
;
607 DBG(dbgprint("[tiocgdev rc=%d]", rc
));
610 #endif /* TIOCGDEV */
611 #endif /* __linux__ */
614 * Try to detect the real device(s) used for the system console
615 * /dev/console if but only if /dev/console is used. On Linux
616 * this can be more than one device, e.g. a serial line as well
617 * as a virtual console as well as a simple printer.
619 * Returns 1 if stdout and stderr should be reconnected and 0
620 * otherwise or less than zero on error.
622 int detect_consoles(const char *device
, int fallback
, struct list_head
*consoles
)
624 int fd
, reconnect
= 0, rc
;
625 dev_t comparedev
= 0;
627 consoles_debug
= getenv("CONSOLES_DEBUG") ? 1 : 0;
629 if (!device
|| !*device
)
630 fd
= fallback
>= 0 ? dup(fallback
) : - 1;
632 fd
= open(device
, O_RDWR
|O_NONBLOCK
|O_NOCTTY
|O_CLOEXEC
);
636 DBG(dbgprint("detection started [device=%s, fallback=%d]",
648 * The Hurd always gives st_rdev as 0, which causes this
649 * method to select the first terminal it finds.
654 DBG(dbgprint("trying device/fallback file descriptor"));
656 if (fstat(fd
, &st
) < 0) {
660 comparedev
= st
.st_rdev
;
663 (fstat(fallback
, &st
) < 0 || comparedev
!= st
.st_rdev
))
667 * Check if the device detection for Linux system console should be used.
669 if (comparedev
== makedev(TTYAUX_MAJOR
, 0)) { /* /dev/tty */
674 if (comparedev
== makedev(TTYAUX_MAJOR
, 1)) { /* /dev/console */
678 if (comparedev
== makedev(TTYAUX_MAJOR
, 2)) { /* /dev/ptmx */
683 if (comparedev
== makedev(TTY_MAJOR
, 0)) { /* /dev/tty0 */
685 if (ioctl(fd
, VT_GETSTATE
, &vt
) < 0) {
689 comparedev
= makedev(TTY_MAJOR
, (int)vt
.v_active
);
693 if (ioctl (fd
, TIOCGDEV
, &devnum
) < 0) {
697 comparedev
= (dev_t
)devnum
;
700 dir
= opendir("/dev");
703 name
= scandev(dir
, comparedev
);
707 rc
= append_console(consoles
, name
);
712 if (list_empty(consoles
))
715 DBG(dbgprint("detection success [rc=%d]", reconnect
));
721 * Detection of devices used for Linux system console using
722 * the /proc/consoles API with kernel 2.6.38 and higher.
724 rc
= detect_consoles_from_proc(consoles
);
726 return reconnect
; /* success */
728 return rc
; /* fatal error */
731 * Detection of devices used for Linux system console using
732 * the sysfs /sys/class/tty/ API with kernel 2.6.37 and higher.
734 rc
= detect_consoles_from_sysfs(consoles
);
736 return reconnect
; /* success */
738 return rc
; /* fatal error */
741 * Detection of devices used for Linux system console using
742 * kernel parameter on the kernels command line.
744 rc
= detect_consoles_from_cmdline(consoles
);
746 return reconnect
; /* success */
748 return rc
; /* fatal error */
751 * Detection of the device used for Linux system console using
752 * the ioctl TIOCGDEV if available (e.g. official 2.6.38).
755 rc
= detect_consoles_from_tiocgdev(consoles
, fallback
, device
);
757 return reconnect
; /* success */
759 return rc
; /* fatal error */
761 if (!list_empty(consoles
)) {
762 DBG(dbgprint("detection success [rc=%d]", reconnect
));
766 #endif /* __linux __ */
772 struct console
*console
;
774 if (device
&& *device
!= '\0')
776 else name
= ttyname(fallback
);
784 rc
= append_console(consoles
, n
);
788 if (list_empty(consoles
))
790 console
= list_last_entry(consoles
, struct console
, entry
);
792 console
->fd
= fallback
;
795 DBG(dbgprint("detection done by fallback [rc=%d]", reconnect
));
801 int main(int argc
, char *argv
[])
810 fd
= open(name
, O_RDWR
);
812 name
= ttyname(STDIN_FILENO
);
817 errx(EXIT_FAILURE
, "usage: %s [<tty>]\n", program_invocation_short_name
);
819 re
= detect_consoles(name
, fd
, &consoles
);
821 list_for_each(p
, &consoles
) {
822 struct console
*c
= list_entry(p
, struct console
, entry
);
823 printf("%s: id=%d %s\n", c
->tty
, c
->id
, re
? "(reconnect) " : "");