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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 #include <time.h> 30 #include <stdio.h> 31 #include <assert.h> 32 #include <string.h> 33 #include <stdlib.h> 34 #include <unistd.h> 35 #include <sys/types.h> 36 #include <sys/stat.h> 37 #include <sys/wait.h> 38 #include <signal.h> 39 #include <fcntl.h> 40 #include <dhcpmsg.h> 41 #include "script_handler.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 SIGARLM 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: struct ifslist *: the interface 114 * const char *: the event name 115 * int: the pipe end owned by the script helper process 116 * output: void 117 */ 118 static void 119 run_script(struct ifslist *ifsp, const char *event, int fd) 120 { 121 int n; 122 char c; 123 char *name; 124 pid_t pid; 125 time_t now; 126 extern int errno; 127 128 if ((pid = fork()) == -1) { 129 return; 130 } 131 if (pid == 0) { 132 name = strrchr(SCRIPT_PATH, '/') + 1; 133 134 /* close all files */ 135 closefrom(0); 136 137 /* redirect stdin, stdout and stderr to /dev/null */ 138 if ((n = open("/dev/null", O_RDWR)) < 0) 139 exit(-1); 140 141 (void) dup2(n, STDOUT_FILENO); 142 (void) dup2(n, STDERR_FILENO); 143 144 (void) execl(SCRIPT_PATH, name, ifsp->if_name, event, NULL); 145 _exit(127); 146 } 147 148 /* 149 * the first timeout fires SCRIPT_TIMEOUT seconds from now. 150 */ 151 timeout = time(NULL) + SCRIPT_TIMEOUT; 152 (void) sigset(SIGALRM, sigalarm_handler); 153 (void) alarm(SCRIPT_TIMEOUT); 154 155 /* 156 * pass script's pid to dhcpagent. 157 */ 158 (void) write(fd, &pid, sizeof (pid)); 159 160 for (;;) { 161 if (waitpid(pid, NULL, 0) >= 0) { 162 /* script has exited */ 163 c = SCRIPT_OK; 164 break; 165 } 166 167 if (errno != EINTR) { 168 return; 169 } 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_cleanup(): cleanup helper function 190 * 191 * input: struct ifslist *: the interface 192 * output: void 193 */ 194 195 static void 196 script_cleanup(struct ifslist *ifsp) 197 { 198 ifsp->if_script_helper_pid = -1; 199 ifsp->if_script_pid = -1; 200 201 if (ifsp->if_script_fd != -1) { 202 assert(ifsp->if_script_event_id != -1); 203 assert(ifsp->if_script_callback != NULL); 204 205 (void) iu_unregister_event(eh, ifsp->if_script_event_id, NULL); 206 (void) close(ifsp->if_script_fd); 207 ifsp->if_script_event_id = -1; 208 ifsp->if_script_fd = -1; 209 ifsp->if_script_callback(ifsp, ifsp->if_callback_msg); 210 ifsp->if_script_callback = NULL; 211 ifsp->if_script_event = NULL; 212 ifsp->if_callback_msg = NULL; 213 214 (void) release_ifs(ifsp); 215 script_count--; 216 } 217 } 218 219 /* 220 * script_exit(): does cleanup and invokes callback when script exits 221 * 222 * input: eh_t *: unused 223 * int: the end of pipe owned by dhcpagent 224 * short: unused 225 * eh_event_id_t: unused 226 * void *: the interface 227 * output: void 228 */ 229 230 /* ARGSUSED */ 231 static void 232 script_exit(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) 233 { 234 char c; 235 236 if (read(fd, &c, 1) <= 0) { 237 c = SCRIPT_FAILED; 238 } 239 240 if (c == SCRIPT_OK) { 241 dhcpmsg(MSG_DEBUG, "script ok"); 242 } else if (c == SCRIPT_KILLED) { 243 dhcpmsg(MSG_DEBUG, "script killed"); 244 } else { 245 dhcpmsg(MSG_DEBUG, "script failed"); 246 } 247 248 script_cleanup(arg); 249 } 250 251 /* 252 * script_start(): tries to run the script 253 * 254 * input: struct ifslist *: the interface 255 * const char *: the event name 256 * script_callback_t: callback function 257 * void *: data to the callback function 258 * output: int: 1 if script starts successfully 259 * int *: the returned value of the callback function if script 260 * starts unsuccessfully 261 */ 262 int 263 script_start(struct ifslist *ifsp, const char *event, 264 script_callback_t *callback, const char *msg, int *status) 265 { 266 int n; 267 int fds[2]; 268 pid_t pid; 269 iu_event_id_t event_id; 270 271 assert(callback != NULL); 272 273 if (access(SCRIPT_PATH, X_OK) == -1) { 274 /* script does not exist */ 275 goto out; 276 } 277 if (ifsp->if_script_pid != -1) { 278 /* script is running, stop it */ 279 dhcpmsg(MSG_ERROR, "script_start: stop script"); 280 script_stop(ifsp); 281 } 282 283 /* 284 * dhcpagent owns one end of the pipe and script helper process 285 * owns the other end. dhcpagent reads on the pipe; and the helper 286 * process notifies it when the script exits. 287 */ 288 if (pipe(fds) < 0) { 289 dhcpmsg(MSG_ERROR, "script_start: can't create pipe"); 290 goto out; 291 } 292 293 if ((pid = fork()) < 0) { 294 dhcpmsg(MSG_ERROR, "script_start: can't fork"); 295 (void) close(fds[0]); 296 (void) close(fds[1]); 297 goto out; 298 } 299 300 if (pid == 0) { 301 /* 302 * SIGCHLD is ignored in dhcpagent, the helper process 303 * needs it. it calls waitpid to wait for the script to exit. 304 */ 305 (void) close(fds[0]); 306 (void) sigset(SIGCHLD, SIG_DFL); 307 (void) sigset(SIGTERM, sigterm_handler); 308 run_script(ifsp, event, fds[1]); 309 exit(0); 310 } 311 312 (void) close(fds[1]); 313 314 /* get the script's pid */ 315 if (read(fds[0], &ifsp->if_script_pid, sizeof (pid_t)) != 316 sizeof (pid_t)) { 317 (void) kill(pid, SIGKILL); 318 ifsp->if_script_pid = -1; 319 (void) close(fds[0]); 320 goto out; 321 } 322 323 ifsp->if_script_helper_pid = pid; 324 event_id = iu_register_event(eh, fds[0], POLLIN, script_exit, ifsp); 325 if (event_id == -1) { 326 (void) close(fds[0]); 327 script_stop(ifsp); 328 goto out; 329 } 330 331 script_count++; 332 ifsp->if_script_event_id = event_id; 333 ifsp->if_script_callback = callback; 334 ifsp->if_script_event = event; 335 ifsp->if_callback_msg = msg; 336 ifsp->if_script_fd = fds[0]; 337 hold_ifs(ifsp); 338 return (1); 339 340 out: 341 /* callback won't be called in script_exit, so call it here */ 342 n = callback(ifsp, msg); 343 if (status != NULL) 344 *status = n; 345 346 return (0); 347 } 348 349 /* 350 * script_stop(): stops the script if it is running 351 * 352 * input: struct ifslist *: the interface 353 * output: void 354 */ 355 void 356 script_stop(struct ifslist *ifsp) 357 { 358 if (ifsp->if_script_pid != -1) { 359 assert(ifsp->if_script_helper_pid != -1); 360 361 /* 362 * sends SIGTERM to the script and asks the helper process 363 * to send SIGKILL if it does not exit after 364 * SCRIPT_TIMEOUT_GRACE seconds. 365 */ 366 (void) kill(ifsp->if_script_pid, SIGTERM); 367 (void) kill(ifsp->if_script_helper_pid, SIGTERM); 368 } 369 370 script_cleanup(ifsp); 371 } 372