Improve message printed when signaling processes to stop. Patch from Matias A. Fonzo...
[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
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
56struct console *consoles;
57
d212d886
DWF
58/*
59 * Read and allocate one line from file,
60 * the caller has to free the result
61 */
62static
63#ifdef __GNUC__
64__attribute__((__nonnull__))
65#endif
66char *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';
80out:
81 fclose(fp);
82err:
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 */
91static
92__attribute__((__malloc__))
93char *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;
106out:
107 free(path);
108err:
109 return ret;
110}
111
35ec6446
DWF
112/*
113 * Read and determine device attribute for tty below
114 * /sys/class/tty.
115 */
d212d886
DWF
116static
117dev_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);
135out:
136 free(path);
137err:
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 146static dev_t comparedev;
d212d886
DWF
147static
148#ifdef __GNUC__
149__attribute__((__nonnull__,__malloc__,__hot__))
150#endif
151char* 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
179static
180struct 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
193static int concount; /* Counter for console IDs */
194
195static
196#ifdef __GNUC__
197__attribute__((__nonnull__,__hot__))
198#endif
199void 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 232int 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__
309console:
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 __ */
491fallback:
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}