dmake: do not set MAKEFLAGS=k
[unleashed/tickless.git] / usr / src / cmd / cmd-inet / sbin / dhcpagent / script_handler.c
blobc2386ae47333a1f2acfb69e3bab3d7cf2a210a52
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
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 #include <poll.h>
39 #include "agent.h"
40 #include "script_handler.h"
41 #include "states.h"
42 #include "interface.h"
45 * scripts are directly managed by a script helper process. dhcpagent creates
46 * the helper process and it, in turn, creates a process to run the script
47 * dhcpagent owns one end of a pipe and the helper process owns the other end
48 * the helper process calls waitpid to wait for the script to exit. an alarm
49 * is set for SCRIPT_TIMEOUT seconds. If the alarm fires, SIGTERM is sent to
50 * the script process and a second alarm is set for SCRIPT_TIMEOUT_GRACE. if
51 * the second alarm fires, SIGKILL is sent to forcefully kill the script. when
52 * script exits, the helper process notifies dhcpagent by closing its end
53 * of the pipe.
56 unsigned int script_count;
59 * the signal to send to the script process. it is a global variable
60 * to this file as sigterm_handler needs it.
63 static int script_signal = SIGTERM;
66 * script's absolute timeout value. the first timeout is set to SCRIPT_TIMEOUT
67 * seconds from the time it is started. SIGTERM is sent on the first timeout
68 * the second timeout is set to SCRIPT_TIMEOUT_GRACE from the first timeout
69 * and SIGKILL is sent on the second timeout.
71 static time_t timeout;
74 * sigalarm_handler(): signal handler for SIGALRM
76 * input: int: signal the handler was called with
77 * output: void
80 /* ARGSUSED */
81 static void
82 sigalarm_handler(int sig)
84 time_t now;
86 /* set a another alarm if it fires too early */
87 now = time(NULL);
88 if (now < timeout)
89 (void) alarm(timeout - now);
93 * sigterm_handler(): signal handler for SIGTERM, fired when dhcpagent wants
94 * to stop the script
95 * input: int: signal the handler was called with
96 * output: void
99 /* ARGSUSED */
100 static void
101 sigterm_handler(int sig)
103 if (script_signal != SIGKILL) {
104 /* send SIGKILL SCRIPT_TIMEOUT_GRACE seconds from now */
105 script_signal = SIGKILL;
106 timeout = time(NULL) + SCRIPT_TIMEOUT_GRACE;
107 (void) alarm(SCRIPT_TIMEOUT_GRACE);
112 * run_script(): it forks a process to execute the script
114 * input: dhcp_smach_t *: the state machine
115 * const char *: the event name
116 * int: the pipe end owned by the script helper process
117 * output: void
120 static void
121 run_script(dhcp_smach_t *dsmp, const char *event, int fd)
123 int n;
124 char c;
125 char *path;
126 char *name;
127 pid_t pid;
128 time_t now;
130 if ((pid = fork()) == -1)
131 return;
133 if (pid == 0) {
134 path = SCRIPT_PATH;
135 name = strrchr(path, '/') + 1;
137 /* close all files */
138 closefrom(0);
140 /* redirect stdin, stdout and stderr to /dev/null */
141 if ((n = open("/dev/null", O_RDWR)) < 0)
142 _exit(127);
144 (void) dup2(n, STDOUT_FILENO);
145 (void) dup2(n, STDERR_FILENO);
146 (void) execl(path, name, dsmp->dsm_name, event, NULL);
147 _exit(127);
151 * the first timeout fires SCRIPT_TIMEOUT seconds from now.
153 timeout = time(NULL) + SCRIPT_TIMEOUT;
154 (void) sigset(SIGALRM, sigalarm_handler);
155 (void) alarm(SCRIPT_TIMEOUT);
158 * pass script's pid to dhcpagent.
160 (void) write(fd, &pid, sizeof (pid));
162 for (;;) {
163 if (waitpid(pid, NULL, 0) >= 0) {
164 /* script has exited */
165 c = SCRIPT_OK;
166 break;
169 if (errno != EINTR)
170 return;
172 now = time(NULL);
173 if (now >= timeout) {
174 (void) kill(pid, script_signal);
175 if (script_signal == SIGKILL) {
176 c = SCRIPT_KILLED;
177 break;
180 script_signal = SIGKILL;
181 timeout = now + SCRIPT_TIMEOUT_GRACE;
182 (void) alarm(SCRIPT_TIMEOUT_GRACE);
186 (void) write(fd, &c, 1);
190 * script_init(): initialize script state on a given state machine
192 * input: dhcp_smach_t *: the state machine
193 * output: void
196 void
197 script_init(dhcp_smach_t *dsmp)
199 dsmp->dsm_script_pid = -1;
200 dsmp->dsm_script_helper_pid = -1;
201 dsmp->dsm_script_event_id = -1;
202 dsmp->dsm_script_fd = -1;
203 dsmp->dsm_script_callback = NULL;
204 dsmp->dsm_script_event = NULL;
205 dsmp->dsm_callback_arg = NULL;
209 * script_cleanup(): cleanup helper function
211 * input: dhcp_smach_t *: the state machine
212 * output: void
215 static void
216 script_cleanup(dhcp_smach_t *dsmp)
219 * We must clear dsm_script_pid prior to invoking the callback or we
220 * could get in an infinite loop via async_finish().
222 dsmp->dsm_script_pid = -1;
223 dsmp->dsm_script_helper_pid = -1;
225 if (dsmp->dsm_script_fd != -1) {
226 assert(dsmp->dsm_script_event_id != -1);
227 (void) iu_unregister_event(eh, dsmp->dsm_script_event_id, NULL);
228 (void) close(dsmp->dsm_script_fd);
230 assert(dsmp->dsm_script_callback != NULL);
231 dsmp->dsm_script_callback(dsmp, dsmp->dsm_callback_arg);
232 script_init(dsmp);
233 script_count--;
234 release_smach(dsmp); /* hold from script_start() */
239 * script_exit(): does cleanup and invokes the callback when the script exits
241 * input: eh_t *: unused
242 * int: the end of pipe owned by dhcpagent
243 * short: unused
244 * eh_event_id_t: unused
245 * void *: the state machine
246 * output: void
249 /* ARGSUSED */
250 static void
251 script_exit(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg)
253 char c;
255 if (read(fd, &c, 1) <= 0)
256 c = SCRIPT_FAILED;
258 if (c == SCRIPT_OK)
259 dhcpmsg(MSG_DEBUG, "script ok");
260 else if (c == SCRIPT_KILLED)
261 dhcpmsg(MSG_DEBUG, "script killed");
262 else
263 dhcpmsg(MSG_DEBUG, "script failed");
265 script_cleanup(arg);
269 * script_start(): tries to start a script.
270 * if a script is already running, it's stopped first.
273 * input: dhcp_smach_t *: the state machine
274 * const char *: the event name
275 * script_callback_t: callback function
276 * void *: data to the callback function
277 * output: boolean_t: B_TRUE if script starts successfully
278 * int *: the returned value of the callback function if script
279 * starts unsuccessfully
282 boolean_t
283 script_start(dhcp_smach_t *dsmp, const char *event,
284 script_callback_t *callback, void *arg, int *status)
286 int n;
287 int fds[2];
288 pid_t pid;
289 iu_event_id_t event_id;
291 assert(callback != NULL);
293 if (dsmp->dsm_script_pid != -1) {
294 /* script is running, stop it */
295 dhcpmsg(MSG_DEBUG, "script_start: stopping ongoing script");
296 script_stop(dsmp);
299 if (access(SCRIPT_PATH, X_OK) == -1) {
300 /* script does not exist */
301 goto out;
305 * dhcpagent owns one end of the pipe and script helper process
306 * owns the other end. dhcpagent reads on the pipe; and the helper
307 * process notifies it when the script exits.
309 if (pipe(fds) < 0) {
310 dhcpmsg(MSG_ERROR, "script_start: can't create pipe");
311 goto out;
314 if ((pid = fork()) < 0) {
315 dhcpmsg(MSG_ERROR, "script_start: can't fork");
316 (void) close(fds[0]);
317 (void) close(fds[1]);
318 goto out;
321 if (pid == 0) {
323 * SIGCHLD is ignored in dhcpagent, the helper process
324 * needs it. it calls waitpid to wait for the script to exit.
326 (void) close(fds[0]);
327 (void) sigset(SIGCHLD, SIG_DFL);
328 (void) sigset(SIGTERM, sigterm_handler);
329 run_script(dsmp, event, fds[1]);
330 exit(0);
333 (void) close(fds[1]);
335 /* get the script's pid */
336 if (read(fds[0], &dsmp->dsm_script_pid, sizeof (pid_t)) !=
337 sizeof (pid_t)) {
338 (void) kill(pid, SIGKILL);
339 dsmp->dsm_script_pid = -1;
340 (void) close(fds[0]);
341 goto out;
344 dsmp->dsm_script_helper_pid = pid;
345 event_id = iu_register_event(eh, fds[0], POLLIN, script_exit, dsmp);
346 if (event_id == -1) {
347 (void) close(fds[0]);
348 script_stop(dsmp);
349 goto out;
352 script_count++;
353 dsmp->dsm_script_event_id = event_id;
354 dsmp->dsm_script_callback = callback;
355 dsmp->dsm_script_event = event;
356 dsmp->dsm_callback_arg = arg;
357 dsmp->dsm_script_fd = fds[0];
358 hold_smach(dsmp);
359 return (B_TRUE);
361 out:
362 /* callback won't be called in script_exit, so call it here */
363 n = callback(dsmp, arg);
364 if (status != NULL)
365 *status = n;
367 return (B_FALSE);
371 * script_stop(): stops the script if it is running
373 * input: dhcp_smach_t *: the state machine
374 * output: void
377 void
378 script_stop(dhcp_smach_t *dsmp)
380 if (dsmp->dsm_script_pid != -1) {
381 assert(dsmp->dsm_script_helper_pid != -1);
384 * sends SIGTERM to the script and asks the helper process
385 * to send SIGKILL if it does not exit after
386 * SCRIPT_TIMEOUT_GRACE seconds.
388 (void) kill(dsmp->dsm_script_pid, SIGTERM);
389 (void) kill(dsmp->dsm_script_helper_pid, SIGTERM);
392 script_cleanup(dsmp);