pt.c
02a2f864
 /*
  * $Id$
  *
  * Process Table
  *
  * Copyright (C) 2001-2003 FhG Fokus
  *
83e91df1
  * 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.
02a2f864
  *
83e91df1
  * 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.
02a2f864
  */
 /*
  * History:
  * --------
  *  2006-06-14	added process table in shared mem (dragos)
4fd7c9fd
  *  2006-09-20	added profile support (-DPROFILING) (hscholz)
f2e1aa50
  *  2006-10-25	sanity check before allowing forking w/ tcp support (is_main
  *               & tcp not started yet); set is_main=0 in childs (andrei)
faa66b46
  *  2007-07-04	added register_fds() and get_max_open_fds(() (andrei)
83e91df1
  *  2010-08-19	use daemon_status_on_fork_cleanup() (andrei)
02a2f864
  */
83e91df1
 /** internal fork functions and process table.
  * @file: pt.c
  * @ingroup core
1d0661db
  */
02a2f864
 
 
 #include "pt.h"
 #include "tcp_init.h"
 #include "sr_module.h"
63b50c56
 #include "socket_info.h"
2c07f591
 #include "rand/fastrand.h"
b4c9f986
 #ifdef PKG_MALLOC
 #include "mem/mem.h"
 #endif
 #ifdef SHM_MEM
 #include "mem/shm_mem.h"
 #endif
 #if defined PKG_MALLOC || defined SHM_MEM
 #include "cfg_core.h"
 #endif
a5583e0c
 #include "daemonize.h"
02a2f864
 
4fd7c9fd
 #include <stdio.h>
27937719
 #include <time.h> /* time(), used to initialize random numbers */
144c394f
 
 #define FORK_DONT_WAIT  /* child doesn't wait for parent before starting 
 						   => faster startup, but the child should not assume
 						   the parent fixed the pt[] entry for it */
 
 
4fd7c9fd
 #ifdef PROFILING
 #include <sys/gmon.h>
 
 	extern void _start(void);
 	extern void etext(void);
 #endif
02a2f864
 
 
 static int estimated_proc_no=0;
faa66b46
 static int estimated_fds_no=0;
 
c2429a58
 /* number of usec to wait before forking a process */
 static unsigned int fork_delay = 0;
 
 unsigned int set_fork_delay(unsigned int v)
 {
 	unsigned int r;
 	r =  fork_delay;
 	fork_delay = v;
 	return r;
 }
faa66b46
 
 /* number of known "common" used fds */
b264d2c6
 static int calc_common_open_fds_no(void)
faa66b46
 {
 	int max_fds_no;
 	
 	/* 1 tcp send unix socket/all_proc, 
 	 *  + 1 udp sock/udp proc + 1 possible dns comm. socket + 
 	 *  + 1 temporary tcp send sock.
a1bd3d76
 	 * Per process:
 	 *  + 1 if mhomed (now mhomed keeps a "cached socket" per process)
 	 *  + 1 if mhomed & ipv6
faa66b46
 	 */
ed990c31
 	max_fds_no=estimated_proc_no*4 /* udp|sctp + tcp unix sock +
 									  tmp. tcp send +
 									  tmp dns.*/
a1bd3d76
 				-1 /* timer (no udp)*/ + 3 /* stdin/out/err */ +
 				2*mhomed
 				;
faa66b46
 	return max_fds_no;
 }
 
 
