logo

utils-std

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

timeout.c (5290B)


  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 <ctype.h>
  9. #include <errno.h>
  10. #include <signal.h> // kill
  11. #include <spawn.h> // posix_spawn
  12. #include <stdbool.h>
  13. #include <stdio.h> // fprintf
  14. #include <stdlib.h> // exit
  15. #include <string.h> // strerror
  16. #include <sys/wait.h>
  17. #include <time.h> // nanosleep
  18. #include <unistd.h> // getopt
  19. #define CMD_EXIT_TIMEOUT 124
  20. #define CMD_EXIT_FAILURE 125
  21. #define CMD_EXIT_E_EXEC 126
  22. #define CMD_EXIT_ENOENT 127
  23. #define CMD_EXIT_KILL 137
  24. extern char **environ;
  25. pid_t child = 0;
  26. const char *argv0 = "timeout";
  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(optarg, &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. char *endptr = NULL;
  75. unsigned long optoul = strtoul(optarg, &endptr, 10);
  76. if(!(errno == 0 && endptr != NULL && *endptr == '\0'))
  77. {
  78. fprintf(
  79. stderr,
  80. "timeout: error: Mix of digit and non-digit characters passed to -s option '%s'\n",
  81. endptr);
  82. return 1;
  83. }
  84. if(errno == 0 && optoul == 0) errno = EINVAL;
  85. if(errno == 0 && optoul >= util_sys_signame_len) errno = ERANGE;
  86. if(errno != 0)
  87. {
  88. fprintf(stderr,
  89. "timeout: error: Invalid number passed to -s '%s' (got: %lu): %s\n",
  90. optarg,
  91. optoul,
  92. strerror(errno));
  93. return 1;
  94. }
  95. if(util_sys_signame[optoul] == NULL)
  96. {
  97. fprintf(stderr, "timeout: error: Unknown signal number %lu\n", optoul);
  98. return 1;
  99. }
  100. term_sig = optoul;
  101. }
  102. else
  103. {
  104. arg = optarg;
  105. if(arg[0] == 'S' && arg[1] == 'I' && arg[2] == 'G') arg += 3;
  106. size_t i = 0;
  107. for(; i < (util_sys_signame_len); i++)
  108. {
  109. if(util_sys_signame[i] == NULL) continue;
  110. if(strcmp(arg, util_sys_signame[i]) == 0)
  111. {
  112. term_sig = i;
  113. term_signame = optarg;
  114. break;
  115. }
  116. }
  117. if(i >= util_sys_signame_len)
  118. {
  119. fprintf(stderr, "timeout: error: Unknown signal name '%s'\n", optarg);
  120. return CMD_EXIT_FAILURE;
  121. }
  122. }
  123. break;
  124. case ':':
  125. fprintf(stderr, "timeout: error: Missing operand for option: '-%c'\n", optopt);
  126. usage();
  127. return 1;
  128. case '?':
  129. fprintf(stderr, "timeout: error: Unrecognised option: '-%c'\n", optopt);
  130. usage();
  131. return 1;
  132. }
  133. }
  134. argv += optind;
  135. argc -= optind;
  136. struct timespec term = {.tv_sec = 0, .tv_nsec = 0};
  137. if(strtodur(argv[0], &term) < 0) return 1;
  138. if(term.tv_sec == 0 && term.tv_nsec == 0)
  139. {
  140. fprintf(stderr, "timeout: error: Got a duration of 0 for (%s) timeout.\n", term_signame);
  141. return CMD_EXIT_FAILURE;
  142. }
  143. argv++;
  144. argc--;
  145. if(signal(SIGCHLD, handle_sigchld) == SIG_ERR)
  146. {
  147. fprintf(
  148. stderr, "timeout: error: Failed registering handler for SIGCHLD: %s\n", strerror(errno));
  149. return CMD_EXIT_FAILURE;
  150. }
  151. if(posix_spawnp(&child, argv[0], NULL, NULL, argv, environ) != 0)
  152. {
  153. fprintf(stderr, "timeout: error: Failed executing '%s': %s\n", argv[0], strerror(errno));
  154. return CMD_EXIT_E_EXEC;
  155. }
  156. // Because PATH-finding typically relies on trial-and-error
  157. errno = 0;
  158. if(nanosleep(&term, &term) < 0)
  159. {
  160. if(errno != EINTR)
  161. {
  162. fprintf(stderr, "timeout: error: Failed sleeping: %s\n", strerror(errno));
  163. return CMD_EXIT_FAILURE;
  164. }
  165. }
  166. if(!kill_child)
  167. {
  168. return cmd_exit_timeout;
  169. }
  170. if(kill(child, term_sig) != 0)
  171. {
  172. fprintf(stderr,
  173. "timeout: error: 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: Failed sleeping: %s\n", strerror(errno));
  188. return CMD_EXIT_FAILURE;
  189. }
  190. }
  191. if(kill(child, SIGKILL) != 0)
  192. {
  193. fprintf(stderr,
  194. "timeout: error: Failed sending SIGKILL to child %d after timeout: %s\n",
  195. child,
  196. strerror(errno));
  197. return CMD_EXIT_FAILURE;
  198. }
  199. return cmd_exit_timeout;
  200. }