7b3be9b141100d5875f8d95d8377509e65cf625e
[sysvinit.git] / src / 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 *
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>
30 #include <sys/ioctl.h>
31 #ifdef __linux__
32 # include <sys/vt.h>
33 # include <sys/kd.h>
34 # include <linux/serial.h>
35 #endif
36 #include <fcntl.h>
37 #include <dirent.h>
38 #include <unistd.h>
39 #include "consoles.h"
40
41 #ifdef __linux__
42 # include <linux/major.h>
43 #endif
44
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
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 /*
88 * Read and determine active attribute for tty below
89 * /sys/class/tty, the caller has to free the result.
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
112 /*
113 * Read and determine device attribute for tty below
114 * /sys/class/tty.
115 */
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
142 /*
143 * Search below /dev for the characer device in
144 * the local `dev_t comparedev' variable.
145 */
146 static dev_t comparedev;
147 static
148 #ifdef __GNUC__
149 __attribute__((__nonnull__,__malloc__,__hot__))
150 #endif
151 char* scandev(DIR *dir)
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
176 /*
177 * Default control characters for an unknown terminal line.
178 */
179 static
180 struct chardata initcp = {
181 CERASE,
182 CKILL,
183 CTRL('r'),
184 0
185 };
186
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
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
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.
228 *
229 * Returns 1 if stdout and stderr should be reconnected and 0
230 * otherwise.
231 */
232 int detect_consoles(const char *device, int fallback)
233 {
234 int fd, ret = 0;
235 #ifdef __linux__
236 char *attrib, *cmdline;
237 FILE *fc;
238 #endif
239 if (!device || *device == '\0')
240 fd = dup(fallback);
241 else {
242 fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC);
243 ret = 1;
244 }
245
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;
259
260 if (ret && (fstat(fallback, &st) < 0 || comparedev != st.st_rdev))
261 dup2(fd, fallback);
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;
306 return ret;
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 */
314 if ((fc = fopen("/proc/consoles", "re"))) {
315 char fbuf[16];
316 int maj, min;
317 DIR *dir;
318 dir = opendir("/dev");
319 if (!dir) {
320 fclose(fc);
321 goto fallback;
322 }
323 while ((fscanf(fc, "%*s %*s (%[^)]) %d:%d", &fbuf[0], &maj, &min) == 3)) {
324 char * name;
325
326 if (!strchr(fbuf, 'E'))
327 continue;
328 comparedev = makedev(maj, min);
329
330 name = scandev(dir);
331 if (!name)
332 continue;
333 consalloc(name);
334 }
335 closedir(dir);
336 fclose(fc);
337 return ret;
338 }
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 */
343 if ((attrib = actattr("console"))) {
344 char *words = attrib, *token;
345 DIR *dir;
346
347 dir = opendir("/dev");
348 if (!dir) {
349 free(attrib);
350 goto fallback;
351 }
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 }
365
366 name = scandev(dir);
367 if (!name)
368 continue;
369 consalloc(name);
370 }
371 closedir(dir);
372 free(attrib);
373 if (!consoles)
374 goto fallback;
375 return ret;
376
377 }
378 /*
379 * Detection of devices used for Linux system console using
380 * kernel parameter on the kernels command line.
381 */
382 if ((cmdline = oneline("/proc/cmdline"))) {
383 char *words= cmdline, *token;
384 DIR *dir;
385
386 dir = opendir("/dev");
387 if (!dir) {
388 free(cmdline);
389 goto fallback;
390 }
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;
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);
446 }
447 closedir(dir);
448 free(cmdline);
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) {
454 #ifdef TIOCGDEV
455 unsigned int devnum;
456 const char *name;
457
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) {
466 close(fd);
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;
483 return ret;
484 }
485 #endif
486 goto fallback;
487 }
488 return ret;
489 }
490 #endif /* __linux __ */
491 fallback:
492 if (fallback >= 0) {
493 const char *name;
494
495 if (device && *device != '\0')
496 name = device;
497 else name = ttyname(fallback);
498
499 if (!name)
500 name = "/dev/tty";
501
502 consalloc(strdup(name));
503 if (consoles)
504 consoles->fd = fallback;
505 }
506 return ret;
507 }