You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kamailio/daemonize.c

650 lines
17 KiB

/*
* $Id$
*
* Copyright (C) 2001-2003 FhG Fokus
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
*
* History:
* --------
* 2004-02-20 removed from ser main.c into its own file (andrei)
* 2004-03-04 moved setuid/setgid in do_suid() (andrei)
* 2004-03-25 added increase_open_fds & set_core_dump (andrei)
* 2004-05-03 applied pgid patch from janakj
* 2007-06-07 added mlock_pages (no swap) support (andrei)
* added set_rt_prio() (andrei)
*/
/*!
* \file
* \brief SIP-router core ::
* \ingroup core
* Module: \ref core
*/
#include <sys/types.h>
#define _XOPEN_SOURCE /*!< needed on linux for the getpgid prototype, but
openbsd 3.2 won't include common types (uint a.s.o)
if defined before including sys/types.h */
#define _XOPEN_SOURCE_EXTENDED /*!< same as \ref _XOPEN_SOURCE */
#define __USE_XOPEN_EXTENDED /*!< same as \ref _XOPEN_SOURCE, overrides features.h */
#define __EXTENSIONS__ /*!< needed on solaris: if XOPEN_SOURCE is defined
struct timeval defintion from <sys/time.h> won't
be included => workarround define _EXTENSIONS_
-andrei */
#include <signal.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h> /* setrlimit */
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#ifdef __OS_linux
#include <sys/prctl.h>
#endif
#ifdef HAVE_SCHED_SETSCHEDULER
#include <sched.h>
#endif
#ifdef _POSIX_MEMLOCK
#define HAVE_MLOCKALL
#include <sys/mman.h>
#endif
#include "daemonize.h"
#include "globals.h"
#include "dprint.h"
#include "signals.h"
#include "cfg/cfg.h"
#define MAX_FD 32 /* maximum number of inherited open file descriptors,
(normally it shouldn't be bigger than 3) */
/** temporary pipe FDs for sending exit status back to the ancestor process.
* This pipe is used to send the desired exit status to the initial process,
* that waits for it in the foreground. This way late errors preventing
* startup (e.g. during modules child inits or TCP late init) can still be
* reported back.
*/
static int daemon_status_fd[2];
/** init daemon status reporting.
* Must be called before any other daemon_status function has a chance to
* run.
*/
void daemon_status_init()
{
daemon_status_fd[0] = -1;
daemon_status_fd[1] = -1;
}
/** pre-daemonize init for daemon status reporting.
* Must be called before forking.
* Typically the parent process will call daemon_status_wait() while
* one of the children will call daemon_status_send() at some point.
*
* @return 0 on success, -1 on error (and sets errno).
*/
int daemon_status_pre_daemonize()
{
int ret;
retry:
ret = pipe(daemon_status_fd);
if (ret < 0 && errno == EINTR)
goto retry;
return ret;
}
/** wait for an exit status to be send by daemon_status_send().
* @param status - filled with the sent status (a char).
* @return 0 on success, -1 on error (e.g. process died before sending
* status, not intialized a.s.o.).
* Side-effects: it will close the write side of the pipe
* (must not be used from the same process as the daemon_status_send()).
* Note: if init is not complete (only init, but no pre-daemonize)
* it will return success always and status 0.
*/
int daemon_status_wait(char* status)
{
int ret;
/* close the output side of the pipe */
if (daemon_status_fd[1] != -1) {
close(daemon_status_fd[1]);
daemon_status_fd[1] = -1;
}
if (daemon_status_fd[0] == -1) {
*status = 0;
return -1;
}
retry:
ret = read(daemon_status_fd[0], status, 1);
if (ret < 0 && errno == EINTR)
goto retry;
return (ret ==1 ) ? 0 : -1;
}
/** send 'status' to a waiting process running daemon_status_wait().
* @param status - status byte
* @return 0 on success, -1 on error.
* Note: if init is not complete (only init, but no pre-daemonize)
* it will return success always.
*/
int daemon_status_send(char status)
{
int ret;
if (daemon_status_fd[1] == -1)
return 0;
retry:
ret = write(daemon_status_fd[1], &status, 1);
if (ret < 0 && errno == EINTR)
goto retry;
return (ret ==1 ) ? 0 : -1;
}
/** cleanup functions for new processes.
* Should be called after fork(), for each new process that _does_ _not_
* use daemon_status_send() or daemon_status_wait().
*/
void daemon_status_on_fork_cleanup()
{
if (daemon_status_fd[0] != -1) {
close(daemon_status_fd[0]);
daemon_status_fd[0] = -1;
}
if (daemon_status_fd[1] != -1) {
close(daemon_status_fd[1]);
daemon_status_fd[1] = -1;
}
}
/** cleanup functions for processes that don't intead to wait.
* Should be called after fork(), for each new process that doesn't
* use daemon_status_wait().
*/
void daemon_status_no_wait()
{
if (daemon_status_fd[0] != -1) {
close(daemon_status_fd[0]);
daemon_status_fd[0] = -1;
}
}
/**
* enable dumpable flag for core dumping after setuid() & friends
* @return 0 when no critical error occured, -1 on such error
*/
int enable_dumpable(void)
{
#ifdef __OS_linux
struct rlimit lim;
/* re-enable core dumping on linux after setuid() & friends */
if(disable_core_dump==0) {
LM_DBG("trying enable core dumping...\n");
if(prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)<=0) {
LM_DBG("core dumping is disabled now...\n");
if(prctl(PR_SET_DUMPABLE, 1, 0, 0, 0)<0) {
LM_WARN("cannot re-enable core dumping!\n");
} else {
LM_DBG("core dumping has just been enabled...\n");
if (getrlimit(RLIMIT_CORE, &lim)<0){
LOG(L_CRIT, "cannot get the maximum core size: %s\n",
strerror(errno));
return -1;
} else {
DBG("current core file limit: %lu (max: %lu)\n",
(unsigned long)lim.rlim_cur, (unsigned long)lim.rlim_max);
}
}
} else {
LM_DBG("core dumping is enabled now (%d)...\n",
prctl(PR_GET_DUMPABLE, 0, 0, 0, 0));
}
}
#endif
return 0;
}
/** daemon init.
*@param name - daemon name used for logging (used when opening syslog).
*@param status_wait - if 1 the original process will wait until it gets
* an exit code send using daemon_status_send().
*@return 0 in the child process (in case of daemonize mode),
* -1 on error.
* The original process that called daemonize() will be terminated if
* dont_daemonize == 0. The exit code depends on status_wait. If status_wait
* is non-zero, the original process will wait for a status code, that
* must be sent with daemon_status_send() (daemon_status_send() must be
* called or the original process will remain waiting until all the children
* close()). If status_wait is 0, the original process will exit immediately
* with exit(0).
* Global variables/config params used:
* dont_daemonize
* chroot_dir
* working_dir
* pid_file - if set the pid will be written here (ascii).
* pgid_file - if set, the pgid will be written here (ascii).
* log_stderr - if not set syslog will be opened (openlog(name,...))
*
*
* Side-effects:
* sets own_pgid after becoming session leader (own process group).
*/
int daemonize(char* name, int status_wait)
{
FILE *pid_stream;
pid_t pid;
int r, p;
char pipe_status;
p=-1;
/* flush std file descriptors to avoid flushes after fork
* (same message appearing multiple times)
* and switch to unbuffered
*/
setbuf(stdout, 0);
setbuf(stderr, 0);
if (chroot_dir&&(chroot(chroot_dir)<0)){
LOG(L_CRIT, "Cannot chroot to %s: %s\n", chroot_dir, strerror(errno));
goto error;
}
if (chdir(working_dir)<0){
LOG(L_CRIT,"cannot chdir to %s: %s\n", working_dir, strerror(errno));
goto error;
}
if (!dont_daemonize) {
if (status_wait) {
if (daemon_status_pre_daemonize() < 0)
goto error;
}
/* fork to become!= group leader*/
if ((pid=fork())<0){
LOG(L_CRIT, "Cannot fork:%s\n", strerror(errno));
goto error;
}else if (pid!=0){
if (status_wait) {
if (daemon_status_wait(&pipe_status) == 0)
exit((int)pipe_status);
else{
LOG(L_ERR, "Main process exited before writing to pipe\n");
exit(-1);
}
}
exit(0);
}
if (status_wait)
daemon_status_no_wait(); /* clean unused read fd */
/* become session leader to drop the ctrl. terminal */
if (setsid()<0){
LOG(L_WARN, "setsid failed: %s\n",strerror(errno));
}else{
own_pgid=1;/* we have our own process group */
}
/* fork again to drop group leadership */
if ((pid=fork())<0){
LOG(L_CRIT, "Cannot fork:%s\n", strerror(errno));
goto error;
}else if (pid!=0){
/*parent process => exit */
exit(0);
}
}
if(enable_dumpable()<0)
goto error;
/* added by noh: create a pid file for the main process */
if (pid_file!=0){
if ((pid_stream=fopen(pid_file, "r"))!=NULL){
if (fscanf(pid_stream, "%d", &p) < 0) {
LM_WARN("could not parse pid file %s\n", pid_file);
}
fclose(pid_stream);
if (p==-1){
LOG(L_CRIT, "pid file %s exists, but doesn't contain a valid"
" pid number\n", pid_file);
goto error;
}
if (kill((pid_t)p, 0)==0 || errno==EPERM){
LOG(L_CRIT, "running process found in the pid file %s\n",
pid_file);
goto error;
}else{
LOG(L_WARN, "pid file contains old pid, replacing pid\n");
}
}
pid=getpid();
if ((pid_stream=fopen(pid_file, "w"))==NULL){
LOG(L_WARN, "unable to create pid file %s: %s\n",
pid_file, strerror(errno));
goto error;
}else{
fprintf(pid_stream, "%i\n", (int)pid);
fclose(pid_stream);
}
}
if (pgid_file!=0){
if ((pid_stream=fopen(pgid_file, "r"))!=NULL){
if (fscanf(pid_stream, "%d", &p) < 0) {
LM_WARN("could not parse pgid file %s\n", pgid_file);
}
fclose(pid_stream);
if (p==-1){
LOG(L_CRIT, "pgid file %s exists, but doesn't contain a valid"
" pgid number\n", pgid_file);
goto error;
}
}
if (own_pgid){
pid=getpgid(0);
if ((pid_stream=fopen(pgid_file, "w"))==NULL){
LOG(L_WARN, "unable to create pgid file %s: %s\n",
pgid_file, strerror(errno));
goto error;
}else{
fprintf(pid_stream, "%i\n", (int)pid);
fclose(pid_stream);
}
}else{
LOG(L_WARN, "we don't have our own process so we won't save"
" our pgid\n");
unlink(pgid_file); /* just to be sure nobody will miss-use the old
value*/
}
}
/* try to replace stdin, stdout & stderr with /dev/null */
if (freopen("/dev/null", "r", stdin)==0){
LOG(L_ERR, "unable to replace stdin with /dev/null: %s\n",
strerror(errno));
/* continue, leave it open */
};
if (freopen("/dev/null", "w", stdout)==0){
LOG(L_ERR, "unable to replace stdout with /dev/null: %s\n",
strerror(errno));
/* continue, leave it open */
};
/* close stderr only if log_stderr=0 */
if ((!log_stderr) &&(freopen("/dev/null", "w", stderr)==0)){
LOG(L_ERR, "unable to replace stderr with /dev/null: %s\n",
strerror(errno));
/* continue, leave it open */
};
/* close all but the daemon_status_fd output as the main process
must still write into it to tell the parent to exit with 0 */
closelog();
for (r=3;r<MAX_FD; r++){
if(r != daemon_status_fd[1])
close(r);
}
if (log_stderr==0)
openlog(name, LOG_PID|LOG_CONS, cfg_get(core, core_cfg, log_facility));
/* LOG_CONS, LOG_PERRROR ? */
return 0;
error:
return -1;
}
int do_suid()
{
struct passwd *pw;
if (gid){
if(setgid(gid)<0){
LOG(L_CRIT, "cannot change gid to %d: %s\n", gid, strerror(errno));
goto error;
}
}
if(uid){
if (!(pw = getpwuid(uid))){
LOG(L_CRIT, "user lookup failed: %s\n", strerror(errno));
goto error;
}
if(initgroups(pw->pw_name, pw->pw_gid)<0){
LOG(L_CRIT, "cannot set supplementary groups: %s\n",
strerror(errno));
goto error;
}
if(setuid(uid)<0){
LOG(L_CRIT, "cannot change uid to %d: %s\n", uid, strerror(errno));
goto error;
}
}
if(enable_dumpable()<0)
goto error;
return 0;
error:
return -1;
}
/*! \brief try to increase the open file limit */
int increase_open_fds(int target)
{
struct rlimit lim;
struct rlimit orig;
if (getrlimit(RLIMIT_NOFILE, &lim)<0){
LOG(L_CRIT, "cannot get the maximum number of file descriptors: %s\n",
strerror(errno));
goto error;
}
orig=lim;
DBG("current open file limits: %lu/%lu\n",
(unsigned long)lim.rlim_cur, (unsigned long)lim.rlim_max);
if ((lim.rlim_cur==RLIM_INFINITY) || (target<=lim.rlim_cur))
/* nothing to do */
goto done;
else if ((lim.rlim_max==RLIM_INFINITY) || (target<=lim.rlim_max)){
lim.rlim_cur=target; /* increase soft limit to target */
}else{
/* more than the hard limit */
LOG(L_INFO, "trying to increase the open file limit"
" past the hard limit (%ld -> %d)\n",
(unsigned long)lim.rlim_max, target);
lim.rlim_max=target;
lim.rlim_cur=target;
}
DBG("increasing open file limits to: %lu/%lu\n",
(unsigned long)lim.rlim_cur, (unsigned long)lim.rlim_max);
if (setrlimit(RLIMIT_NOFILE, &lim)<0){
LOG(L_CRIT, "cannot increase the open file limit to"
" %lu/%lu: %s\n",
(unsigned long)lim.rlim_cur, (unsigned long)lim.rlim_max,
strerror(errno));
if (orig.rlim_max>orig.rlim_cur){
/* try to increase to previous maximum, better than not increasing
* at all */
lim.rlim_max=orig.rlim_max;
lim.rlim_cur=orig.rlim_max;
if (setrlimit(RLIMIT_NOFILE, &lim)==0){
LOG(L_CRIT, " maximum number of file descriptors increased to"
" %u\n",(unsigned)orig.rlim_max);
}
}
goto error;
}
done:
return 0;
error:
return -1;
}
/*! \brief enable core dumps */
int set_core_dump(int enable, long unsigned int size)
{
struct rlimit lim;
struct rlimit newlim;
if (enable){
if (getrlimit(RLIMIT_CORE, &lim)<0){
LOG(L_CRIT, "cannot get the maximum core size: %s\n",
strerror(errno));
goto error;
}
if (lim.rlim_cur<size){
/* first try max limits */
newlim.rlim_max=RLIM_INFINITY;
newlim.rlim_cur=newlim.rlim_max;
if (setrlimit(RLIMIT_CORE, &newlim)==0) goto done;
/* now try with size */
if (lim.rlim_max<size){
newlim.rlim_max=size;
}
newlim.rlim_cur=newlim.rlim_max;
if (setrlimit(RLIMIT_CORE, &newlim)==0) goto done;
/* if this failed too, try rlim_max, better than nothing */
newlim.rlim_max=lim.rlim_max;
newlim.rlim_cur=newlim.rlim_max;
if (setrlimit(RLIMIT_CORE, &newlim)<0){
LOG(L_CRIT, "could increase core limits at all: %s\n",
strerror (errno));
}else{
LOG(L_CRIT, "core limits increased only to %lu\n",
(unsigned long)lim.rlim_max);
}
goto error; /* it's an error we haven't got the size we wanted*/
}else{
newlim.rlim_cur=lim.rlim_cur;
newlim.rlim_max=lim.rlim_max;
goto done; /*nothing to do */
}
}else{
/* disable */
newlim.rlim_cur=0;
newlim.rlim_max=0;
if (setrlimit(RLIMIT_CORE, &newlim)<0){
LOG(L_CRIT, "failed to disable core dumps: %s\n",
strerror(errno));
goto error;
}
}
done:
DBG("core dump limits set to %lu\n", (unsigned long)newlim.rlim_cur);
return 0;
error:
return -1;
}
/*! \brief lock pages in memory (make the process not swapable) */
int mem_lock_pages()
{
#ifdef HAVE_MLOCKALL
if (mlockall(MCL_CURRENT|MCL_FUTURE) !=0){
LOG(L_WARN,"failed to lock the memory pages (disable swap): %s [%d]\n",
strerror(errno), errno);
goto error;
}
return 0;
error:
return -1;
#else /* if MLOCKALL not defined return error */
LOG(L_WARN,"failed to lock the memory pages: no mlockall support\n");
return -1;
#endif
}
/*! \brief tries to set real time priority
* policy: 0 - SCHED_OTHER, 1 - SCHED_RR, 2 - SCHED_FIFO */
int set_rt_prio(int prio, int policy)
{
#ifdef HAVE_SCHED_SETSCHEDULER
struct sched_param sch_p;
int min_prio, max_prio;
int sched_policy;
switch(policy){
case 0:
sched_policy=SCHED_OTHER;
break;
case 1:
sched_policy=SCHED_RR;
break;
case 2:
sched_policy=SCHED_FIFO;
break;
default:
LOG(L_WARN, "WARNING: invalid scheduling policy,using"
" SCHED_OTHER\n");
sched_policy=SCHED_OTHER;
}
memset(&sch_p, 0, sizeof(sch_p));
max_prio=sched_get_priority_max(policy);
min_prio=sched_get_priority_min(policy);
if (prio<min_prio){
LOG(L_WARN, "scheduling priority %d too small, using minimum value"
" (%d)\n", prio, min_prio);
prio=min_prio;
}else if (prio>max_prio){
LOG(L_WARN, "scheduling priority %d too big, using maximum value"
" (%d)\n", prio, max_prio);
prio=max_prio;
}
sch_p.sched_priority=prio;
if (sched_setscheduler(0, sched_policy, &sch_p) != 0){
LOG(L_WARN, "could not switch to real time priority: %s [%d]\n",
strerror(errno), errno);
return -1;
};
return 0;
#else
LOG(L_WARN, "real time support not available\n");
return -1;
#endif
}