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