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>
40 #include "script_handler.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
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
82 sigalarm_handler(int sig
)
86 /* set a another alarm if it fires too early */
89 (void) alarm(timeout
- now
);
93 * sigterm_handler(): signal handler for SIGTERM, fired when dhcpagent wants
95 * input: int: signal the handler was called with
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
121 run_script(dhcp_smach_t
*dsmp
, const char *event
, int fd
)
130 if ((pid
= fork()) == -1)
135 name
= strrchr(path
, '/') + 1;
137 /* close all files */
140 /* redirect stdin, stdout and stderr to /dev/null */
141 if ((n
= open("/dev/null", O_RDWR
)) < 0)
144 (void) dup2(n
, STDOUT_FILENO
);
145 (void) dup2(n
, STDERR_FILENO
);
146 (void) execl(path
, name
, dsmp
->dsm_name
, event
, NULL
);
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
));
163 if (waitpid(pid
, NULL
, 0) >= 0) {
164 /* script has exited */
173 if (now
>= timeout
) {
174 (void) kill(pid
, script_signal
);
175 if (script_signal
== SIGKILL
) {
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
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
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
);
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
244 * eh_event_id_t: unused
245 * void *: the state machine
251 script_exit(iu_eh_t
*ehp
, int fd
, short events
, iu_event_id_t id
, void *arg
)
255 if (read(fd
, &c
, 1) <= 0)
259 dhcpmsg(MSG_DEBUG
, "script ok");
260 else if (c
== SCRIPT_KILLED
)
261 dhcpmsg(MSG_DEBUG
, "script killed");
263 dhcpmsg(MSG_DEBUG
, "script failed");
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
283 script_start(dhcp_smach_t
*dsmp
, const char *event
,
284 script_callback_t
*callback
, void *arg
, int *status
)
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");
299 if (access(SCRIPT_PATH
, X_OK
) == -1) {
300 /* script does not exist */
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.
310 dhcpmsg(MSG_ERROR
, "script_start: can't create pipe");
314 if ((pid
= fork()) < 0) {
315 dhcpmsg(MSG_ERROR
, "script_start: can't fork");
316 (void) close(fds
[0]);
317 (void) close(fds
[1]);
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]);
333 (void) close(fds
[1]);
335 /* get the script's pid */
336 if (read(fds
[0], &dsmp
->dsm_script_pid
, sizeof (pid_t
)) !=
338 (void) kill(pid
, SIGKILL
);
339 dsmp
->dsm_script_pid
= -1;
340 (void) close(fds
[0]);
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]);
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];
362 /* callback won't be called in script_exit, so call it here */
363 n
= callback(dsmp
, arg
);
371 * script_stop(): stops the script if it is running
373 * input: dhcp_smach_t *: the state machine
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
);