logo

utils-std

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

timeout.c (6617B)


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