]>
Commit | Line | Data |
---|---|---|
3a08f74f WF |
1 | /* |
2 | * consoles.c Routines to detect the system consoles | |
3 | * | |
4 | * Copyright (c) 2011 SuSE LINUX Products GmbH, All rights reserved. | |
95226b55 | 5 | * Copyright (C) 2012 Karel Zak <kzak@redhat.com> |
cae29b37 | 6 | * Copyright (C) 2012 Werner Fink <werner@suse.de> |
3a08f74f WF |
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. | |
95226b55 | 12 | * |
3a08f74f WF |
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__ | |
5ad09239 KZ |
34 | # include <sys/vt.h> |
35 | # include <sys/kd.h> | |
36 | # include <linux/serial.h> | |
37 | # include <linux/major.h> | |
3a08f74f | 38 | #endif |
3a08f74f | 39 | #include <dirent.h> |
cde7699c WF |
40 | #include <errno.h> |
41 | #include <fcntl.h> | |
3a08f74f | 42 | #include <unistd.h> |
95226b55 | 43 | |
ab6478ef WF |
44 | #ifdef USE_SULOGIN_EMERGENCY_MOUNT |
45 | # include <sys/mount.h> | |
46 | # include <linux/fs.h> | |
47 | # include <linux/magic.h> | |
ab6478ef WF |
48 | # ifndef MNT_DETACH |
49 | # define MNT_DETACH 2 | |
50 | # endif | |
51 | #endif | |
52 | ||
95226b55 KZ |
53 | #include "c.h" |
54 | #include "canonicalize.h" | |
a73f59fa | 55 | #include "sulogin-consoles.h" |
3a08f74f | 56 | |
3a08f74f WF |
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)) | |
cae29b37 | 67 | #define strsize(string) (strlen((string))+1) |
3a08f74f | 68 | |
b25343bb KZ |
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))) | |
71f207a5 | 78 | dbgprint(const char * const mesg, ...) |
b25343bb KZ |
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 | ||
ab6478ef WF |
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 | ||
ab6478ef WF |
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 | ||
ab6478ef WF |
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) { | |
223939d9 | 116 | warn("cannot get file status of root file system\n"); |
ab6478ef WF |
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 | } | |
4226f910 KZ |
141 | |
142 | #else /* !USE_SULOGIN_EMERGENCY_MOUNT */ | |
143 | ||
144 | void emergency_do_umounts(void) { } | |
145 | void emergency_do_mounts(void) { } | |
146 | ||
ab6478ef WF |
147 | #endif /* USE_SULOGIN_EMERGENCY_MOUNT */ |
148 | ||
3a08f74f WF |
149 | /* |
150 | * Read and allocate one line from file, | |
151 | * the caller has to free the result | |
152 | */ | |
95226b55 | 153 | static __attribute__((__nonnull__)) |
71f207a5 | 154 | char *oneline(const char * const file) |
3a08f74f WF |
155 | { |
156 | FILE *fp; | |
95226b55 | 157 | char *ret = NULL; |
1c8beb3d GJ |
158 | size_t dummy = 0; |
159 | ssize_t len; | |
3a08f74f | 160 | |
b25343bb KZ |
161 | DBG(dbgprint("reading %s", file)); |
162 | ||
40042382 | 163 | if (!(fp = fopen(file, "r" UL_CLOEXECSTR))) |
95226b55 | 164 | return NULL; |
1c8beb3d GJ |
165 | len = getline(&ret, &dummy, fp); |
166 | if (len >= 0) { | |
95226b55 KZ |
167 | char *nl; |
168 | ||
169 | if (len) | |
170 | ret[len-1] = '\0'; | |
171 | if ((nl = strchr(ret, '\n'))) | |
172 | *nl = '\0'; | |
173 | } | |
174 | ||
3a08f74f | 175 | fclose(fp); |
3a08f74f WF |
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 | */ | |
95226b55 | 184 | static __attribute__((__malloc__)) |
71f207a5 | 185 | char *actattr(const char * const tty) |
3a08f74f | 186 | { |
95226b55 | 187 | char *ret, *path; |
3a08f74f | 188 | |
95226b55 KZ |
189 | if (!tty || !*tty) |
190 | return NULL; | |
3a08f74f | 191 | if (asprintf(&path, "/sys/class/tty/%s/active", tty) < 0) |
95226b55 | 192 | return NULL; |
3a08f74f | 193 | |
95226b55 | 194 | ret = oneline(path); |
3a08f74f | 195 | free(path); |
3a08f74f WF |
196 | return ret; |
197 | } | |
198 | ||
199 | /* | |
200 | * Read and determine device attribute for tty below | |
201 | * /sys/class/tty. | |
202 | */ | |
203 | static | |
71f207a5 | 204 | dev_t devattr(const char * const tty) |
3a08f74f | 205 | { |
3a08f74f WF |
206 | dev_t dev = 0; |
207 | char *path, *value; | |
208 | ||
95226b55 KZ |
209 | if (!tty || !*tty) |
210 | return 0; | |
3a08f74f | 211 | if (asprintf(&path, "/sys/class/tty/%s/dev", tty) < 0) |
95226b55 | 212 | return 0; |
3a08f74f | 213 | |
95226b55 KZ |
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 | } | |
3a08f74f | 222 | |
3a08f74f | 223 | free(path); |
3a08f74f WF |
224 | return dev; |
225 | } | |
226 | #endif /* __linux__ */ | |
227 | ||
228 | /* | |
3fd1f771 | 229 | * Search below /dev for the character device in `dev_t comparedev' variable. |
cde7699c WF |
230 | * Note that realpath(3) is used here to avoid not existent devices due the |
231 | * strdup(3) used in our canonicalize_path()! | |
3a08f74f | 232 | */ |
3a08f74f WF |
233 | static |
234 | #ifdef __GNUC__ | |
235 | __attribute__((__nonnull__,__malloc__,__hot__)) | |
236 | #endif | |
71f207a5 | 237 | char* scandev(DIR *dir, const dev_t comparedev) |
3a08f74f | 238 | { |
cde7699c | 239 | char path[PATH_MAX]; |
95226b55 | 240 | char *name = NULL; |
71f207a5 | 241 | const struct dirent *dent; |
cde7699c | 242 | int len, fd; |
3a08f74f | 243 | |
b25343bb KZ |
244 | DBG(dbgprint("scanning /dev for %u:%u", major(comparedev), minor(comparedev))); |
245 | ||
cde7699c WF |
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 | ||
3a08f74f WF |
258 | fd = dirfd(dir); |
259 | rewinddir(dir); | |
260 | while ((dent = readdir(dir))) { | |
3a08f74f | 261 | struct stat st; |
3deb67f5 KZ |
262 | |
263 | #ifdef _DIRENT_HAVE_D_TYPE | |
264 | if (dent->d_type != DT_UNKNOWN && dent->d_type != DT_CHR) | |
265 | continue; | |
266 | #endif | |
3a08f74f WF |
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; | |
cde7699c WF |
273 | if ((len = snprintf(path, sizeof(path), "/dev/%s", dent->d_name)) < 0 || |
274 | (size_t)len >= sizeof(path)) | |
3a08f74f | 275 | continue; |
ab6478ef | 276 | |
cde7699c WF |
277 | name = realpath(path, NULL); |
278 | if (name) | |
279 | goto out; | |
3a08f74f | 280 | } |
95226b55 | 281 | |
cde7699c WF |
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: | |
3a08f74f WF |
300 | return name; |
301 | } | |
302 | ||
303 | /* | |
304 | * Default control characters for an unknown terminal line. | |
305 | */ | |
3a08f74f WF |
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 | */ | |
3a08f74f WF |
312 | static |
313 | #ifdef __GNUC__ | |
b236e43e | 314 | __attribute__((__hot__)) |
3a08f74f | 315 | #endif |
71f207a5 | 316 | int append_console(struct list_head *consoles, const char * const name) |
3a08f74f WF |
317 | { |
318 | struct console *restrict tail; | |
71f207a5 | 319 | const struct console *last = NULL; |
53e0a688 | 320 | |
b25343bb KZ |
321 | DBG(dbgprint("appenging %s", name)); |
322 | ||
cae29b37 WF |
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) | |
f0d6004c | 328 | return -ENOMEM; |
3a08f74f | 329 | |
cae29b37 | 330 | INIT_LIST_HEAD(&tail->entry); |
f5664477 | 331 | INIT_CHARDATA(&tail->cp); |
6c7c1eaf | 332 | |
cae29b37 WF |
333 | list_add_tail(&tail->entry, consoles); |
334 | tail->tty = ((char *) tail) + alignof(struct console); | |
335 | strcpy(tail->tty, name); | |
3a08f74f WF |
336 | |
337 | tail->file = (FILE*)0; | |
338 | tail->flags = 0; | |
339 | tail->fd = -1; | |
6c7c1eaf | 340 | tail->id = last ? last->id + 1 : 0; |
cde7699c | 341 | tail->pid = -1; |
3a08f74f | 342 | memset(&tail->tio, 0, sizeof(tail->tio)); |
3a08f74f | 343 | |
f0d6004c | 344 | return 0; |
3a08f74f WF |
345 | } |
346 | ||
aded518a KZ |
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 | */ | |
cae29b37 | 355 | static int detect_consoles_from_proc(struct list_head *consoles) |
aded518a KZ |
356 | { |
357 | char fbuf[16 + 1]; | |
358 | DIR *dir = NULL; | |
359 | FILE *fc = NULL; | |
624b204d | 360 | int maj, min, rc = 1, matches; |
aded518a | 361 | |
b25343bb | 362 | DBG(dbgprint("trying /proc")); |
aded518a | 363 | |
40042382 | 364 | fc = fopen("/proc/consoles", "r" UL_CLOEXECSTR); |
b25343bb KZ |
365 | if (!fc) { |
366 | rc = 2; | |
367 | goto done; | |
368 | } | |
aded518a KZ |
369 | dir = opendir("/dev"); |
370 | if (!dir) | |
371 | goto done; | |
372 | ||
624b204d | 373 | while ((matches = fscanf(fc, "%*s %*s (%16[^)]) %d:%d", fbuf, &maj, &min)) >= 1) { |
aded518a KZ |
374 | char *name; |
375 | dev_t comparedev; | |
376 | ||
624b204d WF |
377 | if (matches != 3) |
378 | continue; | |
aded518a KZ |
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); | |
cae29b37 | 386 | free(name); |
aded518a KZ |
387 | if (rc < 0) |
388 | goto done; | |
389 | } | |
390 | ||
cae29b37 | 391 | rc = list_empty(consoles) ? 1 : 0; |
aded518a KZ |
392 | done: |
393 | if (dir) | |
394 | closedir(dir); | |
395 | if (fc) | |
396 | fclose(fc); | |
b25343bb | 397 | DBG(dbgprint("[/proc rc=%d]", rc)); |
aded518a KZ |
398 | return rc; |
399 | } | |
615eada9 KZ |
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 | */ | |
cae29b37 | 408 | static int detect_consoles_from_sysfs(struct list_head *consoles) |
615eada9 KZ |
409 | { |
410 | char *attrib = NULL, *words, *token; | |
411 | DIR *dir = NULL; | |
412 | int rc = 1; | |
413 | ||
b25343bb KZ |
414 | DBG(dbgprint("trying /sys")); |
415 | ||
615eada9 | 416 | attrib = actattr("console"); |
b25343bb KZ |
417 | if (!attrib) { |
418 | rc = 2; | |
419 | goto done; | |
420 | } | |
615eada9 KZ |
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); | |
cae29b37 | 448 | free(name); |
615eada9 KZ |
449 | if (rc < 0) |
450 | goto done; | |
451 | } | |
452 | ||
cae29b37 | 453 | rc = list_empty(consoles) ? 1 : 0; |
615eada9 KZ |
454 | done: |
455 | free(attrib); | |
456 | if (dir) | |
457 | closedir(dir); | |
b25343bb | 458 | DBG(dbgprint("[/sys rc=%d]", rc)); |
615eada9 KZ |
459 | return rc; |
460 | } | |
461 | ||
4fcee23d | 462 | |
cae29b37 | 463 | static int detect_consoles_from_cmdline(struct list_head *consoles) |
4fcee23d KZ |
464 | { |
465 | char *cmdline, *words, *token; | |
466 | dev_t comparedev; | |
b25343bb | 467 | DIR *dir = NULL; |
4fcee23d KZ |
468 | int rc = 1, fd; |
469 | ||
b25343bb KZ |
470 | DBG(dbgprint("trying kernel cmdline")); |
471 | ||
4fcee23d | 472 | cmdline = oneline("/proc/cmdline"); |
b25343bb KZ |
473 | if (!cmdline) { |
474 | rc = 2; | |
475 | goto done; | |
476 | } | |
4fcee23d KZ |
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); | |
cae29b37 | 536 | free(name); |
4fcee23d KZ |
537 | if (rc < 0) |
538 | goto done; | |
539 | } | |
540 | ||
cae29b37 | 541 | rc = list_empty(consoles) ? 1 : 0; |
4fcee23d KZ |
542 | done: |
543 | if (dir) | |
544 | closedir(dir); | |
545 | free(cmdline); | |
b25343bb | 546 | DBG(dbgprint("[kernel cmdline rc=%d]", rc)); |
4fcee23d KZ |
547 | return rc; |
548 | } | |
549 | ||
a303e047 | 550 | #ifdef TIOCGDEV |
cae29b37 | 551 | static int detect_consoles_from_tiocgdev(struct list_head *consoles, |
71f207a5 | 552 | const int fallback, |
6ea0921e KZ |
553 | const char *device) |
554 | { | |
6ea0921e KZ |
555 | unsigned int devnum; |
556 | char *name; | |
557 | int rc = 1, fd = -1; | |
558 | dev_t comparedev; | |
559 | DIR *dir = NULL; | |
cae29b37 | 560 | struct console *console; |
6ea0921e | 561 | |
b25343bb KZ |
562 | DBG(dbgprint("trying tiocgdev")); |
563 | ||
6ea0921e KZ |
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); | |
cae29b37 | 594 | free(name); |
6ea0921e KZ |
595 | if (rc < 0) |
596 | goto done; | |
cae29b37 WF |
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; | |
6ea0921e KZ |
604 | done: |
605 | if (fd >= 0) | |
606 | close(fd); | |
b25343bb | 607 | DBG(dbgprint("[tiocgdev rc=%d]", rc)); |
6ea0921e | 608 | return rc; |
6ea0921e | 609 | } |
a303e047 | 610 | #endif /* TIOCGDEV */ |
aded518a KZ |
611 | #endif /* __linux__ */ |
612 | ||
3a08f74f WF |
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 | |
f0d6004c | 620 | * otherwise or less than zero on error. |
3a08f74f | 621 | */ |
71f207a5 | 622 | int detect_consoles(const char *device, const int fallback, struct list_head *consoles) |
3a08f74f | 623 | { |
97909f61 | 624 | int fd, reconnect = 0, rc; |
e0e5974f | 625 | dev_t comparedev = 0; |
4fcee23d | 626 | |
b25343bb KZ |
627 | consoles_debug = getenv("CONSOLES_DEBUG") ? 1 : 0; |
628 | ||
95226b55 | 629 | if (!device || !*device) |
863d371c | 630 | fd = fallback >= 0 ? dup(fallback) : - 1; |
3a08f74f WF |
631 | else { |
632 | fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC); | |
97909f61 | 633 | reconnect = 1; |
3a08f74f WF |
634 | } |
635 | ||
b25343bb KZ |
636 | DBG(dbgprint("detection started [device=%s, fallback=%d]", |
637 | device, fallback)); | |
638 | ||
3a08f74f WF |
639 | if (fd >= 0) { |
640 | DIR *dir; | |
641 | char *name; | |
642 | struct stat st; | |
643 | #ifdef TIOCGDEV | |
644 | unsigned int devnum; | |
99eadc9e JC |
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; | |
3a08f74f | 653 | #endif |
b25343bb | 654 | DBG(dbgprint("trying device/fallback file descriptor")); |
3a08f74f WF |
655 | |
656 | if (fstat(fd, &st) < 0) { | |
657 | close(fd); | |
658 | goto fallback; | |
659 | } | |
660 | comparedev = st.st_rdev; | |
661 | ||
97909f61 KZ |
662 | if (reconnect && |
663 | (fstat(fallback, &st) < 0 || comparedev != st.st_rdev)) | |
3a08f74f WF |
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; | |
e0e5974f | 703 | name = scandev(dir, comparedev); |
874dbb9c KZ |
704 | closedir(dir); |
705 | ||
f0d6004c KZ |
706 | if (name) { |
707 | rc = append_console(consoles, name); | |
cae29b37 | 708 | free(name); |
f0d6004c KZ |
709 | if (rc < 0) |
710 | return rc; | |
711 | } | |
cae29b37 | 712 | if (list_empty(consoles)) |
3a08f74f | 713 | goto fallback; |
874dbb9c KZ |
714 | |
715 | DBG(dbgprint("detection success [rc=%d]", reconnect)); | |
97909f61 | 716 | return reconnect; |
3a08f74f WF |
717 | } |
718 | #ifdef __linux__ | |
719 | console: | |
720 | /* | |
9e930041 | 721 | * Detection of devices used for Linux system console using |
3a08f74f WF |
722 | * the /proc/consoles API with kernel 2.6.38 and higher. |
723 | */ | |
aded518a KZ |
724 | rc = detect_consoles_from_proc(consoles); |
725 | if (rc == 0) | |
726 | return reconnect; /* success */ | |
727 | if (rc < 0) | |
728 | return rc; /* fatal error */ | |
3a08f74f | 729 | |
3a08f74f WF |
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 | */ | |
615eada9 KZ |
734 | rc = detect_consoles_from_sysfs(consoles); |
735 | if (rc == 0) | |
736 | return reconnect; /* success */ | |
737 | if (rc < 0) | |
738 | return rc; /* fatal error */ | |
3a08f74f | 739 | |
3a08f74f WF |
740 | /* |
741 | * Detection of devices used for Linux system console using | |
742 | * kernel parameter on the kernels command line. | |
743 | */ | |
4fcee23d KZ |
744 | rc = detect_consoles_from_cmdline(consoles); |
745 | if (rc == 0) | |
746 | return reconnect; /* success */ | |
747 | if (rc < 0) | |
748 | return rc; /* fatal error */ | |
3a08f74f | 749 | |
6ea0921e KZ |
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 | */ | |
a303e047 | 754 | #ifdef TIOCGDEV |
6ea0921e KZ |
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 */ | |
a303e047 | 760 | #endif |
cae29b37 | 761 | if (!list_empty(consoles)) { |
27a76b7b KZ |
762 | DBG(dbgprint("detection success [rc=%d]", reconnect)); |
763 | return reconnect; | |
764 | } | |
b25343bb | 765 | |
3a08f74f | 766 | #endif /* __linux __ */ |
6ea0921e | 767 | |
3a08f74f WF |
768 | fallback: |
769 | if (fallback >= 0) { | |
8bd64503 KZ |
770 | const char *name; |
771 | char *n; | |
cae29b37 | 772 | struct console *console; |
95226b55 | 773 | |
3a08f74f WF |
774 | if (device && *device != '\0') |
775 | name = device; | |
776 | else name = ttyname(fallback); | |
777 | ||
778 | if (!name) | |
779 | name = "/dev/tty"; | |
780 | ||
21c4058d SK |
781 | n = strdup(name); |
782 | if (!n) | |
783 | return -ENOMEM; | |
784 | rc = append_console(consoles, n); | |
785 | free(n); | |
f0d6004c KZ |
786 | if (rc < 0) |
787 | return rc; | |
cae29b37 WF |
788 | if (list_empty(consoles)) |
789 | return 1; | |
790 | console = list_last_entry(consoles, struct console, entry); | |
791 | if (console) | |
792 | console->fd = fallback; | |
3a08f74f | 793 | } |
b25343bb KZ |
794 | |
795 | DBG(dbgprint("detection done by fallback [rc=%d]", reconnect)); | |
97909f61 | 796 | return reconnect; |
3a08f74f WF |
797 | } |
798 | ||
82d11fbd KZ |
799 | |
800 | #ifdef TEST_PROGRAM | |
801 | int main(int argc, char *argv[]) | |
802 | { | |
803 | char *name = NULL; | |
804 | int fd, re; | |
5d74cf00 | 805 | struct list_head *p, consoles; |
82d11fbd KZ |
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 | ||
5d74cf00 | 818 | INIT_LIST_HEAD(&consoles); |
82d11fbd KZ |
819 | re = detect_consoles(name, fd, &consoles); |
820 | ||
cae29b37 WF |
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 | } | |
82d11fbd KZ |
825 | |
826 | return 0; | |
827 | } | |
828 | #endif |