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]
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
32 #include <sys/types.h>
39 #include "script_handler.h"
41 #include "interface.h"
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
55 unsigned int script_count
;
58 * the signal to send to the script process. it is a global variable
59 * to this file as sigterm_handler needs it.
62 static int script_signal
= SIGTERM
;
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.
70 static time_t timeout
;
73 * sigalarm_handler(): signal handler for SIGALRM
75 * input: int: signal the handler was called with
81 sigalarm_handler(int sig
)
85 /* set a another alarm if it fires too early */
88 (void) alarm(timeout
- now
);
92 * sigterm_handler(): signal handler for SIGTERM, fired when dhcpagent wants
94 * input: int: signal the handler was called with
100 sigterm_handler(int sig
)
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
);
111 * run_script(): it forks a process to execute the script
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
120 run_script(dhcp_smach_t
*dsmp
, const char *event
, int fd
)
129 if ((pid
= fork()) == -1)
134 name
= strrchr(path
, '/') + 1;
136 /* close all files */
139 /* redirect stdin, stdout and stderr to /dev/null */
140 if ((n
= open("/dev/null", O_RDWR
)) < 0)
143 (void) dup2(n
, STDOUT_FILENO
);
144 (void) dup2(n
, STDERR_FILENO
);
145 (void) execl(path
, name
, dsmp
->dsm_name
, event
, NULL
);
150 * the first timeout fires SCRIPT_TIMEOUT seconds from now.
152 timeout
= time(NULL
) + SCRIPT_TIMEOUT
;
153 (void) sigset(SIGALRM
, sigalarm_handler
);
154 (void) alarm(SCRIPT_TIMEOUT
);
157 * pass script's pid to dhcpagent.
159 (void) write(fd
, &pid
, sizeof (pid
));
162 if (waitpid(pid
, NULL
, 0) >= 0) {
163 /* script has exited */
172 if (now
>= timeout
) {
173 (void) kill(pid
, script_signal
);
174 if (script_signal
== SIGKILL
) {
179 script_signal
= SIGKILL
;
180 timeout
= now
+ SCRIPT_TIMEOUT_GRACE
;
181 (void) alarm(SCRIPT_TIMEOUT_GRACE
);
185 (void) write(fd
, &c
, 1);
189 * script_init(): initialize script state on a given state machine
191 * input: dhcp_smach_t *: the state machine
196 script_init(dhcp_smach_t
*dsmp
)
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
;
208 * script_cleanup(): cleanup helper function
210 * input: dhcp_smach_t *: the state machine
215 script_cleanup(dhcp_smach_t
*dsmp
)
218 * We must clear dsm_script_pid prior to invoking the callback or we
219 * could get in an infinite loop via async_finish().
221 dsmp
->dsm_script_pid
= -1;
222 dsmp
->dsm_script_helper_pid
= -1;
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
);
229 assert(dsmp
->dsm_script_callback
!= NULL
);
230 dsmp
->dsm_script_callback(dsmp
, dsmp
->dsm_callback_arg
);
233 release_smach(dsmp
); /* hold from script_start() */
238 * script_exit(): does cleanup and invokes the callback when the script exits
240 * input: eh_t *: unused
241 * int: the end of pipe owned by dhcpagent
243 * eh_event_id_t: unused
244 * void *: the state machine
250 script_exit(iu_eh_t
*ehp
, int fd
, short events
, iu_event_id_t id
, void *arg
)
254 if (read(fd
, &c
, 1) <= 0)
258 dhcpmsg(MSG_DEBUG
, "script ok");
259 else if (c
== SCRIPT_KILLED
)
260 dhcpmsg(MSG_DEBUG
, "script killed");
262 dhcpmsg(MSG_DEBUG
, "script failed");
268 * script_start(): tries to start a script.
269 * if a script is already running, it's stopped first.
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
282 script_start(dhcp_smach_t
*dsmp
, const char *event
,
283 script_callback_t
*callback
, void *arg
, int *status
)
288 iu_event_id_t event_id
;
290 assert(callback
!= NULL
);
292 if (dsmp
->dsm_script_pid
!= -1) {
293 /* script is running, stop it */
294 dhcpmsg(MSG_DEBUG
, "script_start: stopping ongoing script");
298 if (access(SCRIPT_PATH
, X_OK
) == -1) {
299 /* script does not exist */
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.
309 dhcpmsg(MSG_ERROR
, "script_start: can't create pipe");
313 if ((pid
= fork()) < 0) {
314 dhcpmsg(MSG_ERROR
, "script_start: can't fork");
315 (void) close(fds
[0]);
316 (void) close(fds
[1]);
322 * SIGCHLD is ignored in dhcpagent, the helper process
323 * needs it. it calls waitpid to wait for the script to exit.
325 (void) close(fds
[0]);
326 (void) sigset(SIGCHLD
, SIG_DFL
);
327 (void) sigset(SIGTERM
, sigterm_handler
);
328 run_script(dsmp
, event
, fds
[1]);
332 (void) close(fds
[1]);
334 /* get the script's pid */
335 if (read(fds
[0], &dsmp
->dsm_script_pid
, sizeof (pid_t
)) !=
337 (void) kill(pid
, SIGKILL
);
338 dsmp
->dsm_script_pid
= -1;
339 (void) close(fds
[0]);
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]);
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];
361 /* callback won't be called in script_exit, so call it here */
362 n
= callback(dsmp
, arg
);
370 * script_stop(): stops the script if it is running
372 * input: dhcp_smach_t *: the state machine
377 script_stop(dhcp_smach_t
*dsmp
)
379 if (dsmp
->dsm_script_pid
!= -1) {
380 assert(dsmp
->dsm_script_helper_pid
!= -1);
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.
387 (void) kill(dsmp
->dsm_script_pid
, SIGTERM
);
388 (void) kill(dsmp
->dsm_script_helper_pid
, SIGTERM
);
391 script_cleanup(dsmp
);