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. If not, see <https://gnu.org/licenses/>.
21 * Author: Werner Fink <werner@suse.de>
28 #include <sys/types.h>
30 #include <sys/ioctl.h>
34 # include <linux/serial.h>
35 # include <linux/major.h>
42 #if defined(USE_SULOGIN_EMERGENCY_MOUNT)
43 # include <sys/mount.h>
45 # define MS_RELATIME (1<<21)
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 * const 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("cannot 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 * const file
)
160 DBG(dbgprint("reading %s", file
));
162 if (!(fp
= fopen(file
, "r" UL_CLOEXECSTR
)))
164 len
= getline(&ret
, &dummy
, fp
);
170 if ((nl
= strchr(ret
, '\n')))
180 * Read and determine active attribute for tty below
181 * /sys/class/tty, the caller has to free the result.
183 static __attribute__((__malloc__
))
184 char *actattr(const char * const tty
)
190 if (asprintf(&path
, "/sys/class/tty/%s/active", tty
) < 0)
199 * Read and determine device attribute for tty below
203 dev_t
devattr(const char * const tty
)
210 if (asprintf(&path
, "/sys/class/tty/%s/dev", tty
) < 0)
213 value
= oneline(path
);
215 unsigned int maj
, min
;
217 if (sscanf(value
, "%u:%u", &maj
, &min
) == 2)
218 dev
= makedev(maj
, min
);
225 #endif /* __linux__ */
228 * Search below /dev for the character device in `dev_t comparedev' variable.
229 * Note that realpath(3) is used here to avoid not existent devices due the
230 * strdup(3) used in our canonicalize_path()!
234 __attribute__((__nonnull__
,__malloc__
,__hot__
))
236 char* scandev(DIR *dir
, const dev_t comparedev
)
240 const struct dirent
*dent
;
243 DBG(dbgprint("scanning /dev for %u:%u", major(comparedev
), minor(comparedev
)));
246 * Try udev links on character devices first.
248 if ((len
= snprintf(path
, sizeof(path
),
249 "/dev/char/%u:%u", major(comparedev
), minor(comparedev
))) > 0 &&
250 (size_t)len
< sizeof(path
)) {
252 name
= realpath(path
, NULL
);
259 while ((dent
= readdir(dir
))) {
262 #ifdef _DIRENT_HAVE_D_TYPE
263 if (dent
->d_type
!= DT_UNKNOWN
&& dent
->d_type
!= DT_CHR
)
266 if (fstatat(fd
, dent
->d_name
, &st
, 0) < 0)
268 if (!S_ISCHR(st
.st_mode
))
270 if (comparedev
!= st
.st_rdev
)
272 if ((len
= snprintf(path
, sizeof(path
), "/dev/%s", dent
->d_name
)) < 0 ||
273 (size_t)len
>= sizeof(path
))
276 name
= realpath(path
, NULL
);
281 #ifdef USE_SULOGIN_EMERGENCY_MOUNT
283 * There was no /dev mounted hence and no device was found hence we create our own.
285 if (!name
&& (emergency_flags
& MNT_DEVTMPFS
)) {
287 if ((len
= snprintf(path
, sizeof(path
),
288 "/dev/tmp-%u:%u", major(comparedev
), minor(comparedev
))) < 0 ||
289 (size_t)len
>= sizeof(path
))
292 if (mknod(path
, S_IFCHR
|S_IRUSR
|S_IWUSR
, comparedev
) < 0 && errno
!= EEXIST
)
295 name
= realpath(path
, NULL
);
303 * Default control characters for an unknown terminal line.
307 * Allocate an aligned `struct console' memory area,
308 * initialize its default values, and append it to
309 * the global linked list.
313 __attribute__((__hot__
))
315 int append_console(struct list_head
*consoles
, const char * const name
)
317 struct console
*restrict tail
;
318 const struct console
*last
= NULL
;
320 DBG(dbgprint("appending %s", name
));
322 if (!list_empty(consoles
))
323 last
= list_last_entry(consoles
, struct console
, entry
);
325 if (posix_memalign((void *) &tail
, sizeof(void *),
326 alignof(struct console
) + strsize(name
)) != 0)
329 INIT_LIST_HEAD(&tail
->entry
);
330 INIT_CHARDATA(&tail
->cp
);
332 list_add_tail(&tail
->entry
, consoles
);
333 tail
->tty
= ((char *) tail
) + alignof(struct console
);
334 strcpy(tail
->tty
, name
);
336 tail
->file
= (FILE*)0;
339 tail
->id
= last
? last
->id
+ 1 : 0;
341 memset(&tail
->tio
, 0, sizeof(tail
->tio
));
342 #ifdef HAVE_LIBSELINUX
343 tail
->reset_tty_context
= NULL
;
344 tail
->user_tty_context
= NULL
;
353 * < 0 - fatal error (no mem or so... )
355 * 1 - recoverable error
356 * 2 - detection not available
358 static int detect_consoles_from_proc(struct list_head
*consoles
)
363 int maj
, min
, rc
= 1, matches
;
365 DBG(dbgprint("trying /proc"));
367 fc
= fopen("/proc/consoles", "r" UL_CLOEXECSTR
);
372 dir
= opendir("/dev");
376 while ((matches
= fscanf(fc
, "%*s %*s (%16[^)]) %d:%d", fbuf
, &maj
, &min
)) >= 1) {
382 if (!strchr(fbuf
, 'E'))
384 comparedev
= makedev(maj
, min
);
385 name
= scandev(dir
, comparedev
);
388 rc
= append_console(consoles
, name
);
394 rc
= list_empty(consoles
) ? 1 : 0;
400 DBG(dbgprint("[/proc rc=%d]", rc
));
406 * < 0 - fatal error (no mem or so... )
408 * 1 - recoverable error
409 * 2 - detection not available
411 static int detect_consoles_from_sysfs(struct list_head
*consoles
)
413 char *attrib
= NULL
, *words
, *token
;
417 DBG(dbgprint("trying /sys"));
419 attrib
= actattr("console");
427 dir
= opendir("/dev");
431 while ((token
= strsep(&words
, " \t\r\n"))) {
438 comparedev
= devattr(token
);
439 if (comparedev
== makedev(TTY_MAJOR
, 0)) {
440 char *tmp
= actattr(token
);
443 comparedev
= devattr(tmp
);
447 name
= scandev(dir
, comparedev
);
450 rc
= append_console(consoles
, name
);
456 rc
= list_empty(consoles
) ? 1 : 0;
461 DBG(dbgprint("[/sys rc=%d]", rc
));
466 static int detect_consoles_from_cmdline(struct list_head
*consoles
)
468 char *cmdline
, *words
, *token
;
473 DBG(dbgprint("trying kernel cmdline"));
475 cmdline
= oneline("/proc/cmdline");
482 dir
= opendir("/dev");
486 while ((token
= strsep(&words
, " \t\r\n"))) {
497 if (strncmp(token
, "console=", 8) != 0)
501 if (strcmp(token
, "brl") == 0)
503 if ((colon
= strchr(token
, ',')))
506 if (asprintf(&name
, "/dev/%s", token
) < 0)
508 if ((fd
= open(name
, O_RDWR
|O_NONBLOCK
|O_NOCTTY
|O_CLOEXEC
)) < 0) {
514 if (ioctl (fd
, TIOCGDEV
, &devnum
) < 0) {
518 comparedev
= (dev_t
) devnum
;
520 if (fstat(fd
, &st
) < 0) {
524 comparedev
= st
.st_rdev
;
525 if (comparedev
== makedev(TTY_MAJOR
, 0)) {
526 if (ioctl(fd
, VT_GETSTATE
, &vt
) < 0) {
530 comparedev
= makedev(TTY_MAJOR
, (int)vt
.v_active
);
535 name
= scandev(dir
, comparedev
);
538 rc
= append_console(consoles
, name
);
544 rc
= list_empty(consoles
) ? 1 : 0;
549 DBG(dbgprint("[kernel cmdline rc=%d]", rc
));
554 static int detect_consoles_from_tiocgdev(struct list_head
*consoles
,
563 struct console
*console
;
565 DBG(dbgprint("trying tiocgdev"));
567 if (!device
|| !*device
)
570 fd
= open(device
, O_RDWR
|O_NONBLOCK
|O_NOCTTY
|O_CLOEXEC
);
574 if (ioctl (fd
, TIOCGDEV
, &devnum
) < 0)
577 comparedev
= (dev_t
) devnum
;
578 dir
= opendir("/dev");
582 name
= scandev(dir
, comparedev
);
586 name
= (char *) (device
&& *device
? device
: ttyname(fallback
));
596 rc
= append_console(consoles
, name
);
600 if (list_empty(consoles
)) {
604 console
= list_last_entry(consoles
, struct console
, entry
);
605 if (console
&& (!device
|| !*device
))
606 console
->fd
= fallback
;
610 DBG(dbgprint("[tiocgdev rc=%d]", rc
));
613 #endif /* TIOCGDEV */
614 #endif /* __linux__ */
617 * Try to detect the real device(s) used for the system console
618 * /dev/console if but only if /dev/console is used. On Linux
619 * this can be more than one device, e.g. a serial line as well
620 * as a virtual console as well as a simple printer.
622 * Returns 1 if stdout and stderr should be reconnected and 0
623 * otherwise or less than zero on error.
625 int detect_consoles(const char *device
, const int fallback
, struct list_head
*consoles
)
627 int fd
, reconnect
= 0, rc
;
628 dev_t comparedev
= 0;
630 consoles_debug
= getenv("CONSOLES_DEBUG") ? 1 : 0;
632 if (!device
|| !*device
)
633 fd
= fallback
>= 0 ? dup(fallback
) : - 1;
635 fd
= open(device
, O_RDWR
|O_NONBLOCK
|O_NOCTTY
|O_CLOEXEC
);
639 DBG(dbgprint("detection started [device=%s, fallback=%d]",
651 * The Hurd always gives st_rdev as 0, which causes this
652 * method to select the first terminal it finds.
657 DBG(dbgprint("trying device/fallback file descriptor"));
659 if (fstat(fd
, &st
) < 0) {
663 comparedev
= st
.st_rdev
;
666 (fstat(fallback
, &st
) < 0 || comparedev
!= st
.st_rdev
))
670 * Check if the device detection for Linux system console should be used.
672 if (comparedev
== makedev(TTYAUX_MAJOR
, 0)) { /* /dev/tty */
677 if (comparedev
== makedev(TTYAUX_MAJOR
, 1)) { /* /dev/console */
681 if (comparedev
== makedev(TTYAUX_MAJOR
, 2)) { /* /dev/ptmx */
686 if (comparedev
== makedev(TTY_MAJOR
, 0)) { /* /dev/tty0 */
688 if (ioctl(fd
, VT_GETSTATE
, &vt
) < 0) {
692 comparedev
= makedev(TTY_MAJOR
, (int)vt
.v_active
);
696 if (ioctl (fd
, TIOCGDEV
, &devnum
) < 0) {
700 comparedev
= (dev_t
)devnum
;
703 dir
= opendir("/dev");
706 name
= scandev(dir
, comparedev
);
710 rc
= append_console(consoles
, name
);
715 if (list_empty(consoles
))
718 DBG(dbgprint("detection success [rc=%d]", reconnect
));
724 * Detection of devices used for Linux system console using
725 * the /proc/consoles API with kernel 2.6.38 and higher.
727 rc
= detect_consoles_from_proc(consoles
);
729 return reconnect
; /* success */
731 return rc
; /* fatal error */
734 * Detection of devices used for Linux system console using
735 * the sysfs /sys/class/tty/ API with kernel 2.6.37 and higher.
737 rc
= detect_consoles_from_sysfs(consoles
);
739 return reconnect
; /* success */
741 return rc
; /* fatal error */
744 * Detection of devices used for Linux system console using
745 * kernel parameter on the kernels command line.
747 rc
= detect_consoles_from_cmdline(consoles
);
749 return reconnect
; /* success */
751 return rc
; /* fatal error */
754 * Detection of the device used for Linux system console using
755 * the ioctl TIOCGDEV if available (e.g. official 2.6.38).
758 rc
= detect_consoles_from_tiocgdev(consoles
, fallback
, device
);
760 return reconnect
; /* success */
762 return rc
; /* fatal error */
764 if (!list_empty(consoles
)) {
765 DBG(dbgprint("detection success [rc=%d]", reconnect
));
769 #endif /* __linux __ */
775 struct console
*console
;
777 if (device
&& *device
!= '\0')
779 else name
= ttyname(fallback
);
787 rc
= append_console(consoles
, n
);
791 if (list_empty(consoles
))
793 console
= list_last_entry(consoles
, struct console
, entry
);
795 console
->fd
= fallback
;
798 DBG(dbgprint("detection done by fallback [rc=%d]", reconnect
));
804 int main(int argc
, char *argv
[])
808 struct list_head
*p
, consoles
;
812 fd
= open(name
, O_RDWR
);
814 name
= ttyname(STDIN_FILENO
);
819 errx(EXIT_FAILURE
, "usage: %s [<tty>]\n", program_invocation_short_name
);
821 INIT_LIST_HEAD(&consoles
);
822 re
= detect_consoles(name
, fd
, &consoles
);
824 list_for_each(p
, &consoles
) {
825 struct console
*c
= list_entry(p
, struct console
, entry
);
826 printf("%s: id=%d %s\n", c
->tty
, c
->id
, re
? "(reconnect) " : "");