Add #ifdef in bootlogd.c to avoid gcc warnings about unused variable on non-linux...
[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 ( (int) (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;
247 int n;
248 #ifdef __linux__
249 char buf[256];
250 char *p;
251 struct stat st2;
252 int didmount = 0;
253 int r;
254 int fd;
255 #endif
256
257 fstat(0, &st);
258 if (major(st.st_rdev) != 5 || minor(st.st_rdev) != 1) {
259 /*
260 * Old kernel, can find real device easily.
261 */
262 int r = findtty(res, "/dev", rlen, st.st_rdev);
263 if (0 != r)
264 fprintf(stderr, "bootlogd: cannot find console device "
265 "%d:%d under /dev\n", major(st.st_rdev), minor(st.st_rdev));
266 return r;
267 }
268
269 #ifdef TIOCGDEV
270 # ifndef ENOIOCTLCMD
271 # define ENOIOCTLCMD 515
272 # endif
273 if (ioctl(0, TIOCGDEV, &kdev) == 0) {
274 int r = findtty(res, "/dev", rlen, (dev_t)kdev);
275 if (0 != r)
276 fprintf(stderr, "bootlogd: cannot find console device "
277 "%d:%d under /dev\n", major(kdev), minor(kdev));
278 return r;
279 }
280 if (errno != ENOIOCTLCMD) return -1;
281 #endif
282
283 #ifdef __linux__
284 /*
285 * Read /proc/cmdline.
286 */
287 stat("/", &st);
288 if (stat("/proc", &st2) < 0) {
289 perror("bootlogd: /proc");
290 return -1;
291 }
292 if (st.st_dev == st2.st_dev) {
293 if (mount("proc", "/proc", "proc", 0, NULL) < 0) {
294 perror("bootlogd: mount /proc");
295 return -1;
296 }
297 didmount = 1;
298 }
299
300 n = 0;
301 r = -1;
302 if ((fd = open("/proc/cmdline", O_RDONLY)) < 0) {
303 perror("bootlogd: /proc/cmdline");
304 } else {
305 buf[0] = 0;
306 if ((n = read(fd, buf, sizeof(buf) - 1)) >= 0)
307 r = 0;
308 else
309 perror("bootlogd: /proc/cmdline");
310 close(fd);
311 }
312 if (didmount) umount("/proc");
313
314 if (r < 0) return r;
315
316 /*
317 * OK, so find console= in /proc/cmdline.
318 * Parse in reverse, opening as we go.
319 */
320 p = buf + n;
321 *p-- = 0;
322 r = -1;
323 while (p >= buf) {
324 if (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
325 *p-- = 0;
326 continue;
327 }
328 if (strncmp(p, "console=", 8) == 0 &&
329 isconsole(p + 8, res, rlen)) {
330 r = 0;
331 break;
332 }
333 p--;
334 }
335
336 if (r == 0) return r;
337 #endif
338
339 /*
340 * Okay, no console on the command line -
341 * guess the default console.
342 */
343 for (n = 0; defcons[n]; n++)
344 if (isconsole(defcons[n], res, rlen))
345 return 0;
346
347 fprintf(stderr, "bootlogd: cannot deduce real console device\n");
348
349 return -1;
350 }
351
352
353 /*
354 * Write data and make sure it's on disk.
355 */
356 void writelog(FILE *fp, unsigned char *ptr, int len)
357 {
358 time_t t;
359 char *s;
360 char tmp[8];
361 int olen = len;
362 int dosync = 0;
363 int tlen;
364
365 while (len > 0) {
366 tmp[0] = 0;
367 if (didnl) {
368 time(&t);
369 s = ctime(&t);
370 fprintf(fp, "%.24s: ", s);
371 didnl = 0;
372 }
373 switch (*ptr) {
374 case 27: /* ESC */
375 strcpy(tmp, "^[");
376 break;
377 case '\r':
378 line.pos = 0;
379 break;
380 case 8: /* ^H */
381 if (line.pos > 0) line.pos--;
382 break;
383 case '\n':
384 didnl = 1;
385 dosync = 1;
386 break;
387 case '\t':
388 line.pos += (line.pos / 8 + 1) * 8;
389 if (line.pos >= (int)sizeof(line.buf))
390 line.pos = sizeof(line.buf) - 1;
391 break;
392 case 32 ... 127:
393 case 161 ... 255:
394 tmp[0] = *ptr;
395 tmp[1] = 0;
396 break;
397 default:
398 sprintf(tmp, "\\%03o", *ptr);
399 break;
400 }
401 ptr++;
402 len--;
403
404 tlen = strlen(tmp);
405 if (tlen && (line.pos + tlen < (int)sizeof(line.buf))) {
406 memcpy(line.buf + line.pos, tmp, tlen);
407 line.pos += tlen;
408 }
409 if (didnl) {
410 fprintf(fp, "%s\n", line.buf);
411 memset(&line, 0, sizeof(line));
412 }
413 }
414
415 if (dosync) {
416 fflush(fp);
417 if (syncalot) {
418 fdatasync(fileno(fp));
419 }
420 }
421
422 outptr += olen;
423 if (outptr >= endptr)
424 outptr = ringbuf;
425
426 }
427
428
429 /*
430 * Print usage message and exit.
431 */
432 void usage(void)
433 {
434 fprintf(stderr, "Usage: bootlogd [-v] [-r] [-d] [-s] [-c] [-p pidfile] [-l logfile]\n");
435 exit(1);
436 }
437
438 int open_nb(char *buf)
439 {
440 int fd, n;
441
442 if ((fd = open(buf, O_WRONLY|O_NONBLOCK|O_NOCTTY)) < 0)
443 return -1;
444 n = fcntl(fd, F_GETFL);
445 n &= ~(O_NONBLOCK);
446 fcntl(fd, F_SETFL, n);
447
448 return fd;
449 }
450
451 /*
452 * We got a write error on the real console. If its an EIO,
453 * somebody hung up our filedescriptor, so try to re-open it.
454 */
455 int write_err(int pts, int realfd, char *realcons, int e)
456 {
457 int fd;
458
459 if (e != EIO) {
460 werr:
461 close(pts);
462 fprintf(stderr, "bootlogd: writing to console: %s\n",
463 strerror(e));
464 return -1;
465 }
466 close(realfd);
467 if ((fd = open_nb(realcons)) < 0)
468 goto werr;
469
470 return fd;
471 }
472
473 int main(int argc, char **argv)
474 {
475 FILE *fp;
476 struct timeval tv;
477 fd_set fds;
478 char buf[1024];
479 char realcons[1024];
480 char *p;
481 char *logfile;
482 char *pidfile;
483 int rotate;
484 int dontfork;
485 int ptm, pts;
486 int realfd;
487 int n, m, i;
488 int todo;
489
490 fp = NULL;
491 logfile = LOGFILE;
492 pidfile = NULL;
493 rotate = 0;
494 dontfork = 0;
495
496 while ((i = getopt(argc, argv, "cdsl:p:rv")) != EOF) switch(i) {
497 case 'l':
498 logfile = optarg;
499 break;
500 case 'r':
501 rotate = 1;
502 break;
503 case 'v':
504 printf("%s\n", Version);
505 exit(0);
506 break;
507 case 'p':
508 pidfile = optarg;
509 break;
510 case 'c':
511 createlogfile = 1;
512 break;
513 case 'd':
514 dontfork = 1;
515 break;
516 case 's':
517 syncalot = 1;
518 break;
519 default:
520 usage();
521 break;
522 }
523 if (optind < argc) usage();
524
525 signal(SIGTERM, handler);
526 signal(SIGQUIT, handler);
527 signal(SIGINT, handler);
528 signal(SIGTTIN, SIG_IGN);
529 signal(SIGTTOU, SIG_IGN);
530 signal(SIGTSTP, SIG_IGN);
531
532 /*
533 * Open console device directly.
534 */
535 if (consolename(realcons, sizeof(realcons)) < 0)
536 return 1;
537
538 if (strcmp(realcons, "/dev/tty0") == 0)
539 strcpy(realcons, "/dev/tty1");
540 if (strcmp(realcons, "/dev/vc/0") == 0)
541 strcpy(realcons, "/dev/vc/1");
542
543 if ((realfd = open_nb(realcons)) < 0) {
544 fprintf(stderr, "bootlogd: %s: %s\n", buf, strerror(errno));
545 return 1;
546 }
547
548 /*
549 * Grab a pty, and redirect console messages to it.
550 */
551 ptm = -1;
552 pts = -1;
553 buf[0] = 0;
554 if (findpty(&ptm, &pts, buf) < 0) {
555 fprintf(stderr,
556 "bootlogd: cannot allocate pseudo tty: %s\n",
557 strerror(errno));
558 return 1;
559 }
560
561 (void)ioctl(0, TIOCCONS, NULL);
562 #if 1
563 /* Work around bug in 2.1/2.2 kernels. Fixed in 2.2.13 and 2.3.18 */
564 if ((n = open("/dev/tty0", O_RDWR)) >= 0) {
565 (void)ioctl(n, TIOCCONS, NULL);
566 close(n);
567 }
568 #endif
569 if (ioctl(pts, TIOCCONS, NULL) < 0) {
570 fprintf(stderr, "bootlogd: ioctl(%s, TIOCCONS): %s\n",
571 buf, strerror(errno));
572 return 1;
573 }
574
575 /*
576 * Fork and write pidfile if needed.
577 */
578 if (!dontfork) {
579 pid_t child_pid = fork();
580 switch (child_pid) {
581 case -1: /* I am parent and the attempt to create a child failed */
582 fprintf(stderr, "bootlogd: fork failed: %s\n",
583 strerror(errno));
584 exit(1);
585 break;
586 case 0: /* I am the child */
587 break;
588 default: /* I am parent and got child's pid */
589 exit(0);
590 break;
591 }
592 setsid();
593 }
594 if (pidfile) {
595 unlink(pidfile);
596 if ((fp = fopen(pidfile, "w")) != NULL) {
597 fprintf(fp, "%d\n", (int)getpid());
598 fclose(fp);
599 }
600 fp = NULL;
601 }
602
603 /*
604 * Read the console messages from the pty, and write
605 * to the real console and the logfile.
606 */
607 while (!got_signal) {
608
609 /*
610 * We timeout after 5 seconds if we still need to
611 * open the logfile. There might be buffered messages
612 * we want to write.
613 */
614 tv.tv_sec = 0;
615 tv.tv_usec = 500000;
616 FD_ZERO(&fds);
617 FD_SET(ptm, &fds);
618 if (select(ptm + 1, &fds, NULL, NULL, &tv) == 1) {
619 /*
620 * See how much space there is left, read.
621 */
622 if ((n = read(ptm, inptr, endptr - inptr)) >= 0) {
623 /*
624 * Write data (in chunks if needed)
625 * to the real output device.
626 */
627 m = n;
628 p = inptr;
629 while (m > 0) {
630 i = write(realfd, p, m);
631 if (i >= 0) {
632 m -= i;
633 p += i;
634 continue;
635 }
636 /*
637 * Handle EIO (somebody hung
638 * up our filedescriptor)
639 */
640 realfd = write_err(pts, realfd,
641 realcons, errno);
642 if (realfd >= 0) continue;
643 got_signal = 1; /* Not really */
644 break;
645 }
646
647 /*
648 * Increment buffer position. Handle
649 * wraps, and also drag output pointer
650 * along if we cross it.
651 */
652 inptr += n;
653 if (inptr - n < outptr && inptr > outptr)
654 outptr = inptr;
655 if (inptr >= endptr)
656 inptr = ringbuf;
657 if (outptr >= endptr)
658 outptr = ringbuf;
659 }
660 }
661
662 /*
663 * Perhaps we need to open the logfile.
664 */
665 if (fp == NULL && access(logfile, F_OK) == 0) {
666 if (rotate) {
667 snprintf(buf, sizeof(buf), "%s~", logfile);
668 rename(logfile, buf);
669 }
670 fp = fopen(logfile, "a");
671 }
672 if (fp == NULL && createlogfile)
673 fp = fopen(logfile, "a");
674
675 if (inptr >= outptr)
676 todo = inptr - outptr;
677 else
678 todo = endptr - outptr;
679 if (fp && todo)
680 writelog(fp, (unsigned char *)outptr, todo);
681 }
682
683 if (fp) {
684 if (!didnl) fputc('\n', fp);
685 fclose(fp);
686 }
687
688 close(pts);
689 close(ptm);
690 close(realfd);
691
692 return 0;
693 }
694