10Sstevel@tonic-gate /*
20Sstevel@tonic-gate * CDDL HEADER START
30Sstevel@tonic-gate *
40Sstevel@tonic-gate * The contents of this file are subject to the terms of the
53431Scarlsonj * Common Development and Distribution License (the "License").
63431Scarlsonj * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate *
80Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate * See the License for the specific language governing permissions
110Sstevel@tonic-gate * and limitations under the License.
120Sstevel@tonic-gate *
130Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate *
190Sstevel@tonic-gate * CDDL HEADER END
200Sstevel@tonic-gate */
210Sstevel@tonic-gate /*
22*9508SPeter.Memishian@Sun.COM * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
230Sstevel@tonic-gate * Use is subject to license terms.
240Sstevel@tonic-gate */
250Sstevel@tonic-gate
260Sstevel@tonic-gate #include <time.h>
270Sstevel@tonic-gate #include <stdio.h>
280Sstevel@tonic-gate #include <assert.h>
290Sstevel@tonic-gate #include <string.h>
300Sstevel@tonic-gate #include <stdlib.h>
310Sstevel@tonic-gate #include <unistd.h>
320Sstevel@tonic-gate #include <sys/types.h>
330Sstevel@tonic-gate #include <sys/wait.h>
340Sstevel@tonic-gate #include <signal.h>
350Sstevel@tonic-gate #include <fcntl.h>
360Sstevel@tonic-gate #include <dhcpmsg.h>
373431Scarlsonj
383431Scarlsonj #include "agent.h"
390Sstevel@tonic-gate #include "script_handler.h"
403431Scarlsonj #include "states.h"
413431Scarlsonj #include "interface.h"
420Sstevel@tonic-gate
430Sstevel@tonic-gate /*
440Sstevel@tonic-gate * scripts are directly managed by a script helper process. dhcpagent creates
450Sstevel@tonic-gate * the helper process and it, in turn, creates a process to run the script
460Sstevel@tonic-gate * dhcpagent owns one end of a pipe and the helper process owns the other end
470Sstevel@tonic-gate * the helper process calls waitpid to wait for the script to exit. an alarm
480Sstevel@tonic-gate * is set for SCRIPT_TIMEOUT seconds. If the alarm fires, SIGTERM is sent to
490Sstevel@tonic-gate * the script process and a second alarm is set for SCRIPT_TIMEOUT_GRACE. if
500Sstevel@tonic-gate * the second alarm fires, SIGKILL is sent to forcefully kill the script. when
510Sstevel@tonic-gate * script exits, the helper process notifies dhcpagent by closing its end
520Sstevel@tonic-gate * of the pipe.
530Sstevel@tonic-gate */
540Sstevel@tonic-gate
550Sstevel@tonic-gate unsigned int script_count;
560Sstevel@tonic-gate
570Sstevel@tonic-gate /*
580Sstevel@tonic-gate * the signal to send to the script process. it is a global variable
590Sstevel@tonic-gate * to this file as sigterm_handler needs it.
600Sstevel@tonic-gate */
610Sstevel@tonic-gate
620Sstevel@tonic-gate static int script_signal = SIGTERM;
630Sstevel@tonic-gate
640Sstevel@tonic-gate /*
650Sstevel@tonic-gate * script's absolute timeout value. the first timeout is set to SCRIPT_TIMEOUT
660Sstevel@tonic-gate * seconds from the time it is started. SIGTERM is sent on the first timeout
670Sstevel@tonic-gate * the second timeout is set to SCRIPT_TIMEOUT_GRACE from the first timeout
680Sstevel@tonic-gate * and SIGKILL is sent on the second timeout.
690Sstevel@tonic-gate */
70*9508SPeter.Memishian@Sun.COM static time_t timeout;
710Sstevel@tonic-gate
720Sstevel@tonic-gate /*
73*9508SPeter.Memishian@Sun.COM * sigalarm_handler(): signal handler for SIGALRM
740Sstevel@tonic-gate *
750Sstevel@tonic-gate * input: int: signal the handler was called with
760Sstevel@tonic-gate * output: void
770Sstevel@tonic-gate */
780Sstevel@tonic-gate
790Sstevel@tonic-gate /* ARGSUSED */
800Sstevel@tonic-gate static void
sigalarm_handler(int sig)810Sstevel@tonic-gate sigalarm_handler(int sig)
820Sstevel@tonic-gate {
830Sstevel@tonic-gate time_t now;
840Sstevel@tonic-gate
850Sstevel@tonic-gate /* set a another alarm if it fires too early */
860Sstevel@tonic-gate now = time(NULL);
870Sstevel@tonic-gate if (now < timeout)
880Sstevel@tonic-gate (void) alarm(timeout - now);
890Sstevel@tonic-gate }
900Sstevel@tonic-gate
910Sstevel@tonic-gate /*
920Sstevel@tonic-gate * sigterm_handler(): signal handler for SIGTERM, fired when dhcpagent wants
930Sstevel@tonic-gate * to stop the script
940Sstevel@tonic-gate * input: int: signal the handler was called with
950Sstevel@tonic-gate * output: void
960Sstevel@tonic-gate */
970Sstevel@tonic-gate
980Sstevel@tonic-gate /* ARGSUSED */
990Sstevel@tonic-gate static void
sigterm_handler(int sig)1000Sstevel@tonic-gate sigterm_handler(int sig)
1010Sstevel@tonic-gate {
1020Sstevel@tonic-gate if (script_signal != SIGKILL) {
1030Sstevel@tonic-gate /* send SIGKILL SCRIPT_TIMEOUT_GRACE seconds from now */
1040Sstevel@tonic-gate script_signal = SIGKILL;
1050Sstevel@tonic-gate timeout = time(NULL) + SCRIPT_TIMEOUT_GRACE;
1060Sstevel@tonic-gate (void) alarm(SCRIPT_TIMEOUT_GRACE);
1070Sstevel@tonic-gate }
1080Sstevel@tonic-gate }
1090Sstevel@tonic-gate
1100Sstevel@tonic-gate /*
1110Sstevel@tonic-gate * run_script(): it forks a process to execute the script
1120Sstevel@tonic-gate *
1133431Scarlsonj * input: dhcp_smach_t *: the state machine
1140Sstevel@tonic-gate * const char *: the event name
1150Sstevel@tonic-gate * int: the pipe end owned by the script helper process
1160Sstevel@tonic-gate * output: void
1170Sstevel@tonic-gate */
1183431Scarlsonj
1190Sstevel@tonic-gate static void
run_script(dhcp_smach_t * dsmp,const char * event,int fd)1203431Scarlsonj run_script(dhcp_smach_t *dsmp, const char *event, int fd)
1210Sstevel@tonic-gate {
1220Sstevel@tonic-gate int n;
1230Sstevel@tonic-gate char c;
1243431Scarlsonj char *path;
1250Sstevel@tonic-gate char *name;
1260Sstevel@tonic-gate pid_t pid;
1270Sstevel@tonic-gate time_t now;
1280Sstevel@tonic-gate
129*9508SPeter.Memishian@Sun.COM if ((pid = fork()) == -1)
1300Sstevel@tonic-gate return;
131*9508SPeter.Memishian@Sun.COM
1320Sstevel@tonic-gate if (pid == 0) {
1333431Scarlsonj path = SCRIPT_PATH;
1343431Scarlsonj name = strrchr(path, '/') + 1;
1350Sstevel@tonic-gate
1360Sstevel@tonic-gate /* close all files */
1370Sstevel@tonic-gate closefrom(0);
1380Sstevel@tonic-gate
1390Sstevel@tonic-gate /* redirect stdin, stdout and stderr to /dev/null */
1400Sstevel@tonic-gate if ((n = open("/dev/null", O_RDWR)) < 0)
1413431Scarlsonj _exit(127);
1420Sstevel@tonic-gate
1430Sstevel@tonic-gate (void) dup2(n, STDOUT_FILENO);
1440Sstevel@tonic-gate (void) dup2(n, STDERR_FILENO);
1453431Scarlsonj (void) execl(path, name, dsmp->dsm_name, event, NULL);
1460Sstevel@tonic-gate _exit(127);
1470Sstevel@tonic-gate }
1480Sstevel@tonic-gate
1490Sstevel@tonic-gate /*
1500Sstevel@tonic-gate * the first timeout fires SCRIPT_TIMEOUT seconds from now.
1510Sstevel@tonic-gate */
1520Sstevel@tonic-gate timeout = time(NULL) + SCRIPT_TIMEOUT;
1530Sstevel@tonic-gate (void) sigset(SIGALRM, sigalarm_handler);
1540Sstevel@tonic-gate (void) alarm(SCRIPT_TIMEOUT);
1550Sstevel@tonic-gate
1560Sstevel@tonic-gate /*
1570Sstevel@tonic-gate * pass script's pid to dhcpagent.
1580Sstevel@tonic-gate */
1590Sstevel@tonic-gate (void) write(fd, &pid, sizeof (pid));
1600Sstevel@tonic-gate
1610Sstevel@tonic-gate for (;;) {
1620Sstevel@tonic-gate if (waitpid(pid, NULL, 0) >= 0) {
1630Sstevel@tonic-gate /* script has exited */
1640Sstevel@tonic-gate c = SCRIPT_OK;
1650Sstevel@tonic-gate break;
1660Sstevel@tonic-gate }
1670Sstevel@tonic-gate
168*9508SPeter.Memishian@Sun.COM if (errno != EINTR)
1690Sstevel@tonic-gate return;
1700Sstevel@tonic-gate
1710Sstevel@tonic-gate now = time(NULL);
1720Sstevel@tonic-gate if (now >= timeout) {
1730Sstevel@tonic-gate (void) kill(pid, script_signal);
1740Sstevel@tonic-gate if (script_signal == SIGKILL) {
1750Sstevel@tonic-gate c = SCRIPT_KILLED;
1760Sstevel@tonic-gate break;
1770Sstevel@tonic-gate }
1780Sstevel@tonic-gate
1790Sstevel@tonic-gate script_signal = SIGKILL;
1800Sstevel@tonic-gate timeout = now + SCRIPT_TIMEOUT_GRACE;
1810Sstevel@tonic-gate (void) alarm(SCRIPT_TIMEOUT_GRACE);
1820Sstevel@tonic-gate }
1830Sstevel@tonic-gate }
1840Sstevel@tonic-gate
1850Sstevel@tonic-gate (void) write(fd, &c, 1);
1860Sstevel@tonic-gate }
1870Sstevel@tonic-gate
1880Sstevel@tonic-gate /*
189*9508SPeter.Memishian@Sun.COM * script_init(): initialize script state on a given state machine
190*9508SPeter.Memishian@Sun.COM *
191*9508SPeter.Memishian@Sun.COM * input: dhcp_smach_t *: the state machine
192*9508SPeter.Memishian@Sun.COM * output: void
193*9508SPeter.Memishian@Sun.COM */
194*9508SPeter.Memishian@Sun.COM
195*9508SPeter.Memishian@Sun.COM void
script_init(dhcp_smach_t * dsmp)196*9508SPeter.Memishian@Sun.COM script_init(dhcp_smach_t *dsmp)
197*9508SPeter.Memishian@Sun.COM {
198*9508SPeter.Memishian@Sun.COM dsmp->dsm_script_pid = -1;
199*9508SPeter.Memishian@Sun.COM dsmp->dsm_script_helper_pid = -1;
200*9508SPeter.Memishian@Sun.COM dsmp->dsm_script_event_id = -1;
201*9508SPeter.Memishian@Sun.COM dsmp->dsm_script_fd = -1;
202*9508SPeter.Memishian@Sun.COM dsmp->dsm_script_callback = NULL;
203*9508SPeter.Memishian@Sun.COM dsmp->dsm_script_event = NULL;
204*9508SPeter.Memishian@Sun.COM dsmp->dsm_callback_arg = NULL;
205*9508SPeter.Memishian@Sun.COM }
206*9508SPeter.Memishian@Sun.COM
207*9508SPeter.Memishian@Sun.COM /*
2080Sstevel@tonic-gate * script_cleanup(): cleanup helper function
2090Sstevel@tonic-gate *
2103431Scarlsonj * input: dhcp_smach_t *: the state machine
2110Sstevel@tonic-gate * output: void
2120Sstevel@tonic-gate */
2130Sstevel@tonic-gate
2140Sstevel@tonic-gate static void
script_cleanup(dhcp_smach_t * dsmp)2153431Scarlsonj script_cleanup(dhcp_smach_t *dsmp)
2160Sstevel@tonic-gate {
217*9508SPeter.Memishian@Sun.COM /*
218*9508SPeter.Memishian@Sun.COM * We must clear dsm_script_pid prior to invoking the callback or we
219*9508SPeter.Memishian@Sun.COM * could get in an infinite loop via async_finish().
220*9508SPeter.Memishian@Sun.COM */
221*9508SPeter.Memishian@Sun.COM dsmp->dsm_script_pid = -1;
2223431Scarlsonj dsmp->dsm_script_helper_pid = -1;
2230Sstevel@tonic-gate
2243431Scarlsonj if (dsmp->dsm_script_fd != -1) {
2253431Scarlsonj assert(dsmp->dsm_script_event_id != -1);
2263431Scarlsonj (void) iu_unregister_event(eh, dsmp->dsm_script_event_id, NULL);
2273431Scarlsonj (void) close(dsmp->dsm_script_fd);
228*9508SPeter.Memishian@Sun.COM
229*9508SPeter.Memishian@Sun.COM assert(dsmp->dsm_script_callback != NULL);
2303431Scarlsonj dsmp->dsm_script_callback(dsmp, dsmp->dsm_callback_arg);
231*9508SPeter.Memishian@Sun.COM script_init(dsmp);
2320Sstevel@tonic-gate script_count--;
233*9508SPeter.Memishian@Sun.COM release_smach(dsmp); /* hold from script_start() */
2340Sstevel@tonic-gate }
2350Sstevel@tonic-gate }
2360Sstevel@tonic-gate
2370Sstevel@tonic-gate /*
238*9508SPeter.Memishian@Sun.COM * script_exit(): does cleanup and invokes the callback when the script exits
2390Sstevel@tonic-gate *
2400Sstevel@tonic-gate * input: eh_t *: unused
2410Sstevel@tonic-gate * int: the end of pipe owned by dhcpagent
2420Sstevel@tonic-gate * short: unused
2430Sstevel@tonic-gate * eh_event_id_t: unused
2443431Scarlsonj * void *: the state machine
2450Sstevel@tonic-gate * output: void
2460Sstevel@tonic-gate */
2470Sstevel@tonic-gate
2480Sstevel@tonic-gate /* ARGSUSED */
2490Sstevel@tonic-gate static void
script_exit(iu_eh_t * ehp,int fd,short events,iu_event_id_t id,void * arg)2500Sstevel@tonic-gate script_exit(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg)
2510Sstevel@tonic-gate {
252*9508SPeter.Memishian@Sun.COM char c;
2530Sstevel@tonic-gate
254*9508SPeter.Memishian@Sun.COM if (read(fd, &c, 1) <= 0)
2550Sstevel@tonic-gate c = SCRIPT_FAILED;
2560Sstevel@tonic-gate
257*9508SPeter.Memishian@Sun.COM if (c == SCRIPT_OK)
2580Sstevel@tonic-gate dhcpmsg(MSG_DEBUG, "script ok");
259*9508SPeter.Memishian@Sun.COM else if (c == SCRIPT_KILLED)
2600Sstevel@tonic-gate dhcpmsg(MSG_DEBUG, "script killed");
261*9508SPeter.Memishian@Sun.COM else
2620Sstevel@tonic-gate dhcpmsg(MSG_DEBUG, "script failed");
2630Sstevel@tonic-gate
2640Sstevel@tonic-gate script_cleanup(arg);
2650Sstevel@tonic-gate }
2660Sstevel@tonic-gate
2670Sstevel@tonic-gate /*
268*9508SPeter.Memishian@Sun.COM * script_start(): tries to start a script.
269*9508SPeter.Memishian@Sun.COM * if a script is already running, it's stopped first.
270*9508SPeter.Memishian@Sun.COM *
2710Sstevel@tonic-gate *
2723431Scarlsonj * input: dhcp_smach_t *: the state machine
2730Sstevel@tonic-gate * const char *: the event name
2740Sstevel@tonic-gate * script_callback_t: callback function
2750Sstevel@tonic-gate * void *: data to the callback function
2763431Scarlsonj * output: boolean_t: B_TRUE if script starts successfully
2770Sstevel@tonic-gate * int *: the returned value of the callback function if script
2780Sstevel@tonic-gate * starts unsuccessfully
2790Sstevel@tonic-gate */
2803431Scarlsonj
2813431Scarlsonj boolean_t
script_start(dhcp_smach_t * dsmp,const char * event,script_callback_t * callback,void * arg,int * status)2823431Scarlsonj script_start(dhcp_smach_t *dsmp, const char *event,
2833431Scarlsonj script_callback_t *callback, void *arg, int *status)
2840Sstevel@tonic-gate {
2850Sstevel@tonic-gate int n;
2860Sstevel@tonic-gate int fds[2];
2870Sstevel@tonic-gate pid_t pid;
2880Sstevel@tonic-gate iu_event_id_t event_id;
2890Sstevel@tonic-gate
2900Sstevel@tonic-gate assert(callback != NULL);
2910Sstevel@tonic-gate
292*9508SPeter.Memishian@Sun.COM if (dsmp->dsm_script_pid != -1) {
293*9508SPeter.Memishian@Sun.COM /* script is running, stop it */
294*9508SPeter.Memishian@Sun.COM dhcpmsg(MSG_DEBUG, "script_start: stopping ongoing script");
295*9508SPeter.Memishian@Sun.COM script_stop(dsmp);
296*9508SPeter.Memishian@Sun.COM }
297*9508SPeter.Memishian@Sun.COM
2980Sstevel@tonic-gate if (access(SCRIPT_PATH, X_OK) == -1) {
2990Sstevel@tonic-gate /* script does not exist */
3000Sstevel@tonic-gate goto out;
3010Sstevel@tonic-gate }
3020Sstevel@tonic-gate
3030Sstevel@tonic-gate /*
3040Sstevel@tonic-gate * dhcpagent owns one end of the pipe and script helper process
3050Sstevel@tonic-gate * owns the other end. dhcpagent reads on the pipe; and the helper
3060Sstevel@tonic-gate * process notifies it when the script exits.
3070Sstevel@tonic-gate */
3080Sstevel@tonic-gate if (pipe(fds) < 0) {
3090Sstevel@tonic-gate dhcpmsg(MSG_ERROR, "script_start: can't create pipe");
3100Sstevel@tonic-gate goto out;
3110Sstevel@tonic-gate }
3120Sstevel@tonic-gate
3130Sstevel@tonic-gate if ((pid = fork()) < 0) {
3140Sstevel@tonic-gate dhcpmsg(MSG_ERROR, "script_start: can't fork");
3150Sstevel@tonic-gate (void) close(fds[0]);
3160Sstevel@tonic-gate (void) close(fds[1]);
3170Sstevel@tonic-gate goto out;
3180Sstevel@tonic-gate }
3190Sstevel@tonic-gate
3200Sstevel@tonic-gate if (pid == 0) {
3210Sstevel@tonic-gate /*
3220Sstevel@tonic-gate * SIGCHLD is ignored in dhcpagent, the helper process
3230Sstevel@tonic-gate * needs it. it calls waitpid to wait for the script to exit.
3240Sstevel@tonic-gate */
3250Sstevel@tonic-gate (void) close(fds[0]);
3260Sstevel@tonic-gate (void) sigset(SIGCHLD, SIG_DFL);
3270Sstevel@tonic-gate (void) sigset(SIGTERM, sigterm_handler);
3283431Scarlsonj run_script(dsmp, event, fds[1]);
3290Sstevel@tonic-gate exit(0);
3300Sstevel@tonic-gate }
3310Sstevel@tonic-gate
3320Sstevel@tonic-gate (void) close(fds[1]);
3330Sstevel@tonic-gate
3340Sstevel@tonic-gate /* get the script's pid */
3353431Scarlsonj if (read(fds[0], &dsmp->dsm_script_pid, sizeof (pid_t)) !=
3360Sstevel@tonic-gate sizeof (pid_t)) {
3370Sstevel@tonic-gate (void) kill(pid, SIGKILL);
3383431Scarlsonj dsmp->dsm_script_pid = -1;
3390Sstevel@tonic-gate (void) close(fds[0]);
3400Sstevel@tonic-gate goto out;
3410Sstevel@tonic-gate }
3420Sstevel@tonic-gate
3433431Scarlsonj dsmp->dsm_script_helper_pid = pid;
3443431Scarlsonj event_id = iu_register_event(eh, fds[0], POLLIN, script_exit, dsmp);
3450Sstevel@tonic-gate if (event_id == -1) {
3460Sstevel@tonic-gate (void) close(fds[0]);
3473431Scarlsonj script_stop(dsmp);
3480Sstevel@tonic-gate goto out;
3490Sstevel@tonic-gate }
3500Sstevel@tonic-gate
3510Sstevel@tonic-gate script_count++;
3523431Scarlsonj dsmp->dsm_script_event_id = event_id;
3533431Scarlsonj dsmp->dsm_script_callback = callback;
3543431Scarlsonj dsmp->dsm_script_event = event;
3553431Scarlsonj dsmp->dsm_callback_arg = arg;
3563431Scarlsonj dsmp->dsm_script_fd = fds[0];
3573431Scarlsonj hold_smach(dsmp);
3583431Scarlsonj return (B_TRUE);
3590Sstevel@tonic-gate
3600Sstevel@tonic-gate out:
3610Sstevel@tonic-gate /* callback won't be called in script_exit, so call it here */
3623431Scarlsonj n = callback(dsmp, arg);
3630Sstevel@tonic-gate if (status != NULL)
3640Sstevel@tonic-gate *status = n;
3650Sstevel@tonic-gate
3663431Scarlsonj return (B_FALSE);
3670Sstevel@tonic-gate }
3680Sstevel@tonic-gate
3690Sstevel@tonic-gate /*
3700Sstevel@tonic-gate * script_stop(): stops the script if it is running
3710Sstevel@tonic-gate *
3723431Scarlsonj * input: dhcp_smach_t *: the state machine
3730Sstevel@tonic-gate * output: void
3740Sstevel@tonic-gate */
3753431Scarlsonj
3760Sstevel@tonic-gate void
script_stop(dhcp_smach_t * dsmp)3773431Scarlsonj script_stop(dhcp_smach_t *dsmp)
3780Sstevel@tonic-gate {
3793431Scarlsonj if (dsmp->dsm_script_pid != -1) {
3803431Scarlsonj assert(dsmp->dsm_script_helper_pid != -1);
3810Sstevel@tonic-gate
3820Sstevel@tonic-gate /*
3830Sstevel@tonic-gate * sends SIGTERM to the script and asks the helper process
3840Sstevel@tonic-gate * to send SIGKILL if it does not exit after
3850Sstevel@tonic-gate * SCRIPT_TIMEOUT_GRACE seconds.
3860Sstevel@tonic-gate */
3873431Scarlsonj (void) kill(dsmp->dsm_script_pid, SIGTERM);
3883431Scarlsonj (void) kill(dsmp->dsm_script_helper_pid, SIGTERM);
3890Sstevel@tonic-gate }
3900Sstevel@tonic-gate
3913431Scarlsonj script_cleanup(dsmp);
3920Sstevel@tonic-gate }
393