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 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <time.h> 29 #include <stdio.h> 30 #include <assert.h> 31 #include <string.h> 32 #include <stdlib.h> 33 #include <unistd.h> 34 #include <sys/types.h> 35 #include <sys/wait.h> 36 #include <signal.h> 37 #include <fcntl.h> 38 #include <dhcpmsg.h> 39 40 #include "agent.h" 41 #include "script_handler.h" 42 #include "states.h" 43 #include "interface.h" 44 45 /* 46 * scripts are directly managed by a script helper process. dhcpagent creates 47 * the helper process and it, in turn, creates a process to run the script 48 * dhcpagent owns one end of a pipe and the helper process owns the other end 49 * the helper process calls waitpid to wait for the script to exit. an alarm 50 * is set for SCRIPT_TIMEOUT seconds. If the alarm fires, SIGTERM is sent to 51 * the script process and a second alarm is set for SCRIPT_TIMEOUT_GRACE. if 52 * the second alarm fires, SIGKILL is sent to forcefully kill the script. when 53 * script exits, the helper process notifies dhcpagent by closing its end 54 * of the pipe. 55 */ 56 57 unsigned int script_count; 58 59 /* 60 * the signal to send to the script process. it is a global variable 61 * to this file as sigterm_handler needs it. 62 */ 63 64 static int script_signal = SIGTERM; 65 66 /* 67 * script's absolute timeout value. the first timeout is set to SCRIPT_TIMEOUT 68 * seconds from the time it is started. SIGTERM is sent on the first timeout 69 * the second timeout is set to SCRIPT_TIMEOUT_GRACE from the first timeout 70 * and SIGKILL is sent on the second timeout. 71 */ 72 static time_t timeout; 73 74 /* 75 * sigalarm_handler(): signal handler for SIGARLM 76 * 77 * input: int: signal the handler was called with 78 * output: void 79 */ 80 81 /* ARGSUSED */ 82 static void 83 sigalarm_handler(int sig) 84 { 85 time_t now; 86 87 /* set a another alarm if it fires too early */ 88 now = time(NULL); 89 if (now < timeout) 90 (void) alarm(timeout - now); 91 } 92 93 /* 94 * sigterm_handler(): signal handler for SIGTERM, fired when dhcpagent wants 95 * to stop the script 96 * input: int: signal the handler was called with 97 * output: void 98 */ 99 100 /* ARGSUSED */ 101 static void 102 sigterm_handler(int sig) 103 { 104 if (script_signal != SIGKILL) { 105 /* send SIGKILL SCRIPT_TIMEOUT_GRACE seconds from now */ 106 script_signal = SIGKILL; 107 timeout = time(NULL) + SCRIPT_TIMEOUT_GRACE; 108 (void) alarm(SCRIPT_TIMEOUT_GRACE); 109 } 110 } 111 112 /* 113 * run_script(): it forks a process to execute the script 114 * 115 * input: dhcp_smach_t *: the state machine 116 * const char *: the event name 117 * int: the pipe end owned by the script helper process 118 * output: void 119 */ 120 121 static void 122 run_script(dhcp_smach_t *dsmp, const char *event, int fd) 123 { 124 int n; 125 char c; 126 char *path; 127 char *name; 128 pid_t pid; 129 time_t now; 130 131 if ((pid = fork()) == -1) { 132 return; 133 } 134 if (pid == 0) { 135 path = SCRIPT_PATH; 136 name = strrchr(path, '/') + 1; 137 138 /* close all files */ 139 closefrom(0); 140 141 /* redirect stdin, stdout and stderr to /dev/null */ 142 if ((n = open("/dev/null", O_RDWR)) < 0) 143 _exit(127); 144 145 (void) dup2(n, STDOUT_FILENO); 146 (void) dup2(n, STDERR_FILENO); 147 148 (void) execl(path, name, dsmp->dsm_name, event, NULL); 149 _exit(127); 150 } 151 152 /* 153 * the first timeout fires SCRIPT_TIMEOUT seconds from now. 154 */ 155 timeout = time(NULL) + SCRIPT_TIMEOUT; 156 (void) sigset(SIGALRM, sigalarm_handler); 157 (void) alarm(SCRIPT_TIMEOUT); 158 159 /* 160 * pass script's pid to dhcpagent. 161 */ 162 (void) write(fd, &pid, sizeof (pid)); 163 164 for (;;) { 165 if (waitpid(pid, NULL, 0) >= 0) { 166 /* script has exited */ 167 c = SCRIPT_OK; 168 break; 169 } 170 171 if (errno != EINTR) { 172 return; 173 } 174 175 now = time(NULL); 176 if (now >= timeout) { 177 (void) kill(pid, script_signal); 178 if (script_signal == SIGKILL) { 179 c = SCRIPT_KILLED; 180 break; 181 } 182 183 script_signal = SIGKILL; 184 timeout = now + SCRIPT_TIMEOUT_GRACE; 185 (void) alarm(SCRIPT_TIMEOUT_GRACE); 186 } 187 } 188 189 (void) write(fd, &c, 1); 190 } 191 192 /* 193 * script_cleanup(): cleanup helper function 194 * 195 * input: dhcp_smach_t *: the state machine 196 * output: void 197 */ 198 199 static void 200 script_cleanup(dhcp_smach_t *dsmp) 201 { 202 dsmp->dsm_script_helper_pid = -1; 203 dsmp->dsm_script_pid = -1; 204 205 if (dsmp->dsm_script_fd != -1) { 206 assert(dsmp->dsm_script_event_id != -1); 207 assert(dsmp->dsm_script_callback != NULL); 208 209 (void) iu_unregister_event(eh, dsmp->dsm_script_event_id, NULL); 210 (void) close(dsmp->dsm_script_fd); 211 dsmp->dsm_script_event_id = -1; 212 dsmp->dsm_script_fd = -1; 213 dsmp->dsm_script_callback(dsmp, dsmp->dsm_callback_arg); 214 dsmp->dsm_script_callback = NULL; 215 dsmp->dsm_script_event = NULL; 216 dsmp->dsm_callback_arg = NULL; 217 218 release_smach(dsmp); 219 script_count--; 220 } 221 } 222 223 /* 224 * script_exit(): does cleanup and invokes callback when script exits 225 * 226 * input: eh_t *: unused 227 * int: the end of pipe owned by dhcpagent 228 * short: unused 229 * eh_event_id_t: unused 230 * void *: the state machine 231 * output: void 232 */ 233 234 /* ARGSUSED */ 235 static void 236 script_exit(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) 237 { 238 char c; 239 240 if (read(fd, &c, 1) <= 0) { 241 c = SCRIPT_FAILED; 242 } 243 244 if (c == SCRIPT_OK) { 245 dhcpmsg(MSG_DEBUG, "script ok"); 246 } else if (c == SCRIPT_KILLED) { 247 dhcpmsg(MSG_DEBUG, "script killed"); 248 } else { 249 dhcpmsg(MSG_DEBUG, "script failed"); 250 } 251 252 script_cleanup(arg); 253 } 254 255 /* 256 * script_start(): tries to run the script 257 * 258 * input: dhcp_smach_t *: the state machine 259 * const char *: the event name 260 * script_callback_t: callback function 261 * void *: data to the callback function 262 * output: boolean_t: B_TRUE if script starts successfully 263 * int *: the returned value of the callback function if script 264 * starts unsuccessfully 265 */ 266 267 boolean_t 268 script_start(dhcp_smach_t *dsmp, const char *event, 269 script_callback_t *callback, void *arg, int *status) 270 { 271 int n; 272 int fds[2]; 273 pid_t pid; 274 iu_event_id_t event_id; 275 276 assert(callback != NULL); 277 278 if (access(SCRIPT_PATH, X_OK) == -1) { 279 /* script does not exist */ 280 goto out; 281 } 282 if (dsmp->dsm_script_pid != -1) { 283 /* script is running, stop it */ 284 dhcpmsg(MSG_ERROR, "script_start: stop script"); 285 script_stop(dsmp); 286 } 287 288 /* 289 * dhcpagent owns one end of the pipe and script helper process 290 * owns the other end. dhcpagent reads on the pipe; and the helper 291 * process notifies it when the script exits. 292 */ 293 if (pipe(fds) < 0) { 294 dhcpmsg(MSG_ERROR, "script_start: can't create pipe"); 295 goto out; 296 } 297 298 if ((pid = fork()) < 0) { 299 dhcpmsg(MSG_ERROR, "script_start: can't fork"); 300 (void) close(fds[0]); 301 (void) close(fds[1]); 302 goto out; 303 } 304 305 if (pid == 0) { 306 /* 307 * SIGCHLD is ignored in dhcpagent, the helper process 308 * needs it. it calls waitpid to wait for the script to exit. 309 */ 310 (void) close(fds[0]); 311 (void) sigset(SIGCHLD, SIG_DFL); 312 (void) sigset(SIGTERM, sigterm_handler); 313 run_script(dsmp, event, fds[1]); 314 exit(0); 315 } 316 317 (void) close(fds[1]); 318 319 /* get the script's pid */ 320 if (read(fds[0], &dsmp->dsm_script_pid, sizeof (pid_t)) != 321 sizeof (pid_t)) { 322 (void) kill(pid, SIGKILL); 323 dsmp->dsm_script_pid = -1; 324 (void) close(fds[0]); 325 goto out; 326 } 327 328 dsmp->dsm_script_helper_pid = pid; 329 event_id = iu_register_event(eh, fds[0], POLLIN, script_exit, dsmp); 330 if (event_id == -1) { 331 (void) close(fds[0]); 332 script_stop(dsmp); 333 goto out; 334 } 335 336 script_count++; 337 dsmp->dsm_script_event_id = event_id; 338 dsmp->dsm_script_callback = callback; 339 dsmp->dsm_script_event = event; 340 dsmp->dsm_callback_arg = arg; 341 dsmp->dsm_script_fd = fds[0]; 342 hold_smach(dsmp); 343 return (B_TRUE); 344 345 out: 346 /* callback won't be called in script_exit, so call it here */ 347 n = callback(dsmp, arg); 348 if (status != NULL) 349 *status = n; 350 351 return (B_FALSE); 352 } 353 354 /* 355 * script_stop(): stops the script if it is running 356 * 357 * input: dhcp_smach_t *: the state machine 358 * output: void 359 */ 360 361 void 362 script_stop(dhcp_smach_t *dsmp) 363 { 364 if (dsmp->dsm_script_pid != -1) { 365 assert(dsmp->dsm_script_helper_pid != -1); 366 367 /* 368 * sends SIGTERM to the script and asks the helper process 369 * to send SIGKILL if it does not exit after 370 * SCRIPT_TIMEOUT_GRACE seconds. 371 */ 372 (void) kill(dsmp->dsm_script_pid, SIGTERM); 373 (void) kill(dsmp->dsm_script_helper_pid, SIGTERM); 374 } 375 376 script_cleanup(dsmp); 377 } 378