]> granicus.if.org Git - cronie/commitdiff
Added useful utility cronnext to find out time of the next job run.
authorMarco Migliori <sgerwk@aol.com>
Mon, 27 Jun 2016 12:38:05 +0000 (14:38 +0200)
committerTomas Mraz <tmraz@fedoraproject.org>
Mon, 27 Jun 2016 12:38:05 +0000 (14:38 +0200)
man/cronnext.1 [new file with mode: 0644]
src/Makefile.am
src/cronnext.c [new file with mode: 0644]

diff --git a/man/cronnext.1 b/man/cronnext.1
new file mode 100644 (file)
index 0000000..3edb0f2
--- /dev/null
@@ -0,0 +1,48 @@
+.TH CRONNEXT 1 "Nov 30, 2015"
+.SH NAME
+cronnext \- time of next job cron will execute
+.SH SYNOPSIS
+.TP 9
+.B cronnext
+[-i users] [-e users] [-s] [-t time] [-v] [-h]
+.SH DESCRIPTION
+Determine the time cron will execute the next job.
+Without arguments, it prints that time considering all crontabs,
+in number of seconds since the Epoch.
+This number can be converted into other formats using date(1),
+like 'date --date @43243254'
+.SH OPTIONS
+.TP
+-i user,user,user,...
+consider only the crontabs of these users;
+.br
+use "*system*" for the system crontab
+.TP
+-e user,user,user,...
+do not consider the crontabs of these users
+.TP
+-s
+do not consider the system crontab, usually /etc/crontab;
+.br
+the system crontab usually contains the hourly, daily, weekly and montly
+crontabs, which might be better dealt with anacron(8)
+.TP
+-t time
+determine the next job from this time, instead of now;
+.br
+the time is expressed in number of seconds since the Epoch, as obtained for
+example by 'date +%s --date "now + 2 hours"'
+.TP
+-v
+verbose mode: scanning the crontab is done in verbose mode, and for each user
+and job the next execution time is printed both as number of seconds since the
+Epoch and in the standard format
+.TP
+-h
+summary of options
+.SH AUTHOR
+Marco Migliori <sgerwk@aol.com>
+.SH SEE ALSO
+cron(8), cron(1), crontab(5), crontab(1), anacron(8), anacrontab(5), atq(1),
+date(1)
+
index 71124de538079ae7f454350baf1da8ac08b72b36..c074145b2a67ff40f039cabd0241195a475478fa 100644 (file)
@@ -1,18 +1,22 @@
 # Makefile.am - two binaries crond and crontab
 
 sbin_PROGRAMS = crond
-bin_PROGRAMS = crontab
+bin_PROGRAMS = crontab cronnext
 
 crond_SOURCES = \
        cron.c database.c user.c job.c do_command.c popen.c \
        $(common_src)
 crontab_SOURCES = crontab.c $(common_src)
+cronnext_SOURCES = \
+       cronnext.c database.c user.c job.c do_command.c popen.c \
+       $(common_src)
 common_src = entry.c env.c misc.c pw_dup.c security.c \
        externs.h funcs.h globals.h macros.h pathnames.h structs.h \
        bitstring.h
 common_nodist = cron-paths.h
 nodist_crond_SOURCES = $(common_nodist)
 nodist_crontab_SOURCES = $(common_nodist)
+nodist_cronnext_SOURCES = $(common_nodist)
 BUILT_SOURCES = $(common_nodist)
 
 AM_CFLAGS = -I$(top_srcdir)