02a2f864
 
 /* returns 0 on success, -1 on error */
 int init_pt(int proc_no)
 {
f2e1aa50
 #ifdef USE_TCP
 	int r;
 #endif
 	
02a2f864
 	estimated_proc_no+=proc_no;
faa66b46
 	estimated_fds_no+=calc_common_open_fds_no();
02a2f864
 	/*alloc pids*/
 #ifdef SHM_MEM
 	pt=shm_malloc(sizeof(struct process_table)*estimated_proc_no);
 	process_count = shm_malloc(sizeof(int));
 #else
 	pt=pkg_malloc(sizeof(struct process_table)*estimated_proc_no);
 	process_count = pkg_malloc(sizeof(int));
 #endif
 	process_lock = lock_alloc();
 	process_lock = lock_init(process_lock);
 	if (pt==0||process_count==0||process_lock==0){
 		LOG(L_ERR, "ERROR: out  of memory\n");
 		return -1;
 	}
 	memset(pt, 0, sizeof(struct process_table)*estimated_proc_no);
f2e1aa50
 #ifdef USE_TCP
 	for (r=0; r<estimated_proc_no; r++){
 		pt[r].unix_sock=-1;
 		pt[r].idx=-1;
 	}
 #endif
02a2f864
 	process_no=0; /*main process number*/
 	pt[process_no].pid=getpid();
f2e1aa50
 	memcpy(pt[process_no].desc,"main",5);
02a2f864
 	*process_count=1;
 	return 0;
 }
 
 
 /* register no processes, used from mod_init when processes will be forked
  *  from mod_child 
  *  returns 0 on success, -1 on error
  */
 int register_procs(int no)
 {
 	if (pt){
 		LOG(L_CRIT, "BUG: register_procs(%d) called at runtime\n", no);
 		return -1;
 	}
 	estimated_proc_no+=no;
 	return 0;
 }
 
 
 
 /* returns the maximum number of processes */
 int get_max_procs()
 {
2db960ff
 	if (pt==0){
 		LOG(L_CRIT, "BUG: get_max_procs() called too early "
 				"(it must _not_ be called from mod_init())\n");
 		abort(); /* crash to quickly catch offenders */
 	}
02a2f864
 	return estimated_proc_no;
 }
 
 
faa66b46
 /* register no fds, used from mod_init when modules will open more global
  *  fds (from mod_init or child_init(PROC_INIT)
  *  or from child_init(rank) when the module will open fds local to the
  *   process "rank".
  *   (this is needed because some other parts of ser code rely on knowing
  *    the maximum open fd number in a process)
  *  returns 0 on success, -1 on error
  */
 int register_fds(int no)
 {
 	/* can be called at runtime, but should be called from child_init() */
 	estimated_fds_no+=no;
 	return 0;
 }
 
 
 
 /* returns the maximum open fd number */
 int get_max_open_fds()
 {
 	if (pt==0){
 		LOG(L_CRIT, "BUG: get_max_open_fds() called too early "
 				"(it must _not_ be called from mod_init())\n");
 		abort(); /* crash to quickly catch offenders */
 	}
 	return estimated_fds_no;
 }
 
 
02a2f864
 /* return processes pid */
4fd7c9fd
 int my_pid()
02a2f864
 {
 	return pt ? pt[process_no].pid : getpid();
 }
 
144c394f
 
 
63b50c56
 /* close unneeded sockets */
 int close_extra_socks(int child_id, int proc_no)
 {
 #ifdef USE_TCP
 	int r;
 	struct socket_info* si;
 	
 	if (child_id!=PROC_TCP_MAIN){
 		for (r=0; r<proc_no; r++){
 			if (pt[r].unix_sock>=0){
 				/* we can't change the value in pt[] because it's
 				 * shared so we only close it */
 				close(pt[r].unix_sock);
 			}
 		}
 		/* close all listen sockets (needed only in tcp_main */
 		if (!tcp_disable){
 			for(si=tcp_listen; si; si=si->next){
 				close(si->socket);
 				/* safe to change since this is a per process copy */
 				si->socket=-1;
 			}
 #ifdef USE_TLS
 			if (!tls_disable){
 				for(si=tls_listen; si; si=si->next){
 					close(si->socket);
 					/* safe to change since this is a per process copy */
 					si->socket=-1;
 				}
 			}
 #endif /* USE_TLS */
 		}
 		/* we still need the udp sockets (for sending) so we don't close them
 		 * too */
 	}
 #endif /* USE_TCP */
 	return 0;
 }
 
 
 
02a2f864
 /**
  * Forks a new process.
  * @param child_id - rank, if equal to PROC_NOCHLDINIT init_child will not be
  *                   called for the new forked process (see sr_module.h)
  * @param desc - text description for the process table
  * @param make_sock - if to create a unix socket pair for it
  * @returns the pid of the new process
  */
4fd7c9fd
 int fork_process(int child_id, char *desc, int make_sock)
