Adjust more included headers to be compatible with the musl C
[sysvinit.git] / src / consoles.c
CommitLineData
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 30#include <sys/ioctl.h>
a33c1f65 31#include <sys/ttydefaults.h>
d212d886 32#ifdef __linux__
d212d886
DWF
33# include <sys/vt.h>
34# include <sys/kd.h>
35# include <linux/serial.h>
d212d886
DWF
36#endif
37#include <fcntl.h>
32c9fc6d 38#include <dirent.h>
d212d886 39#include <unistd.h>
32c9fc6d
DWF
40#include "consoles.h"
41
d212d886
DWF
42#ifdef __linux__
43# include <linux/major.h>
44#endif
45
32c9fc6d
DWF
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
57struct console *consoles;
58
d212d886
DWF
59/*
60 * Read and allocate one line from file,
61 * the caller has to free the result
62 */
63static
64#ifdef __GNUC__
65__attribute__((__nonnull__))
66#endif
67char *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';
81out:
82 fclose(fp);
83err:
84 return ret;
85}
86
87#ifdef __linux__
88/*
35ec6446
DWF
89 * Read and determine active attribute for tty below
90 * /sys/class/tty, the caller has to free the result.
d212d886
DWF
91 */
92static
93__attribute__((__malloc__))
94char *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;
107out:
108 free(path);
109err:
110 return ret;
111}
112
35ec6446
DWF
113/*
114 * Read and determine device attribute for tty below
115 * /sys/class/tty.
116 */
d212d886
DWF
117static
118dev_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);
136out:
137 free(path);
138err:
139 return dev;
140}
141#endif /* __linux__ */
142
35ec6446
DWF
143/*
144 * Search below /dev for the characer device in
145 * the local `dev_t comparedev' variable.
146 */
32c9fc6d 147static dev_t comparedev;
d212d886
DWF
148static
149#ifdef __GNUC__
150__attribute__((__nonnull__,__malloc__,__hot__))
151#endif
152char* scandev(DIR *dir)
32c9fc6d
DWF
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
35ec6446
DWF
177/*
178 * Default control characters for an unknown terminal line.
179 */
d212d886
DWF
180static
181struct chardata initcp = {
182 CERASE,
183 CKILL,
184 CTRL('r'),
185 0
186};
187
35ec6446
DWF
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
d212d886
DWF
194static int concount; /* Counter for console IDs */
195
196static
197#ifdef __GNUC__
198__attribute__((__nonnull__,__hot__))
199#endif
200void 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
35ec6446
DWF
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.
05f2c1ad
DWF
229 *
230 * Returns 1 if stdout and stderr should be reconnected and 0
231 * otherwise.
35ec6446 232 */
05f2c1ad 233int detect_consoles(const char *device, int fallback)
32c9fc6d 234{
05f2c1ad 235 int fd, ret = 0;
d212d886
DWF
236#ifdef __linux__
237 char *attrib, *cmdline;
32c9fc6d 238 FILE *fc;
4befa570
DWF
239#endif
240 if (!device || *device == '\0')
241 fd = dup(fallback);
05f2c1ad
DWF
242 else {
243 fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC);
244 ret = 1;
245 }
d212d886 246
4befa570
DWF
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;
05f2c1ad
DWF
260
261 if (ret && (fstat(fallback, &st) < 0 || comparedev != st.st_rdev))
262 dup2(fd, fallback);
4befa570
DWF
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;
05f2c1ad 307 return ret;
4befa570
DWF
308 }
309#ifdef __linux__
310console:
311 /*
312 * Detection of devices used for Linux system consolei using
313 * the /proc/consoles API with kernel 2.6.38 and higher.
314 */
d212d886 315 if ((fc = fopen("/proc/consoles", "re"))) {
32c9fc6d
DWF
316 char fbuf[16];
317 int maj, min;
318 DIR *dir;
319 dir = opendir("/dev");
4befa570
DWF
320 if (!dir) {
321 fclose(fc);
322 goto fallback;
323 }
32c9fc6d 324 while ((fscanf(fc, "%*s %*s (%[^)]) %d:%d", &fbuf[0], &maj, &min) == 3)) {
32c9fc6d
DWF
325 char * name;
326
327 if (!strchr(fbuf, 'E'))
328 continue;
329 comparedev = makedev(maj, min);
d212d886 330
32c9fc6d 331 name = scandev(dir);
d212d886
DWF
332 if (!name)
333 continue;
334 consalloc(name);
335 }
336 closedir(dir);
d212d886 337 fclose(fc);
05f2c1ad 338 return ret;
d212d886 339 }
4befa570
DWF
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 */
d212d886
DWF
344 if ((attrib = actattr("console"))) {
345 char *words = attrib, *token;
346 DIR *dir;
347
348 dir = opendir("/dev");
4befa570
DWF
349 if (!dir) {
350 free(attrib);
351 goto fallback;
352 }
d212d886
DWF
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 }
32c9fc6d 366
d212d886 367 name = scandev(dir);
32c9fc6d
DWF
368 if (!name)
369 continue;
d212d886
DWF
370 consalloc(name);
371 }
372 closedir(dir);
d212d886 373 free(attrib);
4befa570
DWF
374 if (!consoles)
375 goto fallback;
05f2c1ad 376 return ret;
32c9fc6d 377
d212d886 378 }
4befa570
DWF
379 /*
380 * Detection of devices used for Linux system console using
381 * kernel parameter on the kernels command line.
382 */
d212d886
DWF
383 if ((cmdline = oneline("/proc/cmdline"))) {
384 char *words= cmdline, *token;
385 DIR *dir;
32c9fc6d 386
d212d886 387 dir = opendir("/dev");
4befa570
DWF
388 if (!dir) {
389 free(cmdline);
390 goto fallback;
391 }
d212d886
DWF
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;
d212d886
DWF
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);
32c9fc6d
DWF
447 }
448 closedir(dir);
d212d886 449 free(cmdline);
4befa570
DWF
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) {
d212d886 455#ifdef TIOCGDEV
4befa570
DWF
456 unsigned int devnum;
457 const char *name;
d212d886 458
4befa570
DWF
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) {
d212d886 467 close(fd);
4befa570
DWF
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;
05f2c1ad 484 return ret;
d212d886 485 }
d212d886 486#endif
4befa570
DWF
487 goto fallback;
488 }
05f2c1ad 489 return ret;
d212d886 490 }
4befa570
DWF
491#endif /* __linux __ */
492fallback:
d212d886 493 if (fallback >= 0) {
4befa570
DWF
494 const char *name;
495
496 if (device && *device != '\0')
497 name = device;
498 else name = ttyname(fallback);
499
d212d886 500 if (!name)
4befa570
DWF
501 name = "/dev/tty";
502
d212d886
DWF
503 consalloc(strdup(name));
504 if (consoles)
505 consoles->fd = fallback;
32c9fc6d 506 }
05f2c1ad 507 return ret;
32c9fc6d 508}