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