2 This program is free software: you can redistribute it and/or modify
3 it under the terms of the GNU General Public License as published by
4 the Free Software Foundation, either version 3 of the License, or
5 (at your option) any later version.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
12 You should have received a copy of the GNU General Public License
13 along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include <sys/types.h>
28 #define PROGRAM_NAME "cronlist"
30 const char IGNORE_DOM
= 'm';
31 const char IGNORE_DOW
= 'w';
32 const char IGNORE_NOTHING
= ' ';
50 struct time_entry EMPTY_TIME_ENTRY
;
54 const int increment
= 8192;
56 char *buf
= malloc(capa
);
60 res
= fread(buf
+offs
, 1, increment
, f
);
62 if (res
< increment
) {
67 buf
= realloc(buf
, capa
);
71 char *empty_string (void)
78 char *slurp_command (char *command
)
81 FILE *p
= popen(command
, "r");
82 if (!p
) return empty_string();
89 char *slurp_file (char *filename
)
92 FILE *f
= fopen(filename
, "r");
93 if (!f
) return empty_string();
100 char *next_line (char *p
)
102 while (*p
&& *p
!= '\n') p
++;
107 char *skip_spaces (char *p
)
109 while (*p
&& isspace(*p
)) p
++;
113 char *skip_blanks (char *p
)
115 while (*p
&& isblank(*p
)) p
++;
119 char *skip_irrelevant (char *p
)
122 while (*p
&& *p
== '#') {
131 return !*p
|| *p
== '\n';
134 typedef int (*fn
)(char *, char **);
136 char *read_number (char *buf
, fn get_number
, int *res
)
140 *res
= strtol(buf
, &p
, 10);
141 if (buf
== p
) return NULL
;
144 else if (!get_number
) return NULL
;
146 *res
= get_number(buf
, &p
);
147 if (buf
== p
) return NULL
;
152 int find_string (char *s
, char *table
[])
155 for (i
= 0; table
[i
]; i
++) {
156 if (!strncasecmp(table
[i
], s
, strlen(table
[i
]))) return i
;
161 int get_from_tables (char *buf
, char **tables
[], char **end
)
164 for (i
= 0; tables
[i
]; i
++) {
165 idx
= find_string(buf
, tables
[i
]);
167 *end
= buf
+ strlen(tables
[i
][idx
]);
176 static char *mon_fullnames
[] = {
177 "January", "February", "March", "April", "May", "June", "July", "August",
178 "September", "October", "November", "December", NULL
};
179 static char *mon_abbrevs
[] = {
180 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
181 "Sep", "Oct", "Nov", "Dec", NULL
};
182 static char **months
[] = { mon_fullnames
, mon_abbrevs
, NULL
};
185 int get_month (char *buf
, char **end
)
187 return get_from_tables(buf
, months
, end
) + 1;
190 static char *dow_fullnames
[] = {
191 "Sunday", "Monday", "Tuesday", "Wednesday",
192 "Thursday", "Friday", "Saturday", NULL
};
193 static char *dow_abbrevs
[] = {
194 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL
};
195 static char **dows
[] = { dow_fullnames
, dow_abbrevs
, NULL
};
198 int get_dow_1 (char *buf
, char **end
)
200 return get_from_tables(buf
, dows
, end
);
203 int get_dow_2 (char *buf
, char **end
)
205 int res
= get_dow_1(buf
, end
);
206 return res
== 0 ? 7 : res
;
210 char *read_range (char *buf
, int min
, int max
, int offs
,
211 fn get_number_1
, fn get_number_2
,
214 char *p
= skip_blanks(buf
);
215 int num1
, num2
, step
, i
;
220 num1
= min
; num2
= max
;
223 p
= read_number(p
, get_number_1
, &num1
); if (!p
) return NULL
;
226 p
= read_number(p
, get_number_2
, &num2
); if (!p
) return NULL
;
232 p
= read_number(p
, NULL
, &step
);
236 if (num1
< min
|| num1
> max
|| num2
< min
|| num2
> max
) return NULL
;
238 for (i
= num1
; i
<= num2
; i
+= step
) {
242 if (*p
!= ',') return p
;
247 int all_full (char *vec
, int len
)
250 for (i
= 0; i
< len
; i
++) {
251 if (!vec
[i
]) return 0;
256 void print_vec (char *vec
, int len
)
259 for (i
= 0; i
< len
; i
++) {
260 if (vec
[i
]) printf(" %2d", i
);
266 char *read_time_entry (char *buf
, struct time_entry
*res
)
268 char raw_dow
[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
269 char *p
= skip_irrelevant(buf
);
272 int fulldom
, fulldow
;
274 if (eoln(p
)) return NULL
;
275 *res
= EMPTY_TIME_ENTRY
;
279 while (*p
&& isalpha(*p
)) p
++;
281 if (!strncmp(keyword
, "reboot", len
)) /* ignore, keep empty time_entry */ ;
282 else if (!strncmp(keyword
, "yearly", len
)) read_time_entry("0 0 1 1 *", res
);
283 else if (!strncmp(keyword
, "annually", len
)) read_time_entry("0 0 1 1 *", res
);
284 else if (!strncmp(keyword
, "monthly", len
)) read_time_entry("0 0 1 * *", res
);
285 else if (!strncmp(keyword
, "weekly", len
)) read_time_entry("0 0 * * 0", res
);
286 else if (!strncmp(keyword
, "daily", len
)) read_time_entry("0 0 * * *", res
);
287 else if (!strncmp(keyword
, "midnight", len
)) read_time_entry("0 0 * * *", res
);
288 else if (!strncmp(keyword
, "hourly", len
)) read_time_entry("0 * * * *", res
);
294 p
= read_range(p
, 0, 59, 0, NULL
, NULL
, res
->m
);
297 p
= read_range(p
, 0, 23, 0, NULL
, NULL
, res
->h
);
300 p
= read_range(p
, 1, 31, 1, NULL
, NULL
, res
->dom
);
303 p
= read_range(p
, 1, 12, 1, get_month
, get_month
, res
->mon
);
306 p
= read_range(p
, 0, 7, 0, get_dow_1
, get_dow_2
, raw_dow
);
309 if (raw_dow
[7]) raw_dow
[0] = 1;
310 memcpy(res
->dow
, raw_dow
, 7);
312 fulldom
= all_full(res
->dom
, 31);
313 fulldow
= all_full(res
->dow
, 7);
315 if (fulldom
&& !fulldow
) res
->ignore
= IGNORE_DOM
;
316 else if (fulldow
&& !fulldom
) res
->ignore
= IGNORE_DOW
;
317 else res
->ignore
= IGNORE_NOTHING
;
322 struct entry
*add_entries (char *buf
, char *username
, struct entry
*link
)
324 struct entry
*list
= link
;
328 struct entry
*entry
= malloc(sizeof(struct entry
));
329 q
= read_time_entry(p
, &entry
->te
);
338 if (username
) entry
->username
= strdup(username
);
341 while (isalnum(*q
)) q
++;
342 entry
->username
= strndup(p
, q
-p
);
347 while (!eoln(q
)) q
++;
348 entry
->command
= strndup(p
, q
-p
);
356 void free_entry_list (struct entry
*list
)
368 char *get_username (void)
370 uid_t uid
= getuid();
371 struct passwd
*pwd
= getpwuid(uid
);
373 if (pwd
) return strdup(pwd
->pw_name
);
375 sprintf(res
, "%u", uid
);
379 void print_te_part (char *arr
, int len
, int offs
)
382 for (i
= 0; i
< len
; i
++) {
384 if (!first
) putchar(',');
385 printf("%d", i
+offs
);
391 void print_entry (struct entry
*e
)
393 print_te_part(e
->te
.m
, 60, 0); putchar(' ');
394 print_te_part(e
->te
.h
, 24, 0); putchar(' ');
395 if (e
->te
.ignore
== IGNORE_DOM
) putchar('-');
396 print_te_part(e
->te
.dom
, 31, 1); putchar(' ');
397 print_te_part(e
->te
.mon
, 12, 1); putchar(' ');
398 if (e
->te
.ignore
== IGNORE_DOW
) putchar('-');
399 print_te_part(e
->te
.dow
, 7, 0); putchar(' ');
400 printf(" %s %s\n", e
->username
, e
->command
);
403 struct entry
*read_crontabs (int user
, int system
)
405 struct entry
*list
= NULL
;
408 char *buf
= slurp_command("crontab -l");
410 char *username
= get_username();
411 list
= add_entries(buf
, username
, list
);
418 char *buf
= slurp_file("/etc/crontab");
420 list
= add_entries(buf
, NULL
, list
);
427 int match_time (struct time_entry
*te
, struct tm
*tm
) {
429 te
->m
[ tm
->tm_min
] &&
430 te
->h
[ tm
->tm_hour
] &&
431 te
->mon
[ tm
->tm_mon
] &&
432 ((te
->ignore
!= IGNORE_DOM
&& te
->dom
[ tm
->tm_mday
-1 ]) ||
433 (te
->ignore
!= IGNORE_DOW
&& te
->dow
[ tm
->tm_wday
]));
436 int tm_ge (struct tm
*tm1
, struct tm
*tm2
)
439 tm1
->tm_year
> tm2
->tm_year
||
440 (tm1
->tm_year
== tm2
->tm_year
&&
441 (tm1
->tm_mon
> tm2
->tm_mon
||
442 (tm1
->tm_mon
== tm2
->tm_mon
&&
443 (tm1
->tm_mday
> tm2
->tm_mday
||
444 (tm1
->tm_mday
== tm2
->tm_mday
&&
445 (tm1
->tm_hour
> tm2
->tm_hour
||
446 (tm1
->tm_hour
== tm2
->tm_hour
&&
447 (tm1
->tm_min
> tm2
->tm_min
||
448 (tm1
->tm_min
== tm2
->tm_min
)))))))));
451 void die (char *s
, ...) {
454 fputs(PROGRAM_NAME
": ", stderr
);
456 vfprintf(stderr
, s
, ap
);
463 puts(PROGRAM_NAME
" lists upcoming cron actions from /etc/crontab\n\
464 and your personal crontab.\n\
466 -f --from=DATETIME list actions starting on or after DATETIME (default now)\n\
467 -t --to=DATETIME list actions starting on or before DATETIME\n\
468 -n --entries=NUMBER stop after NUMBER actions (default 10)\n\
469 -s --system show /etc/crontab only\n\
470 -c --crontab show your personal crontab only\n\
471 -h --help shows this help\n\
473 DATETIME should be a date expression that can be passed to date(1).\n");
477 void get_tm_from_date (char *datespec
, struct tm
*dest
) {
478 char *cmd
= malloc(strlen(datespec
) + 20);
484 sprintf(cmd
, "date -d \"%s\" '+%%s'", datespec
);
486 if (!p
) die("command ‘%s’ failed", cmd
);
487 fgets(number
, 20, p
);
489 if (!isdigit(number
[0])) die("command ‘%s’ didn't return a meaningful value", cmd
);
492 if (!stm
) die("date ‘%s’ not supported", datespec
);
496 int mdays
[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
498 int leap (int year
) {
499 return (year
+1900)%400 == 0 || ((year
+1900)%100 != 0 && year
%4 == 0);
503 int main (int argc
, char *argv
[])
505 static char *shortopts
= "f:t:n:csh";
506 static struct option longopts
[] = {
507 { "from", required_argument
, NULL
, 'f' },
508 { "to", required_argument
, NULL
, 't' },
509 { "entries", required_argument
, NULL
, 'n' },
510 { "crontab", no_argument
, NULL
, 'c' },
511 { "system", no_argument
, NULL
, 's' },
512 { "help", no_argument
, NULL
, 'h' }
515 int have_from
= 0, have_to
= 0, have_n
= 0, only_system
= 0, only_crontab
= 0;
517 struct tm from
, to
, tm
, *stm
;
521 struct entry
*entries
;
524 while ((opt
= getopt_long(argc
, argv
, shortopts
, longopts
, NULL
)) != -1) {
528 get_tm_from_date(optarg
, &from
);
532 get_tm_from_date(optarg
, &to
);
536 n
= strtol(optarg
, &p
, 10);
537 if (*p
|| n
< 0) die("Invalid entry count: %s", optarg
);
549 /* die("Bad argument. Use ‘--help’ for a list of options"); */
553 if (only_system
&& only_crontab
)
554 die("Can't choose both --system and --crontab");
561 if (!have_to
&& !have_n
) {
566 if (n
<= 0) return 0;
568 entries
= read_crontabs(!only_system
, !only_crontab
);
569 if (!entries
) return 0;
574 struct entry
*e
= entries
;
576 if (match_time(&e
->te
, &tm
)) {
577 printf("%04d-%02d-%02d %2d:%02d %s %s\n",
586 if (have_n
&& outputted
>= n
) return 0;
591 if (have_to
&& tm_ge(&tm
, &to
)) return 0;
593 /* after one year, if nothing was output, nothing will be */
594 if (outputted
== 0 &&
595 (tm
.tm_year
> from
.tm_year
+1 ||
596 (tm
.tm_year
== from
.tm_year
+1 && tm
.tm_mon
> from
.tm_mon
)))
601 if (tm
.tm_min
> 59) {
604 if (tm
.tm_hour
> 23) {
606 tm
.tm_wday
= (tm
.tm_wday
+1)%7;
608 if (tm
.tm_mday
> mdays
[tm
.tm_mon
]) {
609 if (tm
.tm_mon
!= 1 ||
614 if (tm
.tm_mon
> 11) {