logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/

timeout.c (6316B)


  1. // utils-std: Collection of commonly available Unix tools
  2. // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
  3. // SPDX-License-Identifier: MPL-2.0
  4. #define _POSIX_C_SOURCE 200809L
  5. #define _DEFAULT_SOURCE // For NSIG in sys_signame.h, thanks glibc
  6. #include "../config.h"
  7. #include "../lib/sys_signame.h"
  8. #include "../libutils/getopt_nolong.h"
  9. #include "../libutils/strtodur.h"
  10. #include <ctype.h>
  11. #include <errno.h>
  12. #include <signal.h> // kill
  13. #include <spawn.h> // posix_spawn
  14. #include <stdbool.h>
  15. #include <stdio.h> // fprintf
  16. #include <stdlib.h> // exit
  17. #include <string.h> // strerror
  18. #include <sys/wait.h>
  19. #include <time.h> // nanosleep
  20. #include <unistd.h> // getopt
  21. #ifdef HAS_GETOPT_LONG
  22. #include <getopt.h>
  23. #endif
  24. #define CMD_EXIT_TIMEOUT 124
  25. #define CMD_EXIT_FAILURE 125
  26. #define CMD_EXIT_E_EXEC 126
  27. #define CMD_EXIT_ENOENT 127
  28. #define CMD_EXIT_KILL 137
  29. extern char **environ;
  30. pid_t child = 0;
  31. const char *argv0 = "timeout";
  32. static void
  33. handle_sigchld(int sig)
  34. {
  35. (void)sig;
  36. int stat_loc = 0;
  37. waitpid(child, &stat_loc, WNOHANG);
  38. // Not exit() to avoid running the atexit handlers
  39. // one of which being gcc --coverage handler which causes a hang
  40. _Exit(WEXITSTATUS(stat_loc));
  41. }
  42. static void
  43. usage(void)
  44. {
  45. fprintf(stderr,
  46. "Usage: timeout [-fp] [-k duration] [-s SIGNAL] duration command [arguments...]\n");
  47. }
  48. int
  49. main(int argc, char *argv[])
  50. {
  51. struct timespec time_kill = {.tv_sec = 0, .tv_nsec = 0};
  52. int term_sig = SIGTERM;
  53. const char *term_signame = "SIGTERM";
  54. bool kill_child = true;
  55. int cmd_exit_timeout = CMD_EXIT_TIMEOUT;
  56. char *arg = NULL;
  57. #ifdef HAS_GETOPT_LONG
  58. // Strictly for GNUisms compatibility so no long-only options
  59. // clang-format off
  60. static struct option opts[] = {
  61. {"kill-after", required_argument, NULL, 'k'},
  62. {"preserve-status", required_argument, NULL, 'p'},
  63. {"signal", required_argument, NULL, 's'},
  64. {0, 0, 0, 0},
  65. };
  66. // clang-format on
  67. // Need + as first character to get POSIX-style option parsing
  68. for(int c = -1; (c = getopt_long(argc, argv, "+:fk:ps:", opts, NULL)) != -1;)
  69. #else
  70. for(int c = -1; (c = getopt_nolong(argc, argv, ":fk:ps:")) != -1;)
  71. #endif
  72. {
  73. switch(c)
  74. {
  75. case 'f':
  76. kill_child = false;
  77. break;
  78. case 'k':
  79. if(strtodur(optarg, &time_kill) < 0) return 1;
  80. if(time_kill.tv_sec == 0 && time_kill.tv_nsec == 0)
  81. {
  82. fprintf(stderr, "timeout: error: Got a duration of 0 for SIGKILL timeout.\n");
  83. return CMD_EXIT_FAILURE;
  84. }
  85. break;
  86. case 'p':
  87. cmd_exit_timeout = 0;
  88. break;
  89. case 's':
  90. if(isdigit(optarg[0]))
  91. {
  92. char *endptr = NULL;
  93. unsigned long optoul = strtoul(optarg, &endptr, 10);
  94. if(!(errno == 0 && endptr != NULL && *endptr == '\0'))
  95. {
  96. fprintf(
  97. stderr,
  98. "timeout: error: Mix of digit and non-digit characters passed to -s option '%s'\n",
  99. endptr);
  100. return 1;
  101. }
  102. if(errno == 0 && optoul == 0) errno = EINVAL;
  103. if(errno == 0 && optoul >= util_sys_signame_len) errno = ERANGE;
  104. if(errno != 0)
  105. {
  106. fprintf(stderr,
  107. "timeout: error: Invalid number passed to -s '%s' (got: %lu): %s\n",
  108. optarg,
  109. optoul,
  110. strerror(errno));
  111. return 1;
  112. }
  113. if(util_sys_signame[optoul] == NULL)
  114. {
  115. fprintf(stderr, "timeout: error: Unknown signal number %lu\n", optoul);
  116. return 1;
  117. }
  118. term_sig = optoul;
  119. }
  120. else
  121. {
  122. arg = optarg;
  123. if(arg[0] == 'S' && arg[1] == 'I' && arg[2] == 'G') arg += 3;
  124. size_t i = 0;
  125. for(; i < (util_sys_signame_len); i++)
  126. {
  127. if(util_sys_signame[i] == NULL) continue;
  128. if(strcmp(arg, util_sys_signame[i]) == 0)
  129. {
  130. term_sig = i;
  131. term_signame = optarg;
  132. break;
  133. }
  134. }
  135. if(i >= util_sys_signame_len)
  136. {
  137. fprintf(stderr, "timeout: error: Unknown signal name '%s'\n", optarg);
  138. return CMD_EXIT_FAILURE;
  139. }
  140. }
  141. break;
  142. case ':':
  143. fprintf(stderr, "timeout: error: Missing operand for option: '-%c'\n", optopt);
  144. usage();
  145. return 1;
  146. case '?':
  147. GETOPT_UNKNOWN_OPT
  148. usage();
  149. return 1;
  150. }
  151. }
  152. argv += optind;
  153. argc -= optind;
  154. struct timespec term = {.tv_sec = 0, .tv_nsec = 0};
  155. if(strtodur(argv[0], &term) < 0) return 1;
  156. if(term.tv_sec == 0 && term.tv_nsec == 0)
  157. {
  158. fprintf(stderr, "timeout: error: Got a duration of 0 for (%s) timeout.\n", term_signame);
  159. return CMD_EXIT_FAILURE;
  160. }
  161. argv++;
  162. argc--;
  163. if(signal(SIGCHLD, handle_sigchld) == SIG_ERR)
  164. {
  165. fprintf(
  166. stderr, "timeout: error: Failed registering handler for SIGCHLD: %s\n", strerror(errno));
  167. return CMD_EXIT_FAILURE;
  168. }
  169. if(posix_spawnp(&child, argv[0], NULL, NULL, argv, environ) != 0)
  170. {
  171. fprintf(stderr, "timeout: error: Failed executing '%s': %s\n", argv[0], strerror(errno));
  172. return CMD_EXIT_E_EXEC;
  173. }
  174. // Because PATH-finding typically relies on trial-and-error
  175. errno = 0;
  176. if(nanosleep(&term, &term) < 0)
  177. {
  178. if(errno == EINTR)
  179. {
  180. fprintf(stderr,
  181. "timeout: warning: Interrupted during sleeping for %s, still had %ld.%09ld seconds "
  182. "remaining\n",
  183. term_signame,
  184. term.tv_sec,
  185. term.tv_nsec);
  186. }
  187. else
  188. {
  189. fprintf(
  190. stderr, "timeout: error: Failed sleeping before %s: %s\n", term_signame, strerror(errno));
  191. return CMD_EXIT_FAILURE;
  192. }
  193. }
  194. if(!kill_child)
  195. {
  196. return cmd_exit_timeout;
  197. }
  198. if(kill(child, term_sig) != 0)
  199. {
  200. fprintf(stderr,
  201. "timeout: error: Failed sending %s to child %d after timeout: %s\n",
  202. term_signame,
  203. child,
  204. strerror(errno));
  205. return CMD_EXIT_FAILURE;
  206. }
  207. if(time_kill.tv_sec == 0 && time_kill.tv_nsec == 0)
  208. {
  209. return cmd_exit_timeout;
  210. }
  211. if(nanosleep(&time_kill, &time_kill) < 0)
  212. {
  213. if(errno == EINTR)
  214. {
  215. fprintf(stderr,
  216. "timeout: warning: Interrupted during KILL-sleep, still had %ld.%09ld seconds "
  217. "remaining\n",
  218. term.tv_sec,
  219. term.tv_nsec);
  220. }
  221. else
  222. {
  223. fprintf(stderr, "timeout: error: Failed sleeping before KILL: %s\n", strerror(errno));
  224. return CMD_EXIT_FAILURE;
  225. }
  226. }
  227. if(kill(child, SIGKILL) != 0)
  228. {
  229. fprintf(stderr,
  230. "timeout: error: Failed sending SIGKILL to child %d after timeout: %s\n",
  231. child,
  232. strerror(errno));
  233. return CMD_EXIT_FAILURE;
  234. }
  235. return cmd_exit_timeout;
  236. }