logo

utils-std

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

timeout.c (6484B)


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