/* Copyright (C) 2008 Andreas Baumann This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "port/sys_internal.h" #include "port/stdio.h" /* for snprintf */ #include "port/string.h" /* for strdup */ #include "port/limits.h" /* for PATH_MAX */ #include "port/noreturn.h" /* for NORETURN */ #define DEFAULT_TEXT_DOMAIN "libwolf" #include "i18n/gettext.h" /* for i18n */ #include "errors.h" #include "log/log.h" #include "log/messages.h" #include "daemon/daemon.h" #include "daemon/daemon_internal.h" #include "daemon/signals.h" #include "daemon/pidfile.h" #include /* for pid_t, ssize_t, off_t */ #include /* for getppid, geteuid, fork, chdir, pipe, sysconf, read, write */ #include /* for malloc, free */ #include /* for errno */ #include /* for umask */ #include /* for O_RDWR */ #include /* for getgrnam, setgid */ #include /* for getpwnam, setuid */ #include /* for FD_XX and select */ static int exit_code_pipe[2] = { -1, -1 }; int daemon_parent_pipe[2] = { -1, -1 }; int daemon_signal_pipe[2] = { -1, -1 }; static void notify_parent_handler( int sig ) { if( daemon_parent_pipe[1] != -1 ) (void)write( daemon_parent_pipe[1], &sig, sizeof( int ) ); } WOLF_SIGNALS_INSTALL_FUNC( notify_parent, notify_parent_handler ) static void notify_handler( int sig ) { if( daemon_signal_pipe[1] != -1 ) (void)write( daemon_signal_pipe[1], &sig, sizeof( int ) ); } WOLF_SIGNALS_INSTALL_FUNC( notify, notify_handler ) typedef enum { TERMINATE_OK, TERMINATE_ERROR, TERMINATE_EXIT_CODE, TERMINATE_PARENT } daemon_state_t; struct wolf_daemon_t { wolf_daemon_params_t params; /**< the parameters for creation */ daemon_state_t state; /**< last status of the daemon */ wolf_error_t error; /**< error of last start */ struct pidfile_t pidfile; /**< pid/lock file of the daemon */ struct group *groupent; /**< group entry */ struct passwd *userent; /**< password entry */ }; wolf_daemon_p wolf_daemon_new( wolf_daemon_params_t params, wolf_error_t *error ) { wolf_daemon_p d; d = (struct wolf_daemon_t *)malloc( sizeof ( struct wolf_daemon_t ) ); if( d == NULL ) { *error = WOLF_ERR_OUT_OF_MEMORY; return NULL; } d->state = TERMINATE_ERROR; d->error = WOLF_ERR_INTERNAL; d->params = params; if( params.daemon_name == NULL ) { *error = WOLF_ERR_INVALID_VALUE; return NULL; } if( params.pid_filename == NULL ) { pidfile_set_from_daemon_name( &d->pidfile, params.daemon_name ); } else { pidfile_set_from_filename( &d->pidfile, params.pid_filename ); } *error = WOLF_OK; return d; } void wolf_daemon_free( wolf_daemon_p d ) { free( d ); d = NULL; } static wolf_error_t close_fd( int fd ) { if( close( fd ) < 0 ) { switch( errno ) { case EBADF: /* skip */ break; default: wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_CLOSE_FD, _( "Error while closing file descriptor %d: %s" ), fd, strerror( errno ) ); return WOLF_ERR_INTERNAL; } } return WOLF_OK; } static wolf_error_t daemon_close_standard_fds( void ) { wolf_error_t error; if( ( error = close_fd( STDIN_FILENO ) ) != WOLF_OK ) return error; if( ( error = close_fd( STDOUT_FILENO ) ) != WOLF_OK ) return error; if( ( error = close_fd( STDERR_FILENO ) ) != WOLF_OK ) return error; return WOLF_OK; } static wolf_error_t daemon_close_all_fds( void ) { long nof_files; int i; wolf_error_t error; nof_files = sysconf( _SC_OPEN_MAX ); if( nof_files < 0 ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_RETRIEVING_MAXNOFFDS_FAILED, _( "Unable to retrieve maximal number of files: %s" ), strerror( errno ) ); return WOLF_ERR_INTERNAL; } wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CLOSING_ALL_FDS, _( "Closing all filedescriptors up to %ld" ), nof_files ); for( i = 0; i < nof_files; i++ ) { if( ( i == STDIN_FILENO ) || ( i == STDOUT_FILENO ) || ( i == STDERR_FILENO ) || ( i == daemon_signal_pipe[0] ) || ( i == daemon_signal_pipe[1] ) || ( i == daemon_parent_pipe[0] ) || ( i == daemon_parent_pipe[1] ) || ( i == exit_code_pipe[0] ) || ( i == exit_code_pipe[1] ) ) { continue; } if( ( error = close_fd( i ) ) != WOLF_OK ) return error; } return WOLF_OK; } static wolf_error_t open_null_fd( int must_fd, int flags ) { int fd; fd = open( "/dev/null", flags ); if( fd < 0 ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_OPEN_FD_AS_DEV_NULL, _( "Unable to open fd %d as /dev/null: %s" ), must_fd, strerror( errno ) ); return WOLF_ERR_INTERNAL; } if( fd != must_fd ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_FD_NUMBERS_WRONG, _( "Something is wrong with the file descriptors (expecting %d, got %d)!" ), must_fd, fd ); return WOLF_ERR_PROGRAMMING; } return WOLF_OK; } static void atomar_write( int fd, void *data, size_t data_len ) { ssize_t res; atomar_write_again: res = write( fd, data, data_len ); if( res < 0 ) { if( errno == EINTR ) goto atomar_write_again; wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_PIPE_ATOMIC_WRITE_FAILED, _( "Error in atomar write to fd %d: %s" ), fd, strerror( errno ) ); } else if( (size_t)res != data_len ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_PIPE_ATOMIC_WRITE_WRONG_OCTETS, _( "Unexpected number of octets %zd in atomar write to fd %d (expected %zd)" ), res, fd, data_len ); } } static void atomar_read( int fd, void *data, size_t data_len ) { ssize_t res; atomar_read_again: res = read( fd, data, data_len ); if( res < 0 ) { if( errno == EINTR ) goto atomar_read_again; wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_PIPE_ATOMIC_READ_FAILED, _( "Error in atmoar read from fd %d: %s" ), fd, strerror( errno ) ); } else if( (size_t)res != data_len ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_PIPE_ATOMIC_READ_WRONG_OCTETS, _( "Unexpected number of octets %zd in atomar read from fd %d (expected %zd)" ), res, fd, data_len ); } } static wolf_error_t install_signal_handlers_parent( void ) { wolf_error_t error; /* signals to ignore */ if( ( error = wolf_signal_install_ignore( SIGPIPE, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, /* later: throthling */ SIGXFSZ, /* later: close connections, reduce channels */ SIGVTALRM, SIGPROF, #if defined( SIGWINCH ) SIGWINCH, #endif #if defined( SIGIO ) && defined( SIGPOLL ) #if SIGIO == SIGPOLL SIGIO, #else #if defined( SIGPOLL ) SIGPOLL, #endif #if defined( SIGIO ) SIGIO, #endif #endif /* SIGIO == SIGPOLL */ #else #if defined( SIGIO ) SIGIO, #endif #if defined( SIGPOLL ) SIGPOLL, #endif #endif /* defined( SIGIO ) && defined( SIGPOLL ) */ #if defined( SIGPWR ) SIGPWR, #endif SIGTERM, SIGINT, SIGHUP, SIGUSR1, SIGUSR2, 0 ) ) != WOLF_OK ) return error; /* signals for empty handlers */ if( ( error = wolf_signal_install_empty( #if defined( SIGCHLD ) && defined( SIGCLD ) #if SIGCLD == SIGCHLD SIGCHLD, #else #if defined( SIGCHLD ) SIGCHLD, #endif #if defined( SIGCLD ) SIGCLD, #endif #endif /* SIGCLD == SIGCHLD */ #else #if defined( SIGCHLD ) SIGCHLD, #endif #if defined( SIGCLD ) SIGCLD, #endif #endif /* defined( SIGCHLD ) && defined( SIGCLD ) */ 0 ) ) != WOLF_OK ) return error; /* fatal signal handlers, make sure the parent can read the * signal and clean up */ if( ( error = wolf_signal_install_notify_parent( SIGILL, SIGABRT, #if defined( SIGIOT ) #if SIGIOT != SIGABRT SIGIOT, #endif #endif SIGBUS, SIGFPE, SIGSEGV, SIGVTALRM, #if defined( SIGSTKFLT ) SIGSTKFLT, #endif #if defined( SIGSYS ) SIGSYS, #endif #if defined( SIGLOST ) SIGLOST, #endif SIGALRM, /* we don't use it, but maybe a plugin */ 0 ) ) != WOLF_OK ) return error; return WOLF_OK; } wolf_error_t wolf_daemon_start( wolf_daemon_p d ) { pid_t pid; mode_t mode; wolf_error_t error; int exit_code; /* Our parent is already the init process (which has pid 1 by * convention), we are already a daemon. This is a programming * error.. */ if( getppid( ) == 1 ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_ALREADY_RUNNING, _( "Already running as daemon!" ) ); return( d->error = WOLF_ERR_PROGRAMMING ); } /* Are we starting as root? If not, bail out, we need root * permissions because we have to listen to ports below 1024 * or we must open logfiles/pidfiles with proper permissions. * Also we must change to the proper running user/group and * we must be root (uid 0 by convention) for that. */ if( geteuid( ) != 0 ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_START_AS_NON_ROOT, _( "Unable to start daemon as not root user!" ) ); return( d->error = WOLF_ERR_INVALID_STATE ); } /* We fork so SIGCHLD signals get to the parent and grandfather. * We don't want that and we are installing empty signal handlers. */ #if defined( SIGCHLD ) && defined( SIGCLD ) #if SIGCLD == SIGCHLD wolf_signal_install_empty( SIGCLD, 0 ); #else #if defined( SIGCHLD ) wolf_signal_install_empty( SIGCHLD, 0 ); #endif #if defined( SIGCLD ) wolf_signal_install_empty( SIGCLD, 0 ); #endif #endif /* SIGCLD == SIGCHLD */ #else #if defined( SIGCHLD ) wolf_signal_install_empty( SIGCHLD, 0 ); #endif #if defined( SIGCLD ) wolf_signal_install_empty( SIGCLD, 0 ); #endif #endif /* defined( SIGCHLD ) && defined( SIGCLD ) */ /* first pipe communicates from parent of daemon and daemon * itself back to the grand-parent (exit codes). */ if( pipe( exit_code_pipe ) < 0 ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_CREATE_EXIT_CODE_PIPE, _( "Unable to create exit code pipe: %s" ), strerror( errno ) ); return ( d->error = WOLF_ERR_INTERNAL ); } wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_EXIT_CODE_PIPE_CREATED, _( "Created exit code pipe (%d,%d)" ), exit_code_pipe[0], exit_code_pipe[1] ); /* first fork: make sure we are no longer process group leader. * So we can get our own process group leader by calling setsid */ switch( ( pid = fork( ) ) ) { case -1: /* error */ wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_FIRST_FORK_FAILED, _( "Unable to fork the first time: %s" ), strerror( errno ) ); return ( d->error = WOLF_ERR_INTERNAL ); case 0: /* the child becomes the daemon */ wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_FIRST_FORK_REACHED, _( "First fork reached" ) ); break; default: /* the parent waits till it gets feedback from * the child (error pipe) */ /* TODO: wait for some time for correct exit * code from the error pipe */ wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_PARENT_AFTER_FIRST_FORK, _( "Parent after first fork: child is %d" ), pid ); d->state = TERMINATE_EXIT_CODE; return( d->error = WOLF_ERR_PROGRAMMING ); /* intensional error code here, so we get into wolf_daemon_exit! */ } /* second pipe communicates from the daemon back to its parent * (for termination and cleanup which can only be done as root) */ if( pipe( daemon_parent_pipe ) < 0 ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_CREATE_PARENT_PIPE, _( "Unable to create parent pipe: %s" ), strerror( errno ) ); return ( d->error = WOLF_ERR_INTERNAL ); } wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_PARENT_PIPE_CREATED, _( "Created parent pipe (%d,%d)" ), daemon_parent_pipe[0], daemon_parent_pipe[1] ); /* Put the first child in it's own process group and finally detach it * from its controlling terminal. This ensure we don't get funny * signals which could kill the daemon. */ if( setsid( ) < 0 ) { /* should actually never fail */ wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_SET_NEW_PROCESS_GROUP_PARENT, _( "Starting new process group session for the parent of the daemon failed: %s" ), strerror( errno ) ); return WOLF_ERR_INTERNAL; } /* We fork so SIGCHLD signals get to the parent and grandfather. * We don't want that and we are installing empty signal handlers. */ #if defined( SIGCHLD ) && defined( SIGCLD ) #if SIGCLD == SIGCHLD wolf_signal_install_empty( SIGCLD, 0 ); #else #if defined( SIGCHLD ) wolf_signal_install_empty( SIGCHLD, 0 ); #endif #if defined( SIGCLD ) wolf_signal_install_empty( SIGCLD, 0 ); #endif #endif /* SIGCLD == SIGCHLD */ #else #if defined( SIGCHLD ) wolf_signal_install_empty( SIGCHLD, 0 ); #endif #if defined( SIGCLD ) wolf_signal_install_empty( SIGCLD, 0 ); #endif #endif /* defined( SIGCHLD ) && defined( SIGCLD ) */ /* Now that the parent is process group leader, fork again. This * way the final child can not inherit a controlling terminal */ switch( ( pid = fork( ) ) ) { case -1: /* error */ wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_SECOND_FORK_FAILED, _( "Unable to fork the second time: %s" ), strerror( errno ) ); return ( d->error = WOLF_ERR_INTERNAL ); case 0: /* the child becomes the daemon */ wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_SECOND_FORK_REACHED, _( "Second fork reached" ) ); break; default: (void)install_signal_handlers_parent( ); wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_PARENT_AFTER_SECOND_FORK, _( "Parent after second fork: child (and daemon) is %d" ), pid ); d->state = TERMINATE_PARENT; return( d->error = WOLF_ERR_PROGRAMMING ); /* intensional error code here, so we get into wolf_daemon_exit! */ } /* Now install signal handlers */ if( ( error = wolf_daemon_signals_initialize( ) ) != WOLF_OK ) return ( d->error = error ); if( ( error = wolf_daemon_signals_install_handlers( ) ) != WOLF_OK ) return ( d->error = error ); /* Put the daemon in it's own process group and finally detach it * from its controlling terminal. This ensure we don't get funny * signals which could kill the daemon. */ if( setsid( ) < 0 ) { /* should actually never fail */ wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_SET_NEW_PROCESS_GROUP, _( "Starting new process group for daemon session failed: %s" ), strerror( errno ) ); return WOLF_ERR_INTERNAL; } /* Change to the root directory for several reasons: * - all but the root directory we may want to unmount without * stopping the daemon * - we don't have write permissions there so a core dump * would not result in DoSA * - non-priviledged users can't do anything in the root * directory (especially they can't write files there) */ if( chdir( "/" ) < 0 ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_CHANGE_ROOT_DIR, _( "Changing to root diretory failed: %s" ), strerror( errno ) ); return WOLF_ERR_INTERNAL; } wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CHANGED_ROOT_DIR, _( "Changed to root directory /" ) ); /* Change the umask to 0133 temporarily so we don't have to * chmod the logfiles, pidfiles later. * * Create pid files with permissions 0644 (rw-r--r--) */ mode = umask( 0133 ); wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_SWITCHED_UMASK, _( "Switched umask from %04o to %04o" ), mode, 0133 ); /* check if another daemon is already running, if yes, bail out */ if( is_daemon_running( &d->pidfile, &pid, &error ) || ( error != WOLF_OK ) ) { if( error == WOLF_OK ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_DAEMON_ALREADY_RUNNING_WITH_PID, _( "Another daemon is already running with pid '%d', can't start!" ), pid ); } return WOLF_ERR_INTERNAL; } /* we loose logfile and syslog file descriptors anyway, so close * them here */ wolf_log_closelogtosyslog( ); wolf_log_closelogtofile( ); wolf_log_closelogtostderr( ); /* close all filedescriptors */ if( daemon_close_all_fds( ) != WOLF_OK ) return WOLF_ERR_INTERNAL; /* reopen the logs to the logfile and syslog (not stderr) */ wolf_log_reopenlogtosyslog( ); wolf_log_reopenlogtofile( ); /* create and lock the pidfile now, write pid of daemon into it */ if( pidfile_create( &d->pidfile ) != WOLF_OK ) { return WOLF_ERR_INTERNAL; } /* Install the final permissions for new files. The reason is * simple: We don't want to create files in a defective part * of the daemon, especially third party code. We don't know * what kind of plugins we gonna integrate.. * * Make sure that no files can be written except the ones belonging * to us (because now we are no longer root) and that no files can * be created with executable permission (code injection!) * * New files always get the permission 640 (rw-r-----) */ mode = umask( 0137 ); wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_SWITCHED_UMASK_FINAL, _( "Switched umask from %04o to %04o" ), mode, 0137 ); /* drop permissions to a non-priviledged user now */ if( ( d->params.group_name != NULL ) && ( d->params.user_name != NULL ) ) { errno = 0; d->groupent = getgrnam( d->params.group_name ); if( d->groupent == NULL ) { if( errno == 0 ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_GROUP_NOT_FOUND, _( "No group '%s' found" ), d->params.group_name ); } else { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_GETGRNAM_FAILED, _( "Unable to retrieve group information for group '%s': %s" ), d->params.group_name, strerror( errno ) ); } return WOLF_ERR_INTERNAL; } errno = 0; d->userent = getpwnam( d->params.user_name ); if( d->userent == NULL ) { if( errno == 0 ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_USER_NOT_FOUND, _( "No user '%s' found" ), d->params.user_name ); } else { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_GETPWNAM_FAILED, _( "Unable to retrieve user information for user '%s': %s" ), d->params.user_name, strerror( errno ) ); } return WOLF_ERR_INTERNAL; } if( setgid( d->userent->pw_gid ) < 0 ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_SETTING_UNPRIVILEGED_GROUP_FAILED, _( "Setting unprivileged group failed: %s" ), strerror( errno ) ); return WOLF_ERR_INTERNAL; } if( setuid( d->userent->pw_uid ) < 0 ) { wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_SETTING_UNPRIVILEGED_USER_FAILED, _( "Setting unprivileged user failed: %s" ), strerror( errno ) ); return WOLF_ERR_INTERNAL; } /* TODO: setsid and setting all groups of the user */ /* TODO: also check the permissions of the home dir * of the unpriviledged user */ wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_SWITCHED_USER, _( "Switched to user '%s' (%d) and group '%s' (%d)" ), d->params.user_name, d->userent->pw_uid, d->params.group_name, d->userent->pw_gid ); } /* assign stdin/stdout/stderr again to something harmless * (/dev/null). Do this as late as possible, so somebody * starting the daemon by hand still gets error messages * on stderr. */ if( ( error = daemon_close_standard_fds( ) ) != WOLF_OK ) return error; if( ( error = open_null_fd( STDIN_FILENO, O_RDONLY ) ) != WOLF_OK ) return error; if( ( error = open_null_fd( STDOUT_FILENO, O_WRONLY ) ) != WOLF_OK ) return error; if( ( error = open_null_fd( STDERR_FILENO, O_WRONLY ) ) != WOLF_OK ) return error; /* signal the grand-parent process, that he can return 0 to * the calling shell/script */ exit_code = EXIT_SUCCESS; atomar_write( exit_code_pipe[1], &exit_code, sizeof( int ) ); d->state = TERMINATE_OK; return ( d->error = WOLF_OK ); } NORETURN void wolf_daemon_exit( wolf_daemon_p d ) { int exit_code; wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_EXIT_CALLED, _( "daemon_exit called int state %d (error %d)" ), d->state, d->error ); switch( d->state ) { case TERMINATE_EXIT_CODE: /* wait here for exit code in exit_code_pipe * so we can return the correct exit code to * the calling script or shell */ wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_WAITING_FOR_EXIT_CODE_ON_PIPE, _( "Waiting on exit_code pipe for exit code" ) ); atomar_read( exit_code_pipe[0], &exit_code, sizeof( int ) ); wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_TERMINATING_GRAND_PARENT, _( "Terminating grand-parent of daemon with code %d (PID: %lu)" ), exit_code, getpid( ) ); wolf_daemon_signals_terminate( ); /* Do not close file descriptors of the child! * We are the father or grand-father process */ _exit( exit_code ); case TERMINATE_PARENT: /* TODO: handle houskeeping here which needs * root rights (log rotation?) */ /* wait for termination signal */ wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_WAITING_FOR_TERMINATION, _( "Waiting on parent pipe for termination signal" ) ); atomar_read( daemon_parent_pipe[0], &d->pidfile.locked, sizeof( bool ) ); atomar_read( daemon_parent_pipe[0], &d->pidfile.running, sizeof( bool ) ); atomar_read( daemon_parent_pipe[0], &d->pidfile.fd, sizeof( int ) ); wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_PARENT_GOT_TERMINATION, _( "Parent got termination (pidfile lock: %d, run: %d, fd: %d).. cleaning up now (PID: %lu)" ), d->pidfile.locked, d->pidfile.running, d->pidfile.fd, getpid( ) ); /* we need root permissions for that! */ (void)pidfile_remove( &d->pidfile ); wolf_daemon_signals_terminate( ); wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_TERMINATING_PARENT, _( "Terminating parent of daemon (PID %lu)" ), getpid( ) ); /* exit code is irrelevant here.. */ exit( EXIT_SUCCESS ); case TERMINATE_OK: /* close and unlock the pidfile here */ (void)pidfile_release( &d->pidfile ); /* This is the daemon terminating, signal the * parent that we are terminating */ atomar_write( daemon_parent_pipe[1], &d->pidfile.locked, sizeof( bool ) ); atomar_write( daemon_parent_pipe[1], &d->pidfile.running, sizeof( bool ) ); atomar_write( daemon_parent_pipe[1], &d->pidfile.fd, sizeof( int ) ); wolf_daemon_signals_terminate( ); wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_TERMINATING_DAEMON_OK, _( "Terminating daemon (PID: %lu)" ), getpid( ) ); /* exit code is irrelevant here */ _exit( EXIT_SUCCESS ); case TERMINATE_ERROR: /* no exit_code_pipe exists yet, we are before the * first fork, so terminate with proper exit code */ if( exit_code_pipe[1] == -1 ) { exit( EXIT_FAILURE ); } /* This is an error case, communicate exit code back * to grand-parent */ exit_code = EXIT_FAILURE; atomar_write( exit_code_pipe[1], &exit_code, sizeof( int ) ); /* also terminate the parent of the daemon */ atomar_write( daemon_parent_pipe[1], &d->pidfile.locked, sizeof( bool ) ); atomar_write( daemon_parent_pipe[1], &d->pidfile.running, sizeof( bool ) ); atomar_write( daemon_parent_pipe[1], &d->pidfile.fd, sizeof( int ) ); wolf_daemon_signals_terminate( ); wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_TERMINATING_DAEMON_ERROR, _( "Terminating daemon (PID: %lu) with error" ), getpid( ) ); /* exit code is irrelevant here */ _exit( EXIT_SUCCESS ); } /* silence up some versions of the GCC compiler ("noreturn function returns"), * because they errornously tread _exit as a return, which is clearly wrong * (experienced with gcc 3.3.5 on OpenBSD 4.3) */ exit( EXIT_SUCCESS ); } wolf_error_t wolf_daemon_signals_initialize( void ) { int res; char errbuf[1024]; if( daemon_signal_pipe[0] != -1 || daemon_signal_pipe[1] != -1 ) { return WOLF_ERR_INVALID_STATE; } res = pipe( daemon_signal_pipe ); if( res < 0 ) { (void)strerror_r( errno, errbuf, 1024 ); wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_CREATE_SIGNAL_PIPE, _( "Unable to create signal pipe: %s" ), errbuf ); return WOLF_ERR_INTERNAL; } wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_SIGNAL_PIPE_CREATED, _( "Created signal pipe (%d,%d)" ), daemon_signal_pipe[0], daemon_signal_pipe[1] ); return WOLF_OK; } void wolf_daemon_signals_terminate( void ) { (void)close( daemon_signal_pipe[0] ); (void)close( daemon_signal_pipe[1] ); } int wolf_daemon_signals_suspend( int timeout, wolf_error_t *error ) { struct timeval tv; fd_set fds; ssize_t res; int sig; char errbuf[1024]; if( daemon_signal_pipe[0] == -1 ) { *error = WOLF_ERR_INVALID_STATE; return -1; } tv.tv_sec = timeout; tv.tv_usec = 0; FD_ZERO( &fds ); FD_SET( daemon_signal_pipe[0], &fds ); signal_select_again: res = select( daemon_signal_pipe[0] + 1, &fds, 0, 0, &tv ); switch( res ) { case 1: /* ready on pipe */ break; case 0: /* timeout */ *error = WOLF_ERR_TIMEOUT; return 0; case -1: /* error */ if( errno == EINTR ) goto signal_select_again; /* gdb no-brainer when pressing Ctrl-C (at least Linux) */ if( errno == 514 ) goto signal_select_again; (void)strerror_r( errno, errbuf, 1024 ); wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_SELECT_FAILED_WAITING_FOR_SIGNAL, _( "Error in select when waiting for signal from pipe: %s" ), errbuf ); *error = WOLF_ERR_INTERNAL; return -1; } res = read( daemon_signal_pipe[0], &sig, sizeof( int ) ); switch( res ) { case sizeof( int ): /* got a signal */ break; case -1: /* error */ (void)strerror_r( errno, errbuf, 1024 ); wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_READ_SIGNAL_FROM_PIPE, _( "Error while reading a signal from the pipe: %s" ), errbuf ); *error = WOLF_ERR_INTERNAL; return -1; default: /* unexpected result on atomic read */ wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_NON_ATOMIC_READ_ON_SIGNAL_PIPE, _( "Unexpected error in read: result is %d" ), res ); *error = WOLF_ERR_PROGRAMMING; return -1; } *error = WOLF_OK; return sig; } wolf_error_t wolf_daemon_signals_install_handlers( void ) { wolf_error_t error; /* signals to ignore */ if( ( error = wolf_signal_install_ignore( SIGPIPE, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, /* later: throthling */ SIGXFSZ, /* later: close connections, reduce channels */ SIGVTALRM, SIGPROF, #if defined( SIGWINCH ) SIGWINCH, #endif #if defined( SIGIO ) && defined( SIGPOLL ) #if SIGIO == SIGPOLL SIGIO, #else #if defined( SIGPOLL ) SIGPOLL, #endif #if defined( SIGIO ) SIGIO, #endif #endif /* SIGIO == SIGPOLL */ #else #if defined( SIGIO ) SIGIO, #endif #if defined( SIGPOLL ) SIGPOLL, #endif #endif /* defined( SIGIO ) && defined( SIGPOLL ) */ #if defined( SIGPWR ) SIGPWR, #endif 0 ) ) != WOLF_OK ) return error; /* signals for empty handlers */ if( ( error = wolf_signal_install_empty( #if defined( SIGCHLD ) && defined( SIGCLD ) #if SIGCLD == SIGCHLD SIGCHLD, #else #if defined( SIGCHLD ) SIGCHLD, #endif #if defined( SIGCLD ) SIGCLD, #endif #endif /* SIGCLD == SIGCHLD */ #else #if defined( SIGCHLD ) SIGCHLD, #endif #if defined( SIGCLD ) SIGCLD, #endif #endif /* defined( SIGCHLD ) && defined( SIGCLD ) */ 0 ) ) != WOLF_OK ) return error; /* fatal signal handlers, log the exception and continue with * default behaviour of the system */ if( ( error = wolf_signal_install_fatal( SIGILL, SIGABRT, #if defined( SIGIOT ) #if SIGIOT != SIGABRT SIGIOT, #endif #endif SIGBUS, SIGFPE, SIGSEGV, SIGVTALRM, #if defined( SIGSTKFLT ) SIGSTKFLT, #endif #if defined( SIGSYS ) SIGSYS, #endif #if defined( SIGLOST ) SIGLOST, #endif SIGALRM, /* we don't use it, but maybe a plugin */ 0 ) ) != WOLF_OK ) return error; /* notify the following signals to the main loop */ if( ( error = wolf_signal_install_notify( SIGTERM, SIGINT, SIGHUP, SIGUSR1, SIGUSR2, 0 ) ) != WOLF_OK ) return error; return WOLF_OK; }