logo

cmd-timer

run command at a specific interval git clone https://anongit.hacktivis.me/git/cmd-timer.git

timer.c (4646B)


  1. // SPDX-FileCopyrightText: 2025 Haelwenn (lanodan) Monnier <contact+cmd-timer@hacktivis.me>
  2. // SPDX-License-Identifier: MPL-2.0
  3. #define _POSIX_C_SOURCE 200809L
  4. #include "strtodur.h"
  5. #include <errno.h>
  6. #include <limits.h> // UINT_MAX
  7. #include <signal.h> // sigaction
  8. #include <spawn.h> // posix_spawnp
  9. #include <stdbool.h>
  10. #include <stdio.h> // fprintf, fputs
  11. #include <stdlib.h> // exit
  12. #include <string.h> // strerror
  13. #include <sys/wait.h> // waitpid
  14. #include <time.h> // timer_create
  15. #include <unistd.h> // sleep, getopt
  16. extern char **environ;
  17. static char **args;
  18. const char *argv0 = "timer";
  19. struct clock_name
  20. {
  21. const char *name;
  22. clockid_t clock;
  23. };
  24. static void
  25. sig_timer(int sig)
  26. {
  27. pid_t child = -1;
  28. errno = 0;
  29. int ret = posix_spawnp(&child, args[0], NULL, NULL, args, environ);
  30. if(ret != 0)
  31. {
  32. fprintf(stderr, "timer: error: Failed spawning command '%s': %s\n", args[0], strerror(ret));
  33. exit(1);
  34. }
  35. }
  36. static void
  37. sig_chld(int sig)
  38. {
  39. int chld_stat = 0;
  40. waitpid((pid_t)-1, &chld_stat, WNOHANG);
  41. if(!WIFEXITED(chld_stat)) return;
  42. if(WEXITSTATUS(chld_stat) != 0) exit(WEXITSTATUS(chld_stat));
  43. }
  44. static void
  45. bad_usage()
  46. {
  47. fputs("Usage: timer [-w] [-c clock_id] <interval> <command> [arguments...]\n", stderr);
  48. exit(1);
  49. }
  50. static void
  51. timer_errx(int err, const char *msg)
  52. {
  53. fprintf(stderr, "timer: error: %s: %s\n", msg, strerror(errno));
  54. exit(err);
  55. }
  56. int
  57. main(int argc, char *argv[])
  58. {
  59. bool opt_w = false;
  60. clockid_t clockid = CLOCK_REALTIME;
  61. for(char c = -1; (c = getopt(argc, argv, ":c:w")) != -1;)
  62. {
  63. switch(c)
  64. {
  65. case 'c':
  66. {
  67. // clang-format off
  68. static struct clock_name clock_names[] = {
  69. {"m", CLOCK_MONOTONIC},
  70. {"r", CLOCK_REALTIME},
  71. #ifdef CLOCK_BOOTTIME
  72. {"b", CLOCK_BOOTTIME},
  73. #endif
  74. #ifdef CLOCK_REALTIME_ALARM
  75. {"ra", CLOCK_REALTIME_ALARM},
  76. #endif
  77. #ifdef CLOCK_BOOTTIME_ALARM
  78. {"ba", CLOCK_BOOTTIME_ALARM},
  79. #endif
  80. #ifdef CLOCK_TAI
  81. {"t", CLOCK_TAI},
  82. #endif
  83. };
  84. static size_t clock_len = sizeof(clock_names)/sizeof(*clock_names);
  85. // clang-format on
  86. size_t i = 0;
  87. for(; i < clock_len; i++)
  88. {
  89. if(strcmp(optarg, clock_names[i].name) == 0)
  90. {
  91. clockid = clock_names[i].clock;
  92. break;
  93. }
  94. }
  95. if(i == clock_len)
  96. {
  97. fprintf(stderr, "timer: error: Unknown clock '%s' given to -c\n", optarg);
  98. bad_usage();
  99. }
  100. break;
  101. }
  102. case 'w':
  103. opt_w = true;
  104. break;
  105. case ':':
  106. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  107. bad_usage();
  108. break;
  109. default:
  110. fprintf(stderr, "timer: error: Unhandled option -%c\n", optopt);
  111. bad_usage();
  112. return 1;
  113. }
  114. }
  115. argc -= optind;
  116. argv += optind;
  117. if(argc < 2)
  118. {
  119. fprintf(stderr, "timer: error: Got '%d' arguments instead of at least 2\n", argc);
  120. bad_usage();
  121. }
  122. struct timespec dur = {
  123. .tv_sec = 0,
  124. .tv_nsec = 0,
  125. };
  126. if(strtodur(*argv, &dur) != 0) return 1;
  127. if(dur.tv_sec == 0 && dur.tv_nsec == 0)
  128. {
  129. fputs("timer: error: Got a duration of 0\n", stderr);
  130. return 1;
  131. }
  132. argc--;
  133. argv++;
  134. struct sigaction sa_alrm;
  135. sa_alrm.sa_handler = &sig_timer;
  136. sigemptyset(&sa_alrm.sa_mask);
  137. sa_alrm.sa_flags = SA_RESTART;
  138. if(sigaction(SIGALRM, &sa_alrm, NULL) != 0)
  139. {
  140. fprintf(stderr, "timer: error: Failed registering signal handler: %s\n", strerror(errno));
  141. return 1;
  142. }
  143. struct sigaction sa_chld;
  144. sa_chld.sa_handler = &sig_chld;
  145. sigemptyset(&sa_chld.sa_mask);
  146. sa_chld.sa_flags = SA_RESTART;
  147. if(sigaction(SIGCHLD, &sa_chld, NULL) != 0)
  148. {
  149. fprintf(stderr, "timer: error: Failed registering signal handler: %s\n", strerror(errno));
  150. return 1;
  151. }
  152. struct sigevent timer_se;
  153. timer_se.sigev_signo = SIGALRM;
  154. timer_se.sigev_notify = SIGEV_SIGNAL;
  155. timer_t timer;
  156. if(timer_create(clockid, &timer_se, &timer) != 0)
  157. {
  158. fprintf(stderr, "timer: error: Failed creating timer: %s\n", strerror(errno));
  159. return 1;
  160. }
  161. struct itimerspec timerspec = {
  162. .it_value = dur,
  163. .it_interval = dur,
  164. };
  165. if(timer_settime(timer, 0, &timerspec, NULL) != 0)
  166. {
  167. fprintf(stderr, "timer: error: Failed setting timer: %s\n", strerror(errno));
  168. return 1;
  169. }
  170. args = argv;
  171. sigset_t sigmask;
  172. sigfillset(&sigmask);
  173. if(sigdelset(&sigmask, SIGALRM) != 0)
  174. timer_errx(1, "Failed adding SIGALRM to sigsuspend sigmask");
  175. if(sigdelset(&sigmask, SIGCHLD) != 0)
  176. timer_errx(1, "Failed adding SIGCHLD to sigsuspend sigmask");
  177. if(sigdelset(&sigmask, SIGINT) != 0) timer_errx(1, "Failed adding SIGINT to sigsuspend sigmask");
  178. if(sigdelset(&sigmask, SIGTERM) != 0)
  179. timer_errx(1, "Failed adding SIGTERM to sigsuspend sigmask");
  180. if(!opt_w) sig_timer(0);
  181. while(sigsuspend(&sigmask))
  182. ;
  183. return 0;
  184. }