]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/sulogin-consoles.c
scriptreplay: cleanup usage()
[thirdparty/util-linux.git] / login-utils / sulogin-consoles.c
1 /*
2 * consoles.c Routines to detect the system consoles
3 *
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>
7 *
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)
11 * any later version.
12 *
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.
17 *
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,
21 * MA 02110-1301, USA.
22 *
23 * Author: Werner Fink <werner@suse.de>
24 */
25
26 #include <limits.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <sys/ioctl.h>
33 #ifdef __linux__
34 # include <sys/vt.h>
35 # include <sys/kd.h>
36 # include <linux/serial.h>
37 # include <linux/major.h>
38 #endif
39 #include <dirent.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <unistd.h>
43
44 #ifdef USE_SULOGIN_EMERGENCY_MOUNT
45 # include <sys/mount.h>
46 # include <linux/fs.h>
47 # include <linux/magic.h>
48 # ifndef MNT_DETACH
49 # define MNT_DETACH 2
50 # endif
51 #endif
52
53 #include "c.h"
54 #include "canonicalize.h"
55 #include "sulogin-consoles.h"
56
57 #if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)
58 # ifndef typeof
59 # define typeof __typeof__
60 # endif
61 # ifndef restrict
62 # define restrict __restrict__
63 # endif
64 #endif
65
66 #define alignof(type) ((sizeof(type)+(sizeof(void*)-1)) & ~(sizeof(void*)-1))
67 #define strsize(string) (strlen((string))+1)
68
69 static int consoles_debug;
70 #define DBG(x) do { \
71 if (consoles_debug) { \
72 fputs("consoles debug: ", stderr); \
73 x; \
74 } \
75 } while (0)
76
77 static inline void __attribute__ ((__format__ (__printf__, 1, 2)))
78 dbgprint(const char * const mesg, ...)
79 {
80 va_list ap;
81 va_start(ap, mesg);
82 vfprintf(stderr, mesg, ap);
83 va_end(ap);
84 fputc('\n', stderr);
85 }
86
87 #ifdef USE_SULOGIN_EMERGENCY_MOUNT
88 /*
89 * Make C library standard calls such like ttyname(3) work
90 * even if the system does not show any of the standard
91 * directories.
92 */
93
94 static uint32_t emergency_flags;
95 # define MNT_PROCFS 0x0001
96 # define MNT_DEVTMPFS 0x0002
97
98 void emergency_do_umounts(void)
99 {
100 if (emergency_flags & MNT_DEVTMPFS)
101 umount2("/dev", MNT_DETACH);
102 if (emergency_flags & MNT_PROCFS)
103 umount2("/proc", MNT_DETACH);
104 }
105
106 void emergency_do_mounts(void)
107 {
108 struct stat rt, xt;
109
110 if (emergency_flags) {
111 emergency_flags = 0;
112 return;
113 }
114
115 if (stat("/", &rt) != 0) {
116 warn("cannot get file status of root file system\n");
117 return;
118 }
119
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;
124
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) {
129
130 emergency_flags |= MNT_DEVTMPFS;
131 mknod("/dev/console", S_IFCHR|S_IRUSR|S_IWUSR,
132 makedev(TTYAUX_MAJOR, 1));
133
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") );
138 }
139 }
140 }
141
142 #else /* !USE_SULOGIN_EMERGENCY_MOUNT */
143
144 void emergency_do_umounts(void) { }
145 void emergency_do_mounts(void) { }
146
147 #endif /* USE_SULOGIN_EMERGENCY_MOUNT */
148
149 /*
150 * Read and allocate one line from file,
151 * the caller has to free the result
152 */
153 static __attribute__((__nonnull__))
154 char *oneline(const char * const file)
155 {
156 FILE *fp;
157 char *ret = NULL;
158 size_t dummy = 0;
159 ssize_t len;
160
161 DBG(dbgprint("reading %s", file));
162
163 if (!(fp = fopen(file, "r" UL_CLOEXECSTR)))
164 return NULL;
165 len = getline(&ret, &dummy, fp);
166 if (len >= 0) {
167 char *nl;
168
169 if (len)
170 ret[len-1] = '\0';
171 if ((nl = strchr(ret, '\n')))
172 *nl = '\0';
173 }
174
175 fclose(fp);
176 return ret;
177 }
178
179 #ifdef __linux__
180 /*
181 * Read and determine active attribute for tty below
182 * /sys/class/tty, the caller has to free the result.
183 */
184 static __attribute__((__malloc__))
185 char *actattr(const char * const tty)
186 {
187 char *ret, *path;
188
189 if (!tty || !*tty)
190 return NULL;
191 if (asprintf(&path, "/sys/class/tty/%s/active", tty) < 0)
192 return NULL;
193
194 ret = oneline(path);
195 free(path);
196 return ret;
197 }
198
199 /*
200 * Read and determine device attribute for tty below
201 * /sys/class/tty.
202 */
203 static
204 dev_t devattr(const char * const tty)
205 {
206 dev_t dev = 0;
207 char *path, *value;
208
209 if (!tty || !*tty)
210 return 0;
211 if (asprintf(&path, "/sys/class/tty/%s/dev", tty) < 0)
212 return 0;
213
214 value = oneline(path);
215 if (value) {
216 unsigned int maj, min;
217
218 if (sscanf(value, "%u:%u", &maj, &min) == 2)
219 dev = makedev(maj, min);
220 free(value);
221 }
222
223 free(path);
224 return dev;
225 }
226 #endif /* __linux__ */
227
228 /*
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()!
232 */
233 static
234 #ifdef __GNUC__
235 __attribute__((__nonnull__,__malloc__,__hot__))
236 #endif
237 char* scandev(DIR *dir, const dev_t comparedev)
238 {
239 char path[PATH_MAX];
240 char *name = NULL;
241 const struct dirent *dent;
242 int len, fd;
243
244 DBG(dbgprint("scanning /dev for %u:%u", major(comparedev), minor(comparedev)));
245
246 /*
247 * Try udev links on character devices first.
248 */
249 if ((len = snprintf(path, sizeof(path),
250 "/dev/char/%u:%u", major(comparedev), minor(comparedev))) > 0 &&
251 (size_t)len < sizeof(path)) {
252
253 name = realpath(path, NULL);
254 if (name)
255 goto out;
256 }
257
258 fd = dirfd(dir);
259 rewinddir(dir);
260 while ((dent = readdir(dir))) {
261 struct stat st;
262
263 #ifdef _DIRENT_HAVE_D_TYPE
264 if (dent->d_type != DT_UNKNOWN && dent->d_type != DT_CHR)
265 continue;
266 #endif
267 if (fstatat(fd, dent->d_name, &st, 0) < 0)
268 continue;
269 if (!S_ISCHR(st.st_mode))
270 continue;
271 if (comparedev != st.st_rdev)
272 continue;
273 if ((len = snprintf(path, sizeof(path), "/dev/%s", dent->d_name)) < 0 ||
274 (size_t)len >= sizeof(path))
275 continue;
276
277 name = realpath(path, NULL);
278 if (name)
279 goto out;
280 }
281
282 #ifdef USE_SULOGIN_EMERGENCY_MOUNT
283 /*
284 * There was no /dev mounted hence and no device was found hence we create our own.
285 */
286 if (!name && (emergency_flags & MNT_DEVTMPFS)) {
287
288 if ((len = snprintf(path, sizeof(path),
289 "/dev/tmp-%u:%u", major(comparedev), minor(comparedev))) < 0 ||
290 (size_t)len >= sizeof(path))
291 goto out;
292
293 if (mknod(path, S_IFCHR|S_IRUSR|S_IWUSR, comparedev) < 0 && errno != EEXIST)
294 goto out;
295
296 name = realpath(path, NULL);
297 }
298 #endif
299 out:
300 return name;
301 }
302
303 /*
304 * Default control characters for an unknown terminal line.
305 */
306
307 /*
308 * Allocate an aligned `struct console' memory area,
309 * initialize its default values, and append it to
310 * the global linked list.
311 */
312 static
313 #ifdef __GNUC__
314 __attribute__((__hot__))
315 #endif
316 int append_console(struct list_head *consoles, const char * const name)
317 {
318 struct console *restrict tail;
319 const struct console *last = NULL;
320
321 DBG(dbgprint("appenging %s", name));
322
323 if (!list_empty(consoles))
324 last = list_last_entry(consoles, struct console, entry);
325
326 if (posix_memalign((void *) &tail, sizeof(void *),
327 alignof(struct console) + strsize(name)) != 0)
328 return -ENOMEM;
329
330 INIT_LIST_HEAD(&tail->entry);
331 INIT_CHARDATA(&tail->cp);
332
333 list_add_tail(&tail->entry, consoles);
334 tail->tty = ((char *) tail) + alignof(struct console);
335 strcpy(tail->tty, name);
336
337 tail->file = (FILE*)0;
338 tail->flags = 0;
339 tail->fd = -1;
340 tail->id = last ? last->id + 1 : 0;
341 tail->pid = -1;
342 memset(&tail->tio, 0, sizeof(tail->tio));
343
344 return 0;
345 }
346
347 #ifdef __linux__
348 /*
349 * return codes:
350 * < 0 - fatal error (no mem or so... )
351 * 0 - success
352 * 1 - recoverable error
353 * 2 - detection not available
354 */
355 static int detect_consoles_from_proc(struct list_head *consoles)
356 {
357 char fbuf[16 + 1];
358 DIR *dir = NULL;
359 FILE *fc = NULL;
360 int maj, min, rc = 1, matches;
361
362 DBG(dbgprint("trying /proc"));
363
364 fc = fopen("/proc/consoles", "r" UL_CLOEXECSTR);
365 if (!fc) {
366 rc = 2;
367 goto done;
368 }
369 dir = opendir("/dev");
370 if (!dir)
371 goto done;
372
373 while ((matches = fscanf(fc, "%*s %*s (%16[^)]) %d:%d", fbuf, &maj, &min)) >= 1) {
374 char *name;
375 dev_t comparedev;
376
377 if (matches != 3)
378 continue;
379 if (!strchr(fbuf, 'E'))
380 continue;
381 comparedev = makedev(maj, min);
382 name = scandev(dir, comparedev);
383 if (!name)
384 continue;
385 rc = append_console(consoles, name);
386 free(name);
387 if (rc < 0)
388 goto done;
389 }
390
391 rc = list_empty(consoles) ? 1 : 0;
392 done:
393 if (dir)
394 closedir(dir);
395 if (fc)
396 fclose(fc);
397 DBG(dbgprint("[/proc rc=%d]", rc));
398 return rc;
399 }
400
401 /*
402 * return codes:
403 * < 0 - fatal error (no mem or so... )
404 * 0 - success
405 * 1 - recoverable error
406 * 2 - detection not available
407 */
408 static int detect_consoles_from_sysfs(struct list_head *consoles)
409 {
410 char *attrib = NULL, *words, *token;
411 DIR *dir = NULL;
412 int rc = 1;
413
414 DBG(dbgprint("trying /sys"));
415
416 attrib = actattr("console");
417 if (!attrib) {
418 rc = 2;
419 goto done;
420 }
421
422 words = attrib;
423
424 dir = opendir("/dev");
425 if (!dir)
426 goto done;
427
428 while ((token = strsep(&words, " \t\r\n"))) {
429 char *name;
430 dev_t comparedev;
431
432 if (*token == '\0')
433 continue;
434
435 comparedev = devattr(token);
436 if (comparedev == makedev(TTY_MAJOR, 0)) {
437 char *tmp = actattr(token);
438 if (!tmp)
439 continue;
440 comparedev = devattr(tmp);
441 free(tmp);
442 }
443
444 name = scandev(dir, comparedev);
445 if (!name)
446 continue;
447 rc = append_console(consoles, name);
448 free(name);
449 if (rc < 0)
450 goto done;
451 }
452
453 rc = list_empty(consoles) ? 1 : 0;
454 done:
455 free(attrib);
456 if (dir)
457 closedir(dir);
458 DBG(dbgprint("[/sys rc=%d]", rc));
459 return rc;
460 }
461
462
463 static int detect_consoles_from_cmdline(struct list_head *consoles)
464 {
465 char *cmdline, *words, *token;
466 dev_t comparedev;
467 DIR *dir = NULL;
468 int rc = 1, fd;
469
470 DBG(dbgprint("trying kernel cmdline"));
471
472 cmdline = oneline("/proc/cmdline");
473 if (!cmdline) {
474 rc = 2;
475 goto done;
476 }
477
478 words= cmdline;
479 dir = opendir("/dev");
480 if (!dir)
481 goto done;
482
483 while ((token = strsep(&words, " \t\r\n"))) {
484 #ifdef TIOCGDEV
485 unsigned int devnum;
486 #else
487 struct vt_stat vt;
488 struct stat st;
489 #endif
490 char *colon, *name;
491
492 if (*token != 'c')
493 continue;
494 if (strncmp(token, "console=", 8) != 0)
495 continue;
496 token += 8;
497
498 if (strcmp(token, "brl") == 0)
499 token += 4;
500 if ((colon = strchr(token, ',')))
501 *colon = '\0';
502
503 if (asprintf(&name, "/dev/%s", token) < 0)
504 continue;
505 if ((fd = open(name, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC)) < 0) {
506 free(name);
507 continue;
508 }
509 free(name);
510 #ifdef TIOCGDEV
511 if (ioctl (fd, TIOCGDEV, &devnum) < 0) {
512 close(fd);
513 continue;
514 }
515 comparedev = (dev_t) devnum;
516 #else
517 if (fstat(fd, &st) < 0) {
518 close(fd);
519 continue;
520 }
521 comparedev = st.st_rdev;
522 if (comparedev == makedev(TTY_MAJOR, 0)) {
523 if (ioctl(fd, VT_GETSTATE, &vt) < 0) {
524 close(fd);
525 continue;
526 }
527 comparedev = makedev(TTY_MAJOR, (int)vt.v_active);
528 }
529 #endif
530 close(fd);
531
532 name = scandev(dir, comparedev);
533 if (!name)
534 continue;
535 rc = append_console(consoles, name);
536 free(name);
537 if (rc < 0)
538 goto done;
539 }
540
541 rc = list_empty(consoles) ? 1 : 0;
542 done:
543 if (dir)
544 closedir(dir);
545 free(cmdline);
546 DBG(dbgprint("[kernel cmdline rc=%d]", rc));
547 return rc;
548 }
549
550 #ifdef TIOCGDEV
551 static int detect_consoles_from_tiocgdev(struct list_head *consoles,
552 const int fallback,
553 const char *device)
554 {
555 unsigned int devnum;
556 char *name;
557 int rc = 1, fd = -1;
558 dev_t comparedev;
559 DIR *dir = NULL;
560 struct console *console;
561
562 DBG(dbgprint("trying tiocgdev"));
563
564 if (!device || !*device)
565 fd = dup(fallback);
566 else
567 fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC);
568
569 if (fd < 0)
570 goto done;
571 if (ioctl (fd, TIOCGDEV, &devnum) < 0)
572 goto done;
573
574 comparedev = (dev_t) devnum;
575 dir = opendir("/dev");
576 if (!dir)
577 goto done;
578
579 name = scandev(dir, comparedev);
580 closedir(dir);
581
582 if (!name) {
583 name = (char *) (device && *device ? device : ttyname(fallback));
584 if (!name)
585 name = "/dev/tty1";
586
587 name = strdup(name);
588 if (!name) {
589 rc = -ENOMEM;
590 goto done;
591 }
592 }
593 rc = append_console(consoles, name);
594 free(name);
595 if (rc < 0)
596 goto done;
597 if (list_empty(consoles)) {
598 rc = 1;
599 goto done;
600 }
601 console = list_last_entry(consoles, struct console, entry);
602 if (console && (!device || !*device))
603 console->fd = fallback;
604 done:
605 if (fd >= 0)
606 close(fd);
607 DBG(dbgprint("[tiocgdev rc=%d]", rc));
608 return rc;
609 }
610 #endif /* TIOCGDEV */
611 #endif /* __linux__ */
612
613 /*
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.
618 *
619 * Returns 1 if stdout and stderr should be reconnected and 0
620 * otherwise or less than zero on error.
621 */
622 int detect_consoles(const char *device, const int fallback, struct list_head *consoles)
623 {
624 int fd, reconnect = 0, rc;
625 dev_t comparedev = 0;
626
627 consoles_debug = getenv("CONSOLES_DEBUG") ? 1 : 0;
628
629 if (!device || !*device)
630 fd = fallback >= 0 ? dup(fallback) : - 1;
631 else {
632 fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC);
633 reconnect = 1;
634 }
635
636 DBG(dbgprint("detection started [device=%s, fallback=%d]",
637 device, fallback));
638
639 if (fd >= 0) {
640 DIR *dir;
641 char *name;
642 struct stat st;
643 #ifdef TIOCGDEV
644 unsigned int devnum;
645 #endif
646 #ifdef __GNU__
647 /*
648 * The Hurd always gives st_rdev as 0, which causes this
649 * method to select the first terminal it finds.
650 */
651 close(fd);
652 goto fallback;
653 #endif
654 DBG(dbgprint("trying device/fallback file descriptor"));
655
656 if (fstat(fd, &st) < 0) {
657 close(fd);
658 goto fallback;
659 }
660 comparedev = st.st_rdev;
661
662 if (reconnect &&
663 (fstat(fallback, &st) < 0 || comparedev != st.st_rdev))
664 dup2(fd, fallback);
665 #ifdef __linux__
666 /*
667 * Check if the device detection for Linux system console should be used.
668 */
669 if (comparedev == makedev(TTYAUX_MAJOR, 0)) { /* /dev/tty */
670 close(fd);
671 device = "/dev/tty";
672 goto fallback;
673 }
674 if (comparedev == makedev(TTYAUX_MAJOR, 1)) { /* /dev/console */
675 close(fd);
676 goto console;
677 }
678 if (comparedev == makedev(TTYAUX_MAJOR, 2)) { /* /dev/ptmx */
679 close(fd);
680 device = "/dev/tty";
681 goto fallback;
682 }
683 if (comparedev == makedev(TTY_MAJOR, 0)) { /* /dev/tty0 */
684 struct vt_stat vt;
685 if (ioctl(fd, VT_GETSTATE, &vt) < 0) {
686 close(fd);
687 goto fallback;
688 }
689 comparedev = makedev(TTY_MAJOR, (int)vt.v_active);
690 }
691 #endif
692 #ifdef TIOCGDEV
693 if (ioctl (fd, TIOCGDEV, &devnum) < 0) {
694 close(fd);
695 goto fallback;
696 }
697 comparedev = (dev_t)devnum;
698 #endif
699 close(fd);
700 dir = opendir("/dev");
701 if (!dir)
702 goto fallback;
703 name = scandev(dir, comparedev);
704 closedir(dir);
705
706 if (name) {
707 rc = append_console(consoles, name);
708 free(name);
709 if (rc < 0)
710 return rc;
711 }
712 if (list_empty(consoles))
713 goto fallback;
714
715 DBG(dbgprint("detection success [rc=%d]", reconnect));
716 return reconnect;
717 }
718 #ifdef __linux__
719 console:
720 /*
721 * Detection of devices used for Linux system console using
722 * the /proc/consoles API with kernel 2.6.38 and higher.
723 */
724 rc = detect_consoles_from_proc(consoles);
725 if (rc == 0)
726 return reconnect; /* success */
727 if (rc < 0)
728 return rc; /* fatal error */
729
730 /*
731 * Detection of devices used for Linux system console using
732 * the sysfs /sys/class/tty/ API with kernel 2.6.37 and higher.
733 */
734 rc = detect_consoles_from_sysfs(consoles);
735 if (rc == 0)
736 return reconnect; /* success */
737 if (rc < 0)
738 return rc; /* fatal error */
739
740 /*
741 * Detection of devices used for Linux system console using
742 * kernel parameter on the kernels command line.
743 */
744 rc = detect_consoles_from_cmdline(consoles);
745 if (rc == 0)
746 return reconnect; /* success */
747 if (rc < 0)
748 return rc; /* fatal error */
749
750 /*
751 * Detection of the device used for Linux system console using
752 * the ioctl TIOCGDEV if available (e.g. official 2.6.38).
753 */
754 #ifdef TIOCGDEV
755 rc = detect_consoles_from_tiocgdev(consoles, fallback, device);
756 if (rc == 0)
757 return reconnect; /* success */
758 if (rc < 0)
759 return rc; /* fatal error */
760 #endif
761 if (!list_empty(consoles)) {
762 DBG(dbgprint("detection success [rc=%d]", reconnect));
763 return reconnect;
764 }
765
766 #endif /* __linux __ */
767
768 fallback:
769 if (fallback >= 0) {
770 const char *name;
771 char *n;
772 struct console *console;
773
774 if (device && *device != '\0')
775 name = device;
776 else name = ttyname(fallback);
777
778 if (!name)
779 name = "/dev/tty";
780
781 n = strdup(name);
782 if (!n)
783 return -ENOMEM;
784 rc = append_console(consoles, n);
785 free(n);
786 if (rc < 0)
787 return rc;
788 if (list_empty(consoles))
789 return 1;
790 console = list_last_entry(consoles, struct console, entry);
791 if (console)
792 console->fd = fallback;
793 }
794
795 DBG(dbgprint("detection done by fallback [rc=%d]", reconnect));
796 return reconnect;
797 }
798
799
800 #ifdef TEST_PROGRAM
801 int main(int argc, char *argv[])
802 {
803 char *name = NULL;
804 int fd, re;
805 struct list_head *p, consoles;
806
807 if (argc == 2) {
808 name = argv[1];
809 fd = open(name, O_RDWR);
810 } else {
811 name = ttyname(STDIN_FILENO);
812 fd = STDIN_FILENO;
813 }
814
815 if (!name)
816 errx(EXIT_FAILURE, "usage: %s [<tty>]\n", program_invocation_short_name);
817
818 INIT_LIST_HEAD(&consoles);
819 re = detect_consoles(name, fd, &consoles);
820
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) " : "");
824 }
825
826 return 0;
827 }
828 #endif