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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <time.h>
30 #include <stdio.h>
31 #include <assert.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <sys/wait.h>
38 #include <signal.h>
39 #include <fcntl.h>
40 #include <dhcpmsg.h>
41 #include "script_handler.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 SIGARLM
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: struct ifslist *: the interface
114  *	    const char *: the event name
115  *	    int: the pipe end owned by the script helper process
116  *  output: void
117  */
118 static void
119 run_script(struct ifslist *ifsp, const char *event, int fd)
120 {
121 	int		n;
122 	char		c;
123 	char		*name;
124 	pid_t		pid;
125 	time_t		now;
126 	extern int	errno;
127 
128 	if ((pid = fork()) == -1) {
129 		return;
130 	}
131 	if (pid == 0) {
132 		name = strrchr(SCRIPT_PATH, '/') + 1;
133 
134 		/* close all files */
135 		closefrom(0);
136 
137 		/* redirect stdin, stdout and stderr to /dev/null */
138 		if ((n = open("/dev/null", O_RDWR)) < 0)
139 			exit(-1);
140 
141 		(void) dup2(n, STDOUT_FILENO);
142 		(void) dup2(n, STDERR_FILENO);
143 
144 		(void) execl(SCRIPT_PATH, name, ifsp->if_name, event, NULL);
145 		_exit(127);
146 	}
147 
148 	/*
149 	 * the first timeout fires SCRIPT_TIMEOUT seconds from now.
150 	 */
151 	timeout = time(NULL) + SCRIPT_TIMEOUT;
152 	(void) sigset(SIGALRM, sigalarm_handler);
153 	(void) alarm(SCRIPT_TIMEOUT);
154 
155 	/*
156 	 * pass script's pid to dhcpagent.
157 	 */
158 	(void) write(fd, &pid, sizeof (pid));
159 
160 	for (;;) {
161 		if (waitpid(pid, NULL, 0) >= 0) {
162 			/* script has exited */
163 			c = SCRIPT_OK;
164 			break;
165 		}
166 
167 		if (errno != EINTR) {
168 			return;
169 		}
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_cleanup(): cleanup helper function
190  *
191  *   input: struct ifslist *: the interface
192  *  output: void
193  */
194 
195 static void
196 script_cleanup(struct ifslist *ifsp)
197 {
198 	ifsp->if_script_helper_pid = -1;
199 	ifsp->if_script_pid = -1;
200 
201 	if (ifsp->if_script_fd != -1) {
202 		assert(ifsp->if_script_event_id != -1);
203 		assert(ifsp->if_script_callback != NULL);
204 
205 		(void) iu_unregister_event(eh, ifsp->if_script_event_id, NULL);
206 		(void) close(ifsp->if_script_fd);
207 		ifsp->if_script_event_id = -1;
208 		ifsp->if_script_fd = -1;
209 		ifsp->if_script_callback(ifsp, ifsp->if_callback_msg);
210 		ifsp->if_script_callback = NULL;
211 		ifsp->if_script_event = NULL;
212 		ifsp->if_callback_msg = NULL;
213 
214 		(void) release_ifs(ifsp);
215 		script_count--;
216 	}
217 }
218 
219 /*
220  * script_exit(): does cleanup and invokes callback when script exits
221  *
222  *   input: eh_t *: unused
223  *	    int: the end of pipe owned by dhcpagent
224  *	    short: unused
225  *	    eh_event_id_t: unused
226  *	    void *: the interface
227  *  output: void
228  */
229 
230 /* ARGSUSED */
231 static void
232 script_exit(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg)
233 {
234 	char		c;
235 
236 	if (read(fd, &c, 1) <= 0) {
237 		c = SCRIPT_FAILED;
238 	}
239 
240 	if (c == SCRIPT_OK) {
241 		dhcpmsg(MSG_DEBUG, "script ok");
242 	} else if (c == SCRIPT_KILLED) {
243 		dhcpmsg(MSG_DEBUG, "script killed");
244 	} else {
245 		dhcpmsg(MSG_DEBUG, "script failed");
246 	}
247 
248 	script_cleanup(arg);
249 }
250 
251 /*
252  * script_start(): tries to run the script
253  *
254  *   input: struct ifslist *: the interface
255  *	    const char *: the event name
256  *	    script_callback_t: callback function
257  *	    void *: data to the callback function
258  *  output: int: 1 if script starts successfully
259  *	    int *: the returned value of the callback function if script
260  *		starts unsuccessfully
261  */
262 int
263 script_start(struct ifslist *ifsp, const char *event,
264 		script_callback_t *callback, const char *msg, int *status)
265 {
266 	int		n;
267 	int		fds[2];
268 	pid_t		pid;
269 	iu_event_id_t	event_id;
270 
271 	assert(callback != NULL);
272 
273 	if (access(SCRIPT_PATH, X_OK) == -1) {
274 		/* script does not exist */
275 		goto out;
276 	}
277 	if (ifsp->if_script_pid != -1) {
278 		/* script is running, stop it */
279 		dhcpmsg(MSG_ERROR, "script_start: stop script");
280 		script_stop(ifsp);
281 	}
282 
283 	/*
284 	 * dhcpagent owns one end of the pipe and script helper process
285 	 * owns the other end. dhcpagent reads on the pipe; and the helper
286 	 * process notifies it when the script exits.
287 	 */
288 	if (pipe(fds) < 0) {
289 		dhcpmsg(MSG_ERROR, "script_start: can't create pipe");
290 		goto out;
291 	}
292 
293 	if ((pid = fork()) < 0) {
294 		dhcpmsg(MSG_ERROR, "script_start: can't fork");
295 		(void) close(fds[0]);
296 		(void) close(fds[1]);
297 		goto out;
298 	}
299 
300 	if (pid == 0) {
301 		/*
302 		 * SIGCHLD is ignored in dhcpagent, the helper process
303 		 * needs it. it calls waitpid to wait for the script to exit.
304 		 */
305 		(void) close(fds[0]);
306 		(void) sigset(SIGCHLD, SIG_DFL);
307 		(void) sigset(SIGTERM, sigterm_handler);
308 		run_script(ifsp, event, fds[1]);
309 		exit(0);
310 	}
311 
312 	(void) close(fds[1]);
313 
314 	/* get the script's pid */
315 	if (read(fds[0], &ifsp->if_script_pid, sizeof (pid_t)) !=
316 	    sizeof (pid_t)) {
317 		(void) kill(pid, SIGKILL);
318 		ifsp->if_script_pid = -1;
319 		(void) close(fds[0]);
320 		goto out;
321 	}
322 
323 	ifsp->if_script_helper_pid = pid;
324 	event_id = iu_register_event(eh, fds[0], POLLIN, script_exit, ifsp);
325 	if (event_id == -1) {
326 		(void) close(fds[0]);
327 		script_stop(ifsp);
328 		goto out;
329 	}
330 
331 	script_count++;
332 	ifsp->if_script_event_id = event_id;
333 	ifsp->if_script_callback = callback;
334 	ifsp->if_script_event = event;
335 	ifsp->if_callback_msg = msg;
336 	ifsp->if_script_fd = fds[0];
337 	hold_ifs(ifsp);
338 	return (1);
339 
340 out:
341 	/* callback won't be called in script_exit, so call it here */
342 	n = callback(ifsp, msg);
343 	if (status != NULL)
344 		*status = n;
345 
346 	return (0);
347 }
348 
349 /*
350  * script_stop(): stops the script if it is running
351  *
352  *   input: struct ifslist *: the interface
353  *  output: void
354  */
355 void
356 script_stop(struct ifslist *ifsp)
357 {
358 	if (ifsp->if_script_pid != -1) {
359 		assert(ifsp->if_script_helper_pid != -1);
360 
361 		/*
362 		 * sends SIGTERM to the script and asks the helper process
363 		 * to send SIGKILL if it does not exit after
364 		 * SCRIPT_TIMEOUT_GRACE seconds.
365 		 */
366 		(void) kill(ifsp->if_script_pid, SIGTERM);
367 		(void) kill(ifsp->if_script_helper_pid, SIGTERM);
368 	}
369 
370 	script_cleanup(ifsp);
371 }
372