Rewrite findtty() in bootlogd to recursively search /dev/ for the
[sysvinit.git] / src / bootlogd.c
CommitLineData
a74aeac6
PR
1/*
2 * bootlogd.c Store output from the console during bootup into a file.
3 * The file is usually located on the /var partition, and
4 * gets written (and fsynced) as soon as possible.
5 *
6 * Version: @(#)bootlogd 2.86pre 12-Jan-2004 miquels@cistron.nl
7 *
8 * Bugs: Uses openpty(), only available in glibc. Sorry.
9 *
10 * This file is part of the sysvinit suite,
11 * Copyright (C) 1991-2004 Miquel van Smoorenburg.
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
26 *
27 * *NOTE* *NOTE* *NOTE*
28 * This is a PROOF OF CONCEPT IMPLEMENTATION
29 *
30 * I have bigger plans for Debian, but for now
31 * this has to do ;)
32 *
33 */
34
35#include <sys/types.h>
36#include <sys/time.h>
37#include <sys/stat.h>
38#include <sys/ioctl.h>
39#include <sys/utsname.h>
40#include <time.h>
41#include <stdio.h>
42#include <errno.h>
43#include <malloc.h>
44#include <stdlib.h>
45#include <unistd.h>
46#include <string.h>
47#include <signal.h>
48#include <getopt.h>
49#include <dirent.h>
50#include <fcntl.h>
51#include <pty.h>
52#include <ctype.h>
53#ifdef __linux__
54#include <sys/mount.h>
55#endif
56
57char *Version = "@(#) bootlogd 2.86 03-Jun-2004 miquels@cistron.nl";
58
59#define LOGFILE "/var/log/boot"
60
61char ringbuf[32768];
62char *endptr = ringbuf + sizeof(ringbuf);
63char *inptr = ringbuf;
64char *outptr = ringbuf;
65
66int got_signal = 0;
67int didnl = 1;
68int createlogfile = 0;
69int syncalot = 0;
70
71struct line {
72 char buf[256];
73 int pos;
74} line;
75
76/*
77 * Console devices as listed on the kernel command line and
78 * the mapping to actual devices in /dev
79 */
80struct consdev {
81 char *cmdline;
82 char *dev1;
83 char *dev2;
84} consdev[] = {
85 { "ttyB", "/dev/ttyB%s", NULL },
86 { "ttySC", "/dev/ttySC%s", "/dev/ttsc/%s" },
87 { "ttyS", "/dev/ttyS%s", "/dev/tts/%s" },
88 { "tty", "/dev/tty%s", "/dev/vc/%s" },
89 { "hvc", "/dev/hvc%s", "/dev/hvc/%s" },
90 { NULL, NULL, NULL },
91};
92
93/*
94 * Devices to try as console if not found on kernel command line.
95 * Tried from left to right (as opposed to kernel cmdline).
96 */
97char *defcons[] = { "tty0", "hvc0", "ttyS0", "ttySC0", "ttyB0", NULL };
98
99/*
100 * Catch signals.
101 */
102void handler(int sig)
103{
104 got_signal = sig;
105}
106
107
108/*
109 * Scan /dev and find the device name.
a74aeac6 110 */
29ed3b1d 111static int findtty(char *res, int rlen, dev_t dev)
a74aeac6
PR
112{
113 DIR *dir;
114 struct dirent *ent;
115 struct stat st;
29ed3b1d
PR
116 int r = -1;
117 char *olddir = getcwd(NULL, 0);
a74aeac6 118
29ed3b1d
PR
119 if (chdir(startdir) < 0 || (dir = opendir(".")) == NULL) {
120 int msglen = strlen(startdir) + 11;
121 char *msg = malloc(msglen);
122 snprintf(msg, msglen, "bootlogd: %s", startdir);
123 perror(msg);
124 free(msg);
125 chdir(olddir);
a74aeac6
PR
126 return -1;
127 }
128 while ((ent = readdir(dir)) != NULL) {
129 if (lstat(ent->d_name, &st) != 0)
130 continue;
29ed3b1d
PR
131 if (S_ISDIR(st.st_mode)
132 && 0 != strcmp(".", ent->d_name)
133 && 0 != strcmp("..", ent->d_name)) {
134 char *path = malloc(rlen);
135 snprintf(path, rlen, "%s/%s", startdir, ent->d_name);
136 r = findtty(res, path, rlen, dev);
137 free(path);
138 if (0 == r) { /* device found, return */
139 closedir(dir);
140 chdir(olddir);
141 return 0;
142 }
143 continue;
144 }
a74aeac6
PR
145 if (!S_ISCHR(st.st_mode))
146 continue;
147 if (st.st_rdev == dev) {
29ed3b1d
PR
148 if (strlen(ent->d_name) + strlen(startdir) + 1 >= rlen) {
149 fprintf(stderr, "bootlogd: console device name too long\n");
150 closedir(dir);
151 chdir(olddir);
152 return -1;
153 } else {
154 snprintf(res, rlen, "%s/%s", startdir, ent->d_name);
155 closedir(dir);
156 chdir(olddir);
157 return 0;
158 }
a74aeac6
PR
159 }
160 }
a74aeac6
PR
161 closedir(dir);
162
29ed3b1d 163 chdir(olddir);
a74aeac6
PR
164 return r;
165}
166
167/*
168 * For some reason, openpty() in glibc sometimes doesn't
169 * work at boot-time. It must be a bug with old-style pty
170 * names, as new-style (/dev/pts) is not available at that
171 * point. So, we find a pty/tty pair ourself if openpty()
172 * fails for whatever reason.
173 */
174int findpty(int *master, int *slave, char *name)
175{
176 char pty[16];
177 char tty[16];
178 int i, j;
179 int found;
180
181 if (openpty(master, slave, name, NULL, NULL) >= 0)
182 return 0;
183
184 found = 0;
185
186 for (i = 'p'; i <= 'z'; i++) {
187 for (j = '0'; j <= 'f'; j++) {
188 if (j == '9' + 1) j = 'a';
189 sprintf(pty, "/dev/pty%c%c", i, j);
190 sprintf(tty, "/dev/tty%c%c", i, j);
191 if ((*master = open(pty, O_RDWR|O_NOCTTY)) >= 0) {
192 *slave = open(tty, O_RDWR|O_NOCTTY);
193 if (*slave >= 0) {
194 found = 1;
195 break;
196 }
197 }
198 }
199 if (found) break;
200 }
201 if (found < 0) return -1;
202
203 if (name) strcpy(name, tty);
204
205 return 0;
206}
207/*
208 * See if a console taken from the kernel command line maps
209 * to a character device we know about, and if we can open it.
210 */
211int isconsole(char *s, char *res, int rlen)
212{
213 struct consdev *c;
214 int l, sl, i, fd;
215 char *p, *q;
216
217 sl = strlen(s);
218
219 for (c = consdev; c->cmdline; c++) {
220 l = strlen(c->cmdline);
221 if (sl <= l) continue;
222 p = s + l;
223 if (strncmp(s, c->cmdline, l) != 0 || !isdigit(*p))
224 continue;
225 for (i = 0; i < 2; i++) {
226 snprintf(res, rlen, i ? c->dev1 : c->dev2, p);
227 if ((q = strchr(res, ',')) != NULL) *q = 0;
228 if ((fd = open(res, O_RDONLY|O_NONBLOCK)) >= 0) {
229 close(fd);
230 return 1;
231 }
232 }
233 }
234 return 0;
235}
236
237/*
238 * Find out the _real_ console. Assume that stdin is connected to
239 * the console device (/dev/console).
240 */
241int consolename(char *res, int rlen)
242{
243#ifdef TIOCGDEV
244 unsigned int kdev;
245#endif
246 struct stat st, st2;
247 char buf[256];
248 char *p;
249 int didmount = 0;
250 int n, r;
251 int fd;
252
253 fstat(0, &st);
254 if (major(st.st_rdev) != 5 || minor(st.st_rdev) != 1) {
255 /*
256 * Old kernel, can find real device easily.
257 */
29ed3b1d
PR
258 int r = findtty(res, "/dev", rlen, st.st_rdev);
259 if (0 != r)
260 fprintf(stderr, "bootlogd: cannot find console device "
261 "%d:%d under /dev\n", major(st.st_rdev), minor(st.st_rdev));
262 return r;
a74aeac6
PR
263 }
264
265#ifdef TIOCGDEV
29ed3b1d
PR
266 if (ioctl(0, TIOCGDEV, &kdev) == 0) {
267 int r = findtty(res, "/dev", rlen, (dev_t)kdev);
268 if (0 != r)
269 fprintf(stderr, "bootlogd: cannot find console device "
270 "%d:%d under /dev\n", major(kdev), minor(kdev));
271 return r;
272 }
a74aeac6
PR
273 if (errno != ENOIOCTLCMD) return -1;
274#endif
275
276#ifdef __linux__
277 /*
278 * Read /proc/cmdline.
279 */
280 stat("/", &st);
281 if (stat("/proc", &st2) < 0) {
282 perror("bootlogd: /proc");
283 return -1;
284 }
285 if (st.st_dev == st2.st_dev) {
286 if (mount("proc", "/proc", "proc", 0, NULL) < 0) {
287 perror("bootlogd: mount /proc");
288 return -1;
289 }
290 didmount = 1;
291 }
292
293 n = 0;
294 r = -1;
295 if ((fd = open("/proc/cmdline", O_RDONLY)) < 0) {
296 perror("bootlogd: /proc/cmdline");
297 } else {
298 buf[0] = 0;
299 if ((n = read(fd, buf, sizeof(buf) - 1)) >= 0)
300 r = 0;
301 else
302 perror("bootlogd: /proc/cmdline");
303 close(fd);
304 }
305 if (didmount) umount("/proc");
306
307 if (r < 0) return r;
308
309 /*
310 * OK, so find console= in /proc/cmdline.
311 * Parse in reverse, opening as we go.
312 */
313 p = buf + n;
314 *p-- = 0;
315 r = -1;
316 while (p >= buf) {
317 if (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
318 *p-- = 0;
319 continue;
320 }
321 if (strncmp(p, "console=", 8) == 0 &&
322 isconsole(p + 8, res, rlen)) {
323 r = 0;
324 break;
325 }
326 p--;
327 }
328
329 if (r == 0) return r;
330#endif
331
332 /*
333 * Okay, no console on the command line -
334 * guess the default console.
335 */
336 for (n = 0; defcons[n]; n++)
337 if (isconsole(defcons[n], res, rlen))
338 return 0;
339
340 fprintf(stderr, "bootlogd: cannot deduce real console device\n");
341
342 return -1;
343}
344
345
346/*
347 * Write data and make sure it's on disk.
348 */
349void writelog(FILE *fp, unsigned char *ptr, int len)
350{
351 time_t t;
352 char *s;
353 char tmp[8];
354 int olen = len;
355 int dosync = 0;
356 int tlen;
357
358 while (len > 0) {
359 tmp[0] = 0;
360 if (didnl) {
361 time(&t);
362 s = ctime(&t);
363 fprintf(fp, "%.24s: ", s);
364 didnl = 0;
365 }
366 switch (*ptr) {
367 case 27: /* ESC */
368 strcpy(tmp, "^[");
369 break;
370 case '\r':
371 line.pos = 0;
372 break;
373 case 8: /* ^H */
374 if (line.pos > 0) line.pos--;
375 break;
376 case '\n':
377 didnl = 1;
378 dosync = syncalot;
379 break;
380 case '\t':
381 line.pos += (line.pos / 8 + 1) * 8;
192c4567 382 if (line.pos >= (int)sizeof(line.buf))
a74aeac6
PR
383 line.pos = sizeof(line.buf) - 1;
384 break;
385 case 32 ... 127:
386 case 161 ... 255:
387 tmp[0] = *ptr;
388 tmp[1] = 0;
389 break;
390 default:
391 sprintf(tmp, "\\%03o", *ptr);
392 break;
393 }
394 ptr++;
395 len--;
396
397 tlen = strlen(tmp);
192c4567 398 if (tlen && (line.pos + tlen < (int)sizeof(line.buf))) {
a74aeac6
PR
399 memcpy(line.buf + line.pos, tmp, tlen);
400 line.pos += tlen;
401 }
402 if (didnl) {
403 fprintf(fp, "%s\n", line.buf);
404 memset(&line, 0, sizeof(line));
405 }
406 }
407
408 if (dosync) {
409 fflush(fp);
410 fdatasync(fileno(fp));
411 }
412
413 outptr += olen;
414 if (outptr >= endptr)
415 outptr = ringbuf;
416
417}
418
419
420/*
421 * Print usage message and exit.
422 */
423void usage(void)
424{
425 fprintf(stderr, "Usage: bootlogd [-v] [-r] [-d] [-s] [-c] [-p pidfile] [-l logfile]\n");
426 exit(1);
427}
428
429int open_nb(char *buf)
430{
431 int fd, n;
432
433 if ((fd = open(buf, O_WRONLY|O_NONBLOCK|O_NOCTTY)) < 0)
434 return -1;
435 n = fcntl(fd, F_GETFL);
436 n &= ~(O_NONBLOCK);
437 fcntl(fd, F_SETFL, n);
438
439 return fd;
440}
441
442/*
443 * We got a write error on the real console. If its an EIO,
444 * somebody hung up our filedescriptor, so try to re-open it.
445 */
446int write_err(int pts, int realfd, char *realcons, int e)
447{
448 int fd;
449
450 if (e != EIO) {
451werr:
452 close(pts);
453 fprintf(stderr, "bootlogd: writing to console: %s\n",
454 strerror(e));
455 return -1;
456 }
457 close(realfd);
458 if ((fd = open_nb(realcons)) < 0)
459 goto werr;
460
461 return fd;
462}
463
464int main(int argc, char **argv)
465{
466 FILE *fp;
467 struct timeval tv;
468 fd_set fds;
469 char buf[1024];
470 char realcons[1024];
471 char *p;
472 char *logfile;
473 char *pidfile;
474 int rotate;
475 int dontfork;
476 int ptm, pts;
477 int realfd;
478 int n, m, i;
479 int todo;
480
481 fp = NULL;
482 logfile = LOGFILE;
483 pidfile = NULL;
484 rotate = 0;
485 dontfork = 0;
486
487 while ((i = getopt(argc, argv, "cdsl:p:rv")) != EOF) switch(i) {
488 case 'l':
489 logfile = optarg;
490 break;
491 case 'r':
492 rotate = 1;
493 break;
494 case 'v':
495 printf("%s\n", Version);
496 exit(0);
497 break;
498 case 'p':
499 pidfile = optarg;
500 break;
501 case 'c':
502 createlogfile = 1;
503 break;
504 case 'd':
505 dontfork = 1;
506 break;
507 case 's':
508 syncalot = 1;
509 break;
510 default:
511 usage();
512 break;
513 }
514 if (optind < argc) usage();
515
516 signal(SIGTERM, handler);
517 signal(SIGQUIT, handler);
518 signal(SIGINT, handler);
519 signal(SIGTTIN, SIG_IGN);
520 signal(SIGTTOU, SIG_IGN);
521 signal(SIGTSTP, SIG_IGN);
522
523 /*
524 * Open console device directly.
525 */
526 if (consolename(realcons, sizeof(realcons)) < 0)
527 return 1;
528
529 if (strcmp(realcons, "/dev/tty0") == 0)
530 strcpy(realcons, "/dev/tty1");
531 if (strcmp(realcons, "/dev/vc/0") == 0)
532 strcpy(realcons, "/dev/vc/1");
533
534 if ((realfd = open_nb(realcons)) < 0) {
535 fprintf(stderr, "bootlogd: %s: %s\n", buf, strerror(errno));
536 return 1;
537 }
538
539 /*
540 * Grab a pty, and redirect console messages to it.
541 */
542 ptm = -1;
543 pts = -1;
544 buf[0] = 0;
545 if (findpty(&ptm, &pts, buf) < 0) {
546 fprintf(stderr,
547 "bootlogd: cannot allocate pseudo tty: %s\n",
548 strerror(errno));
549 return 1;
550 }
551
552 (void)ioctl(0, TIOCCONS, NULL);
553#if 1
554 /* Work around bug in 2.1/2.2 kernels. Fixed in 2.2.13 and 2.3.18 */
555 if ((n = open("/dev/tty0", O_RDWR)) >= 0) {
556 (void)ioctl(n, TIOCCONS, NULL);
557 close(n);
558 }
559#endif
560 if (ioctl(pts, TIOCCONS, NULL) < 0) {
561 fprintf(stderr, "bootlogd: ioctl(%s, TIOCCONS): %s\n",
562 buf, strerror(errno));
563 return 1;
564 }
565
566 /*
567 * Fork and write pidfile if needed.
568 */
569 if (!dontfork) {
570 pid_t child_pid = fork();
571 switch (child_pid) {
572 case -1: /* I am parent and the attempt to create a child failed */
573 fprintf(stderr, "bootlogd: fork failed: %s\n",
574 strerror(errno));
575 exit(1);
576 break;
577 case 0: /* I am the child */
578 break;
579 default: /* I am parent and got child's pid */
580 exit(0);
581 break;
582 }
583 setsid();
584 }
585 if (pidfile) {
586 unlink(pidfile);
587 if ((fp = fopen(pidfile, "w")) != NULL) {
588 fprintf(fp, "%d\n", (int)getpid());
589 fclose(fp);
590 }
591 fp = NULL;
592 }
593
594 /*
595 * Read the console messages from the pty, and write
596 * to the real console and the logfile.
597 */
598 while (!got_signal) {
599
600 /*
601 * We timeout after 5 seconds if we still need to
602 * open the logfile. There might be buffered messages
603 * we want to write.
604 */
605 tv.tv_sec = 0;
606 tv.tv_usec = 500000;
607 FD_ZERO(&fds);
608 FD_SET(ptm, &fds);
609 if (select(ptm + 1, &fds, NULL, NULL, &tv) == 1) {
610 /*
611 * See how much space there is left, read.
612 */
613 if ((n = read(ptm, inptr, endptr - inptr)) >= 0) {
614 /*
615 * Write data (in chunks if needed)
616 * to the real output device.
617 */
618 m = n;
619 p = inptr;
620 while (m > 0) {
621 i = write(realfd, p, m);
622 if (i >= 0) {
623 m -= i;
624 p += i;
625 continue;
626 }
627 /*
628 * Handle EIO (somebody hung
629 * up our filedescriptor)
630 */
631 realfd = write_err(pts, realfd,
632 realcons, errno);
633 if (realfd >= 0) continue;
634 got_signal = 1; /* Not really */
635 break;
636 }
637
638 /*
639 * Increment buffer position. Handle
640 * wraps, and also drag output pointer
641 * along if we cross it.
642 */
643 inptr += n;
644 if (inptr - n < outptr && inptr > outptr)
645 outptr = inptr;
646 if (inptr >= endptr)
647 inptr = ringbuf;
648 if (outptr >= endptr)
649 outptr = ringbuf;
650 }
651 }
652
653 /*
654 * Perhaps we need to open the logfile.
655 */
656 if (fp == NULL && access(logfile, F_OK) == 0) {
657 if (rotate) {
658 snprintf(buf, sizeof(buf), "%s~", logfile);
659 rename(logfile, buf);
660 }
661 fp = fopen(logfile, "a");
662 }
663 if (fp == NULL && createlogfile)
664 fp = fopen(logfile, "a");
665
666 if (inptr >= outptr)
667 todo = inptr - outptr;
668 else
669 todo = endptr - outptr;
670 if (fp && todo)
192c4567 671 writelog(fp, (unsigned char *)outptr, todo);
a74aeac6
PR
672 }
673
674 if (fp) {
675 if (!didnl) fputc('\n', fp);
676 fclose(fp);
677 }
678
679 close(pts);
680 close(ptm);
681 close(realfd);
682
683 return 0;
684}
685