]> git.netwichtig.de Git - user/henk/code/snooze.git/blob - snooze.c
typos
[user/henk/code/snooze.git] / snooze.c
1 /*
2  * snooze - run a command at a particular time
3  *
4  * To the extent possible under law,
5  * Christian Neukirchen <chneukirchen@gmail.com>
6  * has waived all copyright and related or neighboring rights to this work.
7  * http://creativecommons.org/publicdomain/zero/1.0/
8  */
9
10 /*
11 ##% gcc -Os -Wall -g -o $STEM $FILE -Wextra -Wwrite-strings
12 */
13
14 #include <sys/stat.h>
15 #include <sys/types.h>
16
17 #include <ctype.h>
18 #include <errno.h>
19 #include <signal.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <time.h>
24 #include <unistd.h>
25
26 #ifdef __linux__
27 #include <sys/auxv.h>
28 #endif
29
30 static long slack = 60;
31 #define SLEEP_PHASE 300
32 static int nflag, vflag;
33
34 static int timewait = -1;
35 static int randdelay = 0;
36 static char *timefile;
37
38 static sig_atomic_t alarm_rang = 0;
39
40 static void
41 wakeup(int sig)
42 {
43         (void)sig;
44         alarm_rang = 1;
45 }
46
47 static long
48 parse_int(char **s, size_t minn, size_t maxn)
49 {
50         long n;
51         char *end;
52
53         errno = 0;
54         n = strtol(*s, &end, 10);
55         if (errno) {
56                 perror("strtol");
57                 exit(1);
58         }
59         if (n < (long)minn || n >= (long)maxn) {
60                 fprintf(stderr, "number outside %zd <= n < %zd\n", minn, maxn);
61                 exit(1);
62         }
63         *s = end;
64         return n;
65 }
66
67 static int
68 parse(char *expr, char *buf, long bufsiz, int offset)
69 {
70         char *s;
71         long i, n = 0, n0 = 0;
72
73         memset(buf, ' ', bufsiz);
74
75         s = expr;
76         while (*s) {
77                 switch (*s) {
78                 case '0': case '1': case '2': case '3': case '4':
79                 case '5': case '6': case '7': case '8': case '9':
80                         n = parse_int(&s, -offset, bufsiz);
81                         buf[n+offset] = '*';
82                         break;
83                 case '-':
84                         n0 = n;
85                         s++;
86                         n = parse_int(&s, -offset, bufsiz);
87                         for (i = n0; i <= n; i++)
88                                 buf[i+offset] = '*';
89                         break;
90                 case '/':
91                         s++;
92                         n0 = n;
93                         n = parse_int(&s, -offset, bufsiz);
94                         if (n == 0)  // / = *
95                                 n = 1;
96                         for (i = n0; i < bufsiz; i += n)
97                                 buf[i+offset] = '*';
98                         break;
99                 case ',':
100                         s++;
101                         n = 0;
102                         break;
103                 case '*':
104                         s++;
105                         n = 0;
106                         for (i = 0; i < bufsiz; i++)
107                                 buf[i+offset] = '*';
108                         break;
109                 default:
110                         fprintf(stderr, "can't parse: %s %s\n", expr, s);
111                         exit(1);
112                 }
113         }
114
115         return 0;
116 }
117
118 char weekday[8] = {0};
119 char dayofmonth[31] = {0};
120 char month[12] = {0};
121 char dayofyear[366] = {0};
122 char weekofyear[53] = {0};
123 char hour[24] = {0};
124 char minute[60] = {0};
125 char second[61] = {0};
126
127 int
128 isoweek(struct tm *tm)
129 {
130         /* ugh, but easier than the correct formula... */
131         char weekstr[3];
132         char *w = weekstr;
133         strftime(weekstr, sizeof weekstr, "%V", tm);
134         return parse_int(&w, 1, 54);
135 }
136
137 time_t
138 find_next(time_t from)
139 {
140         time_t t;
141         struct tm *tm;
142
143         t = from;
144         tm = localtime(&t);
145
146 next_day:
147         while (!(weekday[tm->tm_wday] == '*'
148             && dayofmonth[tm->tm_mday-1] == '*'
149             && month[tm->tm_mon] == '*'
150             && weekofyear[isoweek(tm)] == '*'
151             && dayofyear[tm->tm_yday] == '*')) {
152                 if (month[tm->tm_mon] != '*') {
153                         // if month is not good, step month
154                         tm->tm_mon++;
155                         tm->tm_mday = 1;
156                 } else {
157                         tm->tm_mday++;
158                 }
159
160                 tm->tm_sec = 0;
161                 tm->tm_min = 0;
162                 tm->tm_hour = 0;
163
164                 t = mktime(tm);
165                 if (t > from+(365*24*60*60))  // no result within a year
166                         return -1;
167         }
168
169         int y = tm->tm_yday;  // save yday
170
171         while (!(hour[tm->tm_hour] == '*'
172             && minute[tm->tm_min] == '*'
173             && second[tm->tm_sec] == '*')) {
174                 if (hour[tm->tm_hour] != '*') {
175                         tm->tm_hour++;
176                         tm->tm_min = 0;
177                         tm->tm_sec = 0;
178                 } else if (minute[tm->tm_min] != '*') {
179                         tm->tm_min++;
180                         tm->tm_sec = 0;
181                 } else {
182                         tm->tm_sec++;
183                 }
184                 t = mktime(tm);
185                 if (tm->tm_yday != y)  // hit a different day, retry...
186                         goto next_day;
187         }
188             
189         return t;
190 }
191
192 static char isobuf[25];
193 char *
194 isotime(const struct tm *tm)
195 {
196         strftime(isobuf, sizeof isobuf, "%FT%T%z", tm);
197         return isobuf;
198 }
199
200 int main(int argc, char *argv[])
201 {
202         int c;
203         time_t t;
204         time_t now = time(0);
205
206         /* default: every day at 00:00:00 */
207         memset(weekday, '*', sizeof weekday);
208         memset(dayofmonth, '*', sizeof dayofmonth);
209         memset(month, '*', sizeof month);
210         memset(dayofyear, '*', sizeof dayofyear);
211         memset(weekofyear, '*', sizeof weekofyear);
212         hour[0] = '*';
213         minute[0] = '*';
214         second[0] = '*';
215
216         while ((c = getopt(argc, argv, "D:W:H:M:S:T:R:d:m:ns:t:vw:")) != -1)
217                 switch(c) {
218                 case 'D': parse(optarg, dayofyear, sizeof dayofyear, -1); break;
219                 case 'W': parse(optarg, weekofyear, sizeof weekofyear, -1); break;
220                 case 'H': parse(optarg, hour, sizeof hour, 0); break;
221                 case 'M': parse(optarg, minute, sizeof minute, 0); break;
222                 case 'S': parse(optarg, second, sizeof second, 0); break;
223                 case 'd': parse(optarg, dayofmonth, sizeof dayofmonth, -1); break;
224                 case 'm': parse(optarg, month, sizeof month, -1); break;
225                 case 'w': parse(optarg, weekday, sizeof weekday, 0);
226                         // special case: sunday is both 0 and 7.
227                         if (weekday[7] == '*') 
228                                 weekday[0] = '*';
229                         break;
230                 case 'n': nflag++; break;
231                 case 'v': vflag++; break;
232                 case 's': slack = atoi(optarg); break;
233                 case 'T': timewait = atoi(optarg); break;
234                 case 't': timefile = optarg; break;
235                 case 'R': randdelay = atoi(optarg); break;
236                 default:
237                         fprintf(stderr, "Usage: %s [-nv] [-t timefile] [-T timewait] [-R randdelay] [-s slack]\n"
238                             "  [-d mday] [-m mon] [-w wday] [-D yday] [-W yweek] [-H hour] [-M min] [-S sec] COMMAND...\n"
239                             "Timespec: exact: 1,3,5\n"
240                             "          range: 1-7\n"
241                             "          every n-th: /10\n", argv[0]);
242                         exit(2);
243                 }
244
245         time_t start = now + 1;
246
247         if (timefile) {
248                 struct stat st;
249                 if (stat(timefile, &st) < 0) {
250                         if (errno != ENOENT)
251                                 perror("stat");
252                         t = start - slack - 1 - timewait;
253                 } else {
254                         t = st.st_mtime;
255                 }
256                 if (timewait == -1) {
257                         while (t < start - slack)
258                                 t = find_next(t + 1);
259                         start = t;
260                 } else {
261                         if (t + timewait > start)
262                                 start = st.st_mtime + timewait;
263                 }
264         }
265
266         if (randdelay) {
267                 long delay;
268 #ifdef __linux__
269                 long rnd = getauxval(AT_RANDOM);
270                 if (rnd > 0)
271                         delay = rnd % randdelay;
272                 else
273 #endif
274                 {
275                         srand48(getpid() ^ start);
276                         delay = lrand48() % randdelay;
277                 }
278                 if (vflag)
279                         printf("randomly delaying by %lds.\n", delay);
280                 start += delay;
281         }
282
283         t = find_next(start);
284         if (t < 0) {
285                 fprintf(stderr, "no satisfying date found within a year.\n");
286                 exit(2);
287         }
288
289         if (nflag) {
290                 /* dry-run, just output the next 5 dates. */
291                 int i;
292                 for (i = 0; i < 5; i++) {
293                         if (t > 0)
294                                 printf("%s\n", isotime(localtime(&t)));
295                         t = find_next(t + 1);
296                 }
297                 exit(0);
298         }
299
300         struct tm *tm = localtime(&t);
301         if (vflag)
302                 printf("Snoozing until %s\n", isotime(tm));
303
304         // setup SIGALRM handler to force early execution
305         struct sigaction sa;
306         sa.sa_handler = &wakeup;
307         sa.sa_flags = SA_RESTART;
308         sigfillset(&sa.sa_mask);
309         sigaction(SIGALRM, &sa, NULL);  // XXX error handling
310
311         while (!alarm_rang) {
312                 now = time(0);
313                 t = mktime(tm);
314                 if (t <= now) {
315                         if (now - t <= slack)  // still about time
316                                 break;
317                         else {  // reschedule to next event
318                                 if (vflag)
319                                         printf("Missed execution at %s\n", isobuf);
320                                 t = find_next(now + 1);
321                                 tm = localtime(&t);
322                                 if (vflag)
323                                         printf("Snoozing until %s\n", isotime(tm));
324                         }
325                 } else {
326                         // do some sleeping, but not more than SLEEP_PHASE
327                         struct timespec ts;
328                         ts.tv_nsec = 0;
329                         ts.tv_sec = t - now > SLEEP_PHASE ? SLEEP_PHASE : t - now;
330                         nanosleep(&ts, 0);
331                         // we just iterate again when this exits early
332                 }
333         }
334
335         // no command to run, the outside script can go on
336         if (argc == optind)
337                 return 0;
338
339         if (vflag) {
340                 now = time(0);
341                 tm = localtime(&now);
342                 printf("Starting execution at %s\n", isotime(tm));
343         }
344
345         execvp(argv[optind], argv+optind);
346         perror("execvp");
347         return 255;
348 }