/*
 * Copyright (c) 1992, 2015, Oracle and/or its affiliates. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */


/*
 * fbconsole - fallback console
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stropts.h>
#include <termios.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/strredir.h>

#include <X11/Xos.h>
#include <X11/Xlib.h>

static char		LogPath[MAXPATHLEN];	/* pathname of log file */

/*
 * Default settings to use if they can't actually be obtained from a
 * descriptor relevant to the application.
 *
 * XXX:	These settings shouldn't be used unless absolutely necessary, since
 *	they're almost certain to get out of sync with the kernel's defaults
 *	(which is what they're intended to be).
 */
static struct termios	termios_dflt = {
	BRKINT|ICRNL|IXON|IGNPAR|IMAXBEL,	    	/* input modes */
	OPOST|ONLCR,				    	/* output modes */
	B9600|(B9600 << IBSHIFT)|CS8|HUPCL|CREAD,    	/* control modes */
	ISIG|ICANON|ECHO|IEXTEN|ECHOE|ECHOK|ECHOCTL|ECHOKE, /* local modes */
						       /* control characters */
	CINTR,		/* VINTR */
	CQUIT,		/* VQUIT */
	CERASE,		/* VERASE */
	CKILL,		/* VKILL */
	CEOT,		/* VEOF */
	CEOL,		/* VEOL */
	CEOL2,		/* VEOL2 */
	CNUL,		/* VSWTCH */
	CSTART,		/* VSTART */
	CSTOP,		/* VSTOP */
	CSUSP,		/* VSUSP */
	CDSUSP,		/* VDSUSP */
	CRPRNT,		/* VRPRNT */
	CFLUSH,		/* VDISCARD */
	CWERASE,	/* VWERASE */
	CLNEXT,		/* VLNEXT */
};


#ifdef	NOTDEF
/*
 * OpenConsole
 *	Returns a file descriptor which has had the console redirected to it
 *
 * This version (unused) opens a pipe and redirects the console to it.
 */
static int
OpenConsole(void)
{
	int	fds[2];
	int	fdcons;

	if (pipe(fds) == -1) {
		fprintf(stderr,"Couldn't open console pipes\n");
		perror("pipe");
		exit(1);
	}

	if ((fdcons = open("/dev/console", O_RDONLY)) == -1) {
		fprintf(stderr,"Couldn't open /dev/console\n");
		perror("open");
		exit(1);
	}

	if (ioctl(fdcons, SRIOCSREDIR, fds[0]) == -1) {
		fprintf(stderr,"Couldn't redirect console to console pipe\n");
		exit(1);
	}

	return fds[1];
}
#endif /* NOTDEF */


/*
 * OpenConsole
 *
 * Opens a pty, copies tty settings into it from /dev/console, and redirects
 * console output to it.  Returns the master end of the pty.
 */
static int
OpenConsole(void)
{
	int	console;
	int	master;
	int	slave;
	char	*slavename;
	struct termios termios;

	if ((console = open("/dev/console", O_RDONLY | O_NOCTTY, 0)) == -1) {
		perror("fbconsole: open /dev/console");
		exit(1);
	}

	if ((master = open("/dev/ptmx", O_RDWR, 0)) == -1) {
		perror("fbconsole: open /dev/ptmx");
		exit(1);
	}

	if (grantpt(master) == -1) {
		fputs("fbconsole: grantpt failed\n", stderr);
		exit(1);
	}

	if (unlockpt(master) == -1) {
		fputs("fbconsole: unlockpt failed\n", stderr);
		exit(1);
	}

	if ((slavename = ptsname(master)) == NULL) {
		fputs("fbconsole: ptsname failed\n", stderr);
		exit(1);
	}

#ifdef	DEBUG
	fprintf(stderr, "slavename = \"%s\"\n", slavename);
#endif

	if ((slave = open(slavename, O_RDWR, 0)) == -1) {
		perror("fbconsole: open slave");
		exit(1);
	}

	if (ioctl(slave, I_PUSH, "ptem") == -1) {
		perror("fbconsole: push ptem");
		exit(1);
	}

	if (ioctl(slave, I_PUSH, "ldterm") == -1) {
		perror("fbconsole: push ldterm");
		exit(1);
	}

	/*
	 * Propagate tty settings from the real console to the new console.  
	 * If the erase character is zero, apply default settings to the new 
	 * console.  If the erase character is nonzero, leave most of the 
	 * settings intact and apply default values only to the modes and to 
	 * the EOF and EOL character.  (Why apply defaults for EOF and EOL?)
	 *
	 * Code originally taken from XView, lib/libxview/ttysw/tty_gtty.c
	 */

	if (tcgetattr(console, &termios) == -1) {
		perror("fbconsole: tcgetattr");
		exit(1);
	}

	if (termios.c_cc[VERASE] == 0) {
		termios = termios_dflt;
	} else {
		termios.c_iflag = termios_dflt.c_iflag;
		termios.c_oflag = termios_dflt.c_oflag;
		termios.c_cflag = termios_dflt.c_cflag;
		termios.c_lflag = termios_dflt.c_lflag;
		termios.c_cc[VEOF] = termios_dflt.c_cc[VEOF];
		termios.c_cc[VEOL] = termios_dflt.c_cc[VEOL];
	}

	if (tcsetattr(slave, TCSANOW, &termios) == -1) {
		perror("fbconsole: tcsetattr");
		exit(1);
	}

	/* redirect console output into the slave side */

	if (ioctl(console, SRIOCSREDIR, slave) == -1) {
		perror("fbconsole: ioctl SRIOCSREDIR");
		exit(1);
	}

	return master;
}