02a2f864
 {
144c394f
 	int pid, child_process_no;
 	int ret;
27937719
 	unsigned int new_seed1;
 	unsigned int new_seed2;
02a2f864
 #ifdef USE_TCP
 	int sockfd[2];
 #endif
 
c2429a58
 	if(unlikely(fork_delay>0))
 		sleep_us(fork_delay);
 
144c394f
 	ret=-1;
02a2f864
 	#ifdef USE_TCP
144c394f
 		sockfd[0]=sockfd[1]=-1;
02a2f864
 		if(make_sock && !tcp_disable){
f2e1aa50
 			 if (!is_main){
 				 LOG(L_CRIT, "BUG: fork_process(..., 1) called from a non "
 						 "\"main\" process! If forking from a module's "
 						 "child_init() fork only if rank==PROC_MAIN or"
 						 " give up tcp send support (use 0 for make_sock)\n");
 				 goto error;
 			 }
 			 if (tcp_main_pid){
 				 LOG(L_CRIT, "BUG: fork_process(..., 1) called, but tcp main "
 						 " is already started\n");
 				 goto error;
 			 }
02a2f864
 			 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd)<0){
 				LOG(L_ERR, "ERROR: fork_process(): socketpair failed: %s\n",
144c394f
 							strerror(errno));
 				goto error;
02a2f864
 			}
 		}
 	#endif
144c394f
 	lock_get(process_lock);
 	if (*process_count>=estimated_proc_no) {
 		LOG(L_CRIT, "ERROR: fork_process(): Process limit of %d exceeded."
 					" Will simulate fork fail.\n", estimated_proc_no);
 		lock_release(process_lock);
 		goto error;
 	}	
02a2f864
 	
144c394f
 	
 	child_process_no = *process_count;
27937719
 	new_seed1=rand();
 	new_seed2=random();
02a2f864
 	pid = fork();
 	if (pid<0) {
 		lock_release(process_lock);
144c394f
 		ret=pid;
 		goto error;
 	}else if (pid==0){
02a2f864
 		/* child */
f2e1aa50
 		is_main=0; /* a forked process cannot be the "main" one */
144c394f
 		process_no=child_process_no;
83e91df1
 		daemon_status_on_fork_cleanup();
63b50c56
 		/* close tcp unix sockets if this is not tcp main */
 #ifdef USE_TCP
 		close_extra_socks(child_id, process_no);
 #endif /* USE_TCP */
27937719
 		srand(new_seed1);
2c07f591
 		fastrand_seed(rand());
27937719
 		srandom(new_seed2+time(0));
fbe11914
 		shm_malloc_on_fork();
4fd7c9fd
 #ifdef PROFILING
 		monstartup((u_long) &_start, (u_long) &etext);
 #endif
144c394f
 #ifdef FORK_DONT_WAIT
 		/* record pid twice to avoid the child using it, before
 		 * parent gets a chance to set it*/
 		pt[process_no].pid=getpid();
 #else
02a2f864
 		/* wait for parent to get out of critical zone.
 		 * this is actually relevant as the parent updates
 		 * the pt & process_count. */
 		lock_get(process_lock);
f2e1aa50
 		lock_release(process_lock);
144c394f
 #endif
02a2f864
 		#ifdef USE_TCP
 			if (make_sock && !tcp_disable){
 				close(sockfd[0]);
 				unix_tcp_sock=sockfd[1];
 			}
 		#endif		
 		if ((child_id!=PROC_NOCHLDINIT) && (init_child(child_id) < 0)) {
144c394f
 			LOG(L_ERR, "ERROR: fork_process(): init_child failed for "
 					" process %d, pid %d, \"%s\"\n", process_no,
 					pt[process_no].pid, pt[process_no].desc);
02a2f864
 			return -1;
 		}
 		return pid;
 	} else {
 		/* parent */
144c394f
 		(*process_count)++;
 #ifdef FORK_DONT_WAIT
 		lock_release(process_lock);
 #endif
02a2f864
 		/* add the process to the list in shm */
144c394f
 		pt[child_process_no].pid=pid;
02a2f864
 		if (desc){
144c394f
 			strncpy(pt[child_process_no].desc, desc, MAX_PT_DESC);
02a2f864
 		}
 		#ifdef USE_TCP
 			if (make_sock && !tcp_disable){
 				close(sockfd[1]);
144c394f
 				pt[child_process_no].unix_sock=sockfd[0];
f2e1aa50
 				pt[child_process_no].idx=-1; /* this is not a "tcp" process*/
02a2f864
 			}
144c394f
 		#endif
 #ifdef FORK_DONT_WAIT
 #else
02a2f864
 		lock_release(process_lock);
144c394f
 #endif
 		ret=pid;
 		goto end;
02a2f864
 	}
