diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 3 | ||||
-rw-r--r-- | src/rc-logger.c | 235 | ||||
-rw-r--r-- | src/rc-logger.h | 11 | ||||
-rw-r--r-- | src/rc.c | 96 |
4 files changed, 316 insertions, 29 deletions
diff --git a/src/Makefile b/src/Makefile index a0c7f5fb..f633b1c1 100644 --- a/src/Makefile +++ b/src/Makefile @@ -57,7 +57,8 @@ LIBRCOBJS = librc.o librc-depend.o librc-daemon.o librc-misc.o librc-strlist.o LDLIBS_LIBRC = RCOBJS = checkown.o env-update.o fstabinfo.o mountinfo.o \ - rc-depend.o rc-misc.o rc-plugin.o rc-status.o rc-update.o \ + rc-depend.o rc-logger.o rc-misc.o rc-plugin.o rc-status.o \ + rc-update.o \ runscript.o start-stop-daemon.o rc.o LDLIBS_RC = -leinfo -lrc -lutil diff --git a/src/rc-logger.c b/src/rc-logger.c new file mode 100644 index 00000000..fa3303c2 --- /dev/null +++ b/src/rc-logger.c @@ -0,0 +1,235 @@ +/* + rc-logger.c + Spawns a logging daemon to capture stdout and stderr so we can log + them to a buffer and/or files. + + Copyright 2007 Gentoo Foundation + Released under the GPLv2 + */ + +#include <sys/types.h> +#include <sys/wait.h> +#include <ctype.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <time.h> +#include <unistd.h> + +#ifdef __linux__ +# include <pty.h> +#else +# include <libutil.h> +#endif + +#include "einfo.h" +#include "rc-logger.h" +#include "rc-misc.h" +#include "rc.h" + +#define LOGFILE RC_SVCDIR "/rc.log" +#define PERMLOG "/var/log/rc.log" +#define MOVELOG "mv " LOGFILE " " PERMLOG ".$$.tmp && cat " PERMLOG \ + ".$$.tmp >>" PERMLOG " 2>/dev/null && rm -f " PERMLOG ".$$.tmp" + +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) { + write (logfd, p++, 1); + continue; + } + + if (! in_term || isalpha (*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 () +{ + if (signal_pipe[1] > -1) { + int sig = SIGTERM; + write (signal_pipe[1], &sig, sizeof (sig)); + 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; + fd_set rset; + int s; + size_t bytes; + int selfd; + int i; + FILE *log = NULL; + + if (! isatty (STDOUT_FILENO)) + return; + + if (! rc_env_bool ("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)); + + tcgetattr (STDOUT_FILENO, &tt); + ioctl (STDOUT_FILENO, TIOCGWINSZ, &ws); + + /* /dev/pts may not be available yet */ + if (openpty (&rc_logger_tty, &slave_tty, NULL, &tt, &ws)) + return; + + rc_logger_pid = fork (); + switch (rc_logger_pid) { + case -1: + eerror ("forkpty: %s", strerror (errno)); + break; + case 0: + rc_in_logger = true; + close (signal_pipe[1]); + signal_pipe[1] = -1; + + runlevel = level; + if ((log = fopen (LOGFILE, "a"))) + write_time (log, "started"); + else { + free (logbuf); + logbuf_size = RC_LINEBUFFER * 10; + logbuf = xmalloc (sizeof (char) * logbuf_size); + logbuf_len = 0; + } + + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + selfd = rc_logger_tty > signal_pipe[0] ? rc_logger_tty : signal_pipe[0]; + while (1) { + FD_ZERO (&rset); + FD_SET (rc_logger_tty, &rset); + FD_SET (signal_pipe[0], &rset); + + if ((s = select (selfd + 1, &rset, NULL, NULL, NULL)) == -1) { + eerror ("select: %s", strerror (errno)); + break; + } + + if (s > 0) { + if (FD_ISSET (rc_logger_tty, &rset)) { + memset (buffer, 0, RC_LINEBUFFER); + bytes = read (rc_logger_tty, buffer, RC_LINEBUFFER); + write (STDOUT_FILENO, buffer, bytes); + + if (log) + write_log (fileno (log), buffer, bytes); + else { + if (logbuf_size - logbuf_len < bytes) { + logbuf_size += RC_LINEBUFFER * 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_ISSET (signal_pipe[0], &rset)) + break; + } + } + free (buffer); + if (logbuf) { + if ((log = fopen (LOGFILE, "a"))) { + write_time (log, "started"); + write_log (fileno (log), logbuf, logbuf_len); + } + free (logbuf); + } + if (log) { + write_time (log, "stopped"); + fclose (log); + } + + /* Try and cat our new logfile to a more permament location and then + * punt it */ + system (MOVELOG); + + exit (0); + default: + setpgid (rc_logger_pid, 0); + fd_stdout = dup (STDOUT_FILENO); + fd_stderr = dup (STDERR_FILENO); + 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; + } +} diff --git a/src/rc-logger.h b/src/rc-logger.h new file mode 100644 index 00000000..1c58c328 --- /dev/null +++ b/src/rc-logger.h @@ -0,0 +1,11 @@ +/* + rc-logger.h + Copyright 2007 Gentoo Foundation + */ + +pid_t rc_logger_pid; +int rc_logger_tty; +extern bool rc_in_logger; + +void rc_logger_open (const char *runlevel); +void rc_logger_close (); @@ -16,6 +16,7 @@ #define SYSLOG_NAMES #include <sys/types.h> +#include <sys/ioctl.h> #include <sys/stat.h> #include <sys/utsname.h> #include <sys/wait.h> @@ -38,6 +39,7 @@ #include "builtins.h" #include "einfo.h" #include "rc.h" +#include "rc-logger.h" #include "rc-misc.h" #include "rc-plugin.h" #include "strlist.h" @@ -92,7 +94,7 @@ static void cleanup (void) pidlist_t *pl = service_pids; rc_plugin_unload (); - + if (! rc_in_plugin && termios_orig) { tcsetattr (fileno (stdin), TCSANOW, termios_orig); free (termios_orig); @@ -112,15 +114,18 @@ static void cleanup (void) rc_deptree_free (deptree); /* Clean runlevel start, stop markers */ - if (! rc_in_plugin) { + if (! rc_in_plugin && ! rc_in_logger) { rmdir (RC_STARTING); rmdir (RC_STOPPING); + + rc_logger_close (); } free (runlevel); } free (applet); + applet = NULL; } static int syslog_decode (char *name, CODE *codetab) @@ -484,7 +489,7 @@ static void sulogin (bool cont) #ifdef __linux__ char *e = getenv ("RC_SYS"); - /* VPS systems cannot do an sulogin */ + /* VPS systems cannot do a sulogin */ if (e && strcmp (e, "VPS") == 0) { execl ("/sbin/halt", "/sbin/halt", "-f", (char *) NULL); eerrorx ("%s: unable to exec `/sbin/halt': %s", applet, strerror (errno)); @@ -496,7 +501,7 @@ static void sulogin (bool cont) if (cont) { int status = 0; #ifdef __linux__ - char *tty = ttyname (fileno (stdout)); + char *tty = ttyname (STDOUT_FILENO); #endif pid_t pid = vfork (); @@ -521,6 +526,8 @@ static void sulogin (bool cont) } waitpid (pid, &status, 0); } else { + rc_logger_close (); + #ifdef __linux__ execle ("/sbin/sulogin", "/sbin/sulogin", (char *) NULL, newenv); eerrorx ("%s: unable to exec `/sbin/sulogin': %s", applet, strerror (errno)); @@ -532,6 +539,8 @@ static void sulogin (bool cont) static void single_user (void) { + rc_logger_close (); + #ifdef __linux__ execl ("/sbin/telinit", "/sbin/telinit", "S", (char *) NULL); eerrorx ("%s: unable to exec `/sbin/telinit': %s", @@ -592,12 +601,6 @@ static int get_ksoftlevel (char *buffer, int buffer_len) return (i); } -static void wait_for_services () -{ - int status = 0; - while (wait (&status) != -1); -} - static void add_pid (pid_t pid) { pidlist_t *sp = service_pids; @@ -630,6 +633,11 @@ static void remove_pid (pid_t pid) } } +static void wait_for_services () +{ + while (waitpid (0, 0, 0) != -1); +} + static void handle_signal (int sig) { int serrno = errno; @@ -637,6 +645,7 @@ static void handle_signal (int sig) pidlist_t *pl; pid_t pid; int status = 0; + struct winsize ws; switch (sig) { case SIGCHLD: @@ -654,6 +663,13 @@ static void handle_signal (int sig) remove_pid (pid); break; + case SIGWINCH: + if (rc_logger_tty >= 0) { + ioctl (STDIN_FILENO, TIOCGWINSZ, &ws); + ioctl (rc_logger_tty, TIOCSWINSZ, &ws); + } + break; + case SIGINT: if (! signame[0]) snprintf (signame, sizeof (signame), "SIGINT"); @@ -695,7 +711,8 @@ static void handle_signal (int sig) errno = serrno; } -static void run_script (const char *script) { +static void run_script (const char *script) +{ int status = 0; pid_t pid = vfork (); @@ -824,12 +841,6 @@ int main (int argc, char **argv) RUNLEVEL = getenv ("RUNLEVEL"); PREVLEVEL = getenv ("PREVLEVEL"); - /* Setup a signal handler */ - signal (SIGINT, handle_signal); - signal (SIGQUIT, handle_signal); - signal (SIGTERM, handle_signal); - signal (SIGUSR1, handle_signal); - /* Ensure our environment is pure Also, add our configuration to it */ env = env_filter (); @@ -895,13 +906,22 @@ int main (int argc, char **argv) snprintf (pidstr, sizeof (pidstr), "%d", getpid ()); setenv ("RC_PID", pidstr, 1); - interactive = exists (INTERACTIVE); - rc_plugin_load (); - /* Load current softlevel */ bootlevel = getenv ("RC_BOOTLEVEL"); runlevel = rc_runlevel_get (); + rc_logger_open (newlevel ? newlevel : runlevel); + + /* Setup a signal handler */ + signal (SIGINT, handle_signal); + signal (SIGQUIT, handle_signal); + signal (SIGTERM, handle_signal); + signal (SIGUSR1, handle_signal); + signal (SIGWINCH, handle_signal); + + interactive = exists (INTERACTIVE); + rc_plugin_load (); + /* Check we're in the runlevel requested, ie from rc single rc shutdown @@ -949,8 +969,8 @@ int main (int argc, char **argv) set_ksoftlevel (cmd); free (cmd); } - #endif + rc_plugin_run (RC_HOOK_RUNLEVEL_START_OUT, newlevel); if (want_interactive ()) @@ -970,6 +990,7 @@ int main (int argc, char **argv) if (! RUNLEVEL || strcmp (RUNLEVEL, "6") != 0) { + rc_logger_close (); execl (SHUTDOWN, SHUTDOWN, "-r", "now", (char *) NULL); eerrorx ("%s: unable to exec `" SHUTDOWN "': %s", applet, strerror (errno)); @@ -978,6 +999,7 @@ int main (int argc, char **argv) if (! RUNLEVEL || strcmp (RUNLEVEL, "0") != 0) { + rc_logger_close (); execl (SHUTDOWN, SHUTDOWN, #ifdef __linux__ "-h", @@ -1022,6 +1044,15 @@ int main (int argc, char **argv) going_down = true; rc_runlevel_set (newlevel); setenv ("RC_SOFTLEVEL", newlevel, 1); + +#ifdef __FreeBSD__ + /* FIXME: we shouldn't have todo this */ + /* For some reason, wait_for_services waits for the logger proccess + * to finish as well, but only on FreeBSD. We cannot allow this so + * we stop logging now. */ + rc_logger_close (); +#endif + rc_plugin_run (RC_HOOK_RUNLEVEL_STOP_IN, newlevel); } else { rc_plugin_run (RC_HOOK_RUNLEVEL_STOP_IN, runlevel); @@ -1283,9 +1314,16 @@ int main (int argc, char **argv) /* After all that we can finally stop the blighter! */ if (! found) { - pid_t pid = rc_service_stop (service); - if (pid > 0 && ! rc_env_bool ("RC_PARALLEL")) - rc_waitpid (pid); + pid_t pid; + + if ((pid = rc_service_stop (service)) > 0) { + add_pid (pid); + + if (! rc_env_bool ("RC_PARALLEL")) { + rc_waitpid (pid); + remove_pid (pid); + } + } } } @@ -1309,6 +1347,7 @@ int main (int argc, char **argv) if (strcmp (runlevel, RC_LEVEL_SHUTDOWN) == 0 || strcmp (runlevel, RC_LEVEL_REBOOT) == 0) { + rc_logger_close (); execl (HALTSH, HALTSH, runlevel, (char *) NULL); eerrorx ("%s: unable to exec `%s': %s", applet, HALTSH, strerror (errno)); @@ -1377,12 +1416,13 @@ interactive_option: } /* Remember the pid if we're running in parallel */ - if ((pid = rc_service_start (service))) + if ((pid = rc_service_start (service)) > 0) { add_pid (pid); - if (! rc_env_bool ("RC_PARALLEL")) { - rc_waitpid (pid); - remove_pid (pid); + if (! rc_env_bool ("RC_PARALLEL")) { + rc_waitpid (pid); + remove_pid (pid); + } } } } |