Home | History | Annotate | Download | only in dhcpagent
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 #include <time.h>
     27 #include <stdio.h>
     28 #include <assert.h>
     29 #include <string.h>
     30 #include <stdlib.h>
     31 #include <unistd.h>
     32 #include <sys/types.h>
     33 #include <sys/wait.h>
     34 #include <signal.h>
     35 #include <fcntl.h>
     36 #include <dhcpmsg.h>
     37 
     38 #include "agent.h"
     39 #include "script_handler.h"
     40 #include "states.h"
     41 #include "interface.h"
     42 
     43 /*
     44  * scripts are directly managed by a script helper process. dhcpagent creates
     45  * the helper process and it, in turn, creates a process to run the script
     46  * dhcpagent owns one end of a pipe and the helper process owns the other end
     47  * the helper process calls waitpid to wait for the script to exit. an alarm
     48  * is set for SCRIPT_TIMEOUT seconds. If the alarm fires, SIGTERM is sent to
     49  * the script process and a second alarm is set for SCRIPT_TIMEOUT_GRACE. if
     50  * the second alarm fires, SIGKILL is sent to forcefully kill the script. when
     51  * script exits, the helper process notifies dhcpagent by closing its end
     52  * of the pipe.
     53  */
     54 
     55 unsigned int	script_count;
     56 
     57 /*
     58  * the signal to send to the script process. it is a global variable
     59  * to this file as sigterm_handler needs it.
     60  */
     61 
     62 static int	script_signal = SIGTERM;
     63 
     64 /*
     65  * script's absolute timeout value. the first timeout is set to SCRIPT_TIMEOUT
     66  * seconds from the time it is started. SIGTERM is sent on the first timeout
     67  * the second timeout is set to SCRIPT_TIMEOUT_GRACE from the first timeout
     68  * and SIGKILL is sent on the second timeout.
     69  */
     70 static time_t	timeout;
     71 
     72 /*
     73  * sigalarm_handler(): signal handler for SIGALRM
     74  *
     75  *   input: int: signal the handler was called with
     76  *  output: void
     77  */
     78 
     79 /* ARGSUSED */
     80 static void
     81 sigalarm_handler(int sig)
     82 {
     83 	time_t	now;
     84 
     85 	/* set a another alarm if it fires too early */
     86 	now = time(NULL);
     87 	if (now < timeout)
     88 		(void) alarm(timeout - now);
     89 }
     90 
     91 /*
     92  * sigterm_handler(): signal handler for SIGTERM, fired when dhcpagent wants
     93  *		      to stop the script
     94  *   input: int: signal the handler was called with
     95  *  output: void
     96  */
     97 
     98 /* ARGSUSED */
     99 static void
    100 sigterm_handler(int sig)
    101 {
    102 	if (script_signal != SIGKILL) {
    103 		/* send SIGKILL SCRIPT_TIMEOUT_GRACE seconds from now */
    104 		script_signal = SIGKILL;
    105 		timeout = time(NULL) + SCRIPT_TIMEOUT_GRACE;
    106 		(void) alarm(SCRIPT_TIMEOUT_GRACE);
    107 	}
    108 }
    109 
    110 /*
    111  * run_script(): it forks a process to execute the script
    112  *
    113  *   input: dhcp_smach_t *: the state machine
    114  *	    const char *: the event name
    115  *	    int: the pipe end owned by the script helper process
    116  *  output: void
    117  */
    118 
    119 static void
    120 run_script(dhcp_smach_t *dsmp, const char *event, int fd)
    121 {
    122 	int		n;
    123 	char		c;
    124 	char		*path;
    125 	char		*name;
    126 	pid_t		pid;
    127 	time_t		now;
    128 
    129 	if ((pid = fork()) == -1)
    130 		return;
    131 
    132 	if (pid == 0) {
    133 		path = SCRIPT_PATH;
    134 		name = strrchr(path, '/') + 1;
    135 
    136 		/* close all files */
    137 		closefrom(0);
    138 
    139 		/* redirect stdin, stdout and stderr to /dev/null */
    140 		if ((n = open("/dev/null", O_RDWR)) < 0)
    141 			_exit(127);
    142 
    143 		(void) dup2(n, STDOUT_FILENO);
    144 		(void) dup2(n, STDERR_FILENO);
    145 		(void) execl(path, name, dsmp->dsm_name, event, NULL);
    146 		_exit(127);
    147 	}
    148 
    149 	/*
    150 	 * the first timeout fires SCRIPT_TIMEOUT seconds from now.
    151 	 */
    152 	timeout = time(NULL) + SCRIPT_TIMEOUT;
    153 	(void) sigset(SIGALRM, sigalarm_handler);
    154 	(void) alarm(SCRIPT_TIMEOUT);
    155 
    156 	/*
    157 	 * pass script's pid to dhcpagent.
    158 	 */
    159 	(void) write(fd, &pid, sizeof (pid));
    160 
    161 	for (;;) {
    162 		if (waitpid(pid, NULL, 0) >= 0) {
    163 			/* script has exited */
    164 			c = SCRIPT_OK;
    165 			break;
    166 		}
    167 
    168 		if (errno != EINTR)
    169 			return;
    170 
    171 		now = time(NULL);
    172 		if (now >= timeout) {
    173 			(void) kill(pid, script_signal);
    174 			if (script_signal == SIGKILL) {
    175 				c = SCRIPT_KILLED;
    176 				break;
    177 			}
    178 
    179 			script_signal = SIGKILL;
    180 			timeout = now + SCRIPT_TIMEOUT_GRACE;
    181 			(void) alarm(SCRIPT_TIMEOUT_GRACE);
    182 		}
    183 	}
    184 
    185 	(void) write(fd, &c, 1);
    186 }
    187 
    188 /*
    189  * script_init(): initialize script state on a given state machine
    190  *
    191  *   input: dhcp_smach_t *: the state machine
    192  *  output: void
    193  */
    194 
    195 void
    196 script_init(dhcp_smach_t *dsmp)
    197 {
    198 	dsmp->dsm_script_pid = -1;
    199 	dsmp->dsm_script_helper_pid = -1;
    200 	dsmp->dsm_script_event_id = -1;
    201 	dsmp->dsm_script_fd = -1;
    202 	dsmp->dsm_script_callback = NULL;
    203 	dsmp->dsm_script_event = NULL;
    204 	dsmp->dsm_callback_arg = NULL;
    205 }
    206 
    207 /*
    208  * script_cleanup(): cleanup helper function
    209  *
    210  *   input: dhcp_smach_t *: the state machine
    211  *  output: void
    212  */
    213 
    214 static void
    215 script_cleanup(dhcp_smach_t *dsmp)
    216 {
    217 	/*
    218 	 * We must clear dsm_script_pid prior to invoking the callback or we
    219 	 * could get in an infinite loop via async_finish().
    220 	 */
    221 	dsmp->dsm_script_pid = -1;
    222 	dsmp->dsm_script_helper_pid = -1;
    223 
    224 	if (dsmp->dsm_script_fd != -1) {
    225 		assert(dsmp->dsm_script_event_id != -1);
    226 		(void) iu_unregister_event(eh, dsmp->dsm_script_event_id, NULL);
    227 		(void) close(dsmp->dsm_script_fd);
    228 
    229 		assert(dsmp->dsm_script_callback != NULL);
    230 		dsmp->dsm_script_callback(dsmp, dsmp->dsm_callback_arg);
    231 		script_init(dsmp);
    232 		script_count--;
    233 		release_smach(dsmp);	/* hold from script_start() */
    234 	}
    235 }
    236 
    237 /*
    238  * script_exit(): does cleanup and invokes the callback when the script exits
    239  *
    240  *   input: eh_t *: unused
    241  *	    int: the end of pipe owned by dhcpagent
    242  *	    short: unused
    243  *	    eh_event_id_t: unused
    244  *	    void *: the state machine
    245  *  output: void
    246  */
    247 
    248 /* ARGSUSED */
    249 static void
    250 script_exit(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg)
    251 {
    252 	char c;
    253 
    254 	if (read(fd, &c, 1) <= 0)
    255 		c = SCRIPT_FAILED;
    256 
    257 	if (c == SCRIPT_OK)
    258 		dhcpmsg(MSG_DEBUG, "script ok");
    259 	else if (c == SCRIPT_KILLED)
    260 		dhcpmsg(MSG_DEBUG, "script killed");
    261 	else
    262 		dhcpmsg(MSG_DEBUG, "script failed");
    263 
    264 	script_cleanup(arg);
    265 }
    266 
    267 /*
    268  * script_start(): tries to start a script.
    269  *		   if a script is already running, it's stopped first.
    270  *
    271  *
    272  *   input: dhcp_smach_t *: the state machine
    273  *	    const char *: the event name
    274  *	    script_callback_t: callback function
    275  *	    void *: data to the callback function
    276  *  output: boolean_t: B_TRUE if script starts successfully
    277  *	    int *: the returned value of the callback function if script
    278  *		starts unsuccessfully
    279  */
    280 
    281 boolean_t
    282 script_start(dhcp_smach_t *dsmp, const char *event,
    283     script_callback_t *callback, void *arg, int *status)
    284 {
    285 	int		n;
    286 	int		fds[2];
    287 	pid_t		pid;
    288 	iu_event_id_t	event_id;
    289 
    290 	assert(callback != NULL);
    291 
    292 	if (dsmp->dsm_script_pid != -1) {
    293 		/* script is running, stop it */
    294 		dhcpmsg(MSG_DEBUG, "script_start: stopping ongoing script");
    295 		script_stop(dsmp);
    296 	}
    297 
    298 	if (access(SCRIPT_PATH, X_OK) == -1) {
    299 		/* script does not exist */
    300 		goto out;
    301 	}
    302 
    303 	/*
    304 	 * dhcpagent owns one end of the pipe and script helper process
    305 	 * owns the other end. dhcpagent reads on the pipe; and the helper
    306 	 * process notifies it when the script exits.
    307 	 */
    308 	if (pipe(fds) < 0) {
    309 		dhcpmsg(MSG_ERROR, "script_start: can't create pipe");
    310 		goto out;
    311 	}
    312 
    313 	if ((pid = fork()) < 0) {
    314 		dhcpmsg(MSG_ERROR, "script_start: can't fork");
    315 		(void) close(fds[0]);
    316 		(void) close(fds[1]);
    317 		goto out;
    318 	}
    319 
    320 	if (pid == 0) {
    321 		/*
    322 		 * SIGCHLD is ignored in dhcpagent, the helper process
    323 		 * needs it. it calls waitpid to wait for the script to exit.
    324 		 */
    325 		(void) close(fds[0]);
    326 		(void) sigset(SIGCHLD, SIG_DFL);
    327 		(void) sigset(SIGTERM, sigterm_handler);
    328 		run_script(dsmp, event, fds[1]);
    329 		exit(0);
    330 	}
    331 
    332 	(void) close(fds[1]);
    333 
    334 	/* get the script's pid */
    335 	if (read(fds[0], &dsmp->dsm_script_pid, sizeof (pid_t)) !=
    336 	    sizeof (pid_t)) {
    337 		(void) kill(pid, SIGKILL);
    338 		dsmp->dsm_script_pid = -1;
    339 		(void) close(fds[0]);
    340 		goto out;
    341 	}
    342 
    343 	dsmp->dsm_script_helper_pid = pid;
    344 	event_id = iu_register_event(eh, fds[0], POLLIN, script_exit, dsmp);
    345 	if (event_id == -1) {
    346 		(void) close(fds[0]);
    347 		script_stop(dsmp);
    348 		goto out;
    349 	}
    350 
    351 	script_count++;
    352 	dsmp->dsm_script_event_id = event_id;
    353 	dsmp->dsm_script_callback = callback;
    354 	dsmp->dsm_script_event = event;
    355 	dsmp->dsm_callback_arg = arg;
    356 	dsmp->dsm_script_fd = fds[0];
    357 	hold_smach(dsmp);
    358 	return (B_TRUE);
    359 
    360 out:
    361 	/* callback won't be called in script_exit, so call it here */
    362 	n = callback(dsmp, arg);
    363 	if (status != NULL)
    364 		*status = n;
    365 
    366 	return (B_FALSE);
    367 }
    368 
    369 /*
    370  * script_stop(): stops the script if it is running
    371  *
    372  *   input: dhcp_smach_t *: the state machine
    373  *  output: void
    374  */
    375 
    376 void
    377 script_stop(dhcp_smach_t *dsmp)
    378 {
    379 	if (dsmp->dsm_script_pid != -1) {
    380 		assert(dsmp->dsm_script_helper_pid != -1);
    381 
    382 		/*
    383 		 * sends SIGTERM to the script and asks the helper process
    384 		 * to send SIGKILL if it does not exit after
    385 		 * SCRIPT_TIMEOUT_GRACE seconds.
    386 		 */
    387 		(void) kill(dsmp->dsm_script_pid, SIGTERM);
    388 		(void) kill(dsmp->dsm_script_helper_pid, SIGTERM);
    389 	}
    390 
    391 	script_cleanup(dsmp);
    392 }
    393