diff --git a/src/cronnext.c b/src/cronnext.c
new file mode 100644 (file)
index 0000000..6d3c373
--- /dev/null
@@ -0,0 +1,300 @@
+/*
+    cronnext - calculate the time cron will execute the next job
+    Copyright (C) 2016 Marco Migliori <sgerwk@aol.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+    The GNU General Public License can also be found in the file
+    `COPYING' that comes with the Anacron source distribution.
+*/
+
+#include "config.h"
+
+#define MAIN_PROGRAM
+
+#include <stdlib.h>
+#include <string.h>
+#include <pwd.h>
+
+#include "globals.h"
+#include "funcs.h"
+#include "cron-paths.h"
+
+/*
+ * print entry flags
+ */
+char *flagname[]= {
+       [MIN_STAR] =    "MIN_STAR",
+       [HR_STAR] =     "HR_STAR",
+       [DOM_STAR] =    "DOM_STAR",
+       [DOW_STAR] =    "DOW_STAR",
+       [WHEN_REBOOT] = "WHEN_REBOOT",
+       [DONT_LOG] =    "DONT_LOG"
+};
+
+void printflags(int flags) {
+       int f;
+       printf("flags: 0x%d = ", flags);
+       for (f = 1; f < sizeof(flagname);  f = f << 1)
+               if (flags & f)
+                       printf("%s ", flagname[f]);
+       printf("\n");
+}
+
+/*
+ * print a crontab entry
+ */
+void printentry(entry *e, int system, time_t next) {
+       if (system)
+               printf("entry user: %s\n", e->pwd->pw_name);
+       printf("cmd: %s\n", e->cmd);
+       printflags(e->flags);
+       printf("delay: %d\n", e->delay);
+       printf("next: %d = ", next);
+       printf("%s", asctime(localtime(&next)));
+}
+
+/*
+ * print a crontab data
+ */
+void printcrontab(user *u) {
+       printf("==========================\n");
+       printf("user: %s\n", u->name);
+       printf("crontab: %s\n", u->tabname);
+       printf("system: %d\n", u->system);
+}
+
+/*
+ * basic algorithm: iterate over time from now to 8 year ahead in default steps
+ * of 1 minute, checking whether time matches a crontab entry at each step (8
+ * years is the largest interval between two crontab matches)
+ *
+ * to save iterations, use larger steps if month or day don't match the entry:
+ * - if the month doesn't match, skip to 00:00 of the first day of next month
+ * - for the day, avoid the complication of the different length of months: if
+ *   neither the day nor the next day match, increase time of one day
+ */
+
+/*
+ * check whether time matches day of month and/or day of week; this requires
+ * checking dom if dow=*, dow if dom=*, either one otherwise; see comment "the
+ * dom/dow situation is odd..." in cron.c
+ */
+int matchday(entry *e, time_t time) {
+       struct tm current;
+       int d;
+
+       localtime_r(&time, &current);
+
+       if (e->flags & DOW_STAR)
+               return bit_test(e->dom, current.tm_mday - 1);
+       if (e->flags & DOM_STAR) 
+               return bit_test(e->dow, current.tm_wday);
+       return bit_test(e->dom, current.tm_mday - 1) ||
+               bit_test(e->dow, current.tm_wday);
+}
+
+/*
+ * next time matching a crontab entry
+ */
+time_t nextmatch(entry *e, time_t start) {
+       time_t time;
+       time_t nexttime;
+       struct tm current;
+       int res;
+
+       /* maximum match interval is 8 years (<102 months of 28 days):
+        * crontab has '* * 29 2 *' and we are on 1 March 2096:
+        * next matching time will be 29 February 2104 */
+
+       for (time = start; time < start + 102 * 28 * 24 * 60 * 60; ) {
+               localtime_r(&time, &current);
+
+               /* month doesn't match: move to 1st of next month */
+               if (!bit_test(e->month, current.tm_mon)) {
+                       current.tm_mon++;
+                       if (current.tm_mon >= 12) {
+                               current.tm_year++;
+                               current.tm_mon = 0;
+                       }
+                       current.tm_mday = 1;
+                       current.tm_hour = 0;
+                       current.tm_min = 0;
+                       time = mktime(&current);
+                       continue;
+               }
+
+               /* neither time nor time+1day match day: increase 1 day */
+               if (!matchday(e, time) && !matchday(e, time + 24 * 60 * 60)) {
+                       time += 24 * 60 * 60;
+                       continue;
+               }
+
+               /* if time matches, return time;
+                * check for month is redudant, but check for day is
+                * necessary because we only know that either time
+                * or time+1day match */
+               if (bit_test(e->month, current.tm_mon) &&
+                       matchday(e, time) &&
+                       bit_test(e->hour, current.tm_hour) &&
+                       bit_test(e->minute, current.tm_min)
+               )
+                       return time;
+
+               /* skip to next minute */
+               time += 60;
+       }
+
+       return -1;
+}
+
+/*
+ * match a user against a list
+ */
+int matchuser(char *user, char *list) {
+       char *pos;
+       int l = strlen(user);
+
+       for (pos = list; pos = strstr(pos, user); pos += l) {
+               if ((pos != list) && (*(pos - 1) != ','))
+                       continue;
+               if ((pos[l] != '\0') && (pos[l] != ','))
+                       continue;
+               return 1;
+       }
+       return 0;
+}
+
+/*
+ * find next sheduled job
+ */
+time_t cronnext(time_t start,
+               char *include, char *exclude, int system,
+               int verbose) {
+       time_t closest, next;
+       static cron_db database = {NULL, NULL, (time_t) 0};
+       user *u;
+       entry *e;
+
+       /* load crontabs */
+       load_database(&database);
+
+       /* find next sheduled time */
+       closest = -1;
+       for (u = database.head; u; u = u->next) {
+               if (include && !matchuser(u->name, include))
+                       continue;
+               if (exclude && matchuser(u->name, exclude))
+                       continue;
+               if (!system && u->system)
+                       continue;
+
+               if (verbose)
+                       printcrontab(u);
+
+               for (e = u->crontab; e; e = e->next) {
+                       next = nextmatch(e, start);
+                       if (next < 0)
+                               continue;
+                       if ((closest < 0) || (next < closest))
+                               closest = next;
+                       if (verbose)
+                               printentry(e, u->system, next);
+               }
+       }
+
+       return closest;
+}
+
+void usage() {
+       printf("find the next time of a scheduled cron job\n");
+       printf("usage:\n");
+       printf("\tcronnext [-i users] [-e users] [-s] [-t time] [-v] [-h]\n");
+       printf("\t\t-i users\tinclude only the crontab of these users\n");
+       printf("\t\t-e users\texclude the crontab of these users\n");
+       printf("\t\t-s\t\tdo not include the system crontab\n");
+       printf("\t\t-t time\t\tstart from this time (seconds since epoch)\n");
+       printf("\t\t-v\t\tverbose mode\n");
+       printf("\t\t-h\t\tthis help\n");
+}
+
+/*
+ * main
+ */
+int main(int argn, char *argv[]) {
+       int opt;
+       char *include, *exclude;
+       int system, verbose;
+       time_t start, next;
+
+       include = NULL;
+       exclude = NULL;
+       system = 1;
+       start = time(NULL);
+       verbose = 0;
+
+       while (-1 != (opt = getopt(argn, argv, "i:e:st:vh"))) {
+               switch (opt) {
+               case 'i':
+                       include = optarg;
+                       break;
+               case 'e':
+                       exclude = optarg;
+                       break;
+               case 's':
+                       system = 0;
+                       break;
+               case 't':
+                       start = atoi(optarg);
+                       break;
+               case 'v':
+                       verbose = 1;
+                       break;
+               case 'h':
+                       usage();
+                       return EXIT_SUCCESS;
+               default:
+                       printf("unrecognized option: %s\n",
+                               argv[optind - 1]);
+                       usage();
+                       exit(EXIT_FAILURE);
+               }
+       }
+
+       /* debug cron */
+       if (verbose) {
+               printf("SPOOL_DIR=%s\n", SPOOL_DIR);
+               set_debug_flags("load");
+       }
+       /* "load,pars" for debugging loading and parsing, "" for nothing
+          see globals.h for symbolic names and macros.h for meaning */
+
+       /* print time of next scheduled command */
+       next = cronnext(start, include, exclude, system, verbose);
+       if (next == -1) {
+               if (verbose)
+                       printf("no sheduled job\n");
+               return EXIT_FAILURE;
+       }
+       else
+               if (verbose)
+                       printf("next of all jobs: %d=%s",
+                               next, asctime(localtime(&next)));
+               else
+                       printf("%d\n", next);
+
+       return EXIT_SUCCESS;
+}
+