/* * Copyright (C) 1997-2009 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file daemon.c Some helpers for programs that detach from the console. */ #include "para.h" #include "daemon.h" #include #include /* getgrnam() */ #include #include "string.h" #include "color.h" /** The internal state of the daemon. */ struct daemon { /** See \ref daemon_flags. */ unsigned flags; /** Set by \ref daemon_set_logfile(). */ char *logfile_name; /** Current loglevel, see \ref daemon_set_loglevel(). */ int loglevel; /** Used by \ref server_uptime() and \ref uptime_str(). */ time_t startuptime; /** The file pointer if the logfile is open. */ FILE *logfile; /** Used by para_log() if \p DF_LOG_HOSTNAME is set. */ char *hostname; /** Used for colored log messages. */ char log_colors[NUM_LOGLEVELS][COLOR_MAXLEN]; }; 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) { int i; static const char *default_log_colors[NUM_LOGLEVELS] = { [LL_DEBUG] = "normal", [LL_INFO] = "normal", [LL_NOTICE] = "normal", [LL_WARNING] = "yellow", [LL_ERROR] = "red", [LL_CRIT] = "magenta bold", [LL_EMERG] = "red bold", }; for (i = 0; i < NUM_LOGLEVELS; i++) { int ret = color_parse(default_log_colors[i], me->log_colors[i]); assert(ret >= 0); } } /** * Set the color for one loglevel. * * \param arg The loglevel/color specifier. * * \a arg must be of the form "ll:[fg [bg]] [attr]". * * \return 1 On success, -1 on errors. */ int daemon_set_log_color(char const *arg) { char *p = strchr(arg, ':'); int ret, ll; if (!p) goto err; ret = get_loglevel_by_name(arg); if (ret < 0) goto err; ll = ret; p++; ret = color_parse(p, me->log_colors[ll]); if (ret < 0) goto err; return 1; err: PARA_ERROR_LOG("%s: color syntax error\n", arg); return -1; } /** * 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) { free(me->logfile_name); me->logfile_name = NULL; if (logfile_name) me->logfile_name = para_strdup(logfile_name); } /** * Suppress log messages with severity lower than the given loglevel. * * \param loglevel The smallest level that should be logged. */ void daemon_set_loglevel(char *loglevel) { int ret = get_loglevel_by_name(loglevel); assert(ret >= 0); me->loglevel = ret; } /** * Set one of the daemon config flags. * * \param flag The flag to set. * * \sa \ref daemon_flags. */ void daemon_set_flag(unsigned flag) { me->flags |= flag; } /** * Clear one of the daemon config flags. * * \param flag The flag to clear. * * \sa \ref daemon_flags. */ void daemon_clear_flag(unsigned flag) { me->flags &= ~flag; } static unsigned daemon_test_flag(unsigned flag) { return me->flags & flag; } /** * Do the usual stuff to become a daemon. * * Fork, become session leader, dup fd 0, 1, 2 to /dev/null. * * \sa fork(2), setsid(2), dup(2). */ void daemonize(void) { pid_t pid; int null; PARA_INFO_LOG("daemonizing\n"); pid = fork(); if (pid < 0) goto err; if (pid) exit(EXIT_SUCCESS); /* parent exits */ /* become session leader */ if (setsid() < 0) goto err; if (chdir("/") < 0) goto err; null = open("/dev/null", O_RDONLY); if (null < 0) goto err; if (dup2(null, STDIN_FILENO) < 0) goto err; if (dup2(null, STDOUT_FILENO) < 0) goto err; if (dup2(null, STDERR_FILENO) < 0) goto err; close(null); return; err: PARA_EMERG_LOG("fatal: %s\n", strerror(errno)); exit(EXIT_FAILURE); } /** * Close the log file of the daemon. */ void daemon_close_log(void) { if (!me->logfile) return; PARA_INFO_LOG("closing logfile\n"); fclose(me->logfile); me->logfile = NULL; } /** * fopen() the logfile in append mode. * * \return Either succeeds or exits. */ void daemon_open_log_or_die(void) { daemon_close_log(); if (!me->logfile_name) return; me->logfile = fopen(me->logfile_name, "a"); if (!me->logfile) { PARA_EMERG_LOG("can not open %s: %s\n", me->logfile_name, strerror(errno)); exit(EXIT_FAILURE); } setlinebuf(me->logfile); } /** * Log the startup message containing the paraslash version. */ void log_welcome(const char *whoami) { PARA_INFO_LOG("welcome to %s " PACKAGE_VERSION " ("BUILD_DATE")\n", whoami); } /** * Give up superuser privileges. * * \param username The user to switch to. * \param groupname The group to switch to. * * This function returns immediately if not invoked with EUID zero. Otherwise, * it tries to obtain the GID of \a groupname and the UID of \a username. On * success, effective and real GID/UID and the saved set-group-ID/set-user-ID * are all set accordingly. On errors, an appropriate message is logged and * exit() is called to terminate the process. * * \sa getpwnam(3), getuid(2), setuid(2), getgrnam(2), setgid(2) */ void drop_privileges_or_die(const char *username, const char *groupname) { struct passwd *p; char *tmp; if (geteuid()) return; if (groupname) { struct group *g = getgrnam(groupname); if (!g) { PARA_EMERG_LOG("failed to get group %s: %s\n", groupname, strerror(errno)); exit(EXIT_FAILURE); } if (setgid(g->gr_gid) < 0) { PARA_EMERG_LOG("failed to set group id %d: %s\n", (int)g->gr_gid, strerror(errno)); exit(EXIT_FAILURE); } } if (!username) { PARA_EMERG_LOG("root privileges, but no user option given\n"); exit(EXIT_FAILURE); } tmp = para_strdup(username); p = getpwnam(tmp); free(tmp); if (!p) { PARA_EMERG_LOG("%s: no such user\n", username); exit(EXIT_FAILURE); } PARA_INFO_LOG("dropping root privileges\n"); if (setuid(p->pw_uid) < 0) { PARA_EMERG_LOG("failed to set effective user ID (%s)", strerror(errno)); exit(EXIT_FAILURE); } PARA_DEBUG_LOG("uid: %d, euid: %d\n", (int)getuid(), (int)geteuid()); } /** * Set/get the server uptime. * * \param set_or_get Chose one of the two modes. * * This should be called at startup time with \a set_or_get equal to \p * UPTIME_SET which sets the uptime to zero. Subsequent calls with \a * set_or_get equal to \p UPTIME_GET return the uptime. * \return Zero if called with \a set_or_get equal to \p UPTIME_SET, the number * of seconds elapsed since the last reset otherwise. * * \sa time(2), difftime(3). */ time_t server_uptime(enum uptime set_or_get) { time_t now; double diff; if (set_or_get == UPTIME_SET) { time(&me->startuptime); return 0; } time(&now); diff = difftime(now, me->startuptime); return (time_t) diff; } /** * Construct string containing uptime. * * \return A dynamically allocated string of the form "days:hours:minutes". * * \sa server_uptime. */ __malloc char *uptime_str(void) { long t = server_uptime(UPTIME_GET); return make_message("%li:%02li:%02li", t / 86400, (t / 3600) % 24, (t / 60) % 60); } /** * The log function for para_server and para_audiod. * * \param ll The log level. * \param fmt The format string describing the log message. */ __printf_2_3 void para_log(int ll, const char* fmt,...) { va_list argp; FILE *fp; struct tm *tm; time_t t1; char *color; ll = PARA_MIN(ll, NUM_LOGLEVELS - 1); ll = PARA_MAX(ll, LL_DEBUG); if (ll < me->loglevel) return; fp = me->logfile? me->logfile : stderr; color = daemon_test_flag(DF_COLOR_LOG)? me->log_colors[ll] : NULL; if (color) fprintf(fp, "%s", color); if (daemon_test_flag(DF_LOG_TIME)) { /* print date and time */ char str[100]; time(&t1); tm = localtime(&t1); strftime(str, sizeof(str), "%b %d %H:%M:%S", tm); fprintf(fp, "%s ", str); } if (daemon_test_flag(DF_LOG_HOSTNAME)) { if (!me->hostname) me->hostname = para_hostname(); fprintf(fp, "%s ", me->hostname); } if (daemon_test_flag(DF_LOG_LL)) /* log loglevel */ fprintf(fp, "(%d) ", ll); if (daemon_test_flag(DF_LOG_PID)) { /* log pid */ pid_t mypid = getpid(); fprintf(fp, "(%d) ", (int)mypid); } va_start(argp, fmt); vfprintf(fp, fmt, argp); va_end(argp); if (color) fprintf(fp, "%s", COLOR_RESET); }