144c394f
 error:
 #ifdef USE_TCP
 	if (sockfd[0]!=-1) close(sockfd[0]);
 	if (sockfd[1]!=-1) close(sockfd[1]);
 #endif
 end:
 	return ret;
02a2f864
 }
 
 /**
  * Forks a new TCP process.
  * @param desc - text description for the process table
  * @param r - index in the tcp_children array
  * @param *reader_fd_1 - pointer to return the reader_fd[1]
  * @returns the pid of the new process
  */
2254e604
 #ifdef USE_TCP
144c394f
 int fork_tcp_process(int child_id, char *desc, int r, int *reader_fd_1)
02a2f864
 {
144c394f
 	int pid, child_process_no;
02a2f864
 	int sockfd[2];
 	int reader_fd[2]; /* for comm. with the tcp children read  */
144c394f
 	int ret;
63b50c56
 	int i;
27937719
 	unsigned int new_seed1;
 	unsigned int new_seed2;
02a2f864
 	
144c394f
 	/* init */
 	sockfd[0]=sockfd[1]=-1;
 	reader_fd[0]=reader_fd[1]=-1;
 	ret=-1;
02a2f864
 	
f2e1aa50
 	if (!is_main){
 		 LOG(L_CRIT, "BUG: fork_tcp_process() called from a non \"main\" "
 				 	"process\n");
 		 goto error;
 	 }
 	 if (tcp_main_pid){
 		 LOG(L_CRIT, "BUG: fork_tcp_process(..., 1) called _after_ starting"
 				 	" tcp main\n");
 		 goto error;
 	 }
02a2f864
 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd)<0){
 		LOG(L_ERR, "ERROR: fork_tcp_process(): socketpair failed: %s\n",
 					strerror(errno));
144c394f
 		goto error;
02a2f864
 	}
 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, reader_fd)<0){
 		LOG(L_ERR, "ERROR: fork_tcp_process(): socketpair failed: %s\n",
 					strerror(errno));
144c394f
 		goto error;
02a2f864
 	}
 	if (tcp_fix_child_sockets(reader_fd)<0){
 		LOG(L_ERR, "ERROR: fork_tcp_process(): failed to set non blocking"
 					"on child sockets\n");
 		/* continue, it's not critical (it will go slower under
 		 * very high connection rates) */
 	}
144c394f
 	lock_get(process_lock);
 	/* set the local process_no */
 	if (*process_count>=estimated_proc_no) {
 		LOG(L_CRIT, "ERROR: fork_tcp_process(): Process limit of %d exceeded."
 					" Simulating fork fail\n", estimated_proc_no);
 		lock_release(process_lock);
 		goto error;
 	}
 	
02a2f864
 	
144c394f
 	child_process_no = *process_count;
27937719
 	new_seed1=rand();
 	new_seed2=random();
