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 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 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 38 #include "agent.h" 39 #include "script_handler.h" 40 #include "states.h" 41 #include "interface.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 SIGALRM 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: dhcp_smach_t *: the state machine 114 * const char *: the event name 115 * int: the pipe end owned by the script helper process 116 * output: void 117 */ 118 119 static void 120 run_script(dhcp_smach_t *dsmp, const char *event, int fd) 121 { 122 int n; 123 char c; 124 char *path; 125 char *name; 126 pid_t pid; 127 time_t now; 128 129 if ((pid = fork()) == -1) 130 return; 131 132 if (pid == 0) { 133 path = SCRIPT_PATH; 134 name = strrchr(path, '/') + 1; 135 136 /* close all files */ 137 closefrom(0); 138 139 /* redirect stdin, stdout and stderr to /dev/null */ 140 if ((n = open("/dev/null", O_RDWR)) < 0) 141 _exit(127); 142 143 (void) dup2(n, STDOUT_FILENO); 144 (void) dup2(n, STDERR_FILENO); 145 (void) execl(path, name, dsmp->dsm_name, event, NULL); 146 _exit(127); 147 } 148 149 /* 150 * the first timeout fires SCRIPT_TIMEOUT seconds from now. 151 */ 152 timeout = time(NULL) + SCRIPT_TIMEOUT; 153 (void) sigset(SIGALRM, sigalarm_handler); 154 (void) alarm(SCRIPT_TIMEOUT); 155 156 /* 157 * pass script's pid to dhcpagent. 158 */ 159 (void) write(fd, &pid, sizeof (pid)); 160 161 for (;;) { 162 if (waitpid(pid, NULL, 0) >= 0) { 163 /* script has exited */ 164 c = SCRIPT_OK; 165 break; 166 } 167 168 if (errno != EINTR) 169 return; 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_init(): initialize script state on a given state machine 190 * 191 * input: dhcp_smach_t *: the state machine 192 * output: void 193 */ 194 195 void 196 script_init(dhcp_smach_t *dsmp) 197 { 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; 205 } 206 207 /* 208 * script_cleanup(): cleanup helper function 209 * 210 * input: dhcp_smach_t *: the state machine 211 * output: void 212 */ 213 214 static void 215 script_cleanup(dhcp_smach_t *dsmp) 216 { 217 /* 218 * We must clear dsm_script_pid prior to invoking the callback or we 219 * could get in an infinite loop via async_finish(). 220 */ 221 dsmp->dsm_script_pid = -1; 222 dsmp->dsm_script_helper_pid = -1; 223 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); 228 229 assert(dsmp->dsm_script_callback != NULL); 230 dsmp->dsm_script_callback(dsmp, dsmp->dsm_callback_arg); 231 script_init(dsmp); 232 script_count--; 233 release_smach(dsmp); /* hold from script_start() */ 234 } 235 } 236 237 /* 238 * script_exit(): does cleanup and invokes the callback when the script exits 239 * 240 * input: eh_t *: unused 241 * int: the end of pipe owned by dhcpagent 242 * short: unused 243 * eh_event_id_t: unused 244 * void *: the state machine 245 * output: void 246 */ 247 248 /* ARGSUSED */ 249 static void 250 script_exit(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) 251 { 252 char c; 253 254 if (read(fd, &c, 1) <= 0) 255 c = SCRIPT_FAILED; 256 257 if (c == SCRIPT_OK) 258 dhcpmsg(MSG_DEBUG, "script ok"); 259 else if (c == SCRIPT_KILLED) 260 dhcpmsg(MSG_DEBUG, "script killed"); 261 else 262 dhcpmsg(MSG_DEBUG, "script failed"); 263 264 script_cleanup(arg); 265 } 266 267 /* 268 * script_start(): tries to start a script. 269 * if a script is already running, it's stopped first. 270 * 271 * 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 279 */ 280 281 boolean_t 282 script_start(dhcp_smach_t *dsmp, const char *event, 283 script_callback_t *callback, void *arg, int *status) 284 { 285 int n; 286 int fds[2]; 287 pid_t pid; 288 iu_event_id_t event_id; 289 290 assert(callback != NULL); 291 292 if (dsmp->dsm_script_pid != -1) { 293 /* script is running, stop it */ 294 dhcpmsg(MSG_DEBUG, "script_start: stopping ongoing script"); 295 script_stop(dsmp); 296 } 297 298 if (access(SCRIPT_PATH, X_OK) == -1) { 299 /* script does not exist */ 300 goto out; 301 } 302 303 /* 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. 307 */ 308 if (pipe(fds) < 0) { 309 dhcpmsg(MSG_ERROR, "script_start: can't create pipe"); 310 goto out; 311 } 312 313 if ((pid = fork()) < 0) { 314 dhcpmsg(MSG_ERROR, "script_start: can't fork"); 315 (void) close(fds[0]); 316 (void) close(fds[1]); 317 goto out; 318 } 319 320 if (pid == 0) { 321 /* 322 * SIGCHLD is ignored in dhcpagent, the helper process 323 * needs it. it calls waitpid to wait for the script to exit. 324 */ 325 (void) close(fds[0]); 326 (void) sigset(SIGCHLD, SIG_DFL); 327 (void) sigset(SIGTERM, sigterm_handler); 328 run_script(dsmp, event, fds[1]); 329 exit(0); 330 } 331 332 (void) close(fds[1]); 333 334 /* get the script's pid */ 335 if (read(fds[0], &dsmp->dsm_script_pid, sizeof (pid_t)) != 336 sizeof (pid_t)) { 337 (void) kill(pid, SIGKILL); 338 dsmp->dsm_script_pid = -1; 339 (void) close(fds[0]); 340 goto out; 341 } 342 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]); 347 script_stop(dsmp); 348 goto out; 349 } 350 351 script_count++; 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]; 357 hold_smach(dsmp); 358 return (B_TRUE); 359 360 out: 361 /* callback won't be called in script_exit, so call it here */ 362 n = callback(dsmp, arg); 363 if (status != NULL) 364 *status = n; 365 366 return (B_FALSE); 367 } 368 369 /* 370 * script_stop(): stops the script if it is running 371 * 372 * input: dhcp_smach_t *: the state machine 373 * output: void 374 */ 375 376 void 377 script_stop(dhcp_smach_t *dsmp) 378 { 379 if (dsmp->dsm_script_pid != -1) { 380 assert(dsmp->dsm_script_helper_pid != -1); 381 382 /* 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. 386 */ 387 (void) kill(dsmp->dsm_script_pid, SIGTERM); 388 (void) kill(dsmp->dsm_script_helper_pid, SIGTERM); 389 } 390 391 script_cleanup(dsmp); 392 } 393