1 /* $NetBSD: timeout.c,v 1.4 2014/08/05 08:20:02 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org> 5 * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org> 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer 13 * in this position and unchanged. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include <sys/cdefs.h> 31 #if !defined(lint) 32 #if 0 33 __FBSDID("$FreeBSD: head/usr.bin/timeout/timeout.c 268763 2014-07-16 13:52:05Z bapt $"); 34 #else 35 __RCSID("$NetBSD: timeout.c,v 1.4 2014/08/05 08:20:02 christos Exp $"); 36 #endif 37 #endif /* not lint */ 38 39 #include <sys/time.h> 40 #include <sys/wait.h> 41 42 #include <err.h> 43 #include <errno.h> 44 #include <getopt.h> 45 #include <limits.h> 46 #include <signal.h> 47 #include <stdbool.h> 48 #include <stdio.h> 49 #include <stdlib.h> 50 #include <string.h> 51 #include <sysexits.h> 52 #include <unistd.h> 53 54 #define EXIT_TIMEOUT 124 55 56 static sig_atomic_t sig_chld = 0; 57 static sig_atomic_t sig_term = 0; 58 static sig_atomic_t sig_alrm = 0; 59 static sig_atomic_t sig_ign = 0; 60 61 static void __dead 62 usage(void) 63 { 64 65 fprintf(stderr, "Usage: %s [--signal sig | -s sig] [--preserve-status]" 66 " [--kill-after time | -k time] [--foreground] <duration> <command>" 67 " <arg ...>\n", getprogname()); 68 69 exit(EX_USAGE); 70 } 71 72 static double 73 parse_duration(const char *duration) 74 { 75 double ret; 76 char *end; 77 78 ret = strtod(duration, &end); 79 if (ret == 0 && end == duration) 80 errx(EXIT_FAILURE, "invalid duration"); 81 82 if (end == NULL || *end == '\0') 83 return (ret); 84 85 if (end != NULL && *(end + 1) != '\0') 86 errx(EX_USAGE, "invalid duration"); 87 88 switch (*end) { 89 case 's': 90 break; 91 case 'm': 92 ret *= 60; 93 break; 94 case 'h': 95 ret *= 60 * 60; 96 break; 97 case 'd': 98 ret *= 60 * 60 * 24; 99 break; 100 default: 101 errx(EX_USAGE, "invalid duration"); 102 } 103 104 if (ret < 0 || ret >= 100000000UL) 105 errx(EX_USAGE, "invalid duration"); 106 107 return (ret); 108 } 109 110 static int 111 parse_signal(const char *str) 112 { 113 long sig; 114 int i; 115 char *ep; 116 117 if (strncasecmp(str, "SIG", 3) == 0) { 118 str += 3; 119 120 for (i = 1; i < sys_nsig; i++) { 121 if (strcasecmp(str, sys_signame[i]) == 0) 122 return (i); 123 } 124 125 goto err; 126 } 127 128 errno = 0; 129 sig = strtol(str, &ep, 10); 130 131 if (str[0] == '\0' || *ep != '\0') 132 goto err; 133 if (errno == ERANGE && (sig == LONG_MAX || sig == LONG_MIN)) 134 goto err; 135 if (sig >= sys_nsig || sig < 0) 136 goto err; 137 138 return (int)sig; 139 140 err: 141 errx(EX_USAGE, "invalid signal"); 142 } 143 144 static void 145 sig_handler(int signo) 146 { 147 if (sig_ign != 0 && signo == sig_ign) { 148 sig_ign = 0; 149 return; 150 } 151 152 switch(signo) { 153 case 0: 154 case SIGINT: 155 case SIGHUP: 156 case SIGQUIT: 157 case SIGTERM: 158 sig_term = signo; 159 break; 160 case SIGCHLD: 161 sig_chld = 1; 162 break; 163 case SIGALRM: 164 sig_alrm = 1; 165 break; 166 } 167 } 168 169 static void 170 set_interval(double iv) 171 { 172 struct itimerval tim; 173 174 memset(&tim, 0, sizeof(tim)); 175 tim.it_value.tv_sec = (time_t)iv; 176 iv -= (double)tim.it_value.tv_sec; 177 tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL); 178 179 if (setitimer(ITIMER_REAL, &tim, NULL) == -1) 180 err(EX_OSERR, "setitimer()"); 181 } 182 183 int 184 main(int argc, char **argv) 185 { 186 int ch; 187 unsigned long i; 188 int foreground, preserve; 189 int error, pstat, status; 190 int killsig = SIGTERM; 191 pid_t pgid, pid, cpid; 192 double first_kill; 193 double second_kill; 194 bool timedout = false; 195 bool do_second_kill = false; 196 struct sigaction signals; 197 int signums[] = { 198 -1, 199 SIGTERM, 200 SIGINT, 201 SIGHUP, 202 SIGCHLD, 203 SIGALRM, 204 SIGQUIT, 205 }; 206 207 setprogname(argv[0]); 208 209 foreground = preserve = 0; 210 second_kill = 0; 211 cpid = -1; 212 pgid = -1; 213 214 const struct option longopts[] = { 215 { "preserve-status", no_argument, &preserve, 1 }, 216 { "foreground", no_argument, &foreground, 1 }, 217 { "kill-after", required_argument, NULL, 'k'}, 218 { "signal", required_argument, NULL, 's'}, 219 { "help", no_argument, NULL, 'h'}, 220 { NULL, 0, NULL, 0 } 221 }; 222 223 while ((ch = getopt_long(argc, argv, "+k:s:h", longopts, NULL)) != -1) { 224 switch (ch) { 225 case 'k': 226 do_second_kill = true; 227 second_kill = parse_duration(optarg); 228 break; 229 case 's': 230 killsig = parse_signal(optarg); 231 break; 232 case 0: 233 break; 234 case 'h': 235 default: 236 usage(); 237 break; 238 } 239 } 240 241 argc -= optind; 242 argv += optind; 243 244 if (argc < 2) 245 usage(); 246 247 first_kill = parse_duration(argv[0]); 248 argc--; 249 argv++; 250 251 if (!foreground) { 252 pgid = setpgid(0,0); 253 254 if (pgid == -1) 255 err(EX_OSERR, "setpgid()"); 256 } 257 258 memset(&signals, 0, sizeof(signals)); 259 sigemptyset(&signals.sa_mask); 260 261 if (killsig != SIGKILL && killsig != SIGSTOP) 262 signums[0] = killsig; 263 264 for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i ++) 265 sigaddset(&signals.sa_mask, signums[i]); 266 267 signals.sa_handler = sig_handler; 268 signals.sa_flags = SA_RESTART; 269 270 for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i ++) 271 if (signums[i] != -1 && signums[i] != 0 && 272 sigaction(signums[i], &signals, NULL) == -1) 273 err(EX_OSERR, "sigaction()"); 274 275 signal(SIGTTIN, SIG_IGN); 276 signal(SIGTTOU, SIG_IGN); 277 278 pid = fork(); 279 if (pid == -1) 280 err(EX_OSERR, "fork()"); 281 else if (pid == 0) { 282 /* child process */ 283 signal(SIGTTIN, SIG_DFL); 284 signal(SIGTTOU, SIG_DFL); 285 286 error = execvp(argv[0], argv); 287 if (error == -1) 288 err(EX_UNAVAILABLE, "exec()"); 289 } 290 291 if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1) 292 err(EX_OSERR, "sigprocmask()"); 293 294 /* parent continues here */ 295 set_interval(first_kill); 296 297 for (;;) { 298 sigemptyset(&signals.sa_mask); 299 sigsuspend(&signals.sa_mask); 300 301 if (sig_chld) { 302 sig_chld = 0; 303 while (((cpid = wait(&status)) < 0) && errno == EINTR) 304 continue; 305 306 if (cpid == pid) { 307 pstat = status; 308 break; 309 } 310 } else if (sig_alrm) { 311 sig_alrm = 0; 312 313 timedout = true; 314 if (!foreground) 315 killpg(pgid, killsig); 316 else 317 kill(pid, killsig); 318 319 if (do_second_kill) { 320 set_interval(second_kill); 321 second_kill = 0; 322 sig_ign = killsig; 323 killsig = SIGKILL; 324 } else 325 break; 326 327 } else if (sig_term) { 328 if (!foreground) 329 killpg(pgid, killsig); 330 else 331 kill(pid, (int)sig_term); 332 333 if (do_second_kill) { 334 set_interval(second_kill); 335 second_kill = 0; 336 sig_ign = killsig; 337 killsig = SIGKILL; 338 } else 339 break; 340 } 341 } 342 343 while (cpid != pid && wait(&pstat) == -1) { 344 if (errno != EINTR) 345 err(EX_OSERR, "waitpid()"); 346 } 347 348 if (WEXITSTATUS(pstat)) 349 pstat = WEXITSTATUS(pstat); 350 else if(WIFSIGNALED(pstat)) 351 pstat = 128 + WTERMSIG(pstat); 352 353 if (timedout && !preserve) 354 pstat = EXIT_TIMEOUT; 355 356 return (pstat); 357 } 358