io_wait.c
0ba367ec
 /* 
  * $Id$
  * 
  * Copyright (C) 2005 iptelorg GmbH
  *
  * This file is part of ser, a free SIP server.
  *
  * ser 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 2 of the License, or
  * (at your option) any later version
  *
  * For a license to use the ser software under conditions
  * other than those described here, or to purchase support for this
  * software, please contact iptel.org by e-mail at the following addresses:
  *    info@iptel.org
  *
  * ser 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, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 /* 
  * tcp io wait common stuff used by tcp_main.c & tcp_read.c
  * (see io_wait.h)
  */
 /* 
  * History:
  * --------
  *  2005-06-15  created by andrei
741a9937
  *  2005-06-26  added kqueue (andrei)
9eda5956
  *  2005-07-04  added /dev/poll (andrei)
0ba367ec
  */
 
 
 
 #ifdef USE_TCP /* for now it make sense only with tcp */
 
 #ifdef HAVE_EPOLL
 #include <unistd.h> /* close() */
 #endif
9eda5956
 #ifdef HAVE_DEVPOLL
 #include <sys/types.h> /* open */
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <unistd.h> /* close, ioctl */
 #endif
0ba367ec
 
 #include <sys/utsname.h> /* uname() */
 #include <stdlib.h> /* strtol() */
 #include "io_wait.h"
 
 
 #include "mem/mem.h"
 
 #ifndef local_malloc
 #define local_malloc pkg_malloc
 #endif
 #ifndef local_free
 #define local_free pkg_free
 #endif
 
 char* poll_support="poll"
 #ifdef HAVE_EPOLL
 ", epoll_lt, epoll_et"
 #endif
 #ifdef HAVE_SIGIO_RT
 ", sigio_rt"
 #endif
 #ifdef HAVE_SELECT
 ", select"
 #endif
 #ifdef HAVE_KQUEUE
 ", kqueue"
 #endif
 #ifdef HAVE_DEVPOLL
 ", /dev/poll"
 #endif
 ;
 
 
 char* poll_method_str[POLL_END]={ "none", "poll", "epoll_lt", "epoll_et", 
 								  "sigio_rt", "select", "kqueue",  "/dev/poll"
 								};
 
 #ifdef HAVE_SIGIO_RT
 static int _sigio_init=0;
 static int _sigio_crt_rtsig;
 static sigset_t _sigio_rtsig_used;
 #endif
 
 
 
 #ifdef HAVE_SIGIO_RT
 /* sigio specific init
  * returns -1 on error, 0 on success */
 static int init_sigio(io_wait_h* h, int rsig)
 {
 	int r;
 	int n;
 	int signo;
 	int start_sig;
 	sigset_t oldset;
 	
 	if (!_sigio_init){
 		_sigio_init=1;
 		_sigio_crt_rtsig=SIGRTMIN;
 		sigemptyset(&_sigio_rtsig_used);
 	}
 	h->signo=0;
 	
 	if (rsig==0){
 		start_sig=_sigio_crt_rtsig;
 		n=SIGRTMAX-SIGRTMIN;
 	}else{
 		if ((rsig < SIGRTMIN) || (rsig >SIGRTMAX)){
 			LOG(L_CRIT, "ERROR: init_sigio: real time signal %d out of"
 					          " range  [%d, %d]\n", rsig, SIGRTMIN, SIGRTMAX);
 			goto error;
 		}
 		start_sig=rsig;
 		n=0;
 	}
 	
 	sigemptyset(&h->sset);
 	sigemptyset(&oldset);
 retry1:
 	/* get current block mask */
 	if (sigprocmask(SIG_BLOCK, &h->sset, &oldset )==-1){
 		if (errno==EINTR) goto retry1;
 		LOG(L_ERR, "ERROR: init_sigio: 1st sigprocmask failed: %s [%d]\n",
 				strerror(errno), errno);
 		/* try to continue */
 	}
 	
 	for (r=start_sig; r<=(n+start_sig); r++){
 		signo=(r>SIGRTMAX)?r-SIGRTMAX+SIGRTMIN:r;
 		if (! sigismember(&_sigio_rtsig_used, signo) &&
 			! sigismember(&oldset, signo)){
 			sigaddset(&_sigio_rtsig_used, signo);
 			h->signo=signo;
 			_sigio_crt_rtsig=(signo<SIGRTMAX)?signo+1:SIGRTMIN;
 			break;
 		}
 	}
 	
 	if (h->signo==0){
 			LOG(L_CRIT, "ERROR: init_sigio: %s\n",
 					rsig?"could not assign requested real-time signal":
 						 "out of real-time signals");
 			goto error;
 	}
 
 	DBG("init_sigio: trying signal %d... \n", h->signo);
 	
 	if (sigaddset(&h->sset, h->signo)==-1){
 		LOG(L_ERR, "ERROR: init_sigio: sigaddset failed for %d: %s [%d]\n",
 				h->signo, strerror(errno), errno);
 		goto error;
 	}
 	if (sigaddset(&h->sset, SIGIO)==-1){
 		LOG(L_ERR, "ERROR: init_sigio: sigaddset failed for %d: %s [%d]\n",
 				SIGIO, strerror(errno), errno);
 		goto error;
 	}
 retry:
 	if (sigprocmask(SIG_BLOCK, &h->sset, 0)==-1){
 		if (errno==EINTR) goto retry;
 		LOG(L_ERR, "ERROR: init_sigio: sigprocmask failed: %s [%d]\n",
 				strerror(errno), errno);
 		goto error;
 	}
 	return 0;
 error:
 	h->signo=0;
 	sigemptyset(&h->sset);
 	return -1;
 }
 
 
 
 /* sigio specific destroy */
 static void destroy_sigio(io_wait_h* h)
 {
 	if (h->signo){
 		sigprocmask(SIG_UNBLOCK, &h->sset, 0);
 		sigemptyset(&h->sset);
 		sigdelset(&_sigio_rtsig_used, h->signo);
 		h->signo=0;
 	}
 }
 #endif
 
 
 
 #ifdef HAVE_EPOLL
 /* epoll specific init
  * returns -1 on error, 0 on success */
 static int init_epoll(io_wait_h* h)
 {
9eda5956
 again:
0ba367ec
 	h->epfd=epoll_create(h->max_fd_no);
 	if (h->epfd==-1){
9eda5956
 		if (errno==EINTR) goto again;
0ba367ec
 		LOG(L_ERR, "ERROR: init_epoll: epoll_create: %s [%d]\n",
 				strerror(errno), errno);
 		return -1;
 	}
 	return 0;
 }
 
 
 
 static void destroy_epoll(io_wait_h* h)
 {
 	if (h->epfd!=-1){
 		close(h->epfd);
 		h->epfd=-1;
 	}
 }
 #endif
 
 
 
