;; Copyright (C) 2003 Dale Mellor ;; ;; 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, ;; USA. ;; This module defines all the functions that can be used by scheme mcron ;; configuration files, namely the procedures for working out next times, the ;; job procedure for registering new jobs (actually a wrapper around the core ;; add-job function), and the procedure for declaring environment modifications. (define-module (mcron job-specifier) #:export (range next-year-from next-year next-month-from next-month next-day-from next-day next-hour-from next-hour next-minute-from next-minute next-second-from next-second set-configuration-user set-configuration-time job find-best-next) #:use-module (mcron core) #:use-module (mcron environment) #:use-module (mcron vixie-time) #:re-export (append-environment-mods)) ;; Function (available to user configuration files) which produces a list of ;; values from start up to (but not including) end. An optional step may be ;; supplied, and (if positive) only every step'th value will go into the ;; list. For example, (range 1 6 2) returns '(1 3 5). (define (range start end . step) (let ((step (if (or (null? step) (<= (car step) 0)) 1 (car step)))) (let loop ((start start)) (if (>= start end) '() (cons start (loop (+ start step))))))) ;; Internal function (not supposed to be used directly in configuration files; ;; it is exported from the module for the convenience of other parts of the ;; mcron implementation) which takes a value and a list of possible next values ;; (all assumed less than 9999). It returns a pair consisting of the smallest ;; element of the list, and the smallest element larger than the current ;; value. If an example of the latter cannot be found, 9999 will be returned. (define (find-best-next current next-list) (let ((current-best (cons 9999 9999))) (for-each (lambda (allowed-time) (if (< allowed-time (car current-best)) (set-car! current-best allowed-time)) (if (and (> allowed-time current) (< allowed-time (cdr current-best))) (set-cdr! current-best allowed-time))) next-list) current-best)) ;; Internal function to return the time corresponding to some near future ;; hour. If hour-list is not supplied, the time returned corresponds to the ;; start of the next hour of the day. ;; ;; If the hour-list is supplied the time returned corresponds to the first hour ;; of the day in the future which is contained in the list. If all the values in ;; the list are less than the current hour, then the time returned will ;; correspond to the first hour in the list *on the following day*. ;; ;; ... except that the function is actually generalized to deal with seconds, ;; minutes, etc., in an obvious way :-) ;; ;; Note that value-list always comes from an optional argument to a procedure, ;; so is wrapped up as the first element of a list (i.e. it is a list inside a ;; list). (define (bump-time time value-list component higher-component set-component! set-higher-component!) (if (null? value-list) (set-component! time (+ (component time) 1)) (let ((best-next (find-best-next (component time) (car value-list)))) (if (eqv? 9999 (cdr best-next)) (begin (set-higher-component! time (+ (higher-component time) 1)) (set-component! time (car best-next))) (set-component! time (cdr best-next))))) (car (mktime time))) ;; Set of configuration methods which use the above general function to bump ;; specific components of time to the next legitimate value. In each case, all ;; the components smaller than that of interest are taken to zero, so that for ;; example the time of the next year will be the time at which the next year ;; actually starts. (define (next-year-from current-time . year-list) (let ((time (localtime current-time))) (set-tm:mon time 0) (set-tm:mday time 1) (set-tm:hour time 0) (set-tm:min time 0) (set-tm:sec time 0) (bump-time time year-list tm:year tm:year set-tm:year set-tm:year))) (define (next-month-from current-time . month-list) (let ((time (localtime current-time))) (set-tm:mday time 1) (set-tm:hour time 0) (set-tm:min time 0) (set-tm:sec time 0) (bump-time time month-list tm:mon tm:year set-tm:mon set-tm:year))) (define (next-day-from current-time . day-list) (let ((time (localtime current-time))) (set-tm:hour time 0) (set-tm:min time 0) (set-tm:sec time 0) (bump-time time day-list tm:mday tm:mon set-tm:mday set-tm:mon))) (define (next-hour-from current-time . hour-list) (let ((time (localtime current-time))) (set-tm:min time 0) (set-tm:sec time 0) (bump-time time hour-list tm:hour tm:mday set-tm:hour set-tm:mday))) (define (next-minute-from current-time . minute-list) (let ((time (localtime current-time))) (set-tm:sec time 0) (bump-time time minute-list tm:min tm:hour set-tm:min set-tm:hour))) (define (next-second-from current-time . second-list) (let ((time (localtime current-time))) (bump-time time second-list tm:sec tm:min set-tm:sec set-tm:min))) ;; The current-action-time is the time a job was last run, the time from which ;; the next time to run a job must be computed. (When the program is first run, ;; this time is set to the configuration time so that jobs run from that moment ;; forwards.) Once we have this, we supply versions of the time computation ;; commands above which implicitly assume this value. (define current-action-time 0) ;; We want to provide functions which take a single optional argument (as well ;; as implicitly the current action time), but unlike usual scheme behaviour if ;; the argument is missing we want to act like it is really missing, and if it ;; is there we want to act like it is a genuine argument, not a list of ;; optionals. (define (maybe-args function args) (if (null? args) (function current-action-time) (function current-action-time (car args)))) ;; These are the convenience functions we were striving to define for the ;; configuration files. They are wrappers for the next-X-from functions above, ;; but implicitly use the current-action-time for the time argument. (define (next-year . args) (maybe-args next-year-from args)) (define (next-month . args) (maybe-args next-month-from args)) (define (next-day . args) (maybe-args next-day-from args)) (define (next-hour . args) (maybe-args next-hour-from args)) (define (next-minute . args) (maybe-args next-minute-from args)) (define (next-second . args) (maybe-args next-second-from args)) ;; The default user for running jobs is the current one (who invoked this ;; program). There are exceptions: when cron parses /etc/crontab the user is ;; specified on each individual line; when cron parses /var/cron/tabs/* the user ;; is derived from the filename of the crontab. These cases are dealt with by ;; mutating this variable. Note that the variable is only used at configuration ;; time; a UID is stored with each job and it is that which takes effect when ;; the job actually runs. (define configuration-user (getpw (getuid))) (define configuration-time (current-time)) (define (set-configuration-user user) (set! configuration-user (if (or (string? user) (integer? user)) (getpw user) user))) (define (set-configuration-time time) (set! configuration-time time)) ;; The job function, available to configuration files for adding a job rule to ;; the system. ;; ;; Here we must 'normalize' the next-time-function so that it is always a lambda ;; function which takes one argument (the last time the job ran) and returns a ;; single value (the next time the job should run). If the input value is a ;; string this is parsed as a Vixie-style time specification, and if it is a ;; list then we arrange to eval it (but note that such lists are expected to ;; ignore the function parameter - the last run time is always read from the ;; current-action-time global variable). A similar normalization is applied to ;; the action. ;; ;; Here we also compute the first time that the job is supposed to run, by ;; finding the next legitimate time from the current configuration time (set ;; right at the top of this program). (define (job time-proc action . displayable) (let ((action (cond ((procedure? action) action) ((list? action) (lambda () (primitive-eval action))) ((string? action) (lambda () (system action))) (else (display "job: invalid second argument (action; should be lamdba") (display "function, string or list)\n") (primitive-exit 2)))) (time-proc (cond ((procedure? time-proc) time-proc) ((string? time-proc) (parse-vixie-time time-proc)) ((list? time-proc) (lambda (current-time) (primitive-eval time-proc))) (else (display "job: invalid first argument (next-time-function; should ") (display "be function, string or list)") (primitive-exit 3)))) (displayable (cond ((not (null? displayable)) (car displayable)) ((procedure? action) "Lambda function") ((string? action) action) ((list? action) (with-output-to-string (lambda () (display action))))))) (add-job (lambda (current-time) (set! current-action-time current-time) (time-proc current-time)) action displayable configuration-time configuration-user)))