]>
Commit | Line | Data |
---|---|---|
32c9fc6d DWF |
1 | /* |
2 | * consoles.c Routines to detect the system consoles | |
3 | * | |
4 | * Copyright (c) 2011 SuSE LINUX Products GmbH, All rights reserved. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2, or (at your option) | |
9 | * any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program (see the file COPYING); if not, write to the | |
18 | * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, | |
19 | * MA 02110-1301, USA. | |
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> | |
d212d886 DWF |
30 | #include <sys/ioctl.h> |
31 | #ifdef __linux__ | |
d212d886 DWF |
32 | # include <sys/vt.h> |
33 | # include <sys/kd.h> | |
34 | # include <linux/serial.h> | |
d212d886 DWF |
35 | #endif |
36 | #include <fcntl.h> | |
32c9fc6d | 37 | #include <dirent.h> |
d212d886 | 38 | #include <unistd.h> |
32c9fc6d DWF |
39 | #include "consoles.h" |
40 | ||
d212d886 DWF |
41 | #ifdef __linux__ |
42 | # include <linux/major.h> | |
43 | #endif | |
44 | ||
32c9fc6d DWF |
45 | #if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) |
46 | # ifndef typeof | |
47 | # define typeof __typeof__ | |
48 | # endif | |
49 | # ifndef restrict | |
50 | # define restrict __restrict__ | |
51 | # endif | |
52 | #endif | |
53 | ||
54 | #define alignof(type) ((sizeof(type)+(sizeof(void*)-1)) & ~(sizeof(void*)-1)) | |
55 | ||
56 | struct console *consoles; | |
57 | ||
d212d886 DWF |
58 | /* |
59 | * Read and allocate one line from file, | |
60 | * the caller has to free the result | |
61 | */ | |
62 | static | |
63 | #ifdef __GNUC__ | |
64 | __attribute__((__nonnull__)) | |
65 | #endif | |
66 | char *oneline(const char *file) | |
67 | { | |
68 | FILE *fp; | |
69 | char *ret = (char*)0, *nl; | |
70 | size_t len = 0; | |
71 | ||
72 | if ((fp = fopen(file, "re")) == (FILE*)0) | |
73 | goto err; | |
74 | if (getline(&ret, &len, fp) < 0) | |
75 | goto out; | |
76 | if (len) | |
77 | ret[len-1] = '\0'; | |
78 | if ((nl = strchr(ret, '\n'))) | |
79 | *nl = '\0'; | |
80 | out: | |
81 | fclose(fp); | |
82 | err: | |
83 | return ret; | |
84 | } | |
85 | ||
86 | #ifdef __linux__ | |
87 | /* | |
35ec6446 DWF |
88 | * Read and determine active attribute for tty below |
89 | * /sys/class/tty, the caller has to free the result. | |
d212d886 DWF |
90 | */ |
91 | static | |
92 | __attribute__((__malloc__)) | |
93 | char *actattr(const char *tty) | |
94 | { | |
95 | char *ret = (char*)0; | |
96 | char *path; | |
97 | ||
98 | if (!tty || *tty == '\0') | |
99 | goto err; | |
100 | ||
101 | if (asprintf(&path, "/sys/class/tty/%s/active", tty) < 0) | |
102 | goto err; | |
103 | ||
104 | if ((ret = oneline(path)) == (char*)0) | |
105 | goto out; | |
106 | out: | |
107 | free(path); | |
108 | err: | |
109 | return ret; | |
110 | } | |
111 | ||
35ec6446 DWF |
112 | /* |
113 | * Read and determine device attribute for tty below | |
114 | * /sys/class/tty. | |
115 | */ | |
d212d886 DWF |
116 | static |
117 | dev_t devattr(const char *tty) | |
118 | { | |
119 | unsigned int maj, min; | |
120 | dev_t dev = 0; | |
121 | char *path, *value; | |
122 | ||
123 | if (!tty || *tty == '\0') | |
124 | goto err; | |
125 | ||
126 | if (asprintf(&path, "/sys/class/tty/%s/dev", tty) < 0) | |
127 | goto err; | |
128 | ||
129 | if ((value = oneline(path)) == (char*)0) | |
130 | goto out; | |
131 | ||
132 | if (sscanf(value, "%u:%u", &maj, &min) == 2) | |
133 | dev = makedev(maj, min); | |
134 | free(value); | |
135 | out: | |
136 | free(path); | |
137 | err: | |
138 | return dev; | |
139 | } | |
140 | #endif /* __linux__ */ | |
141 | ||
35ec6446 DWF |
142 | /* |
143 | * Search below /dev for the characer device in | |
144 | * the local `dev_t comparedev' variable. | |
145 | */ | |
32c9fc6d | 146 | static dev_t comparedev; |
d212d886 DWF |
147 | static |
148 | #ifdef __GNUC__ | |
149 | __attribute__((__nonnull__,__malloc__,__hot__)) | |
150 | #endif | |
151 | char* scandev(DIR *dir) | |
32c9fc6d DWF |
152 | { |
153 | char *name = (char*)0; | |
154 | struct dirent *dent; | |
155 | int fd; | |
156 | ||
157 | fd = dirfd(dir); | |
158 | rewinddir(dir); | |
159 | while ((dent = readdir(dir))) { | |
160 | char path[PATH_MAX]; | |
161 | struct stat st; | |
162 | if (fstatat(fd, dent->d_name, &st, 0) < 0) | |
163 | continue; | |
164 | if (!S_ISCHR(st.st_mode)) | |
165 | continue; | |
166 | if (comparedev != st.st_rdev) | |
167 | continue; | |
168 | if ((size_t)snprintf(path, sizeof(path), "/dev/%s", dent->d_name) >= sizeof(path)) | |
169 | continue; | |
170 | name = realpath(path, NULL); | |
171 | break; | |
172 | } | |
173 | return name; | |
174 | } | |
175 | ||
35ec6446 DWF |
176 | /* |
177 | * Default control characters for an unknown terminal line. | |
178 | */ | |
d212d886 DWF |
179 | static |
180 | struct chardata initcp = { | |
181 | CERASE, | |
182 | CKILL, | |
183 | CTRL('r'), | |
184 | 0 | |
185 | }; | |
186 | ||
35ec6446 DWF |
187 | /* |
188 | * Allocate an aligned `struct console' memory area, | |
189 | * initialize its default values, and append it to | |
190 | * the global linked list. | |
191 | */ | |
192 | ||
d212d886 DWF |
193 | static int concount; /* Counter for console IDs */ |
194 | ||
195 | static | |
196 | #ifdef __GNUC__ | |
197 | __attribute__((__nonnull__,__hot__)) | |
198 | #endif | |
199 | void consalloc(char * name) | |
200 | { | |
201 | struct console *restrict tail; | |
202 | ||
203 | if (posix_memalign((void*)&tail, sizeof(void*), alignof(typeof(struct console))) != 0) | |
204 | perror("memory allocation"); | |
205 | ||
206 | tail->next = (struct console*)0; | |
207 | tail->tty = name; | |
208 | ||
209 | tail->file = (FILE*)0; | |
210 | tail->flags = 0; | |
211 | tail->fd = -1; | |
212 | tail->id = concount++; | |
213 | tail->pid = 0; | |
214 | memset(&tail->tio, 0, sizeof(tail->tio)); | |
215 | memcpy(&tail->cp, &initcp, sizeof(struct chardata)); | |
216 | ||
217 | if (!consoles) | |
218 | consoles = tail; | |
219 | else | |
220 | consoles->next = tail; | |
221 | } | |
222 | ||
35ec6446 DWF |
223 | /* |
224 | * Try to detect the real device(s) used for the system console | |
225 | * /dev/console if but only if /dev/console is used. On Linux | |
226 | * this can be more than one device, e.g. a serial line as well | |
227 | * as a virtual console as well as a simple printer. | |
05f2c1ad DWF |
228 | * |
229 | * Returns 1 if stdout and stderr should be reconnected and 0 | |
230 | * otherwise. | |
35ec6446 | 231 | */ |
05f2c1ad | 232 | int detect_consoles(const char *device, int fallback) |
32c9fc6d | 233 | { |
05f2c1ad | 234 | int fd, ret = 0; |
d212d886 DWF |
235 | #ifdef __linux__ |
236 | char *attrib, *cmdline; | |
32c9fc6d | 237 | FILE *fc; |
4befa570 DWF |
238 | #endif |
239 | if (!device || *device == '\0') | |
240 | fd = dup(fallback); | |
05f2c1ad DWF |
241 | else { |
242 | fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC); | |
243 | ret = 1; | |
244 | } | |
d212d886 | 245 | |
4befa570 DWF |
246 | if (fd >= 0) { |
247 | DIR *dir; | |
248 | char *name; | |
249 | struct stat st; | |
250 | #ifdef TIOCGDEV | |
251 | unsigned int devnum; | |
252 | #endif | |
253 | ||
254 | if (fstat(fd, &st) < 0) { | |
255 | close(fd); | |
256 | goto fallback; | |
257 | } | |
258 | comparedev = st.st_rdev; | |
05f2c1ad DWF |
259 | |
260 | if (ret && (fstat(fallback, &st) < 0 || comparedev != st.st_rdev)) | |
261 | dup2(fd, fallback); | |
4befa570 DWF |
262 | #ifdef __linux__ |
263 | /* | |
264 | * Check if the device detection for Linux system console should be used. | |
265 | */ | |
266 | if (comparedev == makedev(TTYAUX_MAJOR, 0)) { /* /dev/tty */ | |
267 | close(fd); | |
268 | device = "/dev/tty"; | |
269 | goto fallback; | |
270 | } | |
271 | if (comparedev == makedev(TTYAUX_MAJOR, 1)) { /* /dev/console */ | |
272 | close(fd); | |
273 | goto console; | |
274 | } | |
275 | if (comparedev == makedev(TTYAUX_MAJOR, 2)) { /* /dev/ptmx */ | |
276 | close(fd); | |
277 | device = "/dev/tty"; | |
278 | goto fallback; | |
279 | } | |
280 | if (comparedev == makedev(TTY_MAJOR, 0)) { /* /dev/tty0 */ | |
281 | struct vt_stat vt; | |
282 | if (ioctl(fd, VT_GETSTATE, &vt) < 0) { | |
283 | close(fd); | |
284 | goto fallback; | |
285 | } | |
286 | comparedev = makedev(TTY_MAJOR, (int)vt.v_active); | |
287 | } | |
288 | #endif | |
289 | #ifdef TIOCGDEV | |
290 | if (ioctl (fd, TIOCGDEV, &devnum) < 0) { | |
291 | close(fd); | |
292 | goto fallback; | |
293 | } | |
294 | comparedev = (dev_t)devnum; | |
295 | #endif | |
296 | close(fd); | |
297 | dir = opendir("/dev"); | |
298 | if (!dir) | |
299 | goto fallback; | |
300 | name = scandev(dir); | |
301 | if (name) | |
302 | consalloc(name); | |
303 | closedir(dir); | |
304 | if (!consoles) | |
305 | goto fallback; | |
05f2c1ad | 306 | return ret; |
4befa570 DWF |
307 | } |
308 | #ifdef __linux__ | |
309 | console: | |
310 | /* | |
311 | * Detection of devices used for Linux system consolei using | |
312 | * the /proc/consoles API with kernel 2.6.38 and higher. | |
313 | */ | |
d212d886 | 314 | if ((fc = fopen("/proc/consoles", "re"))) { |
32c9fc6d DWF |
315 | char fbuf[16]; |
316 | int maj, min; | |
317 | DIR *dir; | |
318 | dir = opendir("/dev"); | |
4befa570 DWF |
319 | if (!dir) { |
320 | fclose(fc); | |
321 | goto fallback; | |
322 | } | |
32c9fc6d | 323 | while ((fscanf(fc, "%*s %*s (%[^)]) %d:%d", &fbuf[0], &maj, &min) == 3)) { |
32c9fc6d DWF |
324 | char * name; |
325 | ||
326 | if (!strchr(fbuf, 'E')) | |
327 | continue; | |
328 | comparedev = makedev(maj, min); | |
d212d886 | 329 | |
32c9fc6d | 330 | name = scandev(dir); |
d212d886 DWF |
331 | if (!name) |
332 | continue; | |
333 | consalloc(name); | |
334 | } | |
335 | closedir(dir); | |
d212d886 | 336 | fclose(fc); |
05f2c1ad | 337 | return ret; |
d212d886 | 338 | } |
4befa570 DWF |
339 | /* |
340 | * Detection of devices used for Linux system console using | |
341 | * the sysfs /sys/class/tty/ API with kernel 2.6.37 and higher. | |
342 | */ | |
d212d886 DWF |
343 | if ((attrib = actattr("console"))) { |
344 | char *words = attrib, *token; | |
345 | DIR *dir; | |
346 | ||
347 | dir = opendir("/dev"); | |
4befa570 DWF |
348 | if (!dir) { |
349 | free(attrib); | |
350 | goto fallback; | |
351 | } | |
d212d886 DWF |
352 | while ((token = strsep(&words, " \t\r\n"))) { |
353 | char * name; | |
354 | ||
355 | if (*token == '\0') | |
356 | continue; | |
357 | comparedev = devattr(token); | |
358 | if (comparedev == makedev(TTY_MAJOR, 0)) { | |
359 | char *tmp = actattr(token); | |
360 | if (!tmp) | |
361 | continue; | |
362 | comparedev = devattr(tmp); | |
363 | free(tmp); | |
364 | } | |
32c9fc6d | 365 | |
d212d886 | 366 | name = scandev(dir); |
32c9fc6d DWF |
367 | if (!name) |
368 | continue; | |
d212d886 DWF |
369 | consalloc(name); |
370 | } | |
371 | closedir(dir); | |
d212d886 | 372 | free(attrib); |
4befa570 DWF |
373 | if (!consoles) |
374 | goto fallback; | |
05f2c1ad | 375 | return ret; |
32c9fc6d | 376 | |
d212d886 | 377 | } |
4befa570 DWF |
378 | /* |
379 | * Detection of devices used for Linux system console using | |
380 | * kernel parameter on the kernels command line. | |
381 | */ | |
d212d886 DWF |
382 | if ((cmdline = oneline("/proc/cmdline"))) { |
383 | char *words= cmdline, *token; | |
384 | DIR *dir; | |
32c9fc6d | 385 | |
d212d886 | 386 | dir = opendir("/dev"); |
4befa570 DWF |
387 | if (!dir) { |
388 | free(cmdline); | |
389 | goto fallback; | |
390 | } | |
d212d886 DWF |
391 | while ((token = strsep(&words, " \t\r\n"))) { |
392 | #ifdef TIOCGDEV | |
393 | unsigned int devnum; | |
394 | #else | |
395 | struct vt_stat vt; | |
396 | struct stat st; | |
397 | #endif | |
398 | char *colon, *name; | |
d212d886 DWF |
399 | |
400 | if (*token != 'c') | |
401 | continue; | |
402 | ||
403 | if (strncmp(token, "console=", 8) != 0) | |
404 | continue; | |
405 | token += 8; | |
406 | ||
407 | if (strcmp(token, "brl") == 0) | |
408 | token += 4; | |
409 | if ((colon = strchr(token, ','))) | |
410 | *colon = '\0'; | |
411 | ||
412 | if (asprintf(&name, "/dev/%s", token) < 0) | |
413 | continue; | |
414 | ||
415 | if ((fd = open(name, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC)) < 0) { | |
416 | free(name); | |
417 | continue; | |
418 | } | |
419 | free(name); | |
420 | #ifdef TIOCGDEV | |
421 | if (ioctl (fd, TIOCGDEV, &devnum) < 0) { | |
422 | close(fd); | |
423 | continue; | |
424 | } | |
425 | comparedev = (dev_t)devnum; | |
426 | #else | |
427 | if (fstat(fd, &st) < 0) { | |
428 | close(fd); | |
429 | continue; | |
430 | } | |
431 | comparedev = st.st_rdev; | |
432 | if (comparedev == makedev(TTY_MAJOR, 0)) { | |
433 | if (ioctl(fd, VT_GETSTATE, &vt) < 0) { | |
434 | close(fd); | |
435 | continue; | |
436 | } | |
437 | comparedev = makedev(TTY_MAJOR, (int)vt.v_active); | |
438 | } | |
439 | #endif | |
440 | close(fd); | |
441 | ||
442 | name = scandev(dir); | |
443 | if (!name) | |
444 | continue; | |
445 | consalloc(name); | |
32c9fc6d DWF |
446 | } |
447 | closedir(dir); | |
d212d886 | 448 | free(cmdline); |
4befa570 DWF |
449 | /* |
450 | * Detection of the device used for Linux system console using | |
451 | * the ioctl TIOCGDEV if available (e.g. official 2.6.38). | |
452 | */ | |
453 | if (!consoles) { | |
d212d886 | 454 | #ifdef TIOCGDEV |
4befa570 DWF |
455 | unsigned int devnum; |
456 | const char *name; | |
d212d886 | 457 | |
4befa570 DWF |
458 | if (!device || *device == '\0') |
459 | fd = dup(fallback); | |
460 | else fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC); | |
461 | ||
462 | if (fd < 0) | |
463 | goto fallback; | |
464 | ||
465 | if (ioctl (fd, TIOCGDEV, &devnum) < 0) { | |
d212d886 | 466 | close(fd); |
4befa570 DWF |
467 | goto fallback; |
468 | } | |
469 | comparedev = (dev_t)devnum; | |
470 | close(fd); | |
471 | ||
472 | if (device && *device != '\0') | |
473 | name = device; | |
474 | else name = ttyname(fallback); | |
475 | ||
476 | if (!name) | |
477 | name = "/dev/tty1"; | |
478 | ||
479 | consalloc(strdup(name)); | |
480 | if (consoles) { | |
481 | if (!device || *device == '\0') | |
482 | consoles->fd = fallback; | |
05f2c1ad | 483 | return ret; |
d212d886 | 484 | } |
d212d886 | 485 | #endif |
4befa570 DWF |
486 | goto fallback; |
487 | } | |
05f2c1ad | 488 | return ret; |
d212d886 | 489 | } |
4befa570 DWF |
490 | #endif /* __linux __ */ |
491 | fallback: | |
d212d886 | 492 | if (fallback >= 0) { |
4befa570 DWF |
493 | const char *name; |
494 | ||
495 | if (device && *device != '\0') | |
496 | name = device; | |
497 | else name = ttyname(fallback); | |
498 | ||
d212d886 | 499 | if (!name) |
4befa570 DWF |
500 | name = "/dev/tty"; |
501 | ||
d212d886 DWF |
502 | consalloc(strdup(name)); |
503 | if (consoles) | |
504 | consoles->fd = fallback; | |
32c9fc6d | 505 | } |
05f2c1ad | 506 | return ret; |
32c9fc6d | 507 | } |