Leftover.
[sysvinit.git] / src / bootlogd.c
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
57 char *Version = "@(#) bootlogd 2.86 03-Jun-2004 miquels@cistron.nl";
58
59 #define LOGFILE "/var/log/boot"
60
61 char ringbuf[32768];
62 char *endptr = ringbuf + sizeof(ringbuf);
63 char *inptr = ringbuf;
64 char *outptr = ringbuf;
65
66 int got_signal = 0;
67 int didnl = 1;
68 int createlogfile = 0;
69 int syncalot = 0;
70
71 struct 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 */
80 struct 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 */
97 char *defcons[] = { "tty0", "hvc0", "ttyS0", "ttySC0", "ttyB0", NULL };
98
99 /*
100 * Catch signals.
101 */
102 void handler(int sig)
103 {
104 got_signal = sig;
105 }
106
107
108 /*
109 * Scan /dev and find the device name.
110 */
111 static int findtty(char *res, const char *startdir, int rlen, dev_t dev)
112 {
113 DIR *dir;
114 struct dirent *ent;
115 struct stat st;
116 int r = -1;
117 char *olddir = getcwd(NULL, 0);
118
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);
126 return -1;
127 }
128 while ((ent = readdir(dir)) != NULL) {
129 if (lstat(ent->d_name, &st) != 0)
130 continue;
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 }
145 if (!S_ISCHR(st.st_mode))
146 continue;
147 if (st.st_rdev == dev) {
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 }
159 }
160 }
161 closedir(dir);
162
163 chdir(olddir);
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 */
174 int 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) 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 */
211 int 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 */
241 int 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 */
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;
263 }
264
265 #ifdef TIOCGDEV
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 }
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 */
349 void 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 = 1;
379 break;
380 case '\t':
381 line.pos += (line.pos / 8 + 1) * 8;
382 if (line.pos >= (int)sizeof(line.buf))
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);
398 if (tlen && (line.pos + tlen < (int)sizeof(line.buf))) {
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 if (syncalot) {
411 fdatasync(fileno(fp));
412 }
413 }
414
415 outptr += olen;
416 if (outptr >= endptr)
417 outptr = ringbuf;
418
419 }
420
421
422 /*
423 * Print usage message and exit.
424 */
425 void usage(void)
426 {
427 fprintf(stderr, "Usage: bootlogd [-v] [-r] [-d] [-s] [-c] [-p pidfile] [-l logfile]\n");
428 exit(1);
429 }
430
431 int open_nb(char *buf)
432 {
433 int fd, n;
434
435 if ((fd = open(buf, O_WRONLY|O_NONBLOCK|O_NOCTTY)) < 0)
436 return -1;
437 n = fcntl(fd, F_GETFL);
438 n &= ~(O_NONBLOCK);
439 fcntl(fd, F_SETFL, n);
440
441 return fd;
442 }
443
444 /*
445 * We got a write error on the real console. If its an EIO,
446 * somebody hung up our filedescriptor, so try to re-open it.
447 */
448 int write_err(int pts, int realfd, char *realcons, int e)
449 {
450 int fd;
451
452 if (e != EIO) {
453 werr:
454 close(pts);
455 fprintf(stderr, "bootlogd: writing to console: %s\n",
456 strerror(e));
457 return -1;
458 }
459 close(realfd);
460 if ((fd = open_nb(realcons)) < 0)
461 goto werr;
462
463 return fd;
464 }
465
466 int main(int argc, char **argv)
467 {
468 FILE *fp;
469 struct timeval tv;
470 fd_set fds;
471 char buf[1024];
472 char realcons[1024];
473 char *p;
474 char *logfile;
475 char *pidfile;
476 int rotate;
477 int dontfork;
478 int ptm, pts;
479 int realfd;
480 int n, m, i;
481 int todo;
482
483 fp = NULL;
484 logfile = LOGFILE;
485 pidfile = NULL;
486 rotate = 0;
487 dontfork = 0;
488
489 while ((i = getopt(argc, argv, "cdsl:p:rv")) != EOF) switch(i) {
490 case 'l':
491 logfile = optarg;
492 break;
493 case 'r':
494 rotate = 1;
495 break;
496 case 'v':
497 printf("%s\n", Version);
498 exit(0);
499 break;
500 case 'p':
501 pidfile = optarg;
502 break;
503 case 'c':
504 createlogfile = 1;
505 break;
506 case 'd':
507 dontfork = 1;
508 break;
509 case 's':
510 syncalot = 1;
511 break;
512 default:
513 usage();
514 break;
515 }
516 if (optind < argc) usage();
517
518 signal(SIGTERM, handler);
519 signal(SIGQUIT, handler);
520 signal(SIGINT, handler);
521 signal(SIGTTIN, SIG_IGN);
522 signal(SIGTTOU, SIG_IGN);
523 signal(SIGTSTP, SIG_IGN);
524
525 /*
526 * Open console device directly.
527 */
528 if (consolename(realcons, sizeof(realcons)) < 0)
529 return 1;
530
531 if (strcmp(realcons, "/dev/tty0") == 0)
532 strcpy(realcons, "/dev/tty1");
533 if (strcmp(realcons, "/dev/vc/0") == 0)
534 strcpy(realcons, "/dev/vc/1");
535
536 if ((realfd = open_nb(realcons)) < 0) {
537 fprintf(stderr, "bootlogd: %s: %s\n", buf, strerror(errno));
538 return 1;
539 }
540
541 /*
542 * Grab a pty, and redirect console messages to it.
543 */
544 ptm = -1;
545 pts = -1;
546 buf[0] = 0;
547 if (findpty(&ptm, &pts, buf) < 0) {
548 fprintf(stderr,
549 "bootlogd: cannot allocate pseudo tty: %s\n",
550 strerror(errno));
551 return 1;
552 }
553
554 (void)ioctl(0, TIOCCONS, NULL);
555 #if 1
556 /* Work around bug in 2.1/2.2 kernels. Fixed in 2.2.13 and 2.3.18 */
557 if ((n = open("/dev/tty0", O_RDWR)) >= 0) {
558 (void)ioctl(n, TIOCCONS, NULL);
559 close(n);
560 }
561 #endif
562 if (ioctl(pts, TIOCCONS, NULL) < 0) {
563 fprintf(stderr, "bootlogd: ioctl(%s, TIOCCONS): %s\n",
564 buf, strerror(errno));
565 return 1;
566 }
567
568 /*
569 * Fork and write pidfile if needed.
570 */
571 if (!dontfork) {
572 pid_t child_pid = fork();
573 switch (child_pid) {
574 case -1: /* I am parent and the attempt to create a child failed */
575 fprintf(stderr, "bootlogd: fork failed: %s\n",
576 strerror(errno));
577 exit(1);
578 break;
579 case 0: /* I am the child */
580 break;
581 default: /* I am parent and got child's pid */
582 exit(0);
583 break;
584 }
585 setsid();
586 }
587 if (pidfile) {
588 unlink(pidfile);
589 if ((fp = fopen(pidfile, "w")) != NULL) {
590 fprintf(fp, "%d\n", (int)getpid());
591 fclose(fp);
592 }
593 fp = NULL;
594 }
595
596 /*
597 * Read the console messages from the pty, and write
598 * to the real console and the logfile.
599 */
600 while (!got_signal) {
601
602 /*
603 * We timeout after 5 seconds if we still need to
604 * open the logfile. There might be buffered messages
605 * we want to write.
606 */
607 tv.tv_sec = 0;
608 tv.tv_usec = 500000;
609 FD_ZERO(&fds);
610 FD_SET(ptm, &fds);
611 if (select(ptm + 1, &fds, NULL, NULL, &tv) == 1) {
612 /*
613 * See how much space there is left, read.
614 */
615 if ((n = read(ptm, inptr, endptr - inptr)) >= 0) {
616 /*
617 * Write data (in chunks if needed)
618 * to the real output device.
619 */
620 m = n;
621 p = inptr;
622 while (m > 0) {
623 i = write(realfd, p, m);
624 if (i >= 0) {
625 m -= i;
626 p += i;
627 continue;
628 }
629 /*
630 * Handle EIO (somebody hung
631 * up our filedescriptor)
632 */
633 realfd = write_err(pts, realfd,
634 realcons, errno);
635 if (realfd >= 0) continue;
636 got_signal = 1; /* Not really */
637 break;
638 }
639
640 /*
641 * Increment buffer position. Handle
642 * wraps, and also drag output pointer
643 * along if we cross it.
644 */
645 inptr += n;
646 if (inptr - n < outptr && inptr > outptr)
647 outptr = inptr;
648 if (inptr >= endptr)
649 inptr = ringbuf;
650 if (outptr >= endptr)
651 outptr = ringbuf;
652 }
653 }
654
655 /*
656 * Perhaps we need to open the logfile.
657 */
658 if (fp == NULL && access(logfile, F_OK) == 0) {
659 if (rotate) {
660 snprintf(buf, sizeof(buf), "%s~", logfile);
661 rename(logfile, buf);
662 }
663 fp = fopen(logfile, "a");
664 }
665 if (fp == NULL && createlogfile)
666 fp = fopen(logfile, "a");
667
668 if (inptr >= outptr)
669 todo = inptr - outptr;
670 else
671 todo = endptr - outptr;
672 if (fp && todo)
673 writelog(fp, (unsigned char *)outptr, todo);
674 }
675
676 if (fp) {
677 if (!didnl) fputc('\n', fp);
678 fclose(fp);
679 }
680
681 close(pts);
682 close(ptm);
683 close(realfd);
684
685 return 0;
686 }
687