/*
 * OpenLog
 *	Opens the console log file; returns a file descriptor
 */
static FILE *
OpenLog(
	const char *dpyName,
	char	*path)
{
	const char *tmpName;
	FILE	*log;
	int	tmpFd;

	if (path == NULL) {
		tmpName = getenv("TMPDIR");
		if (tmpName == NULL) {
			tmpName = P_tmpdir;
		}
		if (snprintf(LogPath, sizeof(LogPath), "%s/wscon-%s-XXXXXX",
		    tmpName, dpyName) > sizeof(LogPath)) {
			tmpFd = -1;
		} else {
			tmpFd = mkstemp(LogPath);
		}
		path = LogPath;
	} else {
		LogPath[0] = '\0';

		if ((strcmp(path,"-") == 0) || 
		    (strcmp(path,"stderr") == 0)) {
			return stderr;
		}
		tmpFd = open(path, O_WRONLY | O_CREAT | O_EXCL, 
		    S_IRUSR|S_IWUSR);
	}

#ifdef	DEBUG
	fprintf(stderr, "log file = \"%s\"\n", path);
#endif

	if ( (tmpFd < 0) || (log = fdopen(tmpFd, "w")) == NULL) {
		fprintf(stderr,
		    "fbconsole: couldn't open console log file '%s'\n",path);
		exit(1);
	}
	setvbuf(log, NULL, _IONBF, 0);

	fchmod(fileno(log), S_IRUSR|S_IWUSR);

	return log;
}

/*
 * CloseLog
 */
static void
CloseLog(void)
{
	if (LogPath[0] == '\0')
		return;

	if (unlink(LogPath) == -1)
		perror("unlink");
}

/*
 * CleanupAndExit
 *	Closes log file and exits
 */
static void
CleanupAndExit(void)
{
	CloseLog();
	exit(0);
}


/*
 * SignalHandler
 *	The signal handler for SIGINT and SIGTERM.
 */
/*ARGSUSED*/
void
SignalHandler(int sig)
{
	CleanupAndExit();
}


/*
 * DisplayErrorHandler
 *	X I/O error handler.
 */
/*ARGSUSED*/
int
DisplayErrorHandler(Display *dpy)
{
	CleanupAndExit();
	return 0;
}


/*
 * LogConsole
 *	Reads a console message and writes it to the console log file
 */
static void
LogConsole(
	int	console,
	FILE	*log)
{
	char	buf[1024];
	int	rcount;

	rcount = read(console, buf, 1024);

	if (rcount == -1)
		return;

	(void) fwrite(buf, rcount, 1, log);
}

/*
 * InputLoop
 *	Waits for input from the console message pipe or the xserver.
 *	On input from the console - logs it to the console log file.
 *	On any input (or EOF) from the xserver, exits
 */
static void
InputLoop(
	Display		*dpy,
	int		console,
	FILE		*log)
{
	struct pollfd	fds[2];
	XEvent		event;
	int		fdcount = 1;
	
#define CONSOLE_FD 0
#define XSERVER_FD 1	
	
	fds[CONSOLE_FD].fd 	= console;
	fds[CONSOLE_FD].events 	= POLLIN;
	if (dpy != NULL) {
		fds[XSERVER_FD].fd 	= ConnectionNumber(dpy);
		fds[XSERVER_FD].events 	= POLLIN;
		fdcount++;
	}
	
	while (poll(fds, fdcount, -1) >= 0) {

		if ((dpy != NULL) && (fds[XSERVER_FD].revents)) {
			while (XPending(dpy))
				XNextEvent(dpy, &event);
		}

		if (fds[CONSOLE_FD].revents & POLLIN) {
			LogConsole(console,log);
		}
	}
}

/*
 * Usage
 *	Prints a usage message
 */
static void
Usage(void)
{
	
	fprintf(stderr,"Usage: fbconsole [-d <display>] [-f <logfile>] [-n]\n");
	exit(1);
}

/*
 * main
 */
int
main(int argc, char **argv)
{
	Display	*dpy = NULL;
	char	*dpyName = NULL;
	int	console;
	char	*logFile = NULL;
	FILE	*log;
	int	opt;
	int	noDisplay = False;

	while ((opt = getopt(argc, argv, "d:f:n")) != EOF) {
		switch (opt) {
		case 'd':
			dpyName = optarg;
			break;
		case 'n':
		    	noDisplay = True;
			break;
		case 'f':
			logFile = optarg;
			break;
		case '?':
			Usage();
			break;
		}
	}

	if (noDisplay) {
	    dpyName = XDisplayName(dpyName);	    
	} else {
	    if ((dpy = XOpenDisplay(dpyName)) == NULL) {
		fprintf(stderr,
			"Couldn't open display connection %s\n",
			XDisplayName(dpyName));
		exit(1);
	    }
	    (void) XSetIOErrorHandler(DisplayErrorHandler);
	    dpyName = XDisplayString(dpy);
	}

	console = OpenConsole();

	log = OpenLog(dpyName, logFile);

	(void)sigset(SIGHUP, SignalHandler);
	(void)sigset(SIGINT, SignalHandler);
	(void)sigset(SIGQUIT, SignalHandler);
	(void)sigset(SIGTERM, SignalHandler);
	(void)sigset(SIGPIPE, SignalHandler);

	InputLoop(dpy, console, log);

	return(0);
}