diff options
Diffstat (limited to 'src/openrc/rc-logger.c')
| -rw-r--r-- | src/openrc/rc-logger.c | 314 | 
1 files changed, 314 insertions, 0 deletions
| diff --git a/src/openrc/rc-logger.c b/src/openrc/rc-logger.c new file mode 100644 index 00000000..b00550a7 --- /dev/null +++ b/src/openrc/rc-logger.c @@ -0,0 +1,314 @@ +/* + * rc-logger.c + * Spawns a logging daemon to capture stdout and stderr so we can log + * them to a buffer and/or files. + */ + +/* + * Copyright (c) 2007-2015 The OpenRC Authors. + * See the Authors file at the top-level directory of this distribution and + * https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS + * + * This file is part of OpenRC. It is subject to the license terms in + * the LICENSE file found in the top-level directory of this + * distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE + * This file may not be copied, modified, propagated, or distributed + *    except according to the terms contained in the LICENSE file. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/wait.h> + +#include <ctype.h> +#include <fcntl.h> +#include <poll.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <time.h> +#include <unistd.h> + +#if defined(__linux__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) \ +	|| defined(__GNU__) +#  include <pty.h> +#elif defined(__NetBSD__) || defined(__OpenBSD__) +#  include <util.h> +#else +#  include <libutil.h> +#endif + +#include "einfo.h" +#include "rc-logger.h" +#include "queue.h" +#include "rc.h" +#include "misc.h" + +#define TMPLOG RC_SVCDIR "/rc.log" +#define DEFAULTLOG "/var/log/rc.log" + +static int signal_pipe[2] = { -1, -1 }; +static int fd_stdout = -1; +static int fd_stderr = -1; +static const char *runlevel = NULL; +static bool in_escape = false; +static bool in_term = false; + +static char *logbuf = NULL; +static size_t logbuf_size = 0; +static size_t logbuf_len = 0; + +pid_t rc_logger_pid = -1; +int rc_logger_tty = -1; +bool rc_in_logger = false; + +static void +write_log(int logfd, const char *buffer, size_t bytes) +{ +	const char *p = buffer; + +	while ((size_t)(p - buffer) < bytes) { +		switch (*p) { +		case '\r': +			goto cont; +		case '\033': +			in_escape = true; +			in_term = false; +			goto cont; +		case '\n': +			in_escape = in_term = false; +			break; +		case '[': +			if (in_escape) +				in_term = true; +			break; +		} + +		if (!in_escape) { +			if (!isprint((int) *p) && *p != '\n') +				goto cont; +			if (write(logfd, p++, 1) == -1) +				eerror("write: %s", strerror(errno)); +			continue; +		} + +		if (!in_term || isalpha((unsigned char)*p)) +			in_escape = in_term = false; +cont: +		p++; +	} +} + +static void +write_time(FILE *f, const char *s) +{ +	time_t now = time(NULL); +	struct tm *tm = localtime(&now); + +	fprintf(f, "\nrc %s logging %s at %s\n", runlevel, s, asctime(tm)); +	fflush(f); +} + +void +rc_logger_close(void) +{ +	int sig = SIGTERM; + +	if (signal_pipe[1] > -1) { +		if (write(signal_pipe[1], &sig, sizeof(sig)) == -1) +			eerror("write: %s", strerror(errno)); +		close(signal_pipe[1]); +		signal_pipe[1] = -1; +	} + +	if (rc_logger_pid > 0) +		waitpid(rc_logger_pid, 0, 0); + +	if (fd_stdout > -1) +		dup2(fd_stdout, STDOUT_FILENO); +	if (fd_stderr > -1) +		dup2(fd_stderr, STDERR_FILENO); +} + +void +rc_logger_open(const char *level) +{ +	int slave_tty; +	struct termios tt; +	struct winsize ws; +	char buffer[BUFSIZ]; +	struct pollfd fd[2]; +	int s = 0; +	size_t bytes; +	int i; +	FILE *log = NULL; +	FILE *plog = NULL; +	const char *logfile; +	int log_error = 0; + +	if (!rc_conf_yesno("rc_logger")) +		return; + +	if (pipe(signal_pipe) == -1) +		eerrorx("pipe: %s", strerror(errno)); +	for (i = 0; i < 2; i++) +		if ((s = fcntl (signal_pipe[i], F_GETFD, 0) == -1 || +			fcntl (signal_pipe[i], F_SETFD, s | FD_CLOEXEC) == -1)) +			eerrorx("fcntl: %s", strerror (errno)); + +	if (isatty(STDOUT_FILENO)) { +		tcgetattr(STDOUT_FILENO, &tt); +		ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); +		if (openpty(&rc_logger_tty, &slave_tty, NULL, &tt, &ws)) +			return; +	} else +		if (openpty(&rc_logger_tty, &slave_tty, NULL, NULL, NULL)) +			return; + +	if ((s = fcntl(rc_logger_tty, F_GETFD, 0)) == 0) +		fcntl(rc_logger_tty, F_SETFD, s | FD_CLOEXEC); + +	if ((s = fcntl(slave_tty, F_GETFD, 0)) == 0) +		fcntl(slave_tty, F_SETFD, s | FD_CLOEXEC); + +	rc_logger_pid = fork(); +	switch (rc_logger_pid) { +	case -1: +		eerror("fork: %s", strerror(errno)); +		break; +	case 0: +		rc_in_logger = true; +		close(signal_pipe[1]); +		signal_pipe[1] = -1; + +		runlevel = level; +		if ((log = fopen(TMPLOG, "ae"))) +			write_time(log, "started"); +		else { +			free(logbuf); +			logbuf_size = BUFSIZ * 10; +			logbuf = xmalloc(sizeof (char) * logbuf_size); +			logbuf_len = 0; +		} + +		fd[0].fd = signal_pipe[0]; +		fd[0].events = fd[1].events = POLLIN; +		fd[0].revents = fd[1].revents = 0; +		if (rc_logger_tty >= 0) +			fd[1].fd = rc_logger_tty; +		for (;;) { +			if ((s = poll(fd, +				    rc_logger_tty >= 0 ? 2 : 1, -1)) == -1) +			{ +				eerror("poll: %s", strerror(errno)); +				break; +			} else if (s == 0) +				continue; + +			if (fd[1].revents & (POLLIN | POLLHUP)) { +				memset(buffer, 0, BUFSIZ); +				bytes = read(rc_logger_tty, buffer, BUFSIZ); +				if (write(STDOUT_FILENO, buffer, bytes) == -1) +					eerror("write: %s", strerror(errno)); + +				if (log) +					write_log(fileno (log), buffer, bytes); +				else { +					if (logbuf_size - logbuf_len < bytes) { +						logbuf_size += BUFSIZ * 10; +						logbuf = xrealloc(logbuf, +						    sizeof(char ) * +						    logbuf_size); +					} + +					memcpy(logbuf + logbuf_len, +					    buffer, bytes); +					logbuf_len += bytes; +				} +			} + +			/* Only SIGTERMS signals come down this pipe */ +			if (fd[0].revents & (POLLIN | POLLHUP)) +				break; +		} +		if (logbuf) { +			if ((log = fopen(TMPLOG, "ae"))) { +				write_time(log, "started"); +				write_log(fileno(log), logbuf, logbuf_len); +			} +			free(logbuf); +		} +		if (log) { +			write_time(log, "stopped"); +			fclose(log); +		} + +		/* Append the temporary log to the real log */ +		logfile = rc_conf_value("rc_log_path"); +		if (logfile == NULL) +			logfile = DEFAULTLOG; +		if (!strcmp(logfile, TMPLOG)) { +			eerror("Cowardly refusing to concatenate a logfile into itself."); +			eerrorx("Please change rc_log_path to something other than %s to get rid of this message", TMPLOG); +		} + +		if ((plog = fopen(logfile, "ae"))) { +			if ((log = fopen(TMPLOG, "re"))) { +				while ((bytes = fread(buffer, sizeof(*buffer), BUFSIZ, log)) > 0) { +					if (fwrite(buffer, sizeof(*buffer), bytes, plog) < bytes) { +						log_error = 1; +						eerror("Error: write(%s) failed: %s", logfile, strerror(errno)); +						break; +					} +				} +				fclose(log); +			} else { +				log_error = 1; +				eerror("Error: fopen(%s) failed: %s", TMPLOG, strerror(errno)); +			} + +			fclose(plog); +		} else { +			/* +			 * logfile or its basedir may be read-only during sysinit and +			 * shutdown so skip the error in this case +			 */ +			if (errno != EROFS && ((strcmp(level, RC_LEVEL_SHUTDOWN) != 0) && (strcmp(level, RC_LEVEL_SYSINIT) != 0))) { +				log_error = 1; +				eerror("Error: fopen(%s) failed: %s", logfile, strerror(errno)); +			} +		} + +		/* Try to keep the temporary log in case of errors */ +		if (!log_error) { +			if (errno != EROFS && ((strcmp(level, RC_LEVEL_SHUTDOWN) != 0) && (strcmp(level, RC_LEVEL_SYSINIT) != 0))) +				if (unlink(TMPLOG) == -1) +					eerror("Error: unlink(%s) failed: %s", TMPLOG, strerror(errno)); +		} else if (exists(TMPLOG)) +			eerrorx("Warning: temporary logfile left behind: %s", TMPLOG); + +		exit(0); +		/* NOTREACHED */ + +	default: +		setpgid(rc_logger_pid, 0); +		fd_stdout = dup(STDOUT_FILENO); +		fd_stderr = dup(STDERR_FILENO); +		if ((s = fcntl(fd_stdout, F_GETFD, 0)) == 0) +			fcntl(fd_stdout, F_SETFD, s | FD_CLOEXEC); + +		if ((s = fcntl(fd_stderr, F_GETFD, 0)) == 0) +			fcntl(fd_stderr, F_SETFD, s | FD_CLOEXEC); +		dup2(slave_tty, STDOUT_FILENO); +		dup2(slave_tty, STDERR_FILENO); +		if (slave_tty != STDIN_FILENO && +		    slave_tty != STDOUT_FILENO && +		    slave_tty != STDERR_FILENO) +			close(slave_tty); +		close(signal_pipe[0]); +		signal_pipe[0] = -1; +		break; +	} +} | 
