]>
git.wh0rd.org - sysvinit.git/blob - src/bootlogd.c
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.
6 * Version: @(#)bootlogd 2.86pre 12-Jan-2004 miquels@cistron.nl
8 * Bugs: Uses openpty(), only available in glibc. Sorry.
10 * This file is part of the sysvinit suite,
11 * Copyright (C) 1991-2004 Miquel van Smoorenburg.
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.
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.
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
27 * *NOTE* *NOTE* *NOTE*
28 * This is a PROOF OF CONCEPT IMPLEMENTATION
30 * I have bigger plans for Debian, but for now
35 #include <sys/types.h>
38 #include <sys/ioctl.h>
39 #include <sys/utsname.h>
54 #include <sys/mount.h>
57 char *Version
= "@(#) bootlogd 2.86 03-Jun-2004 miquels@cistron.nl";
59 #define LOGFILE "/var/log/boot"
62 char *endptr
= ringbuf
+ sizeof(ringbuf
);
63 char *inptr
= ringbuf
;
64 char *outptr
= ringbuf
;
68 int createlogfile
= 0;
77 * Console devices as listed on the kernel command line and
78 * the mapping to actual devices in /dev
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" },
94 * Devices to try as console if not found on kernel command line.
95 * Tried from left to right (as opposed to kernel cmdline).
97 char *defcons
[] = { "tty0", "hvc0", "ttyS0", "ttySC0", "ttyB0", NULL
};
102 void handler(int sig
)
109 * Scan /dev and find the device name.
110 * Side-effect: directory is changed to /dev
112 * FIXME: scan subdirectories for devfs support ?
114 int findtty(char *res
, int rlen
, dev_t dev
)
121 if (chdir("/dev") < 0 || (dir
= opendir(".")) == NULL
) {
122 perror("bootlogd: /dev");
125 while ((ent
= readdir(dir
)) != NULL
) {
126 if (lstat(ent
->d_name
, &st
) != 0)
128 if (!S_ISCHR(st
.st_mode
))
130 if (st
.st_rdev
== dev
) {
135 fprintf(stderr
, "bootlogd: cannot find console device "
136 "%d:%d in /dev\n", major(dev
), minor(dev
));
138 } else if ((int)strlen(ent
->d_name
) + 5 >= rlen
) {
139 fprintf(stderr
, "bootlogd: console device name too long\n");
142 snprintf(res
, rlen
, "/dev/%s", ent
->d_name
);
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.
155 int findpty(int *master
, int *slave
, char *name
)
162 if (openpty(master
, slave
, name
, NULL
, NULL
) >= 0)
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
);
182 if (found
< 0) return -1;
184 if (name
) strcpy(name
, tty
);
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.
192 int isconsole(char *s
, char *res
, int rlen
)
200 for (c
= consdev
; c
->cmdline
; c
++) {
201 l
= strlen(c
->cmdline
);
202 if (sl
<= l
) continue;
204 if (strncmp(s
, c
->cmdline
, l
) != 0 || !isdigit(*p
))
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) {
219 * Find out the _real_ console. Assume that stdin is connected to
220 * the console device (/dev/console).
222 int consolename(char *res
, int rlen
)
235 if (major(st
.st_rdev
) != 5 || minor(st
.st_rdev
) != 1) {
237 * Old kernel, can find real device easily.
239 return findtty(res
, rlen
, st
.st_rdev
);
243 if (ioctl(0, TIOCGDEV
, &kdev
) == 0)
244 return findtty(res
, rlen
, (dev_t
)kdev
);
245 if (errno
!= ENOIOCTLCMD
) return -1;
250 * Read /proc/cmdline.
253 if (stat("/proc", &st2
) < 0) {
254 perror("bootlogd: /proc");
257 if (st
.st_dev
== st2
.st_dev
) {
258 if (mount("proc", "/proc", "proc", 0, NULL
) < 0) {
259 perror("bootlogd: mount /proc");
267 if ((fd
= open("/proc/cmdline", O_RDONLY
)) < 0) {
268 perror("bootlogd: /proc/cmdline");
271 if ((n
= read(fd
, buf
, sizeof(buf
) - 1)) >= 0)
274 perror("bootlogd: /proc/cmdline");
277 if (didmount
) umount("/proc");
282 * OK, so find console= in /proc/cmdline.
283 * Parse in reverse, opening as we go.
289 if (*p
== ' ' || *p
== '\t' || *p
== '\r' || *p
== '\n') {
293 if (strncmp(p
, "console=", 8) == 0 &&
294 isconsole(p
+ 8, res
, rlen
)) {
301 if (r
== 0) return r
;
305 * Okay, no console on the command line -
306 * guess the default console.
308 for (n
= 0; defcons
[n
]; n
++)
309 if (isconsole(defcons
[n
], res
, rlen
))
312 fprintf(stderr
, "bootlogd: cannot deduce real console device\n");
319 * Write data and make sure it's on disk.
321 void writelog(FILE *fp
, unsigned char *ptr
, int len
)
335 fprintf(fp
, "%.24s: ", s
);
346 if (line
.pos
> 0) line
.pos
--;
353 line
.pos
+= (line
.pos
/ 8 + 1) * 8;
354 if (line
.pos
>= (int)sizeof(line
.buf
))
355 line
.pos
= sizeof(line
.buf
) - 1;
363 sprintf(tmp
, "\\%03o", *ptr
);
370 if (tlen
&& (line
.pos
+ tlen
< (int)sizeof(line
.buf
))) {
371 memcpy(line
.buf
+ line
.pos
, tmp
, tlen
);
375 fprintf(fp
, "%s\n", line
.buf
);
376 memset(&line
, 0, sizeof(line
));
382 fdatasync(fileno(fp
));
386 if (outptr
>= endptr
)
393 * Print usage message and exit.
397 fprintf(stderr
, "Usage: bootlogd [-v] [-r] [-d] [-s] [-c] [-p pidfile] [-l logfile]\n");
401 int open_nb(char *buf
)
405 if ((fd
= open(buf
, O_WRONLY
|O_NONBLOCK
|O_NOCTTY
)) < 0)
407 n
= fcntl(fd
, F_GETFL
);
409 fcntl(fd
, F_SETFL
, n
);
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.
418 int write_err(int pts
, int realfd
, char *realcons
, int e
)
425 fprintf(stderr
, "bootlogd: writing to console: %s\n",
430 if ((fd
= open_nb(realcons
)) < 0)
436 int main(int argc
, char **argv
)
459 while ((i
= getopt(argc
, argv
, "cdsl:p:rv")) != EOF
) switch(i
) {
467 printf("%s\n", Version
);
486 if (optind
< argc
) usage();
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
);
496 * Open console device directly.
498 if (consolename(realcons
, sizeof(realcons
)) < 0)
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");
506 if ((realfd
= open_nb(realcons
)) < 0) {
507 fprintf(stderr
, "bootlogd: %s: %s\n", buf
, strerror(errno
));
512 * Grab a pty, and redirect console messages to it.
517 if (findpty(&ptm
, &pts
, buf
) < 0) {
519 "bootlogd: cannot allocate pseudo tty: %s\n",
524 (void)ioctl(0, TIOCCONS
, NULL
);
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
);
532 if (ioctl(pts
, TIOCCONS
, NULL
) < 0) {
533 fprintf(stderr
, "bootlogd: ioctl(%s, TIOCCONS): %s\n",
534 buf
, strerror(errno
));
539 * Fork and write pidfile if needed.
542 pid_t child_pid
= fork();
544 case -1: /* I am parent and the attempt to create a child failed */
545 fprintf(stderr
, "bootlogd: fork failed: %s\n",
549 case 0: /* I am the child */
551 default: /* I am parent and got child's pid */
559 if ((fp
= fopen(pidfile
, "w")) != NULL
) {
560 fprintf(fp
, "%d\n", (int)getpid());
567 * Read the console messages from the pty, and write
568 * to the real console and the logfile.
570 while (!got_signal
) {
573 * We timeout after 5 seconds if we still need to
574 * open the logfile. There might be buffered messages
581 if (select(ptm
+ 1, &fds
, NULL
, NULL
, &tv
) == 1) {
583 * See how much space there is left, read.
585 if ((n
= read(ptm
, inptr
, endptr
- inptr
)) >= 0) {
587 * Write data (in chunks if needed)
588 * to the real output device.
593 i
= write(realfd
, p
, m
);
600 * Handle EIO (somebody hung
601 * up our filedescriptor)
603 realfd
= write_err(pts
, realfd
,
605 if (realfd
>= 0) continue;
606 got_signal
= 1; /* Not really */
611 * Increment buffer position. Handle
612 * wraps, and also drag output pointer
613 * along if we cross it.
616 if (inptr
- n
< outptr
&& inptr
> outptr
)
620 if (outptr
>= endptr
)
626 * Perhaps we need to open the logfile.
628 if (fp
== NULL
&& access(logfile
, F_OK
) == 0) {
630 snprintf(buf
, sizeof(buf
), "%s~", logfile
);
631 rename(logfile
, buf
);
633 fp
= fopen(logfile
, "a");
635 if (fp
== NULL
&& createlogfile
)
636 fp
= fopen(logfile
, "a");
639 todo
= inptr
- outptr
;
641 todo
= endptr
- outptr
;
643 writelog(fp
, (unsigned char *)outptr
, todo
);
647 if (!didnl
) fputc('\n', fp
);