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 #if defined(USE_SULOGIN_EMERGENCY_MOUNT)
45 # include <sys/mount.h>
47 # define MS_RELATIME (1<<21)
55 #include "canonicalize.h"
56 #include "sulogin-consoles.h"
58 #if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)
60 # define typeof __typeof__
63 # define restrict __restrict__
67 #define alignof(type) ((sizeof(type)+(sizeof(void*)-1)) & ~(sizeof(void*)-1))
68 #define strsize(string) (strlen((string))+1)
70 static int consoles_debug
;
72 if (consoles_debug) { \
73 fputs("consoles debug: ", stderr); \
78 static inline void __attribute__ ((__format__ (__printf__
, 1, 2)))
79 dbgprint(const char * const mesg
, ...)
83 vfprintf(stderr
, mesg
, ap
);
88 #ifdef USE_SULOGIN_EMERGENCY_MOUNT
90 * Make C library standard calls such like ttyname(3) work
91 * even if the system does not show any of the standard
95 static uint32_t emergency_flags
;
96 # define MNT_PROCFS 0x0001
97 # define MNT_DEVTMPFS 0x0002
99 void emergency_do_umounts(void)
101 if (emergency_flags
& MNT_DEVTMPFS
)
102 umount2("/dev", MNT_DETACH
);
103 if (emergency_flags
& MNT_PROCFS
)
104 umount2("/proc", MNT_DETACH
);
107 void emergency_do_mounts(void)
111 if (emergency_flags
) {
116 if (stat("/", &rt
) != 0) {
117 warn("cannot get file status of root file system\n");
121 if (stat("/proc", &xt
) == 0
122 && rt
.st_dev
== xt
.st_dev
123 && mount("proc", "/proc", "proc", MS_RELATIME
, NULL
) == 0)
124 emergency_flags
|= MNT_PROCFS
;
126 if (stat("/dev", &xt
) == 0
127 && rt
.st_dev
== xt
.st_dev
128 && mount("devtmpfs", "/dev", "devtmpfs",
129 MS_RELATIME
, "mode=0755,nr_inodes=0") == 0) {
131 emergency_flags
|= MNT_DEVTMPFS
;
132 mknod("/dev/console", S_IFCHR
|S_IRUSR
|S_IWUSR
,
133 makedev(TTYAUX_MAJOR
, 1));
135 if (symlink("/proc/self/fd", "/dev/fd") == 0) {
136 ignore_result( symlink("fd/0", "/dev/stdin") );
137 ignore_result( symlink("fd/1", "/dev/stdout") );
138 ignore_result( symlink("fd/2", "/dev/stderr") );
143 #else /* !USE_SULOGIN_EMERGENCY_MOUNT */
145 void emergency_do_umounts(void) { }
146 void emergency_do_mounts(void) { }
148 #endif /* USE_SULOGIN_EMERGENCY_MOUNT */
151 * Read and allocate one line from file,
152 * the caller has to free the result
154 static __attribute__((__nonnull__
))
155 char *oneline(const char * const file
)
162 DBG(dbgprint("reading %s", file
));
164 if (!(fp
= fopen(file
, "r" UL_CLOEXECSTR
)))
166 len
= getline(&ret
, &dummy
, fp
);
172 if ((nl
= strchr(ret
, '\n')))
182 * Read and determine active attribute for tty below
183 * /sys/class/tty, the caller has to free the result.
185 static __attribute__((__malloc__
))
186 char *actattr(const char * const tty
)
192 if (asprintf(&path
, "/sys/class/tty/%s/active", tty
) < 0)
201 * Read and determine device attribute for tty below
205 dev_t
devattr(const char * const tty
)
212 if (asprintf(&path
, "/sys/class/tty/%s/dev", tty
) < 0)
215 value
= oneline(path
);
217 unsigned int maj
, min
;
219 if (sscanf(value
, "%u:%u", &maj
, &min
) == 2)
220 dev
= makedev(maj
, min
);
227 #endif /* __linux__ */
230 * Search below /dev for the character device in `dev_t comparedev' variable.
231 * Note that realpath(3) is used here to avoid not existent devices due the
232 * strdup(3) used in our canonicalize_path()!
236 __attribute__((__nonnull__
,__malloc__
,__hot__
))
238 char* scandev(DIR *dir
, const dev_t comparedev
)
242 const struct dirent
*dent
;
245 DBG(dbgprint("scanning /dev for %u:%u", major(comparedev
), minor(comparedev
)));
248 * Try udev links on character devices first.
250 if ((len
= snprintf(path
, sizeof(path
),
251 "/dev/char/%u:%u", major(comparedev
), minor(comparedev
))) > 0 &&
252 (size_t)len
< sizeof(path
)) {
254 name
= realpath(path
, NULL
);
261 while ((dent
= readdir(dir
))) {
264 #ifdef _DIRENT_HAVE_D_TYPE
265 if (dent
->d_type
!= DT_UNKNOWN
&& dent
->d_type
!= DT_CHR
)
268 if (fstatat(fd
, dent
->d_name
, &st
, 0) < 0)
270 if (!S_ISCHR(st
.st_mode
))
272 if (comparedev
!= st
.st_rdev
)
274 if ((len
= snprintf(path
, sizeof(path
), "/dev/%s", dent
->d_name
)) < 0 ||
275 (size_t)len
>= sizeof(path
))
278 name
= realpath(path
, NULL
);
283 #ifdef USE_SULOGIN_EMERGENCY_MOUNT
285 * There was no /dev mounted hence and no device was found hence we create our own.
287 if (!name
&& (emergency_flags
& MNT_DEVTMPFS
)) {
289 if ((len
= snprintf(path
, sizeof(path
),
290 "/dev/tmp-%u:%u", major(comparedev
), minor(comparedev
))) < 0 ||
291 (size_t)len
>= sizeof(path
))
294 if (mknod(path
, S_IFCHR
|S_IRUSR
|S_IWUSR
, comparedev
) < 0 && errno
!= EEXIST
)
297 name
= realpath(path
, NULL
);
305 * Default control characters for an unknown terminal line.
309 * Allocate an aligned `struct console' memory area,
310 * initialize its default values, and append it to
311 * the global linked list.
315 __attribute__((__hot__
))
317 int append_console(struct list_head
*consoles
, const char * const name
)
319 struct console
*restrict tail
;
320 const struct console
*last
= NULL
;
322 DBG(dbgprint("appending %s", name
));
324 if (!list_empty(consoles
))
325 last
= list_last_entry(consoles
, struct console
, entry
);
327 if (posix_memalign((void *) &tail
, sizeof(void *),
328 alignof(struct console
) + strsize(name
)) != 0)
331 INIT_LIST_HEAD(&tail
->entry
);
332 INIT_CHARDATA(&tail
->cp
);
334 list_add_tail(&tail
->entry
, consoles
);
335 tail
->tty
= ((char *) tail
) + alignof(struct console
);
336 strcpy(tail
->tty
, name
);
338 tail
->file
= (FILE*)0;
341 tail
->id
= last
? last
->id
+ 1 : 0;
343 memset(&tail
->tio
, 0, sizeof(tail
->tio
));
344 #ifdef HAVE_LIBSELINUX
345 tail
->reset_tty_context
= NULL
;
346 tail
->user_tty_context
= NULL
;
355 * < 0 - fatal error (no mem or so... )
357 * 1 - recoverable error
358 * 2 - detection not available
360 static int detect_consoles_from_proc(struct list_head
*consoles
)
365 int maj
, min
, rc
= 1, matches
;
367 DBG(dbgprint("trying /proc"));
369 fc
= fopen("/proc/consoles", "r" UL_CLOEXECSTR
);
374 dir
= opendir("/dev");
378 while ((matches
= fscanf(fc
, "%*s %*s (%16[^)]) %d:%d", fbuf
, &maj
, &min
)) >= 1) {
384 if (!strchr(fbuf
, 'E'))
386 comparedev
= makedev(maj
, min
);
387 name
= scandev(dir
, comparedev
);
390 rc
= append_console(consoles
, name
);
396 rc
= list_empty(consoles
) ? 1 : 0;
402 DBG(dbgprint("[/proc rc=%d]", rc
));
408 * < 0 - fatal error (no mem or so... )
410 * 1 - recoverable error
411 * 2 - detection not available
413 static int detect_consoles_from_sysfs(struct list_head
*consoles
)
415 char *attrib
= NULL
, *words
, *token
;
419 DBG(dbgprint("trying /sys"));
421 attrib
= actattr("console");
429 dir
= opendir("/dev");
433 while ((token
= strsep(&words
, " \t\r\n"))) {
440 comparedev
= devattr(token
);
441 if (comparedev
== makedev(TTY_MAJOR
, 0)) {
442 char *tmp
= actattr(token
);
445 comparedev
= devattr(tmp
);
449 name
= scandev(dir
, comparedev
);
452 rc
= append_console(consoles
, name
);
458 rc
= list_empty(consoles
) ? 1 : 0;
463 DBG(dbgprint("[/sys rc=%d]", rc
));
468 static int detect_consoles_from_cmdline(struct list_head
*consoles
)
470 char *cmdline
, *words
, *token
;
475 DBG(dbgprint("trying kernel cmdline"));
477 cmdline
= oneline("/proc/cmdline");
484 dir
= opendir("/dev");
488 while ((token
= strsep(&words
, " \t\r\n"))) {
499 if (strncmp(token
, "console=", 8) != 0)
503 if (strcmp(token
, "brl") == 0)
505 if ((colon
= strchr(token
, ',')))
508 if (asprintf(&name
, "/dev/%s", token
) < 0)
510 if ((fd
= open(name
, O_RDWR
|O_NONBLOCK
|O_NOCTTY
|O_CLOEXEC
)) < 0) {
516 if (ioctl (fd
, TIOCGDEV
, &devnum
) < 0) {
520 comparedev
= (dev_t
) devnum
;
522 if (fstat(fd
, &st
) < 0) {
526 comparedev
= st
.st_rdev
;
527 if (comparedev
== makedev(TTY_MAJOR
, 0)) {
528 if (ioctl(fd
, VT_GETSTATE
, &vt
) < 0) {
532 comparedev
= makedev(TTY_MAJOR
, (int)vt
.v_active
);
537 name
= scandev(dir
, comparedev
);
540 rc
= append_console(consoles
, name
);
546 rc
= list_empty(consoles
) ? 1 : 0;
551 DBG(dbgprint("[kernel cmdline rc=%d]", rc
));
556 static int detect_consoles_from_tiocgdev(struct list_head
*consoles
,
565 struct console
*console
;
567 DBG(dbgprint("trying tiocgdev"));
569 if (!device
|| !*device
)
572 fd
= open(device
, O_RDWR
|O_NONBLOCK
|O_NOCTTY
|O_CLOEXEC
);
576 if (ioctl (fd
, TIOCGDEV
, &devnum
) < 0)
579 comparedev
= (dev_t
) devnum
;
580 dir
= opendir("/dev");
584 name
= scandev(dir
, comparedev
);
588 name
= (char *) (device
&& *device
? device
: ttyname(fallback
));
598 rc
= append_console(consoles
, name
);
602 if (list_empty(consoles
)) {
606 console
= list_last_entry(consoles
, struct console
, entry
);
607 if (console
&& (!device
|| !*device
))
608 console
->fd
= fallback
;
612 DBG(dbgprint("[tiocgdev rc=%d]", rc
));
615 #endif /* TIOCGDEV */
616 #endif /* __linux__ */
619 * Try to detect the real device(s) used for the system console
620 * /dev/console if but only if /dev/console is used. On Linux
621 * this can be more than one device, e.g. a serial line as well
622 * as a virtual console as well as a simple printer.
624 * Returns 1 if stdout and stderr should be reconnected and 0
625 * otherwise or less than zero on error.
627 int detect_consoles(const char *device
, const int fallback
, struct list_head
*consoles
)
629 int fd
, reconnect
= 0, rc
;
630 dev_t comparedev
= 0;
632 consoles_debug
= getenv("CONSOLES_DEBUG") ? 1 : 0;
634 if (!device
|| !*device
)
635 fd
= fallback
>= 0 ? dup(fallback
) : - 1;
637 fd
= open(device
, O_RDWR
|O_NONBLOCK
|O_NOCTTY
|O_CLOEXEC
);
641 DBG(dbgprint("detection started [device=%s, fallback=%d]",
653 * The Hurd always gives st_rdev as 0, which causes this
654 * method to select the first terminal it finds.
659 DBG(dbgprint("trying device/fallback file descriptor"));
661 if (fstat(fd
, &st
) < 0) {
665 comparedev
= st
.st_rdev
;
668 (fstat(fallback
, &st
) < 0 || comparedev
!= st
.st_rdev
))
672 * Check if the device detection for Linux system console should be used.
674 if (comparedev
== makedev(TTYAUX_MAJOR
, 0)) { /* /dev/tty */
679 if (comparedev
== makedev(TTYAUX_MAJOR
, 1)) { /* /dev/console */
683 if (comparedev
== makedev(TTYAUX_MAJOR
, 2)) { /* /dev/ptmx */
688 if (comparedev
== makedev(TTY_MAJOR
, 0)) { /* /dev/tty0 */
690 if (ioctl(fd
, VT_GETSTATE
, &vt
) < 0) {
694 comparedev
= makedev(TTY_MAJOR
, (int)vt
.v_active
);
698 if (ioctl (fd
, TIOCGDEV
, &devnum
) < 0) {
702 comparedev
= (dev_t
)devnum
;
705 dir
= opendir("/dev");
708 name
= scandev(dir
, comparedev
);
712 rc
= append_console(consoles
, name
);
717 if (list_empty(consoles
))
720 DBG(dbgprint("detection success [rc=%d]", reconnect
));
726 * Detection of devices used for Linux system console using
727 * the /proc/consoles API with kernel 2.6.38 and higher.
729 rc
= detect_consoles_from_proc(consoles
);
731 return reconnect
; /* success */
733 return rc
; /* fatal error */
736 * Detection of devices used for Linux system console using
737 * the sysfs /sys/class/tty/ API with kernel 2.6.37 and higher.
739 rc
= detect_consoles_from_sysfs(consoles
);
741 return reconnect
; /* success */
743 return rc
; /* fatal error */
746 * Detection of devices used for Linux system console using
747 * kernel parameter on the kernels command line.
749 rc
= detect_consoles_from_cmdline(consoles
);
751 return reconnect
; /* success */
753 return rc
; /* fatal error */
756 * Detection of the device used for Linux system console using
757 * the ioctl TIOCGDEV if available (e.g. official 2.6.38).
760 rc
= detect_consoles_from_tiocgdev(consoles
, fallback
, device
);
762 return reconnect
; /* success */
764 return rc
; /* fatal error */
766 if (!list_empty(consoles
)) {
767 DBG(dbgprint("detection success [rc=%d]", reconnect
));
771 #endif /* __linux __ */
777 struct console
*console
;
779 if (device
&& *device
!= '\0')
781 else name
= ttyname(fallback
);
789 rc
= append_console(consoles
, n
);
793 if (list_empty(consoles
))
795 console
= list_last_entry(consoles
, struct console
, entry
);
797 console
->fd
= fallback
;
800 DBG(dbgprint("detection done by fallback [rc=%d]", reconnect
));
806 int main(int argc
, char *argv
[])
810 struct list_head
*p
, consoles
;
814 fd
= open(name
, O_RDWR
);
816 name
= ttyname(STDIN_FILENO
);
821 errx(EXIT_FAILURE
, "usage: %s [<tty>]\n", program_invocation_short_name
);
823 INIT_LIST_HEAD(&consoles
);
824 re
= detect_consoles(name
, fd
, &consoles
);
826 list_for_each(p
, &consoles
) {
827 struct console
*c
= list_entry(p
, struct console
, entry
);
828 printf("%s: id=%d %s\n", c
->tty
, c
->id
, re
? "(reconnect) " : "");