logo

utils-std

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

timeout.c (5288B)


  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. for(int c = -1; (c = getopt(argc, argv, ":fk:ps:")) != -1;)
  53. {
  54. switch(c)
  55. {
  56. case 'f':
  57. kill_child = false;
  58. break;
  59. case 'k':
  60. if(strtodur(optarg, &time_kill) < 0) return 1;
  61. if(time_kill.tv_sec == 0 && time_kill.tv_nsec == 0)
  62. {
  63. fprintf(stderr, "timeout: error: Got a duration of 0 for SIGKILL timeout.\n");
  64. return CMD_EXIT_FAILURE;
  65. }
  66. break;
  67. case 'p':
  68. cmd_exit_timeout = 0;
  69. break;
  70. case 's':
  71. if(isdigit(optarg[0]))
  72. {
  73. char *endptr = NULL;
  74. unsigned long optoul = strtoul(optarg, &endptr, 10);
  75. if(!(errno == 0 && endptr != NULL && *endptr == '\0'))
  76. {
  77. fprintf(
  78. stderr,
  79. "timeout: error: Mix of digit and non-digit characters passed to -s option '%s'\n",
  80. endptr);
  81. return 1;
  82. }
  83. if(errno == 0 && optoul == 0) errno = EINVAL;
  84. if(errno == 0 && optoul >= util_sys_signame_len) errno = ERANGE;
  85. if(errno != 0)
  86. {
  87. fprintf(stderr,
  88. "timeout: error: Invalid number passed to -s '%s' (got: %lu): %s\n",
  89. optarg,
  90. optoul,
  91. strerror(errno));
  92. return 1;
  93. }
  94. if(util_sys_signame[optoul] == NULL)
  95. {
  96. fprintf(stderr, "timeout: error: Unknown signal number %lu\n", optoul);
  97. return 1;
  98. }
  99. term_sig = optoul;
  100. }
  101. else
  102. {
  103. arg = optarg;
  104. if(arg[0] == 'S' && arg[1] == 'I' && arg[2] == 'G') arg += 3;
  105. size_t i = 0;
  106. for(; i < (util_sys_signame_len); i++)
  107. {
  108. if(util_sys_signame[i] == NULL) continue;
  109. if(strcmp(arg, util_sys_signame[i]) == 0)
  110. {
  111. term_sig = i;
  112. term_signame = optarg;
  113. break;
  114. }
  115. }
  116. if(i >= util_sys_signame_len)
  117. {
  118. fprintf(stderr, "timeout: error: Unknown signal name '%s'\n", optarg);
  119. return CMD_EXIT_FAILURE;
  120. }
  121. }
  122. break;
  123. case ':':
  124. fprintf(stderr, "timeout: error: Missing operand for option: '-%c'\n", optopt);
  125. usage();
  126. return 1;
  127. case '?':
  128. fprintf(stderr, "timeout: error: Unrecognised option: '-%c'\n", optopt);
  129. usage();
  130. return 1;
  131. }
  132. }
  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: error: Got a duration of 0 for (%s) timeout.\n", term_signame);
  140. return CMD_EXIT_FAILURE;
  141. }
  142. argv++;
  143. argc--;
  144. if(signal(SIGCHLD, handle_sigchld) == SIG_ERR)
  145. {
  146. fprintf(
  147. stderr, "timeout: error: 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: error: Failed executing '%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: Failed sleeping: %s\n", strerror(errno));
  162. return CMD_EXIT_FAILURE;
  163. }
  164. }
  165. if(!kill_child)
  166. {
  167. return cmd_exit_timeout;
  168. }
  169. if(kill(child, term_sig) != 0)
  170. {
  171. fprintf(stderr,
  172. "timeout: error: Failed sending %s to child %d after timeout: %s\n",
  173. term_signame,
  174. child,
  175. strerror(errno));
  176. return CMD_EXIT_FAILURE;
  177. }
  178. if(time_kill.tv_sec == 0 && time_kill.tv_nsec == 0)
  179. {
  180. return cmd_exit_timeout;
  181. }
  182. if(nanosleep(&time_kill, &time_kill) < 0)
  183. {
  184. if(errno != EINTR)
  185. {
  186. fprintf(stderr, "timeout: error: Failed sleeping: %s\n", strerror(errno));
  187. return CMD_EXIT_FAILURE;
  188. }
  189. }
  190. if(kill(child, SIGKILL) != 0)
  191. {
  192. fprintf(stderr,
  193. "timeout: error: Failed sending SIGKILL to child %d after timeout: %s\n",
  194. child,
  195. strerror(errno));
  196. return CMD_EXIT_FAILURE;
  197. }
  198. return cmd_exit_timeout;
  199. }