root/src/cronnext.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. set_cron_watched
  2. do_command
  3. get_security_context
  4. free_security_context
  5. printflags
  6. printentry
  7. printcrontab
  8. matchday
  9. nextmatch
  10. matchuser
  11. cronnext
  12. database
  13. usage
  14. main

   1 /*
   2     cronnext - calculate the time cron will execute the next job
   3     Copyright (C) 2016 Marco Migliori <sgerwk@aol.com>
   4 
   5     This program is free software; you can redistribute it and/or modify
   6     it under the terms of the GNU General Public License as published by
   7     the Free Software Foundation; either version 2 of the License, or
   8     (at your option) any later version.
   9 
  10     This program is distributed in the hope that it will be useful,
  11     but WITHOUT ANY WARRANTY; without even the implied warranty of
  12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13     GNU General Public License for more details.
  14 
  15     You should have received a copy of the GNU General Public License along
  16     with this program; if not, write to the Free Software Foundation, Inc.,
  17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18  
  19     The GNU General Public License can also be found in the file
  20     `COPYING' that comes with the Anacron source distribution.
  21 */
  22 
  23 #include "config.h"
  24 
  25 #define MAIN_PROGRAM
  26 
  27 #include <stdlib.h>
  28 #include <string.h>
  29 #include <pwd.h>
  30 
  31 #include "globals.h"
  32 #include "funcs.h"
  33 #include "cron-paths.h"
  34 
  35 /* flags to crontab search */
  36 #define ENTRIES  0x01                   // print entries
  37 #define CRONTABS 0x02                   // print crontabs
  38 #define SYSTEM   0x04                   // include system crontab
  39 #define ALLJOBS  0x08                   // print all jobs in interval
  40 
  41 #ifdef WITH_INOTIFY
  42 void set_cron_watched(int fd) {
     /* [previous][next][first][last][top][bottom][index][help]  */
  43 /* empty stub */
  44         (void)fd;
  45 }
  46 #endif
  47 
  48 void do_command(entry *e, user *u) {
     /* [previous][next][first][last][top][bottom][index][help]  */
  49 /* empty stub */
  50         (void)e;
  51         (void)u;
  52 }
  53 
  54 #ifdef WITH_SELINUX
  55 int get_security_context(const char *name, int crontab_fd,
     /* [previous][next][first][last][top][bottom][index][help]  */
  56                          security_context_t *rcontext, const char *tabname) {
  57 /* empty stub */
  58         (void)name;
  59         (void)crontab_fd;
  60         (void)tabname;
  61         *rcontext = NULL;
  62         return 0;
  63 }
  64 
  65 void free_security_context(security_context_t *scontext) {
     /* [previous][next][first][last][top][bottom][index][help]  */
  66 /* empty stub */
  67         (void)scontext;
  68 }
  69 #endif
  70 
  71 /*
  72  * print entry flags
  73  */
  74 const char *flagname[]= {
  75         "MIN_STAR",
  76         "HR_STAR",
  77         "DOM_STAR",
  78         "DOW_STAR",
  79         "WHEN_REBOOT",
  80         "DONT_LOG"
  81 };
  82 
  83 void printflags(char *indent, int flags) {
     /* [previous][next][first][last][top][bottom][index][help]  */
  84         int f;
  85         int first = 1;
  86 
  87         printf("%s    flagnames:", indent);
  88         for (f = 0; f < sizeof(flagname)/sizeof(char *);  f++)
  89                 if (flags & (int)1 << f) {
  90                         printf("%s%s", first ? " " : "|", flagname[f]);
  91                         first = 0;
  92                 }
  93         printf("\n");
  94 }
  95 
  96 /*
  97  * print a crontab entry
  98  */
  99 void printentry(char *indent, entry *e, time_t next) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 100         printf("%s  - user: %s\n", indent, e->pwd->pw_name);
 101         printf("%s    cmd: \"%s\"\n", indent, e->cmd);
 102         printf("%s    flags: 0x%02X\n", indent, e->flags);
 103         printflags(indent, e->flags);
 104         printf("%s    delay: %d\n", indent, e->delay);
 105         printf("%s    next: %ld\n", indent, (long)next);
 106         printf("%s    nextstring: ", indent);
 107         printf("%s", asctime(localtime(&next)));
 108 }
 109 
 110 /*
 111  * print a crontab data
 112  */
 113 void printcrontab(user *u) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 114         printf("  - user: \"%s\"\n", u->name);
 115         printf("    crontab: %s\n", u->tabname);
 116         printf("    system: %d\n", u->system);
 117         printf("    entries:\n");
 118 }
 119 
 120 /*
 121  * basic algorithm: iterate over time from now to 8 year ahead in default steps
 122  * of 1 minute, checking whether time matches a crontab entry at each step (8
 123  * years is the largest interval between two crontab matches)
 124  *
 125  * to save iterations, use larger steps if month or day don't match the entry:
 126  * - if the month doesn't match, skip to 00:00 of the first day of next month
 127  * - for the day, avoid the complication of the different length of months: if
 128  *   neither the day nor the next day match, increase time of one day
 129  */
 130 
 131 /*
 132  * check whether time matches day of month and/or day of week; this requires
 133  * checking dom if dow=*, dow if dom=*, either one otherwise; see comment "the
 134  * dom/dow situation is odd..." in cron.c
 135  */
 136 int matchday(entry *e, time_t time) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 137         struct tm current;
 138 
 139         localtime_r(&time, &current);
 140 
 141         if (e->flags & DOW_STAR)
 142                 return bit_test(e->dom, current.tm_mday - 1);
 143         if (e->flags & DOM_STAR) 
 144                 return bit_test(e->dow, current.tm_wday);
 145         return bit_test(e->dom, current.tm_mday - 1) ||
 146                 bit_test(e->dow, current.tm_wday);
 147 }
 148 
 149 /*
 150  * next time matching a crontab entry
 151  */
 152 time_t nextmatch(entry *e, time_t start, time_t end) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 153         time_t time;
 154         struct tm current;
 155 
 156         for (time = start; time <= end; ) {
 157                 localtime_r(&time, &current);
 158 
 159                 /* month doesn't match: move to 1st of next month */
 160                 if (!bit_test(e->month, current.tm_mon)) {
 161                         current.tm_mon++;
 162                         if (current.tm_mon >= 12) {
 163                                 current.tm_year++;
 164                                 current.tm_mon = 0;
 165                         }
 166                         current.tm_mday = 1;
 167                         current.tm_hour = 0;
 168                         current.tm_min = 0;
 169                         time = mktime(&current);
 170                         continue;
 171                 }
 172 
 173                 /* neither time nor time+1day match day: increase 1 day */
 174                 if (!matchday(e, time) && !matchday(e, time + 24 * 60 * 60)) {
 175                         time += 24 * 60 * 60;
 176                         continue;
 177                 }
 178 
 179                 /* if time matches, return time;
 180                  * check for month is redudant, but check for day is
 181                  * necessary because we only know that either time
 182                  * or time+1day match */
 183                 if (bit_test(e->month, current.tm_mon) &&
 184                         matchday(e, time) &&
 185                         bit_test(e->hour, current.tm_hour) &&
 186                         bit_test(e->minute, current.tm_min)
 187                 )
 188                         return time;
 189 
 190                 /* skip to next minute */
 191                 time += 60;
 192         }
 193 
 194         return -1;
 195 }
 196 
 197 /*
 198  * match a user against a list
 199  */
 200 int matchuser(char *user, char *list) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 201         char *pos;
 202         size_t l = strlen(user);
 203 
 204         for (pos = list; (pos = strstr(pos, user)) != NULL; pos += l) {
 205                 if ((pos != list) && (*(pos - 1) != ','))
 206                         continue;
 207                 if ((pos[l] != '\0') && (pos[l] != ','))
 208                         continue;
 209                 return 1;
 210         }
 211         return 0;
 212 }
 213 
 214 /*
 215  * find next sheduled job
 216  */
 217 time_t cronnext(cron_db database,
     /* [previous][next][first][last][top][bottom][index][help]  */
 218                 time_t start, time_t end,
 219                 char *include, char *exclude, int flags) {
 220         time_t closest, next;
 221         user *u;
 222         entry *e;
 223         char *indent = "";
 224 
 225         if (flags & CRONTABS) {
 226                 printf("crontabs:\n");
 227                 indent = "    ";
 228         }
 229         else if (flags & ALLJOBS)
 230                 printf("jobs:\n");
 231 
 232         /* find next sheduled time */
 233         closest = -1;
 234         for (u = database.head; u; u = u->next) {
 235                 if (include && !matchuser(u->name, include))
 236                         continue;
 237                 if (exclude && matchuser(u->name, exclude))
 238                         continue;
 239                 if (!(flags & SYSTEM) && u->system)
 240                         continue;
 241 
 242                 if (flags & CRONTABS)
 243                         printcrontab(u);
 244 
 245                 for (e = u->crontab; e; e = e->next)
 246                         for (next = nextmatch(e, start, end);
 247                              next <= end;
 248                              next = nextmatch(e, next + 60, end)) {
 249                                 if (next < 0)
 250                                         break;
 251                                 if (closest < 0 || next < closest)
 252                                         closest = next;
 253                                 if (flags & ENTRIES)
 254                                         printentry(indent, e, next);
 255                                 if (! (flags & ALLJOBS))
 256                                         break;
 257                         }
 258         }
 259 
 260         return closest;
 261 }
 262 
 263 /*
 264  * load installed crontabs and/or crontab files
 265  */
 266 cron_db database(int installed, char **additional) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 267         cron_db db = {NULL, NULL, (time_t) 0};
 268         struct passwd pw;
 269         int fd;
 270         user *u;
 271 
 272         if (installed)
 273                 load_database(&db);
 274 
 275         for ( ; *additional != NULL; additional++) {
 276                 fd = open(*additional, O_RDONLY);
 277                 if (fd == -1) {
 278                         perror(*additional);
 279                         continue;
 280                 }
 281                 memset(&pw, 0, sizeof(pw));
 282                 pw.pw_name = *additional;
 283                 pw.pw_passwd = "";
 284                 pw.pw_dir = ".";
 285                 u = load_user(fd, &pw, *additional, *additional, *additional);
 286                 if (u == NULL) {
 287                         printf("cannot load crontab %s\n", *additional);
 288                         continue;
 289                 }
 290                 link_user(&db, u);
 291         }
 292 
 293         return db;
 294 }
 295 
 296 void usage() {
     /* [previous][next][first][last][top][bottom][index][help]  */
 297         fprintf(stderr, "Find the time of the next scheduled cron job.\n");
 298         fprintf(stderr, "Usage:\n");
 299         fprintf(stderr, " cronnext [options] [file ...]\n");
 300         fprintf(stderr, "\n");
 301         fprintf(stderr, "Options:\n");
 302         fprintf(stderr, " -i users  include only the crontab of these users\n");
 303         fprintf(stderr, " -e users  exclude the crontab of these users\n");
 304         fprintf(stderr, " -s        do not include the system crontab\n");
 305         fprintf(stderr, " -a        examine installed crontabs even if files are given\n");
 306         fprintf(stderr, " -t time   start from this time (seconds since epoch)\n");
 307         fprintf(stderr, " -q time   end check at this time (seconds since epoch)\n");
 308         fprintf(stderr, " -l        print next jobs to be executed\n");
 309         fprintf(stderr, " -c        print next execution of each job\n");
 310         fprintf(stderr, " -f        print all jobs executed in the given interval\n");
 311         fprintf(stderr, " -h        this help\n");
 312         fprintf(stderr, " -V        print version and exit\n");
 313 }
 314 
 315 /*
 316  * main
 317  */
 318 int main(int argn, char *argv[]) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 319         int opt;
 320         char *include, *exclude;
 321         int flags;
 322         time_t start, next, end = 0;
 323         int endtime, printjobs;
 324         cron_db db;
 325         int installed = 0;
 326 
 327         include = NULL;
 328         exclude = NULL;
 329         flags = SYSTEM;
 330         endtime = 0;
 331         printjobs = 0;
 332         start = time(NULL) / 60 * 60;
 333 
 334         while (-1 != (opt = getopt(argn, argv, "i:e:ast:q:lcfhV"))) {
 335                 switch (opt) {
 336                 case 'i':
 337                         include = optarg;
 338                         break;
 339                 case 'e':
 340                         exclude = optarg;
 341                         break;
 342                 case 'a':
 343                         installed = 1;
 344                         break;
 345                 case 's':
 346                         flags &= ~SYSTEM;
 347                         break;
 348                 case 't':
 349                         start = atoi(optarg) / 60 * 60;
 350                         break;
 351                 case 'q':
 352                         end = atoi(optarg) / 60 * 60;
 353                         endtime = 1;
 354                         break;
 355                 case 'l':
 356                         printjobs = 1;
 357                         break;
 358                 case 'c':
 359                         flags |= ENTRIES | CRONTABS;
 360                         break;
 361                 case 'f':
 362                         flags |= ALLJOBS | ENTRIES;
 363                         break;
 364                 case 'h':
 365                         usage();
 366                         return EXIT_SUCCESS;
 367                 case 'V':
 368                         puts(PACKAGE_STRING);
 369                         return EXIT_SUCCESS;
 370                 default:
 371                         fprintf(stderr, "unrecognized option: %s\n",
 372                                 argv[optind - 1]);
 373                         usage();
 374                         exit(EXIT_FAILURE);
 375                 }
 376         }
 377 
 378         if (flags & ALLJOBS && !endtime) {
 379                 fprintf(stderr, "no ending time specified: -f requires -q\n");
 380                 usage();
 381                 exit(EXIT_FAILURE);
 382         }
 383 
 384         /* maximum match interval is 8 years:
 385          * crontab has '* * 29 2 *' and we are on 1 March 2096:
 386          * next matching time will be 29 February 2104 */
 387         if (!endtime)
 388                 end = start + 8 * 12 * 31 * 24 * 60 * 60;
 389 
 390         /* debug cron */
 391         if (flags & CRONTABS) {
 392                 printf("spool: %s\n", SPOOL_DIR);
 393                 set_debug_flags("");
 394         }
 395         /* "load,pars" for debugging loading and parsing, "" for nothing
 396            see globals.h for symbolic names and macros.h for meaning */
 397 
 398         /* load database */
 399         db = database(installed || argv[optind] == NULL, argv + optind);
 400 
 401         /* find time of next scheduled command */
 402         next = cronnext(db, start, end, include, exclude, flags);
 403 
 404         /* print time */
 405         if (next == -1)
 406                 return EXIT_FAILURE;
 407         else
 408                 printf("next: %ld\n", (long) next);
 409 
 410         /* print next jobs */
 411         if (printjobs) {
 412                 printf("nextjobs:\n");
 413                 cronnext(db, next, next, include, exclude, (flags & SYSTEM) | ENTRIES);
 414         }
 415 
 416         return EXIT_SUCCESS;
 417 }
 418 

/* [previous][next][first][last][top][bottom][index][help]  */