logo

utils-std

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

timeout.c (5181B)


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