]>
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.
111 static int findtty(char *res
, const char *startdir
, size_t rlen
, dev_t dev
)
117 if ((dir
= opendir(startdir
)) == NULL
) {
118 int msglen
= strlen(startdir
) + 11;
119 char *msg
= malloc(msglen
);
120 snprintf(msg
, msglen
, "bootlogd: %s", startdir
);
125 while ((ent
= readdir(dir
)) != NULL
) {
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
);
131 if (lstat(path
, &st
) != 0) {
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 */
149 if (!S_ISCHR(st
.st_mode
))
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");
157 snprintf(res
, rlen
, "%s/%s", startdir
, ent
->d_name
);
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.
175 int findpty(int *master
, int *slave
, char *name
)
182 if (openpty(master
, slave
, name
, NULL
, NULL
) >= 0)
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
);
202 if (!found
) return -1;
204 if (name
) strcpy(name
, tty
);
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.
212 int isconsole(char *s
, char *res
, size_t rlen
)
220 for (c
= consdev
; c
->cmdline
; c
++) {
221 l
= strlen(c
->cmdline
);
222 if (sl
<= l
) continue;
224 if (strncmp(s
, c
->cmdline
, l
) != 0 || !isdigit(*p
))
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) {
239 * Find out the _real_ console. Assume that stdin is connected to
240 * the console device (/dev/console).
242 int consolename(char *res
, size_t rlen
)
259 if (major(st
.st_rdev
) != 5 || minor(st
.st_rdev
) != 1) {
261 * Old kernel, can find real device easily.
263 int r
= findtty(res
, "/dev", rlen
, st
.st_rdev
);
265 fprintf(stderr
, "bootlogd: cannot find console device "
266 "%d:%d under /dev\n", major(st
.st_rdev
), minor(st
.st_rdev
));
272 # define ENOIOCTLCMD 515
274 if (ioctl(0, TIOCGDEV
, &kdev
) == 0) {
275 int r
= findtty(res
, "/dev", rlen
, (dev_t
)kdev
);
277 fprintf(stderr
, "bootlogd: cannot find console device "
278 "%d:%d under /dev\n", major(kdev
), minor(kdev
));
281 if (errno
!= ENOIOCTLCMD
) return -1;
286 * Read /proc/cmdline.
289 if (stat("/proc", &st2
) < 0) {
290 perror("bootlogd: /proc");
293 if (st
.st_dev
== st2
.st_dev
) {
294 if (mount("proc", "/proc", "proc", 0, NULL
) < 0) {
295 perror("bootlogd: mount /proc");
303 if ((fd
= open("/proc/cmdline", O_RDONLY
)) < 0) {
304 perror("bootlogd: /proc/cmdline");
307 if ((n
= read(fd
, buf
, sizeof(buf
) - 1)) >= 0) {
309 buf
[sizeof(buf
)-1] = 0; /* enforce null termination */
311 perror("bootlogd: /proc/cmdline");
314 if (didmount
) umount("/proc");
319 * OK, so find console= in /proc/cmdline.
320 * Parse in reverse, opening as we go.
326 if (*p
== ' ' || *p
== '\t' || *p
== '\r' || *p
== '\n') {
330 if (strncmp(p
, "console=", 8) == 0 &&
331 isconsole(p
+ 8, res
, rlen
)) {
338 if (r
== 0) return r
;
342 * Okay, no console on the command line -
343 * guess the default console.
345 for (n
= 0; defcons
[n
]; n
++)
346 if (isconsole(defcons
[n
], res
, rlen
))
349 fprintf(stderr
, "bootlogd: cannot deduce real console device\n");
356 * Write data and make sure it's on disk.
358 void writelog(FILE *fp
, unsigned char *ptr
, int len
)
372 fprintf(fp
, "%.24s: ", s
);
383 if (line
.pos
> 0) line
.pos
--;
390 line
.pos
+= (line
.pos
/ 8 + 1) * 8;
391 if (line
.pos
>= (int)sizeof(line
.buf
))
392 line
.pos
= sizeof(line
.buf
) - 1;
400 sprintf(tmp
, "\\%03o", *ptr
);
407 if (tlen
&& (line
.pos
+ tlen
< (int)sizeof(line
.buf
))) {
408 memcpy(line
.buf
+ line
.pos
, tmp
, tlen
);
412 fprintf(fp
, "%s\n", line
.buf
);
413 memset(&line
, 0, sizeof(line
));
420 fdatasync(fileno(fp
));
425 if (outptr
>= endptr
)
432 * Print usage message and exit.
436 fprintf(stderr
, "Usage: bootlogd [-v] [-r] [-d] [-s] [-c] [-p pidfile] [-l logfile]\n");
440 int open_nb(char *buf
)
444 if ((fd
= open(buf
, O_WRONLY
|O_NONBLOCK
|O_NOCTTY
)) < 0)
446 n
= fcntl(fd
, F_GETFL
);
448 fcntl(fd
, F_SETFL
, n
);
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.
457 int write_err(int pts
, int realfd
, char *realcons
, int e
)
464 fprintf(stderr
, "bootlogd: writing to console: %s\n",
469 if ((fd
= open_nb(realcons
)) < 0)
475 int main(int argc
, char **argv
)
498 while ((i
= getopt(argc
, argv
, "cdsl:p:rv")) != EOF
) switch(i
) {
506 printf("%s\n", Version
);
525 if (optind
< argc
) usage();
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
);
535 * Open console device directly.
537 if (consolename(realcons
, sizeof(realcons
)) < 0)
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");
545 if ((realfd
= open_nb(realcons
)) < 0) {
546 fprintf(stderr
, "bootlogd: %s: %s\n", buf
, strerror(errno
));
551 * Grab a pty, and redirect console messages to it.
556 if (findpty(&ptm
, &pts
, buf
) < 0) {
558 "bootlogd: cannot allocate pseudo tty: %s\n",
563 (void)ioctl(0, TIOCCONS
, NULL
);
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
);
571 if (ioctl(pts
, TIOCCONS
, NULL
) < 0) {
572 fprintf(stderr
, "bootlogd: ioctl(%s, TIOCCONS): %s\n",
573 buf
, strerror(errno
));
578 * Fork and write pidfile if needed.
581 pid_t child_pid
= fork();
583 case -1: /* I am parent and the attempt to create a child failed */
584 fprintf(stderr
, "bootlogd: fork failed: %s\n",
588 case 0: /* I am the child */
590 default: /* I am parent and got child's pid */
598 if ((fp
= fopen(pidfile
, "w")) != NULL
) {
599 fprintf(fp
, "%d\n", (int)getpid());
606 * Read the console messages from the pty, and write
607 * to the real console and the logfile.
609 while (!got_signal
) {
612 * We timeout after 5 seconds if we still need to
613 * open the logfile. There might be buffered messages
620 if (select(ptm
+ 1, &fds
, NULL
, NULL
, &tv
) == 1) {
622 * See how much space there is left, read.
624 if ((n
= read(ptm
, inptr
, endptr
- inptr
)) >= 0) {
626 * Write data (in chunks if needed)
627 * to the real output device.
632 i
= write(realfd
, p
, m
);
639 * Handle EIO (somebody hung
640 * up our filedescriptor)
642 realfd
= write_err(pts
, realfd
,
644 if (realfd
>= 0) continue;
645 got_signal
= 1; /* Not really */
650 * Increment buffer position. Handle
651 * wraps, and also drag output pointer
652 * along if we cross it.
655 if (inptr
- n
< outptr
&& inptr
> outptr
)
659 if (outptr
>= endptr
)
665 * Perhaps we need to open the logfile.
667 if (fp
== NULL
&& access(logfile
, F_OK
) == 0) {
669 snprintf(buf
, sizeof(buf
), "%s~", logfile
);
670 rename(logfile
, buf
);
672 fp
= fopen(logfile
, "a");
674 if (fp
== NULL
&& createlogfile
)
675 fp
= fopen(logfile
, "a");
678 todo
= inptr
- outptr
;
680 todo
= endptr
- outptr
;
682 writelog(fp
, (unsigned char *)outptr
, todo
);
686 if (!didnl
) fputc('\n', fp
);