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>
43 #ifdef USE_SULOGIN_EMERGENCY_MOUNT
44 # include <sys/mount.h>
45 # include <linux/fs.h>
46 # include <linux/magic.h>
53 #include "canonicalize.h"
54 #include "sulogin-consoles.h"
56 #if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)
58 # define typeof __typeof__
61 # define restrict __restrict__
65 #define alignof(type) ((sizeof(type)+(sizeof(void*)-1)) & ~(sizeof(void*)-1))
66 #define strsize(string) (strlen((string))+1)
68 static int consoles_debug
;
70 if (consoles_debug) { \
71 fputs("consoles debug: ", stderr); \
76 static inline void __attribute__ ((__format__ (__printf__
, 1, 2)))
77 dbgprint(const char *mesg
, ...)
81 vfprintf(stderr
, mesg
, ap
);
86 #ifdef USE_SULOGIN_EMERGENCY_MOUNT
88 * Make C library standard calls such like ttyname(3) work
89 * even if the system does not show any of the standard
93 static uint32_t emergency_flags
;
94 # define MNT_PROCFS 0x0001
95 # define MNT_DEVTMPFS 0x0002
97 void emergency_do_umounts(void)
99 if (emergency_flags
& MNT_DEVTMPFS
)
100 umount2("/dev", MNT_DETACH
);
101 if (emergency_flags
& MNT_PROCFS
)
102 umount2("/proc", MNT_DETACH
);
105 void emergency_do_mounts(void)
109 if (emergency_flags
) {
114 if (stat("/", &rt
) != 0) {
115 warn("can not get file status of root file system\n");
119 if (stat("/proc", &xt
) == 0
120 && rt
.st_dev
== xt
.st_dev
121 && mount("proc", "/proc", "proc", MS_RELATIME
, NULL
) == 0)
122 emergency_flags
|= MNT_PROCFS
;
124 if (stat("/dev", &xt
) == 0
125 && rt
.st_dev
== xt
.st_dev
126 && mount("devtmpfs", "/dev", "devtmpfs",
127 MS_RELATIME
, "mode=0755,nr_inodes=0") == 0) {
129 emergency_flags
|= MNT_DEVTMPFS
;
130 mknod("/dev/console", S_IFCHR
|S_IRUSR
|S_IWUSR
,
131 makedev(TTYAUX_MAJOR
, 1));
133 if (symlink("/proc/self/fd", "/dev/fd") == 0) {
134 ignore_result( symlink("fd/0", "/dev/stdin") );
135 ignore_result( symlink("fd/1", "/dev/stdout") );
136 ignore_result( symlink("fd/2", "/dev/stderr") );
141 #else /* !USE_SULOGIN_EMERGENCY_MOUNT */
143 void emergency_do_umounts(void) { }
144 void emergency_do_mounts(void) { }
146 #endif /* USE_SULOGIN_EMERGENCY_MOUNT */
149 * Read and allocate one line from file,
150 * the caller has to free the result
152 static __attribute__((__nonnull__
))
153 char *oneline(const char *file
)
159 DBG(dbgprint("reading %s", file
));
161 if (!(fp
= fopen(file
, "re")))
163 if (getline(&ret
, &len
, fp
) >= 0) {
168 if ((nl
= strchr(ret
, '\n')))
178 * Read and determine active attribute for tty below
179 * /sys/class/tty, the caller has to free the result.
181 static __attribute__((__malloc__
))
182 char *actattr(const char *tty
)
188 if (asprintf(&path
, "/sys/class/tty/%s/active", tty
) < 0)
197 * Read and determine device attribute for tty below
201 dev_t
devattr(const char *tty
)
208 if (asprintf(&path
, "/sys/class/tty/%s/dev", tty
) < 0)
211 value
= oneline(path
);
213 unsigned int maj
, min
;
215 if (sscanf(value
, "%u:%u", &maj
, &min
) == 2)
216 dev
= makedev(maj
, min
);
223 #endif /* __linux__ */
226 * Search below /dev for the characer device in `dev_t comparedev' variable.
230 __attribute__((__nonnull__
,__malloc__
,__hot__
))
232 char* scandev(DIR *dir
, dev_t comparedev
)
238 DBG(dbgprint("scanning /dev for %u:%u", major(comparedev
), minor(comparedev
)));
242 while ((dent
= readdir(dir
))) {
245 if (fstatat(fd
, dent
->d_name
, &st
, 0) < 0)
247 if (!S_ISCHR(st
.st_mode
))
249 if (comparedev
!= st
.st_rdev
)
251 if ((size_t)snprintf(path
, sizeof(path
), "/dev/%s", dent
->d_name
) >= sizeof(path
))
253 #ifdef USE_SULOGIN_EMERGENCY_MOUNT
254 if (emergency_flags
& MNT_DEVTMPFS
)
255 mknod(path
, S_IFCHR
|S_IRUSR
|S_IWUSR
, comparedev
);
258 name
= canonicalize_path(path
);
266 * Default control characters for an unknown terminal line.
270 * Allocate an aligned `struct console' memory area,
271 * initialize its default values, and append it to
272 * the global linked list.
276 __attribute__((__nonnull__
,__hot__
))
278 int append_console(struct list_head
*consoles
, const char *name
)
280 struct console
*restrict tail
;
281 struct console
*last
= NULL
;
283 DBG(dbgprint("appenging %s", name
));
285 if (!list_empty(consoles
))
286 last
= list_last_entry(consoles
, struct console
, entry
);
288 if (posix_memalign((void *) &tail
, sizeof(void *),
289 alignof(struct console
) + strsize(name
)) != 0)
292 INIT_LIST_HEAD(&tail
->entry
);
293 INIT_CHARDATA(&tail
->cp
);
295 list_add_tail(&tail
->entry
, consoles
);
296 tail
->tty
= ((char *) tail
) + alignof(struct console
);
297 strcpy(tail
->tty
, name
);
299 tail
->file
= (FILE*)0;
302 tail
->id
= last
? last
->id
+ 1 : 0;
304 memset(&tail
->tio
, 0, sizeof(tail
->tio
));
312 * < 0 - fatal error (no mem or so... )
314 * 1 - recoverable error
315 * 2 - detection not available
317 static int detect_consoles_from_proc(struct list_head
*consoles
)
322 int maj
, min
, rc
= 1;
324 DBG(dbgprint("trying /proc"));
326 fc
= fopen("/proc/consoles", "re");
331 dir
= opendir("/dev");
335 while (fscanf(fc
, "%*s %*s (%16[^)]) %d:%d", fbuf
, &maj
, &min
) == 3) {
339 if (!strchr(fbuf
, 'E'))
341 comparedev
= makedev(maj
, min
);
342 name
= scandev(dir
, comparedev
);
345 rc
= append_console(consoles
, name
);
351 rc
= list_empty(consoles
) ? 1 : 0;
357 DBG(dbgprint("[/proc rc=%d]", rc
));
363 * < 0 - fatal error (no mem or so... )
365 * 1 - recoverable error
366 * 2 - detection not available
368 static int detect_consoles_from_sysfs(struct list_head
*consoles
)
370 char *attrib
= NULL
, *words
, *token
;
374 DBG(dbgprint("trying /sys"));
376 attrib
= actattr("console");
384 dir
= opendir("/dev");
388 while ((token
= strsep(&words
, " \t\r\n"))) {
395 comparedev
= devattr(token
);
396 if (comparedev
== makedev(TTY_MAJOR
, 0)) {
397 char *tmp
= actattr(token
);
400 comparedev
= devattr(tmp
);
404 name
= scandev(dir
, comparedev
);
407 rc
= append_console(consoles
, name
);
413 rc
= list_empty(consoles
) ? 1 : 0;
418 DBG(dbgprint("[/sys rc=%d]", rc
));
423 static int detect_consoles_from_cmdline(struct list_head
*consoles
)
425 char *cmdline
, *words
, *token
;
430 DBG(dbgprint("trying kernel cmdline"));
432 cmdline
= oneline("/proc/cmdline");
439 dir
= opendir("/dev");
443 while ((token
= strsep(&words
, " \t\r\n"))) {
454 if (strncmp(token
, "console=", 8) != 0)
458 if (strcmp(token
, "brl") == 0)
460 if ((colon
= strchr(token
, ',')))
463 if (asprintf(&name
, "/dev/%s", token
) < 0)
465 if ((fd
= open(name
, O_RDWR
|O_NONBLOCK
|O_NOCTTY
|O_CLOEXEC
)) < 0) {
471 if (ioctl (fd
, TIOCGDEV
, &devnum
) < 0) {
475 comparedev
= (dev_t
) devnum
;
477 if (fstat(fd
, &st
) < 0) {
481 comparedev
= st
.st_rdev
;
482 if (comparedev
== makedev(TTY_MAJOR
, 0)) {
483 if (ioctl(fd
, VT_GETSTATE
, &vt
) < 0) {
487 comparedev
= makedev(TTY_MAJOR
, (int)vt
.v_active
);
492 name
= scandev(dir
, comparedev
);
495 rc
= append_console(consoles
, name
);
501 rc
= list_empty(consoles
) ? 1 : 0;
506 DBG(dbgprint("[kernel cmdline rc=%d]", rc
));
511 static int detect_consoles_from_tiocgdev(struct list_head
*consoles
,
520 struct console
*console
;
522 DBG(dbgprint("trying tiocgdev"));
524 if (!device
|| !*device
)
527 fd
= open(device
, O_RDWR
|O_NONBLOCK
|O_NOCTTY
|O_CLOEXEC
);
531 if (ioctl (fd
, TIOCGDEV
, &devnum
) < 0)
534 comparedev
= (dev_t
) devnum
;
535 dir
= opendir("/dev");
539 name
= scandev(dir
, comparedev
);
543 name
= (char *) (device
&& *device
? device
: ttyname(fallback
));
553 rc
= append_console(consoles
, name
);
557 if (list_empty(consoles
)) {
561 console
= list_last_entry(consoles
, struct console
, entry
);
562 if (console
&& (!device
|| !*device
))
563 console
->fd
= fallback
;
567 DBG(dbgprint("[tiocgdev rc=%d]", rc
));
570 #endif /* TIOCGDEV */
571 #endif /* __linux__ */
574 * Try to detect the real device(s) used for the system console
575 * /dev/console if but only if /dev/console is used. On Linux
576 * this can be more than one device, e.g. a serial line as well
577 * as a virtual console as well as a simple printer.
579 * Returns 1 if stdout and stderr should be reconnected and 0
580 * otherwise or less than zero on error.
582 int detect_consoles(const char *device
, int fallback
, struct list_head
*consoles
)
584 int fd
, reconnect
= 0, rc
;
585 dev_t comparedev
= 0;
587 consoles_debug
= getenv("CONSOLES_DEBUG") ? 1 : 0;
589 if (!device
|| !*device
)
592 fd
= open(device
, O_RDWR
|O_NONBLOCK
|O_NOCTTY
|O_CLOEXEC
);
596 DBG(dbgprint("detection started [device=%s, fallback=%d]",
606 DBG(dbgprint("trying device/fallback file descriptor"));
608 if (fstat(fd
, &st
) < 0) {
612 comparedev
= st
.st_rdev
;
615 (fstat(fallback
, &st
) < 0 || comparedev
!= st
.st_rdev
))
619 * Check if the device detection for Linux system console should be used.
621 if (comparedev
== makedev(TTYAUX_MAJOR
, 0)) { /* /dev/tty */
626 if (comparedev
== makedev(TTYAUX_MAJOR
, 1)) { /* /dev/console */
630 if (comparedev
== makedev(TTYAUX_MAJOR
, 2)) { /* /dev/ptmx */
635 if (comparedev
== makedev(TTY_MAJOR
, 0)) { /* /dev/tty0 */
637 if (ioctl(fd
, VT_GETSTATE
, &vt
) < 0) {
641 comparedev
= makedev(TTY_MAJOR
, (int)vt
.v_active
);
645 if (ioctl (fd
, TIOCGDEV
, &devnum
) < 0) {
649 comparedev
= (dev_t
)devnum
;
652 dir
= opendir("/dev");
655 name
= scandev(dir
, comparedev
);
659 rc
= append_console(consoles
, name
);
664 if (list_empty(consoles
))
667 DBG(dbgprint("detection success [rc=%d]", reconnect
));
673 * Detection of devices used for Linux system consolei using
674 * the /proc/consoles API with kernel 2.6.38 and higher.
676 rc
= detect_consoles_from_proc(consoles
);
678 return reconnect
; /* success */
680 return rc
; /* fatal error */
683 * Detection of devices used for Linux system console using
684 * the sysfs /sys/class/tty/ API with kernel 2.6.37 and higher.
686 rc
= detect_consoles_from_sysfs(consoles
);
688 return reconnect
; /* success */
690 return rc
; /* fatal error */
693 * Detection of devices used for Linux system console using
694 * kernel parameter on the kernels command line.
696 rc
= detect_consoles_from_cmdline(consoles
);
698 return reconnect
; /* success */
700 return rc
; /* fatal error */
703 * Detection of the device used for Linux system console using
704 * the ioctl TIOCGDEV if available (e.g. official 2.6.38).
707 rc
= detect_consoles_from_tiocgdev(consoles
, fallback
, device
);
709 return reconnect
; /* success */
711 return rc
; /* fatal error */
713 if (!list_empty(consoles
)) {
714 DBG(dbgprint("detection success [rc=%d]", reconnect
));
718 #endif /* __linux __ */
724 struct console
*console
;
726 if (device
&& *device
!= '\0')
728 else name
= ttyname(fallback
);
736 rc
= append_console(consoles
, n
);
740 if (list_empty(consoles
))
742 console
= list_last_entry(consoles
, struct console
, entry
);
744 console
->fd
= fallback
;
747 DBG(dbgprint("detection done by fallback [rc=%d]", reconnect
));
753 int main(int argc
, char *argv
[])
762 fd
= open(name
, O_RDWR
);
764 name
= ttyname(STDIN_FILENO
);
769 errx(EXIT_FAILURE
, "usage: %s [<tty>]\n", program_invocation_short_name
);
771 re
= detect_consoles(name
, fd
, &consoles
);
773 list_for_each(p
, &consoles
) {
774 struct console
*c
= list_entry(p
, struct console
, entry
);
775 printf("%s: id=%d %s\n", c
->tty
, c
->id
, re
? "(reconnect) " : "");