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