02a2f864
 	pid = fork();
 	if (pid<0) {
 		lock_release(process_lock);
144c394f
 		ret=pid;
 		goto end;
02a2f864
 	}
 	if (pid==0){
f2e1aa50
 		is_main=0; /* a forked process cannot be the "main" one */
144c394f
 		process_no=child_process_no;
63b50c56
 		/* close unneeded unix sockets */
 		close_extra_socks(child_id, process_no);
 		/* same for unneeded tcp_children <-> tcp_main unix socks */
 		for (i=0; i<r; i++){
 			if (tcp_children[i].unix_sock>=0){
 				close(tcp_children[i].unix_sock);
 				/* tcp_children is per process, so it's safe to change
 				 * the unix_sock to -1 */
 				tcp_children[i].unix_sock=-1;
 			}
 		}
83e91df1
 		daemon_status_on_fork_cleanup();
27937719
 		srand(new_seed1);
2c07f591
 		fastrand_seed(rand());
27937719
 		srandom(new_seed2+time(0));
fbe11914
 		shm_malloc_on_fork();
4fd7c9fd
 #ifdef PROFILING
 		monstartup((u_long) &_start, (u_long) &etext);
 #endif
144c394f
 #ifdef FORK_DONT_WAIT
 		/* record pid twice to avoid the child using it, before
 -		 * parent gets a chance to set it*/
 		pt[process_no].pid=getpid();
 #else
02a2f864
 		/* wait for parent to get out of critical zone */
 		lock_get(process_lock);
 		lock_release(process_lock);
144c394f
 #endif
 		close(sockfd[0]);
 		unix_tcp_sock=sockfd[1];
 		close(reader_fd[0]);
 		if (reader_fd_1) *reader_fd_1=reader_fd[1];
 		if ((child_id!=PROC_NOCHLDINIT) && (init_child(child_id) < 0)) {
02a2f864
 			LOG(L_ERR, "ERROR: fork_tcp_process(): init_child failed for "
144c394f
 					"process %d, pid %d, \"%s\"\n", process_no, 
 					pt[process_no].pid, pt[process_no].desc);
02a2f864
 			return -1;
 		}
 		return pid;
 	} else {
144c394f
 		/* parent */
 		(*process_count)++;
 #ifdef FORK_DONT_WAIT
 		lock_release(process_lock);
 #endif
02a2f864
 		/* add the process to the list in shm */
144c394f
 		pt[child_process_no].pid=pid;
 		pt[child_process_no].unix_sock=sockfd[0];
 		pt[child_process_no].idx=r;
02a2f864
 		if (desc){
144c394f
 			snprintf(pt[child_process_no].desc, MAX_PT_DESC, "%s child=%d", 
02a2f864
 						desc, r);
 		}
144c394f
 #ifdef FORK_DONT_WAIT
 #else
 		lock_release(process_lock);
 #endif
02a2f864
 		
 		close(sockfd[1]);
 		close(reader_fd[1]);
 		
 		tcp_children[r].pid=pid;
144c394f
 		tcp_children[r].proc_no=child_process_no;
02a2f864
 		tcp_children[r].busy=0;
 		tcp_children[r].n_reqs=0;
 		tcp_children[r].unix_sock=reader_fd[0];
 		
144c394f
 		ret=pid;
 		goto end;
02a2f864
 	}
144c394f
 error:
 	if (sockfd[0]!=-1) close(sockfd[0]);
 	if (sockfd[1]!=-1) close(sockfd[1]);
 	if (reader_fd[0]!=-1) close(reader_fd[0]);
 	if (reader_fd[1]!=-1) close(reader_fd[1]);
 end:
 	return ret;
02a2f864
 }
2254e604
 #endif
b4c9f986
 
 #ifdef PKG_MALLOC
 /* Dumps pkg memory status.
  * Per-child process callback that is called
  * when mem_dump_pkg cfg var is changed.
  */
33bfeb9d
 void mem_dump_pkg_cb(str *gname, str *name)
b4c9f986
 {
 	int	old_memlog;
e4f42ce1
 	int memlog;
b4c9f986
 
 	if (cfg_get(core, core_cfg, mem_dump_pkg) == my_pid()) {
 		/* set memlog to ALERT level to force
 		printing the log messages */
e4f42ce1
 		old_memlog = cfg_get(core, core_cfg, memlog);
b4c9f986
 		memlog = L_ALERT;
e4f42ce1
 		/* ugly hack to temporarily switch memlog to something visible,
 		   possible race with a parallel cfg_set */
 		((struct cfg_group_core*)core_cfg)->memlog=memlog;
b4c9f986
 
 		LOG(memlog, "Memory status (pkg) of process %d:\n", my_pid());
 		pkg_status();
 
e4f42ce1
 		((struct cfg_group_core*)core_cfg)->memlog=old_memlog;
b4c9f986
 	}
 }
 #endif
 
 #ifdef SHM_MEM
 /* Dumps shm memory status.
  * fixup function that is called
  * when mem_dump_shm cfg var is set.
  */
33bfeb9d
 int mem_dump_shm_fixup(void *handle, str *gname, str *name, void **val)
b4c9f986
 {
 	int	old_memlog;
e4f42ce1
 	int memlog;
b4c9f986
 
 	if ((long)(void*)(*val)) {
 		/* set memlog to ALERT level to force
 		printing the log messages */
e4f42ce1
 		old_memlog = cfg_get(core, core_cfg, memlog);
b4c9f986
 		memlog = L_ALERT;
e4f42ce1
 		/* ugly hack to temporarily switch memlog to something visible,
 		   possible race with a parallel cfg_set */
 		((struct cfg_group_core*)core_cfg)->memlog=memlog;
b4c9f986
 
 		LOG(memlog, "Memory status (shm)\n");
 		shm_status();
 
e4f42ce1
 		((struct cfg_group_core*)core_cfg)->memlog=old_memlog;
b4c9f986
 		*val = (void*)(long)0;
 	}
 	return 0;
 }
 #endif