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