SummaryRefsLogTreeCommitDiffStats
path: root/scm/mcron/vixie-specification.scm
blob: e7fab0a6806213f4a27ead75b4158f618a296879 (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
;;   Copyright (C) 2003 Dale Mellor
;; 
;;   This file is part of GNU mcron.
;;
;;   GNU mcron 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 3 of the License, or (at your option)
;;   any later version.
;;
;;   GNU mcron 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 GNU mcron.  If not, see <http://www.gnu.org/licenses/>.



;; This file provides methods for reading a complete Vixie-style configuration
;; file, either from a real file or an already opened port. It also exposes the
;; method for parsing the time-specification part of a Vixie string, so that
;; these can be used to form the next-time-function of a job in a Guile
;; configuration file.

(define-module (mcron vixie-specification)
  #:export (parse-user-vixie-line
            parse-system-vixie-line
            read-vixie-port
            read-vixie-file
            check-system-crontab)
  #:use-module ((mcron config) :select (config-socket-file))
  #:use-module (mcron mcron-core)
  #:use-module (mcron job-specifier)
  #:use-module (mcron redirect)
  #:use-module (mcron vixie-time))


(use-modules (ice-9 regex) (ice-9 rdelim)
             (srfi srfi-1) (srfi srfi-2) (srfi srfi-13) (srfi srfi-14))



;; A line in a Vixie-style crontab file which gives a command specification
;; carries two pieces of information: a time specification consisting of five
;; space-separated items, and a command which is also separated from the time
;; specification by a space. The line is broken into the two components, and the
;; job procedure run to add the two pieces of information to the job list (this
;; will in turn use the above function to turn the time specification into a
;; function for computing future run times of the command).

(define parse-user-vixie-line-regexp
  (make-regexp "^[[:space:]]*(([^[:space:]]+[[:space:]]+){5})(.*)$"))

(define (parse-user-vixie-line line)
  (let ((match (regexp-exec parse-user-vixie-line-regexp line)))
    (if (not match) 
        (throw 'mcron-error 10 "Bad job line in Vixie file."))
    (job (match:substring match 1)
         (lambda () (with-mail-out (match:substring match 3)))
         (match:substring match 3))))



;; The case of reading a line from /etc/crontab is similar to above but the user
;; ID appears in the sixth field, before the action.

(define parse-system-vixie-line-regexp
  (make-regexp (string-append "^[[:space:]]*(([^[:space:]]+[[:space:]]+){5})"
                              "([[:alpha:]][[:alnum:]_]*)[[:space:]]+(.*)$")))

(define (parse-system-vixie-line line)
  (let ((match (regexp-exec parse-system-vixie-line-regexp line)))
    (if (not match) 
        (throw 'mcron-error 11 "Bad job line in /etc/crontab."))
    (let ((user (match:substring match 3)))
      (set-configuration-user user)
      (job (match:substring match 1)
           (lambda () (with-mail-out (match:substring match 4)
                                     user))
           (match:substring match 4)))))



;; Procedure to act on an environment variable specification in a Vixie-style
;; configuration file, by adding an entry to the alist above. Returns #t if the
;; operation was successful, #f if the line could not be interpreted as an
;; environment specification.

(define parse-vixie-environment-regexp1
  (make-regexp
   "^[ \t]*([[:alpha:]_][[:alnum:]_]*)[ \t]*=[ \t]*\"(.*)\"[ \t]*$"))
(define parse-vixie-environment-regexp2
  (make-regexp
   "^[ \t]*([[:alpha:]_][[:alnum:]_]*)[ \t]*=[ \t]*'(.*)'[ \t]*$"))
(define parse-vixie-environment-regexp3
  (make-regexp
   "^[ \t]*([[:alpha:]_][[:alnum:]_]*)[ \t]*=[ \t]*(.*[^ \t])[ \t]*$"))
(define parse-vixie-environment-regexp4
  (make-regexp
   "^[ \t]*([[:alpha:]_][[:alnum:]_]*)[ \t]*=[ \t]*$"))


(define (parse-vixie-environment string)
  (let ((match (or (regexp-exec parse-vixie-environment-regexp1 string)
                   (regexp-exec parse-vixie-environment-regexp2 string)
                   (regexp-exec parse-vixie-environment-regexp3 string))))
    (if match
        (append-environment-mods (match:substring match 1)
                                 (match:substring match 2))
        (and-let* ((match (regexp-exec parse-vixie-environment-regexp4 string)))
                  (append-environment-mods (match:substring match 1) #f)))))




;; The next procedure reads an entire Vixie-style file. For each line in the
;; file there are three possibilities (after continuation lines have been
;; appended): the line is blank or contains only a comment, the line contains an
;; environment modifier which will be handled in the mcron environment module,
;; or the line contains a command specification in which case we use the
;; procedure above to add an entry to the internal job list.
;;
;; Note that the environment modifications are cleared, so that there is no
;; interference between crontab files (this might lead to unpredictable
;; behaviour because the order in which crontab files are processed, if there is
;; more than one, is generally undefined).

(define read-vixie-file-comment-regexp
  (make-regexp "^[[:space:]]*(#.*)?$"))


(define (read-vixie-port port . parse-vixie-line)
  (clear-environment-mods)
  (if port
      (let ((parse-vixie-line
             (if (null? parse-vixie-line) parse-user-vixie-line
                 (car parse-vixie-line))))
        (do ((line (read-line port) (read-line port))
             (line-number 1 (1+ line-number)))
            ((eof-object? line))

          (let ((report-line line-number))
            ;; If the line ends with \, append the next line.
            (while (and (>= (string-length line) 1)
                        (char=? (string-ref line
                                            (- (string-length line) 1))
                                #\\))
                   (let ((next-line (read-line port)))
                     (if (eof-object? next-line)
                         (set! next-line ""))
                     (set! line-number (1+ line-number))
                     (set! line
                           (string-append
                            (substring line 0 (- (string-length line) 1))
                            next-line))))

            (catch 'mcron-error
                   (lambda ()
                     ;; Consider the three cases mentioned in the description.
                     (or (regexp-exec read-vixie-file-comment-regexp line)
                         (parse-vixie-environment line)
                         (parse-vixie-line line)))
                   (lambda (key exit-code . msg)
                     (throw 'mcron-error exit-code
                            (apply string-append
                                   (number->string report-line)
                                   ": "
                                   msg)))))))))



;; If a file cannot be opened, we must silently ignore it because it may have
;; been removed by crontab. However, if the file is there it must be parseable,
;; otherwise the error must be propagated to the caller.

(define (read-vixie-file file-path . parse-vixie-line)
  (let ((port #f))
    (catch #t (lambda () (set! port (open-input-file file-path)))
           (lambda (key . args) (set! port #f)))
    (if port
        (catch 'mcron-error
               (lambda ()
                 (if (null? parse-vixie-line)
                     (read-vixie-port port)
                     (read-vixie-port port (car parse-vixie-line)))
                 (close port))
               (lambda (key exit-code . msg)
                 (close port)
                 (throw 'mcron-error exit-code
                        (apply string-append file-path ":" msg)))))))


;; A procedure which determines if the /etc/crontab file has been recently
;; modified, and, if so, signals the main routine to re-read the file. We run
;; under the with-mail-to command so that the process runs as a child,
;; preventing lockup. If cron is supposed to check for updates to /etc/crontab,
;; then this procedure will be called about 5 seconds before every minute.

(define (check-system-crontab)
  (with-mail-out (lambda ()
                  (let ((mtime (stat:mtime (stat "/etc/crontab"))))
                    (if (> mtime (- (current-time) 60))
                        (let ((socket (socket AF_UNIX SOCK_STREAM 0)))
                          (connect socket AF_UNIX config-socket-file)
                          (display "/etc/crontab" socket)
                          (close socket)))))))