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