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