Drop hurd specific dependency on libc0.3 (>= 2.3.2.ds1-12). It is
[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 * Side-effect: directory is changed to /dev
111 *
112 * FIXME: scan subdirectories for devfs support ?
113 */
114 int findtty(char *res, int rlen, dev_t dev)
115 {
116 DIR *dir;
117 struct dirent *ent;
118 struct stat st;
119 int r = 0;
120
121 if (chdir("/dev") < 0 || (dir = opendir(".")) == NULL) {
122 perror("bootlogd: /dev");
123 return -1;
124 }
125 while ((ent = readdir(dir)) != NULL) {
126 if (lstat(ent->d_name, &st) != 0)
127 continue;
128 if (!S_ISCHR(st.st_mode))
129 continue;
130 if (st.st_rdev == dev) {
131 break;
132 }
133 }
134 if (ent == NULL) {
135 fprintf(stderr, "bootlogd: cannot find console device "
136 "%d:%d in /dev\n", major(dev), minor(dev));
137 r = -1;
138 } else if (strlen(ent->d_name) + 5 >= rlen) {
139 fprintf(stderr, "bootlogd: console device name too long\n");
140 r = -1;
141 } else
142 snprintf(res, rlen, "/dev/%s", ent->d_name);
143 closedir(dir);
144
145 return r;
146 }
147
148 /*
149 * For some reason, openpty() in glibc sometimes doesn't
150 * work at boot-time. It must be a bug with old-style pty
151 * names, as new-style (/dev/pts) is not available at that
152 * point. So, we find a pty/tty pair ourself if openpty()
153 * fails for whatever reason.
154 */
155 int findpty(int *master, int *slave, char *name)
156 {
157 char pty[16];
158 char tty[16];
159 int i, j;
160 int found;
161
162 if (openpty(master, slave, name, NULL, NULL) >= 0)
163 return 0;
164
165 found = 0;
166
167 for (i = 'p'; i <= 'z'; i++) {
168 for (j = '0'; j <= 'f'; j++) {
169 if (j == '9' + 1) j = 'a';
170 sprintf(pty, "/dev/pty%c%c", i, j);
171 sprintf(tty, "/dev/tty%c%c", i, j);
172 if ((*master = open(pty, O_RDWR|O_NOCTTY)) >= 0) {
173 *slave = open(tty, O_RDWR|O_NOCTTY);
174 if (*slave >= 0) {
175 found = 1;
176 break;
177 }
178 }
179 }
180 if (found) break;
181 }
182 if (found < 0) return -1;
183
184 if (name) strcpy(name, tty);
185
186 return 0;
187 }
188 /*
189 * See if a console taken from the kernel command line maps
190 * to a character device we know about, and if we can open it.
191 */
192 int isconsole(char *s, char *res, int rlen)
193 {
194 struct consdev *c;
195 int l, sl, i, fd;
196 char *p, *q;
197
198 sl = strlen(s);
199
200 for (c = consdev; c->cmdline; c++) {
201 l = strlen(c->cmdline);
202 if (sl <= l) continue;
203 p = s + l;
204 if (strncmp(s, c->cmdline, l) != 0 || !isdigit(*p))
205 continue;
206 for (i = 0; i < 2; i++) {
207 snprintf(res, rlen, i ? c->dev1 : c->dev2, p);
208 if ((q = strchr(res, ',')) != NULL) *q = 0;
209 if ((fd = open(res, O_RDONLY|O_NONBLOCK)) >= 0) {
210 close(fd);
211 return 1;
212 }
213 }
214 }
215 return 0;
216 }
217
218 /*
219 * Find out the _real_ console. Assume that stdin is connected to
220 * the console device (/dev/console).
221 */
222 int consolename(char *res, int rlen)
223 {
224 #ifdef TIOCGDEV
225 unsigned int kdev;
226 #endif
227 struct stat st, st2;
228 char buf[256];
229 char *p;
230 int didmount = 0;
231 int n, r;
232 int fd;
233
234 fstat(0, &st);
235 if (major(st.st_rdev) != 5 || minor(st.st_rdev) != 1) {
236 /*
237 * Old kernel, can find real device easily.
238 */
239 return findtty(res, rlen, st.st_rdev);
240 }
241
242 #ifdef TIOCGDEV
243 if (ioctl(0, TIOCGDEV, &kdev) == 0)
244 return findtty(res, rlen, (dev_t)kdev);
245 if (errno != ENOIOCTLCMD) return -1;
246 #endif
247
248 #ifdef __linux__
249 /*
250 * Read /proc/cmdline.
251 */
252 stat("/", &st);
253 if (stat("/proc", &st2) < 0) {
254 perror("bootlogd: /proc");
255 return -1;
256 }
257 if (st.st_dev == st2.st_dev) {
258 if (mount("proc", "/proc", "proc", 0, NULL) < 0) {
259 perror("bootlogd: mount /proc");
260 return -1;
261 }
262 didmount = 1;
263 }
264
265 n = 0;
266 r = -1;
267 if ((fd = open("/proc/cmdline", O_RDONLY)) < 0) {
268 perror("bootlogd: /proc/cmdline");
269 } else {
270 buf[0] = 0;
271 if ((n = read(fd, buf, sizeof(buf) - 1)) >= 0)
272 r = 0;
273 else
274 perror("bootlogd: /proc/cmdline");
275 close(fd);
276 }
277 if (didmount) umount("/proc");
278
279 if (r < 0) return r;
280
281 /*
282 * OK, so find console= in /proc/cmdline.
283 * Parse in reverse, opening as we go.
284 */
285 p = buf + n;
286 *p-- = 0;
287 r = -1;
288 while (p >= buf) {
289 if (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
290 *p-- = 0;
291 continue;
292 }
293 if (strncmp(p, "console=", 8) == 0 &&
294 isconsole(p + 8, res, rlen)) {
295 r = 0;
296 break;
297 }
298 p--;
299 }
300
301 if (r == 0) return r;
302 #endif
303
304 /*
305 * Okay, no console on the command line -
306 * guess the default console.
307 */
308 for (n = 0; defcons[n]; n++)
309 if (isconsole(defcons[n], res, rlen))
310 return 0;
311
312 fprintf(stderr, "bootlogd: cannot deduce real console device\n");
313
314 return -1;
315 }
316
317
318 /*
319 * Write data and make sure it's on disk.
320 */
321 void writelog(FILE *fp, unsigned char *ptr, int len)
322 {
323 time_t t;
324 char *s;
325 char tmp[8];
326 int olen = len;
327 int dosync = 0;
328 int tlen;
329
330 while (len > 0) {
331 tmp[0] = 0;
332 if (didnl) {
333 time(&t);
334 s = ctime(&t);
335 fprintf(fp, "%.24s: ", s);
336 didnl = 0;
337 }
338 switch (*ptr) {
339 case 27: /* ESC */
340 strcpy(tmp, "^[");
341 break;
342 case '\r':
343 line.pos = 0;
344 break;
345 case 8: /* ^H */
346 if (line.pos > 0) line.pos--;
347 break;
348 case '\n':
349 didnl = 1;
350 dosync = syncalot;
351 break;
352 case '\t':
353 line.pos += (line.pos / 8 + 1) * 8;
354 if (line.pos >= sizeof(line.buf))
355 line.pos = sizeof(line.buf) - 1;
356 break;
357 case 32 ... 127:
358 case 161 ... 255:
359 tmp[0] = *ptr;
360 tmp[1] = 0;
361 break;
362 default:
363 sprintf(tmp, "\\%03o", *ptr);
364 break;
365 }
366 ptr++;
367 len--;
368
369 tlen = strlen(tmp);
370 if (tlen && (line.pos + tlen < sizeof(line.buf))) {
371 memcpy(line.buf + line.pos, tmp, tlen);
372 line.pos += tlen;
373 }
374 if (didnl) {
375 fprintf(fp, "%s\n", line.buf);
376 memset(&line, 0, sizeof(line));
377 }
378 }
379
380 if (dosync) {
381 fflush(fp);
382 fdatasync(fileno(fp));
383 }
384
385 outptr += olen;
386 if (outptr >= endptr)
387 outptr = ringbuf;
388
389 }
390
391
392 /*
393 * Print usage message and exit.
394 */
395 void usage(void)
396 {
397 fprintf(stderr, "Usage: bootlogd [-v] [-r] [-d] [-s] [-c] [-p pidfile] [-l logfile]\n");
398 exit(1);
399 }
400
401 int open_nb(char *buf)
402 {
403 int fd, n;
404
405 if ((fd = open(buf, O_WRONLY|O_NONBLOCK|O_NOCTTY)) < 0)
406 return -1;
407 n = fcntl(fd, F_GETFL);
408 n &= ~(O_NONBLOCK);
409 fcntl(fd, F_SETFL, n);
410
411 return fd;
412 }
413
414 /*
415 * We got a write error on the real console. If its an EIO,
416 * somebody hung up our filedescriptor, so try to re-open it.
417 */
418 int write_err(int pts, int realfd, char *realcons, int e)
419 {
420 int fd;
421
422 if (e != EIO) {
423 werr:
424 close(pts);
425 fprintf(stderr, "bootlogd: writing to console: %s\n",
426 strerror(e));
427 return -1;
428 }
429 close(realfd);
430 if ((fd = open_nb(realcons)) < 0)
431 goto werr;
432
433 return fd;
434 }
435
436 int main(int argc, char **argv)
437 {
438 FILE *fp;
439 struct timeval tv;
440 fd_set fds;
441 char buf[1024];
442 char realcons[1024];
443 char *p;
444 char *logfile;
445 char *pidfile;
446 int rotate;
447 int dontfork;
448 int ptm, pts;
449 int realfd;
450 int n, m, i;
451 int todo;
452
453 fp = NULL;
454 logfile = LOGFILE;
455 pidfile = NULL;
456 rotate = 0;
457 dontfork = 0;
458
459 while ((i = getopt(argc, argv, "cdsl:p:rv")) != EOF) switch(i) {
460 case 'l':
461 logfile = optarg;
462 break;
463 case 'r':
464 rotate = 1;
465 break;
466 case 'v':
467 printf("%s\n", Version);
468 exit(0);
469 break;
470 case 'p':
471 pidfile = optarg;
472 break;
473 case 'c':
474 createlogfile = 1;
475 break;
476 case 'd':
477 dontfork = 1;
478 break;
479 case 's':
480 syncalot = 1;
481 break;
482 default:
483 usage();
484 break;
485 }
486 if (optind < argc) usage();
487
488 signal(SIGTERM, handler);
489 signal(SIGQUIT, handler);
490 signal(SIGINT, handler);
491 signal(SIGTTIN, SIG_IGN);
492 signal(SIGTTOU, SIG_IGN);
493 signal(SIGTSTP, SIG_IGN);
494
495 /*
496 * Open console device directly.
497 */
498 if (consolename(realcons, sizeof(realcons)) < 0)
499 return 1;
500
501 if (strcmp(realcons, "/dev/tty0") == 0)
502 strcpy(realcons, "/dev/tty1");
503 if (strcmp(realcons, "/dev/vc/0") == 0)
504 strcpy(realcons, "/dev/vc/1");
505
506 if ((realfd = open_nb(realcons)) < 0) {
507 fprintf(stderr, "bootlogd: %s: %s\n", buf, strerror(errno));
508 return 1;
509 }
510
511 /*
512 * Grab a pty, and redirect console messages to it.
513 */
514 ptm = -1;
515 pts = -1;
516 buf[0] = 0;
517 if (findpty(&ptm, &pts, buf) < 0) {
518 fprintf(stderr,
519 "bootlogd: cannot allocate pseudo tty: %s\n",
520 strerror(errno));
521 return 1;
522 }
523
524 (void)ioctl(0, TIOCCONS, NULL);
525 #if 1
526 /* Work around bug in 2.1/2.2 kernels. Fixed in 2.2.13 and 2.3.18 */
527 if ((n = open("/dev/tty0", O_RDWR)) >= 0) {
528 (void)ioctl(n, TIOCCONS, NULL);
529 close(n);
530 }
531 #endif
532 if (ioctl(pts, TIOCCONS, NULL) < 0) {
533 fprintf(stderr, "bootlogd: ioctl(%s, TIOCCONS): %s\n",
534 buf, strerror(errno));
535 return 1;
536 }
537
538 /*
539 * Fork and write pidfile if needed.
540 */
541 if (!dontfork) {
542 pid_t child_pid = fork();
543 switch (child_pid) {
544 case -1: /* I am parent and the attempt to create a child failed */
545 fprintf(stderr, "bootlogd: fork failed: %s\n",
546 strerror(errno));
547 exit(1);
548 break;
549 case 0: /* I am the child */
550 break;
551 default: /* I am parent and got child's pid */
552 exit(0);
553 break;
554 }
555 setsid();
556 }
557 if (pidfile) {
558 unlink(pidfile);
559 if ((fp = fopen(pidfile, "w")) != NULL) {
560 fprintf(fp, "%d\n", (int)getpid());
561 fclose(fp);
562 }
563 fp = NULL;
564 }
565
566 /*
567 * Read the console messages from the pty, and write
568 * to the real console and the logfile.
569 */
570 while (!got_signal) {
571
572 /*
573 * We timeout after 5 seconds if we still need to
574 * open the logfile. There might be buffered messages
575 * we want to write.
576 */
577 tv.tv_sec = 0;
578 tv.tv_usec = 500000;
579 FD_ZERO(&fds);
580 FD_SET(ptm, &fds);
581 if (select(ptm + 1, &fds, NULL, NULL, &tv) == 1) {
582 /*
583 * See how much space there is left, read.
584 */
585 if ((n = read(ptm, inptr, endptr - inptr)) >= 0) {
586 /*
587 * Write data (in chunks if needed)
588 * to the real output device.
589 */
590 m = n;
591 p = inptr;
592 while (m > 0) {
593 i = write(realfd, p, m);
594 if (i >= 0) {
595 m -= i;
596 p += i;
597 continue;
598 }
599 /*
600 * Handle EIO (somebody hung
601 * up our filedescriptor)
602 */
603 realfd = write_err(pts, realfd,
604 realcons, errno);
605 if (realfd >= 0) continue;
606 got_signal = 1; /* Not really */
607 break;
608 }
609
610 /*
611 * Increment buffer position. Handle
612 * wraps, and also drag output pointer
613 * along if we cross it.
614 */
615 inptr += n;
616 if (inptr - n < outptr && inptr > outptr)
617 outptr = inptr;
618 if (inptr >= endptr)
619 inptr = ringbuf;
620 if (outptr >= endptr)
621 outptr = ringbuf;
622 }
623 }
624
625 /*
626 * Perhaps we need to open the logfile.
627 */
628 if (fp == NULL && access(logfile, F_OK) == 0) {
629 if (rotate) {
630 snprintf(buf, sizeof(buf), "%s~", logfile);
631 rename(logfile, buf);
632 }
633 fp = fopen(logfile, "a");
634 }
635 if (fp == NULL && createlogfile)
636 fp = fopen(logfile, "a");
637
638 if (inptr >= outptr)
639 todo = inptr - outptr;
640 else
641 todo = endptr - outptr;
642 if (fp && todo)
643 writelog(fp, outptr, todo);
644 }
645
646 if (fp) {
647 if (!didnl) fputc('\n', fp);
648 fclose(fp);
649 }
650
651 close(pts);
652 close(ptm);
653 close(realfd);
654
655 return 0;
656 }
657