X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=daemon.c;h=a4e2f3193730d456ce9908eb55888cdb759154e0;hp=b0d8fd744c8f7f0d208afd5f96ca03fe336ece16;hb=ac1f19d550a81c8408c8fce2e237996c950253ab;hpb=abfb661f35e99e99c09a94d84839356d905af080 diff --git a/daemon.c b/daemon.c index b0d8fd74..a4e2f319 100644 --- a/daemon.c +++ b/daemon.c @@ -1,8 +1,4 @@ -/* - * Copyright (C) 1997-2014 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ +/* Copyright (C) 1997 Andre Noll , see file COPYING. */ /** \file daemon.c Some helpers for programs that detach from the console. */ @@ -11,6 +7,7 @@ #include /* getgrnam() */ #include #include +#include #include "para.h" #include "daemon.h" @@ -33,16 +30,18 @@ struct daemon { char *hostname; /** Used for colored log messages. */ char log_colors[NUM_LOGLEVELS][COLOR_MAXLEN]; + char *old_cwd; + /* + * If these pointers are non-NULL, the functions are called from + * daemon_log() before and after writing each log message. + */ + void (*pre_log_hook)(void); + void (*post_log_hook)(void); }; static struct daemon the_daemon, *me = &the_daemon; -/** - * Activate default log colors. - * - * This should be called early if color support is wanted. - */ -void daemon_set_default_log_colors(void) +static void daemon_set_default_log_colors(void) { int i; static const char *default_log_colors[NUM_LOGLEVELS] = { @@ -61,11 +60,9 @@ void daemon_set_default_log_colors(void) /** * Set the color for one loglevel. * - * \param arg The loglevel/color specifier. - * - * \a arg must be of the form "ll:[fg [bg]] [attr]". + * \param arg Must be of the form "ll:[fg [bg]] [attr]". */ -void daemon_set_log_color_or_die(char const *arg) +void daemon_set_log_color_or_die(const char *arg) { char *p = strchr(arg, ':'); int ret, ll; @@ -80,20 +77,59 @@ void daemon_set_log_color_or_die(char const *arg) color_parse_or_die(p, me->log_colors[ll]); return; err: - PARA_EMERG_LOG("%s: color syntax error\n", arg); + PARA_EMERG_LOG("%s: invalid color argument\n", arg); exit(EXIT_FAILURE); } +/** + * Initialize color mode if necessary. + * + * \param color_arg The argument given to --color. + * \param color_arg_auto The value for automatic color detection. + * \param color_arg_no The value to disable colored log messages. + * \param logfile_given In auto mode colors are disabled if this value is true. + * + * If color_arg equals color_arg_no, color mode is disabled. That is, calls to + * para_log() will not produce colored output. If color_arg equals + * color_arg_auto, the function detects automatically whether to activate + * colors. Otherwise color mode is enabled. + * + * If color mode is to be enabled, the default colors are set for each + * loglevel. They can be overwritten by calling daemon_set_log_color_or_die(). + * + * \return Whether colors have been enabled by the function. + */ +bool daemon_init_colors_or_die(int color_arg, int color_arg_auto, + int color_arg_no, bool logfile_given) +{ + if (color_arg == color_arg_no) + return false; + if (color_arg == color_arg_auto) { + if (logfile_given) + return false; + if (!isatty(STDERR_FILENO)) + return false; + } + daemon_set_flag(DF_COLOR_LOG); + daemon_set_default_log_colors(); + return true; +} + /** * Init or change the name of the log file. * * \param logfile_name The full path of the logfile. */ -void daemon_set_logfile(char *logfile_name) +void daemon_set_logfile(const char *logfile_name) { free(me->logfile_name); me->logfile_name = NULL; - if (logfile_name) + if (!logfile_name) + return; + if (me->old_cwd && logfile_name[0] != '/') + me->logfile_name = make_message("%s/%s", me->old_cwd, + logfile_name); + else me->logfile_name = para_strdup(logfile_name); } @@ -102,7 +138,7 @@ void daemon_set_logfile(char *logfile_name) * * \param loglevel The smallest level that should be logged. */ -void daemon_set_loglevel(char *loglevel) +void daemon_set_loglevel(const char *loglevel) { int ret = get_loglevel_by_name(loglevel); @@ -111,27 +147,37 @@ void daemon_set_loglevel(char *loglevel) } /** - * Set one of the daemon config flags. + * Register functions to be called before and after a message is logged. * - * \param flag The flag to set. + * \param pre_log_hook Called before the message is logged. + * \param post_log_hook Called after the message is logged. * - * \sa \ref daemon_flags. + * The purpose of this function is to provide a primitive for multi-threaded + * applications to serialize the access to the log facility, preventing + * interleaving log messages. This can be achieved by having the pre-log hook + * acquire a lock which blocks the other threads on the attempt to log a + * message at the same time. The post-log hook is responsible for releasing + * the lock. + * + * If these hooks are unnecessary, for example because the application is + * single-threaded, this function does not need to be called. */ -void daemon_set_flag(unsigned flag) +void daemon_set_hooks(void (*pre_log_hook)(void), void (*post_log_hook)(void)) { - me->flags |= flag; + me->pre_log_hook = pre_log_hook; + me->post_log_hook = post_log_hook; } /** - * Clear one of the daemon config flags. + * Set one of the daemon config flags. * - * \param flag The flag to clear. + * \param flag The flag to set. * * \sa \ref daemon_flags. */ -void daemon_clear_flag(unsigned flag) +void daemon_set_flag(unsigned flag) { - me->flags &= ~flag; + me->flags |= flag; } static bool daemon_test_flag(unsigned flag) @@ -139,10 +185,6 @@ static bool daemon_test_flag(unsigned flag) return me->flags & flag; } -static void dummy_sighandler(__a_unused int s) -{ -} - /** * Do the usual stuff to become a daemon. * @@ -150,36 +192,49 @@ static void dummy_sighandler(__a_unused int s) * * Fork, become session leader, cd to /, and dup fd 0, 1, 2 to /dev/null. If \a * parent_waits is false, the parent process terminates immediately. - * Otherwise, it calls pause() to sleep until it receives \p SIGTERM or \p - * SIGCHLD and exits successfully thereafter. This behaviour is useful if the - * daemon process should not detach from the console until the child process - * has completed its setup. + * Otherwise, a pipe is created prior to the fork() and the parent tries to + * read a single byte from the reading end of the pipe. The child is supposed + * to write to the writing end of the pipe after it completed its setup + * procedure successfully. This behaviour is useful to let the parent process + * die with an error if the child process aborts early, since in this case the + * read() will return non-positive. + * + * \return This function either succeeds or calls exit(3). If parent_waits is + * true, the return value is the file descriptor of the writing end of the + * pipe. Otherwise the function returns zero. * * \sa fork(2), setsid(2), dup(2), pause(2). */ -void daemonize(bool parent_waits) +int daemonize(bool parent_waits) { pid_t pid; - int null; + int null, pipe_fd[2]; - PARA_INFO_LOG("daemonizing\n"); + if (parent_waits && pipe(pipe_fd) < 0) + goto err; + PARA_INFO_LOG("subsequent log messages go to %s\n", me->logfile_name? + me->logfile_name : "/dev/null"); pid = fork(); if (pid < 0) goto err; - if (pid) { + if (pid) { /* parent exits */ if (parent_waits) { - signal(SIGTERM, dummy_sighandler); - signal(SIGCHLD, dummy_sighandler); - pause(); + char c; + close(pipe_fd[1]); + exit(read(pipe_fd[0], &c, 1) <= 0? + EXIT_FAILURE : EXIT_SUCCESS); } - exit(EXIT_SUCCESS); /* parent exits */ + exit(EXIT_SUCCESS); } + if (parent_waits) + close(pipe_fd[0]); /* become session leader */ if (setsid() < 0) goto err; + me->old_cwd = getcwd(NULL, 0); if (chdir("/") < 0) goto err; - null = open("/dev/null", O_RDONLY); + null = open("/dev/null", O_RDWR); if (null < 0) goto err; if (dup2(null, STDIN_FILENO) < 0) @@ -189,7 +244,7 @@ void daemonize(bool parent_waits) if (dup2(null, STDERR_FILENO) < 0) goto err; close(null); - return; + return parent_waits? pipe_fd[1] : 0; err: PARA_EMERG_LOG("fatal: %s\n", strerror(errno)); exit(EXIT_FAILURE); @@ -214,25 +269,48 @@ void daemon_close_log(void) */ void daemon_open_log_or_die(void) { - daemon_close_log(); + FILE *new_log; + if (!me->logfile_name) return; - me->logfile = fopen(me->logfile_name, "a"); - if (!me->logfile) { + new_log = fopen(me->logfile_name, "a"); + if (!new_log) { PARA_EMERG_LOG("can not open %s: %s\n", me->logfile_name, strerror(errno)); exit(EXIT_FAILURE); } - setlinebuf(me->logfile); + daemon_close_log(); + me->logfile = new_log; + /* equivalent to setlinebuf(), but portable */ + setvbuf(me->logfile, NULL, _IOLBF, 0); } /** * Log the startup message containing the paraslash version. + * + * \param name The name of the executable. + * + * First the given \a name is prefixed with the string "para_". Next the git + * version is appended. The resulting string is logged with priority "INFO". */ -void daemon_log_welcome(const char *whoami) +void daemon_log_welcome(const char *name) { - PARA_INFO_LOG("welcome to %s " PACKAGE_VERSION " ("BUILD_DATE")\n", - whoami); + PARA_INFO_LOG("welcome to para_%s-" PACKAGE_VERSION " \n", name); +} + +/** + * Renice the calling process. + * + * \param prio The priority value to set. + * + * Errors are not considered fatal, but a warning message is logged if the + * underlying call to setpriority(2) fails. + */ +void daemon_set_priority(int prio) +{ + if (setpriority(PRIO_PROCESS, 0, prio) < 0) + PARA_WARNING_LOG("could not set priority to %d: %s\n", prio, + strerror(errno)); } /** @@ -330,8 +408,6 @@ time_t daemon_get_uptime(const struct timeval *current_time) * \param current_time See a \ref daemon_get_uptime(). * * \return A dynamically allocated string of the form "days:hours:minutes". - * - * \sa server_uptime. */ __malloc char *daemon_get_uptime_str(const struct timeval *current_time) { @@ -361,6 +437,8 @@ __printf_2_3 void daemon_log(int ll, const char* fmt,...) return; fp = me->logfile? me->logfile : stderr; + if (me->pre_log_hook) + me->pre_log_hook(); color = daemon_test_flag(DF_COLOR_LOG)? me->log_colors[ll] : NULL; if (color) fprintf(fp, "%s", color); @@ -393,4 +471,6 @@ __printf_2_3 void daemon_log(int ll, const char* fmt,...) va_end(argp); if (color) fprintf(fp, "%s", COLOR_RESET); + if (me->post_log_hook) + me->post_log_hook(); }