741a9937
 #ifdef HAVE_KQUEUE
 /* kqueue specific init
  * returns -1 on error, 0 on success */
 static int init_kqueue(io_wait_h* h)
 {
9eda5956
 again:
741a9937
 	h->kq_fd=kqueue();
 	if (h->kq_fd==-1){
9eda5956
 		if (errno==EINTR) goto again;
741a9937
 		LOG(L_ERR, "ERROR: init_kqueue: kqueue: %s [%d]\n",
 				strerror(errno), errno);
 		return -1;
 	}
 	return 0;
 }
 
 
 
 static void destroy_kqueue(io_wait_h* h)
 {
 	if (h->kq_fd!=-1){
 		close(h->kq_fd);
 		h->kq_fd=-1;
 	}
 }
 #endif
 
 
 
9eda5956
 #ifdef HAVE_DEVPOLL
 /* /dev/poll specific init
  * returns -1 on error, 0 on success */
 static int init_devpoll(io_wait_h* h)
 {
 again:
 	h->dpoll_fd=open("/dev/poll", O_RDWR);
 	if (h->dpoll_fd==-1){
 		if (errno==EINTR) goto again;
 		LOG(L_ERR, "ERROR: init_/dev/poll: open: %s [%d]\n",
 				strerror(errno), errno);
 		return -1;
 	}
 	return 0;
 }
 
 
 
 static void destroy_devpoll(io_wait_h* h)
 {
 	if (h->dpoll_fd!=-1){
 		close(h->dpoll_fd);
 		h->dpoll_fd=-1;
 	}
 }
 #endif
 
 
 
