logo

utils-std

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

timeout.c (5388B)


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