/*
 * bootlogd.c	Store output from the console during bootup into a file.
 *		The file is usually located on the /var partition, and
 *		gets written (and fsynced) as soon as possible.
 *
 * Version:	@(#)bootlogd  2.74  01-Apr-1998  miquels@cistron.nl
 *
 * Bugs:	Uses openpty(), only available in glibc. Sorry.
 *
 *		This file is part of the sysvinit suite,
 *		Copyright 1991-1998 Miquel van Smoorenburg.
 *
 *		This program is free software; you can redistribute it and/or
 *		modify it under the terms of the GNU General Public License
 *		as published by the Free Software Foundation; either version
 *		2 of the License, or (at your option) any later version.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/utsname.h>
#include <time.h>
#include <stdio.h>
#include <errno.h>
#include <malloc.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <getopt.h>
#include <dirent.h>
#include <fcntl.h>
#include <pty.h>

char *Version = "@(#) bootlogd 2.74 01-Apr-1998 MvS";

/*
 *	Until the kernel knows about TIOCGDEV, use a really ugly
 *	non-portable (not even between architectures) hack.
 */
#define TIOCTTYGSTRUCT_HACK	1


/*
 *	Scan /dev and find the device name.
 *	Side-effect: directory is changed to /dev
 */
int findtty(char *res, dev_t dev)
{
	DIR *dir;
	struct dirent *ent;
	struct stat st;
	int r = 0;

	if (chdir("/dev") < 0 || (dir = opendir(".")) == NULL) {
		perror("bootlogd: /dev");
		return -1;
	}
	while ((ent = readdir(dir)) != NULL) {
		if (lstat(ent->d_name, &st) != 0)
			continue;
		if (!S_ISCHR(st.st_mode))
			continue;
		if (st.st_rdev == dev) {
			printf("Found it: %s: %04x\n", ent->d_name, (int)st.st_rdev);
			break;
		}
	}
	if (ent == NULL) {
		fprintf(stderr, "bootlogd: cannot find console device\n");
		r = -1;
	} else
		strcpy(res, ent->d_name);
	closedir(dir);

	return r;
}

/*
 *	Find out the _real_ console. Assume that stdin is connected to
 *	the console device (/dev/console).
 */
int consolename(char *res)
{
	struct stat st;
#if TIOCTTYGSTRUCT_HACK
	char buf[4096];
	struct utsname uts;
	int offset = -1;
	int *kdev;
	dev_t dev;
#endif

	fstat(0, &st);
	if (st.st_rdev != 0x0501 && 0) {
		/*
		 *	Old kernel, can find real device easily.
		 */
		printf("Looking for device %04x\n", (int)st.st_rdev);
		return findtty(res, st.st_rdev);
	}
	/*
	 *	New kernel and new console device - hard to find
	 *	out what device the real console is ..
	 */
#if TIOCTTYGSTRUCT_HACK
	if (ioctl(0, TIOCTTYGSTRUCT, buf) != 0) {
		perror("bootlogd: TIOCTTYGSTRUCT");
		return -1;
	}
	uname(&uts);
	if (strncmp(uts.release, "2.0.", 4) == 0)
		offset = 236;
	if (strncmp(uts.release, "2.1.", 4) == 0)
		offset = 268;
	if (offset < 0) {
		fprintf(stderr, "bootlogd: don't know offsetof"
		"(struct tty_struct, device) for %s\n", uts.release);
		return -1;
	}
	kdev = (int *)(&buf[offset]);
	dev = (dev_t)(*kdev);
	printf("Looking for device %04x\n", (int)dev);
	return findtty(res, st.st_rdev);
#endif
}


int main(int argc, char **argv)
{
	int ptm, pts;
	char buf[1024];
	int realfd;
	int n;

	/*
	 *	Open console device directly.
	 */
	if (consolename(buf) < 0)
		return 1;
	if ((realfd = open(buf, O_WRONLY)) < 0) {
		fprintf(stderr, "bootlogd: %s: %s\n", buf, strerror(errno));
		return 1;
	}

	/*
	 *	Grab a pty, and redirect console messages to it.
	 */
	if (openpty(&ptm, &pts, buf, NULL, NULL) < 0) {
		fprintf(stderr, "bootlogd: cannot allocate pseudo tty\n");
		return 1;
	}
	(void)ioctl(0, TIOCCONS, NULL);
	if (ioctl(pts, TIOCCONS, NULL) < 0) {
		perror("bootlogd: TIOCCONS");
		return 1;
	}

	/*
	 *	Read the console messages from the pty, and write
	 *	to the real console.
	 */
	while((n = read(ptm, buf, sizeof(buf))) >= 0) {
		write(realfd, buf, n);
	}

	close(pts);
	close(ptm);
	close(realfd);

	return 0;
}