0ba367ec
 #ifdef HAVE_SELECT
 static int init_select(io_wait_h* h)
 {
 	FD_ZERO(&h->master_set);
 	return 0;
 }
 #endif
 
 
 
 /* return system version (major.minor.minor2) as
  *  (major<<16)|(minor)<<8|(minor2)
  * (if some of them are missing, they are set to 0)
  * if the parameters are not null they are set to the coresp. part 
  */
 static unsigned int get_sys_version(int* major, int* minor, int* minor2)
 {
 	struct utsname un;
 	int m1;
 	int m2;
 	int m3;
 	char* p;
 	
 	memset (&un, 0, sizeof(un));
 	m1=m2=m3=0;
 	/* get sys version */
 	uname(&un);
 	m1=strtol(un.release, &p, 10);
 	if (*p=='.'){
 		p++;
 		m2=strtol(p, &p, 10);
 		if (*p=='.'){
 			p++;
 			m3=strtol(p, &p, 10);
 		}
 	}
 	if (major) *major=m1;
 	if (minor) *minor=m2;
 	if (minor2) *minor2=m3;
 	return ((m1<<16)|(m2<<8)|(m3));
 }
 
 
 
 /*
  * returns 0 on success, and an error message on error
  */
 char* check_poll_method(enum poll_types poll_method)
 {
 	char* ret;
26376604
 	unsigned int os_ver;
 
c742b6a4
 	ret=0;
26376604
 	os_ver=get_sys_version(0,0,0);	
0ba367ec
 	switch(poll_method){
 		case POLL_NONE:
 			break;
 		case POLL_POLL:
 			/* always supported */
 			break;
 		case POLL_SELECT:
 			/* should be always supported */
 #ifndef HAVE_SELECT
 			ret="select not supported, try re-compiling with -DHAVE_SELECT";
 #endif
 			break;
 		case POLL_EPOLL_LT:
 		case POLL_EPOLL_ET:
 #ifndef HAVE_EPOLL
 			ret="epoll not supported, try re-compiling with -DHAVE_EPOLL";
 #else
741a9937
 			/* only on 2.6 + */
26376604
 			if (os_ver<0x020542) /* if ver < 2.5.66 */
0ba367ec
 			 	ret="epoll not supported on kernels < 2.6";
 #endif
 			break;
 		case POLL_SIGIO_RT:
 #ifndef HAVE_SIGIO_RT
 			ret="sigio_rt not supported, try re-compiling with"
 				" -DHAVE_SIGIO_RT";
741a9937
 #else
 			/* only on 2.2 +  ?? */
 			if (os_ver<0x020200) /* if ver < 2.2.0 */
 			 	ret="epoll not supported on kernels < 2.2 (?)";
0ba367ec
 #endif
 			break;
741a9937
 		case POLL_KQUEUE:
 #ifndef HAVE_KQUEUE
 			ret="kqueue not supported, try re-compiling with -DHAVE_KQUEUE";
 #else
55d8155e
 		/* only in FreeBSD 4.1, NETBSD 2.0, OpenBSD 2.9, Darwin */
741a9937
 	#ifdef __OS_freebsd
 			if (os_ver<0x0401) /* if ver < 4.1 */
 				ret="kqueue not supported on FreeBSD < 4.1";
 	#elif defined (__OS_netbsd)
 			if (os_ver<0x020000) /* if ver < 2.0 */
 				ret="kqueue not supported on NetBSD < 2.0";
 	#elif defined (__OS_openbsd)
8386f8d7
 			if (os_ver<0x0209) /* if ver < 2.9 ? */
 				ret="kqueue not supported on OpenBSD < 2.9 (?)";
741a9937
 	#endif /* assume that the rest support kqueue ifdef HAVE_KQUEUE */
 #endif
9eda5956
 			break;
 		case POLL_DEVPOLL:
 #ifndef HAVE_DEVPOLL
 			ret="/dev/poll not supported, try re-compiling with"
 					" -DHAVE_DEVPOLL";
 #else
 	/* only in Solaris >= 7.0 (?) */
 	#ifdef __OS_solaris
 		if (os_ver<0x0507) /* ver < 5.7 */
 			ret="/dev/poll not supported on Solaris < 7.0 (SunOS 5.7)";
 	#endif
 #endif
 			break;
741a9937
 
0ba367ec
 		default:
 			ret="unknown not supported method";
 	}
 	return ret;
 }
 
 
 
 enum poll_types choose_poll_method()
 {
 	enum poll_types poll_method;
26376604
 	unsigned int os_ver;
 
 	os_ver=get_sys_version(0,0,0);	
0ba367ec
 	poll_method=0;
 #ifdef HAVE_EPOLL
26376604
 	if (os_ver>=0x020542) /* if ver >= 2.5.66 */
0ba367ec
 		poll_method=POLL_EPOLL_LT; /* or POLL_EPOLL_ET */
 		
 #endif
741a9937
 #ifdef HAVE_KQUEUE
 	if (poll_method==0)
55d8155e
 		/* only in FreeBSD 4.1, NETBSD 2.0, OpenBSD 2.9, Darwin */
741a9937
 	#ifdef __OS_freebsd
 		if (os_ver>=0x0401) /* if ver >= 4.1 */
 	#elif defined (__OS_netbsd)
 		if (os_ver>=0x020000) /* if ver >= 2.0 */
 	#elif defined (__OS_openbsd)
8386f8d7
 		if (os_ver>=0x0209) /* if ver >= 2.9 (?) */
741a9937
 	#endif /* assume that the rest support kqueue ifdef HAVE_KQUEUE */
 			poll_method=POLL_KQUEUE;
 #endif
9eda5956
 #ifdef HAVE_DEVPOLL
 	#ifdef __OS_solaris
 	if (poll_method==0)
 		/* only in Solaris >= 7.0 (?) */
 		if (os_ver>=0x0507) /* if ver >=SunOS 5.7 */
 			poll_method=POLL_DEVPOLL;
 	#endif
 #endif
0ba367ec
 #ifdef  HAVE_SIGIO_RT
741a9937
 		if (poll_method==0) 
 			if (os_ver>=0x020200) /* if ver >= 2.2.0 */
 				poll_method=POLL_SIGIO_RT;
0ba367ec
 #endif
 		if (poll_method==0) poll_method=POLL_POLL;
 	return poll_method;
 }
 
 
 
 char* poll_method_name(enum poll_types poll_method)
 {
 	if ((poll_method>=POLL_NONE) && (poll_method<POLL_END))
 		return poll_method_str[poll_method];
 	else
 		return "invalid poll method";
 }
 
 
 
 
 /* converts a string into a poll_method
  * returns POLL_NONE (0) on error, else the corresponding poll type */
 enum poll_types get_poll_type(char* s)
 {
 	int r;
 	int l;
 	
 	l=strlen(s);
 	for (r=POLL_END-1; r>POLL_NONE; r--)
 		if ((strlen(poll_method_str[r])==l) &&
 			(strncasecmp(poll_method_str[r], s, l)==0))
 			break;
 	return r; 
 }
 
 
 
 /* initializes the static vars/arrays
  * params:      h - pointer to the io_wait_h that will be initialized
  *         max_fd - maximum allowed fd number
  *         poll_m - poll method (0 for automatic best fit)
  */
 int init_io_wait(io_wait_h* h, int max_fd, enum poll_types poll_method)
 {
 	char * poll_err;
 	
 	memset(h, 0, sizeof(*h));
 	h->max_fd_no=max_fd;
 #ifdef HAVE_EPOLL
 	h->epfd=-1;
 #endif
741a9937
 #ifdef HAVE_KQUEUE
 	h->kq_fd=-1;
9eda5956
 #endif
 #ifdef HAVE_DEVPOLL
 	h->dpoll_fd=-1;
741a9937
 #endif
0ba367ec
 	poll_err=check_poll_method(poll_method);
 	
 	/* set an appropiate poll method */
 	if (poll_err || (poll_method==0)){
 		poll_method=choose_poll_method();
 		if (poll_err){
 			LOG(L_ERR, "ERROR: init_io_wait: %s, using %s instead\n",
 					poll_err, poll_method_str[poll_method]);
 		}else{
 			LOG(L_INFO, "init_io_wait: using %s as the io watch method"
 					" (auto detected)\n", poll_method_str[poll_method]);
 		}
 	}
 	
 	h->poll_method=poll_method;
 	
9eda5956
 	/* common stuff, everybody has fd_hash */
0ba367ec
 	h->fd_hash=local_malloc(sizeof(*(h->fd_hash))*h->max_fd_no);
 	if (h->fd_hash==0){
 		LOG(L_CRIT, "ERROR: init_io_wait: could not alloc"
2b04f2a8
 					" fd hashtable (%ld bytes)\n",
 					(long)sizeof(*(h->fd_hash))*h->max_fd_no );
0ba367ec
 		goto error;
 	}
 	memset((void*)h->fd_hash, 0, sizeof(*(h->fd_hash))*h->max_fd_no);
 	
 	switch(poll_method){
 		case POLL_POLL:
 #ifdef HAVE_SELECT
 		case POLL_SELECT:
 #endif
 #ifdef HAVE_SIGIO_RT
 		case POLL_SIGIO_RT:
9eda5956
 #endif
 #ifdef HAVE_DEVPOLL
 		case POLL_DEVPOLL:
0ba367ec
 #endif
 			h->fd_array=local_malloc(sizeof(*(h->fd_array))*h->max_fd_no);
 			if (h->fd_array==0){
 				LOG(L_CRIT, "ERROR: init_io_wait: could not"
2b04f2a8
 							" alloc fd array (%ld bytes)\n",
 							(long)sizeof(*(h->fd_hash))*h->max_fd_no);
0ba367ec
 				goto error;
 			}
 			memset((void*)h->fd_array, 0, sizeof(*(h->fd_array))*h->max_fd_no);
 #ifdef HAVE_SIGIO_RT
 			if ((poll_method==POLL_SIGIO_RT) && (init_sigio(h, 0)<0)){
 				LOG(L_CRIT, "ERROR: init_io_wait: sigio init failed\n");
 				goto error;
 			}
 #endif
9eda5956
 #ifdef HAVE_DEVPOLL
 			if ((poll_method==POLL_DEVPOLL) && (init_devpoll(h)<0)){
 				LOG(L_CRIT, "ERROR: init_io_wait: /dev/poll init failed\n");
 				goto error;
 			}
 #endif
0ba367ec
 #ifdef HAVE_SELECT
 			if ((poll_method==POLL_SELECT) && (init_select(h)<0)){
 				LOG(L_CRIT, "ERROR: init_io_wait: select init failed\n");
 				goto error;
 			}
 #endif
 			
 			break;
 #ifdef HAVE_EPOLL
 		case POLL_EPOLL_LT:
 		case POLL_EPOLL_ET:
 			h->ep_array=local_malloc(sizeof(*(h->ep_array))*h->max_fd_no);
 			if (h->ep_array==0){
 				LOG(L_CRIT, "ERROR: init_io_wait: could not alloc"
 							" epoll array\n");
 				goto error;
 			}
 			memset((void*)h->ep_array, 0, sizeof(*(h->ep_array))*h->max_fd_no);
 			if (init_epoll(h)<0){
 				LOG(L_CRIT, "ERROR: init_io_wait: epoll init failed\n");
 				goto error;
 			}
 			break;
741a9937
 #endif
 #ifdef HAVE_KQUEUE
 		case POLL_KQUEUE:
 			h->kq_array=local_malloc(sizeof(*(h->kq_array))*h->max_fd_no);
 			if (h->kq_array==0){
 				LOG(L_CRIT, "ERROR: init_io_wait: could not alloc"
 							" kqueue event array\n");
 				goto error;
 			}
 			h->kq_changes_size=KQ_CHANGES_ARRAY_SIZE;
 			h->kq_changes=local_malloc(sizeof(*(h->kq_changes))*
 										h->kq_changes_size);
 			if (h->kq_changes==0){
 				LOG(L_CRIT, "ERROR: init_io_wait: could not alloc"
 							" kqueue changes array\n");
 				goto error;
 			}
 			h->kq_nchanges=0;
 			memset((void*)h->kq_array, 0, sizeof(*(h->kq_array))*h->max_fd_no);
 			memset((void*)h->kq_changes, 0,
 						sizeof(*(h->kq_changes))* h->kq_changes_size);
 			if (init_kqueue(h)<0){
 				LOG(L_CRIT, "ERROR: init_io_wait: kqueue init failed\n");
 				goto error;
 			}
 			break;
0ba367ec
 #endif
 		default:
 			LOG(L_CRIT, "BUG: init_io_wait: unknown/unsupported poll"
 						" method %s (%d)\n",
 						poll_method_str[poll_method], poll_method);
 			goto error;
 	}
 	return 0;
 error:
 	return -1;
 }
 
 
 
 /* destroys everything init_io_wait allocated */
 void destroy_io_wait(io_wait_h* h)
 {
 	switch(h->poll_method){
 #ifdef HAVE_EPOLL
 		case POLL_EPOLL_LT:
 		case POLL_EPOLL_ET:
 			destroy_epoll(h);
 			if (h->ep_array){
 				local_free(h->ep_array);
 				h->ep_array=0;
 			}
 		break;
 #endif
741a9937
 #ifdef HAVE_KQUEUE
 		case POLL_KQUEUE:
 			destroy_kqueue(h);
 			if (h->kq_array){
 				local_free(h->kq_array);
 				h->kq_array=0;
 			}
 			if (h->kq_changes){
 				local_free(h->kq_changes);
 				h->kq_changes=0;
 			}
 			break;
 #endif
0ba367ec
 #ifdef HAVE_SIGIO_RT
 		case POLL_SIGIO_RT:
 			destroy_sigio(h);
 			break;
9eda5956
 #endif
 #ifdef HAVE_DEVPOLL
 		case POLL_DEVPOLL:
 			destroy_devpoll(h);
 			break;
0ba367ec
 #endif
 		default: /*do  nothing*/
 			;
 	}
 		if (h->fd_array){
 			local_free(h->fd_array);
 			h->fd_array=0;
 		}
 		if (h->fd_hash){
 			local_free(h->fd_hash);
 			h->fd_hash=0;
 		}
 }
 
 
 
 #endif