diff --git a/Makefile b/Makefile index c7cc359..e6f9d8f 100644 --- a/Makefile +++ b/Makefile @@ -66,6 +66,7 @@ install: $(INSTALL_PROGRAM) -m0700 -g root crond $(DESTDIR)$(SBINDIR)/crond $(INSTALL_PROGRAM) -m4750 -g $(CRONTAB_GROUP) crontab $(DESTDIR)$(BINDIR)/crontab $(INSTALL_DATA) crontab.1 $(DESTDIR)$(MANDIR)/man1/crontab.1 + $(INSTALL_DATA) crontab.5 $(DESTDIR)$(MANDIR)/man5/crontab.5 $(INSTALL_DATA) crond.8 $(DESTDIR)$(MANDIR)/man8/crond.8 $(INSTALL_DIR) $(DESTDIR)$(SCRONTABS) $(INSTALL_DIR) $(DESTDIR)$(CRONTABS) @@ -78,8 +79,9 @@ clean: force force: ; man: force - -pandoc -t man -f markdown -s crontab.markdown -o crontab.1 - -pandoc -t man -f markdown -s crond.markdown -o crond.8 + -pandoc -t man -f markdown -s crontab.1.markdown -o crontab.1 + -pandoc -t man -f markdown -s crontab.5.markdown -o crontab.5 + -pandoc -t man -f markdown -s crond.8.markdown -o crond.8 # for maintainer's use only TARNAME = /home/abs/_dcron/dcron-$(VERSION).tar.gz diff --git a/crond.8 b/crond.8 index 1582235..cb7eb86 100644 --- a/crond.8 +++ b/crond.8 @@ -1,193 +1,191 @@ -.TH CROND 8 "1 May 2011" +.\" Automatically generated by Pandoc 2.3.1 +.\" +.TH "CROND" "8" "20 Nov 2019" "" "" +.hy .SH NAME .PP -crond - dillon's lightweight cron daemon +crond \- dillon's lightweight cron daemon .SH SYNOPSIS .PP -\f[B]crond [-s dir] [-c dir] [-t dir] [-m user\@host] [-M mailhandler] [-S|-L file] [-l loglevel] [-b|-f|-d]\f[] +\f[B]crond [\-s dir] [\-c dir] [\-t dir] [\-m user\@host] [\-M +mailhandler] [\-S|\-L file] [\-l loglevel] [\-b|\-f|\-d]\f[] .SH OPTIONS .PP -\f[B]crond\f[] is a background daemon that parses individual -crontab files and executes commands on behalf of the users in -question. +\f[B]crond\f[] is a background daemon that parses individual crontab +files and executes commands on behalf of the users in question. .TP -.B -s dir +.B \-s dir directory of system crontabs (defaults to /etc/cron.d) .RS .RE .TP -.B -c dir -directory of per-user crontabs (defaults to -/var/spool/cron/crontabs) +.B \-c dir +directory of per\-user crontabs (defaults to /var/spool/cron/crontabs) .RS .RE .TP -.B -t dir +.B \-t dir directory of timestamps for \@freq and FREQ=\&... jobs (defaults to /var/spool/cron/cronstamps) .RS .RE .TP -.B -m user\@host -where should the output of cronjobs be directed? (defaults to local -user) Some mail handlers (like msmtp) can't route mail to local -users. -If that's what you're using, then you should supply a remote -address using this switch. +.B \-m user\@host +where should the output of cronjobs be directed? +(defaults to local user) Some mail handlers (like msmtp) can't route +mail to local users. +If that's what you're using, then you should supply a remote address +using this switch. Cron output for all users will be directed to that address. -Alternatively, you could supply a different mail handler using the --M switch, to log or otherwise process the messages instead of -mailing them. -Alternatively, you could just direct the stdout and stderr of your -cron jobs to /dev/null. +Alternatively, you could supply a different mail handler using the \-M +switch, to log or otherwise process the messages instead of mailing +them. +Alternatively, you could just direct the stdout and stderr of your cron +jobs to /dev/null. .RS .RE .TP -.B -M mailhandler -Any output that cronjobs print to stdout or stderr gets formatted -as an email and piped to \f[C]/usr/sbin/sendmail\ -t\ -oem\ -i\f[]. +.B \-M mailhandler +Any output that cronjobs print to stdout or stderr gets formatted as an +email and piped to \f[C]/usr/sbin/sendmail\ \-t\ \-oem\ \-i\f[]. Attempts to mail this are also logged. -This switch permits the user to substitute a different mailhandler, -or a script, for sendmail. -That custom mailhandler is called with no arguments, and with the -mail headers and cronjob output supplied to stdin. -When a custom mailhandler is used, mailing is no longer logged -(have your mailhandler do that if you want it). -When cron jobs generate no stdout or stderr, nothing is sent to -either sendmail or a custom mailhandler. +This switch permits the user to substitute a different mailhandler, or a +script, for sendmail. +That custom mailhandler is called with no arguments, and with the mail +headers and cronjob output supplied to stdin. +When a custom mailhandler is used, mailing is no longer logged (have +your mailhandler do that if you want it). +When cron jobs generate no stdout or stderr, nothing is sent to either +sendmail or a custom mailhandler. .RS .RE .TP -.B -S +.B \-S log events to syslog, using syslog facility LOG_CRON and identity `crond' (this is the default behavior). .RS .RE .TP -.B -L file +.B \-L file log to specified file instead of syslog. .RS .RE .TP -.B -l loglevel +.B \-l loglevel log events at the specified, or more important, loglevels. The default is `notice'. -Valid level names are as described in logger(1) and syslog(3): -alert, crit, debug, emerg, err, error (deprecated synonym for err), -info, notice, panic (deprecated synonym for emerg), warning, warn -(deprecated synonym for warning). +Valid level names are as described in logger(1) and syslog(3): alert, +crit, debug, emerg, err, error (deprecated synonym for err), info, +notice, panic (deprecated synonym for emerg), warning, warn (deprecated +synonym for warning). .RS .RE .TP -.B -b -run \f[B]crond\f[] in the background (default unless -d or -f is +.B \-b +run \f[B]crond\f[] in the background (default unless \-d or \-f is specified) .RS .RE .TP -.B -f +.B \-f run \f[B]crond\f[] in the foreground. -All log messages are sent to stderr instead of syslog or a -L file. +All log messages are sent to stderr instead of syslog or a \-L file. .RS .RE .TP -.B -d +.B \-d turn on debugging. -This option sets the logging level to `debug' and causes -\f[B]crond\f[] to run in the foreground. +This option sets the logging level to `debug' and causes \f[B]crond\f[] +to run in the foreground. .RS .RE .SH DESCRIPTION .PP -\f[B]crond\f[] is responsible for scanning the crontab files and -running their commands at the appropriate time. -It always synchronizes to the top of the minute, matching the -current time against its internal list of parsed crontabs. +\f[B]crond\f[] is responsible for scanning the crontab files and running +their commands at the appropriate time. +It always synchronizes to the top of the minute, matching the current +time against its internal list of parsed crontabs. That list is stored so that it can be scanned very quickly, and \f[B]crond\f[] can deal with several hundred crontabs with several thousand entries without using noticeable CPU. .PP -Cron jobs are not re-executed if a previous instance of them is -still running. -For example, if you have a crontab command \f[C]sleep\ 70\f[], that -you request to be run every minute, \f[B]crond\f[] will skip this -job when it sees it is still running. -So the job won't be run more frequently than once every two -minutes. +Cron jobs are not re\-executed if a previous instance of them is still +running. +For example, if you have a crontab command \f[C]sleep\ 70\f[], that you +request to be run every minute, \f[B]crond\f[] will skip this job when +it sees it is still running. +So the job won't be run more frequently than once every two minutes. If you do not like this feature, you can run your commands in the background with an \f[C]&\f[]. .PP -\f[B]crond\f[] automatically detects when the clock has been -changed, during its per-minute scans. -Backwards time-changes of an hour or less won't re-run cron jobs -from the intervening period. -\f[B]crond\f[] will effectively sleep until it catches back up to -the original time. -Forwards time-changes of an hour or less (or if the computer is -suspended and resumed again within an hour) will run any missed -jobs exactly once. -Changes greater than an hour in either direction cause -\f[B]crond\f[] to re-calculate when jobs should be run, and not -attempt to execute any missed commands. -This is effectively the same as if \f[B]crond\f[] had been stopped -and re-started. -.PP -For example, suppose it's 10 am, and a job is scheduled to run -every day at 10:30 am. -If you set the system's clock forward to 11 am, crond will -immediately run the 10:30 job. -If on the other hand you set the system's clock forward to noon, -the 10:30 am job will be skipped until the next day. +\f[B]crond\f[] automatically detects when the clock has been changed, +during its per\-minute scans. +Backwards time\-changes of an hour or less won't re\-run cron jobs from +the intervening period. +\f[B]crond\f[] will effectively sleep until it catches back up to the +original time. +Forwards time\-changes of an hour or less (or if the computer is +suspended and resumed again within an hour) will run any missed jobs +exactly once. +Changes greater than an hour in either direction cause \f[B]crond\f[] to +re\-calculate when jobs should be run, and not attempt to execute any +missed commands. +This is effectively the same as if \f[B]crond\f[] had been stopped and +re\-started. +.PP +For example, suppose it's 10 am, and a job is scheduled to run every day +at 10:30 am. +If you set the system's clock forward to 11 am, crond will immediately +run the 10:30 job. +If on the other hand you set the system's clock forward to noon, the +10:30 am job will be skipped until the next day. Jobs scheduled using \@daily and the like work differently; see crontab(1) for details. .PP -\f[B]crond\f[] has a number of built in limitations to reduce the -chance of it being ill-used. -Potentially infinite loops during parsing are dealt with via a -failsafe counter, and non-root crontabs are limited to 256 crontab -entries. +\f[B]crond\f[] has a number of built in limitations to reduce the chance +of it being ill\-used. +Potentially infinite loops during parsing are dealt with via a failsafe +counter, and non\-root crontabs are limited to 256 crontab entries. Crontab lines may not be longer than 1024 characters, including the newline. .PP -Whenever \f[B]crond\f[] must run a job, it first creates a -daemon-owned temporary file O_EXCL and O_APPEND to store any -output, then fork()s and changes its user and group permissions to -match that of the user the job is being run for, then -\f[B]exec\f[]s \f[B]/bin/sh -c \f[] to run the job. -The temporary file remains under the ownership of the daemon to -prevent the user from tampering with it. -Upon job completion, \f[B]crond\f[] verifies the secureness of the -mail file and, if it has been appended to, mails the file to the -specified address. -The \f[B]sendmail\f[] program (or custom mail handler, if supplied) -is run under the user's uid to prevent mail related security holes. +Whenever \f[B]crond\f[] must run a job, it first creates a daemon\-owned +temporary file O_EXCL and O_APPEND to store any output, then fork()s and +changes its user and group permissions to match that of the user the job +is being run for, then \f[B]exec\f[]s \f[B]/bin/sh \-c \f[] to run the +job. +The temporary file remains under the ownership of the daemon to prevent +the user from tampering with it. +Upon job completion, \f[B]crond\f[] verifies the secureness of the mail +file and, if it has been appended to, mails the file to the specified +address. +The \f[B]sendmail\f[] program (or custom mail handler, if supplied) is +run under the user's uid to prevent mail related security holes. .PP When a user edits their crontab, \f[B]crontab\f[] first copies the -crontab to a user owned file before running the user's preferred -editor. -The suid \f[B]crontab\f[] keeps an open descriptor to the file -which it later uses to copy the file back, thereby ensuring the -user has not tampered with the file type. -.PP -\f[B]crontab\f[] notifies \f[B]crond\f[] that a user's crontab file -has been modified (or created or deleted) through the -\[lq]cron.update\[rq] file, which resides in the per-user crontabs -directory (usually /var/spool/cron/crontabs). -\f[B]crontab\f[] appends the filename of the modified crontab file -to \[lq]cron.update\[rq]; and \f[B]crond\f[] inspects this file to +crontab to a user owned file before running the user's preferred editor. +The suid \f[B]crontab\f[] keeps an open descriptor to the file which it +later uses to copy the file back, thereby ensuring the user has not +tampered with the file type. +.PP +\f[B]crontab\f[] notifies \f[B]crond\f[] that a user's crontab file has +been modified (or created or deleted) through the \[lq]cron.update\[rq] +file, which resides in the per\-user crontabs directory (usually +/var/spool/cron/crontabs). +\f[B]crontab\f[] appends the filename of the modified crontab file to +\[lq]cron.update\[rq]; and \f[B]crond\f[] inspects this file to determine when to reparse or otherwise update its internal list of parsed crontabs. .PP Whenever a \[lq]cron.update\[rq] file is seen, \f[B]crond\f[] also -re-reads timestamp files from its timestamp directory (usually +re\-reads timestamp files from its timestamp directory (usually /var/spool/cron/cronstamps). Normally these will just mirror \f[B]crond\f[]'s own internal -representations, but this mechanism could be used to manually -notify \f[B]crond\f[] that you've externally updated the -timestamps. +representations, but this mechanism could be used to manually notify +\f[B]crond\f[] that you've externally updated the timestamps. .PP -The \[lq]cron.update\[rq] file can also be used to ask -\f[B]crond\f[] to schedule a \[lq]named\[rq] cron job. +The \[lq]cron.update\[rq] file can also be used to ask \f[B]crond\f[] to +schedule a \[lq]named\[rq] cron job. To do this, append a line of the form: .IP .nf @@ -197,40 +195,36 @@ clio\ job1\ !job2 .fi .PP to \[lq]cron.update\[rq]. -This request that user clio's job1 should be scheduled (waiting -first for the successful completion of any jobs named in job1's -AFTER= tag), and job2 should also be scheduled (without waiting for -other jobs). +This request that user clio's job1 should be scheduled (waiting first +for the successful completion of any jobs named in job1's AFTER= tag), +and job2 should also be scheduled (without waiting for other jobs). See crontab(1) for more about tags and named jobs. .PP -The directory of per-user crontabs is re-parsed once every hour in -any case. -Any crontabs in the system directory (usually /etc/cron.d) are -parsed at the same time. +The directory of per\-user crontabs is re\-parsed once every hour in any +case. +Any crontabs in the system directory (usually /etc/cron.d) are parsed at +the same time. This directory can be used by packaging systems. -When you install a package foo, it might write its own foo-specific +When you install a package foo, it might write its own foo\-specific crontab to /etc/cron.d/foo. .PP -The superuser has a per-user crontab along with other users. +The superuser has a per\-user crontab along with other users. It usually resides at /var/spool/cron/crontabs/root. .PP Users can only have a crontab if they have an entry in /etc/passwd; however they do not need to have login shell privileges. -Cron jobs are always run under /bin/sh; see crontab(1) for more -details. +Cron jobs are always run under /bin/sh; see crontab(1) for more details. .PP -Unlike \f[B]crontab\f[], the \f[B]crond\f[] program does not keep -open descriptors to crontab files while running their jobs, as this -could cause \f[B]crond\f[] to run out of descriptors. +Unlike \f[B]crontab\f[], the \f[B]crond\f[] program does not keep open +descriptors to crontab files while running their jobs, as this could +cause \f[B]crond\f[] to run out of descriptors. .SH SEE ALSO .PP -\f[B]crontab\f[](1) +\f[B]crontab\f[](1) \f[B]crontab\f[](5) .SH AUTHORS .PP -Matthew Dillon (dillon\@apollo.backplane.com): original -developer +Matthew Dillon (dillon\@apollo.backplane.com): original developer .PD 0 .P .PD -Jim Pryor (profjim\@jimpryor.net): current -developer +James Pryor (dubiousjim\@gmail.com): current developer diff --git a/crond.markdown b/crond.8.markdown similarity index 99% rename from crond.markdown rename to crond.8.markdown index e52727b..225da3f 100644 --- a/crond.markdown +++ b/crond.8.markdown @@ -178,6 +178,7 @@ out of descriptors. SEE ALSO ======== **crontab**(1) +**crontab**(5) AUTHORS ======= diff --git a/crontab.1 b/crontab.1 index 1e2f59f..55a0bb4 100644 --- a/crontab.1 +++ b/crontab.1 @@ -1,254 +1,66 @@ -.TH CRONTAB 1 "1 May 2011" +.\" Automatically generated by Pandoc 2.3.1 +.\" +.TH "CRONTAB" "1" "20 Nov 2019" "" "" +.hy .SH NAME .PP -crontab - manipulate per-user crontabs (dillon's lightweight cron +crontab \- manipulate per\-user crontabs (dillon's lightweight cron daemon) .SH SYNOPSIS .PP -\f[B]crontab file [-u user]\f[] - replace crontab from file +\f[B]crontab file [\-u user]\f[] \- replace crontab from file .PP -\f[B]crontab - [-u user]\f[] - replace crontab from stdin +\f[B]crontab \- [\-u user]\f[] \- replace crontab from stdin .PP -\f[B]crontab -l [-u user]\f[] - list crontab for user +\f[B]crontab \-l [\-u user]\f[] \- list crontab for user .PP -\f[B]crontab -e [-u user]\f[] - edit crontab for user +\f[B]crontab \-e [\-u user]\f[] \- edit crontab for user .PP -\f[B]crontab -d [-u user]\f[] - delete crontab for user +\f[B]crontab \-d [\-u user]\f[] \- delete crontab for user .PP -\f[B]crontab -c dir\f[] - specify crontab directory +\f[B]crontab \-c dir\f[] \- specify crontab directory .SH DESCRIPTION .PP -\f[B]crontab\f[] manipulates the per-user crontabs. +The \f[B]crontab\f[] command manipulates the per\-user crontabs. +If you are looking for the format of these files, see \[lq]man 5 +crontab\[rq]. .PP -Generally the -e option is used to edit your crontab. -\f[B]crontab\f[] will use the editor specified by your EDITOR or -VISUAL environment variable (or /usr/bin/vi) to edit the crontab. -.PP -\f[B]crontab\f[] doesn't provide the kinds of protections that -programs like \f[B]visudo\f[] do against syntax errors and -simultaneous edits. -Errors won't be detected until \f[B]crond\f[] reads the crontab -file. -What \f[B]crontab\f[] does is provide a mechanism for users who may -not themselves have write privileges to the crontab folder to -nonetheless install or edit their crontabs. -It also notifies a running crond daemon of any changes to these -files. -.PP -Only users who belong to the same group as the \f[B]crontab\f[] -binary will be able to install or edit crontabs. -However it'll be possible for the superuser to install crontabs -even for users who don't have the privileges to install them -themselves. -(Even for users who don't have a login shell.) -Only the superuser may use the -u or -c switches to specify a -different user and/or crontab directory. -.PP -The superuser also has his or her own per-user crontab, saved as -/var/spool/cron/crontabs/root. -.PP -Unlike other cron daemons, this crond/crontab package doesn't try -to do everything under the sun. -It doesn't try to keep track of user's preferred shells; that would -require special-casing users with no login shell. -Instead, it just runs all commands using \f[C]/bin/sh\f[]. -(Commands can of course be script files written in any shell you -like.) -.PP -Nor does it do any special environment handling. -A shell script is better-suited to doing that than a cron daemon. -This cron daemon sets up only four environment variables: USER, -LOGNAME, HOME, and SHELL. -.PP -Our crontab format is roughly similar to that used by vixiecron. -Individual fields may contain a time, a time range, a time range -with a skip factor, a symbolic range for the day of week and month -in year, and additional subranges delimited with commas. -Blank lines in the crontab or lines that begin with a hash (#) are -ignored. -If you specify both a day in the month and a day of week, it will -be interpreted as the Nth such day in the month. -.PP -Some examples: -.IP -.nf -\f[C] -#\ MIN\ HOUR\ DAY\ MONTH\ DAYOFWEEK\ \ COMMAND -#\ run\ `date`\ at\ 6:10\ am\ every\ day -10\ 6\ *\ *\ *\ date - -#\ run\ every\ two\ hours\ at\ the\ top\ of\ the\ hour -0\ */2\ *\ *\ *\ date - -#\ run\ every\ two\ hours\ between\ 11\ pm\ and\ 7\ am,\ and\ again\ at\ 8\ am -0\ 23-7/2,8\ *\ *\ *\ date - -#\ run\ at\ 4:00\ am\ on\ January\ 1st -0\ 4\ 1\ jan\ *\ date - -#\ run\ every\ day\ at\ 11\ am,\ appending\ all\ output\ to\ a\ file -0\ 11\ *\ *\ *\ date\ >>\ /var/log/date-output\ 2>&1 -\f[] -.fi -.PP -To request the last Monday, etc. -in a month, ask for the \[lq]6th\[rq] one. -This will always match the last Monday, etc., even if there are -only four Mondays in the month: -.IP -.nf -\f[C] -#\ run\ at\ 11\ am\ on\ the\ first\ and\ last\ Mon,\ Tue,\ Wed\ of\ each\ month -0\ 11\ 1,6\ *\ mon-wed\ date - -#\ run\ at\ noon\ on\ the\ fourth\ and\ last\ Friday\ of\ each\ month -0\ 12\ 4,6\ *\ fri\ date -\f[] -.fi -.PP -When the fourth Monday in a month is also the last, this will match against -both the \[lq]4th\[rq] and the \[lq]6th\[rq] but the job is scheduled only -once. -.PP -The following formats are also recognized: -.IP -.nf -\f[C] -#\ schedule\ this\ job\ only\ once,\ when\ crond\ starts\ up -\@reboot\ date - -#\ schedule\ this\ job\ whenever\ crond\ is\ running,\ and\ sees\ that\ at\ least\ one -#\ hour\ has\ elapsed\ since\ it\ last\ ran -\@hourly\ ID=job1\ date -\f[] -.fi -.PP -The formats \@hourly, \@daily, \@weekly, \@monthly, and \@yearly -need to update timestamp files when their jobs have been run. -The timestamp files are saved as -/var/spool/cron/cronstamps/user.jobname. -So for all of these formats, the cron command needs a jobname, -given by prefixing the command with \f[C]ID=jobname\f[]. -(This syntax was chosen to maximize the chance that our crontab -files will be readable by other cron daemons as well. -They might just interpret the ID=jobname as a command-line -environment variable assignment.) -.PP -There's also this esoteric option, whose usefulness will be -explained later: +Generally the \-e option is used to edit your crontab. +\f[B]crontab\f[] will use the editor specified by your EDITOR or VISUAL +environment variable (or /usr/bin/vi) to edit the crontab: .IP .nf \f[C] -#\ don\[aq]t\ ever\ schedule\ this\ job\ on\ its\ own;\ only\ run\ it\ when\ it\[aq]s\ triggered -#\ as\ a\ "dependency"\ of\ another\ job\ (see\ below),\ or\ when\ the\ user\ explicitly -#\ requests\ it\ through\ the\ "cron.update"\ file\ (see\ crond(8)) -\@noauto\ ID=namedjob\ date +$\ crontab\ \-e \f[] .fi .PP -There's also a format available for finer-grained control of -frequencies: -.IP -.nf -\f[C] -#\ run\ whenever\ it\[aq]s\ between\ 2-4\ am,\ and\ at\ least\ one\ day\ (1d) -#\ has\ elapsed\ since\ this\ job\ ran -*\ 2-4\ *\ *\ *\ ID=job2\ FREQ=1d\ date - -#\ as\ before,\ but\ re-try\ every\ 10\ minutes\ (10m)\ if\ my_command -#\ exits\ with\ code\ 11\ (EAGAIN) -*\ 2-4\ *\ *\ *\ ID=job3\ FREQ=1d/10m\ my_command -\f[] -.fi -.PP -These formats also update timestamp files, and so also require -their jobs to be assigned IDs. -.PP -Notice the technique used in the second example: jobs can exit with -code 11 to indicate they lacked the resources to run (for example, -no network was available), and so should be tried again after a -brief delay. -This works for jobs using either \@freq or FREQ=\&... formats; but -the FREQ=\&.../10m syntax is the only way to customize the length -of the delay before re-trying. -.PP -Jobs can be made to \[lq]depend\[rq] on, or wait until AFTER other -jobs have successfully completed. -Consider the following crontab: -.IP -.nf -\f[C] -*\ *\ *\ *\ *\ ID=job4\ FREQ=1d\ first_command -*\ *\ *\ *\ *\ ID=job5\ FREQ=1h\ AFTER=job4/30m\ second_command -\f[] -.fi -.PP -Here, whenever job5 is up to be run, if job4 is scheduled to run -within the next 30 minutes (30m), job5 will first wait for it to -successfully complete. -.PP -(What if job4 doesn't successfully complete? If job4 returns with -exit code EAGAIN, job5 will continue to wait until job4 is -retried\[em]even if that won't be within the hour. -If job4 returns with any other non-zero exit code, job5 will be -removed from the queue without running.) -.PP -Jobs can be told to wait for multiple other jobs, as follows: -.IP -.nf -\f[C] -10\ *\ *\ *\ *\ ID=job6\ AFTER=job4/1h,job7\ third_command -\f[] -.fi -.PP -The waiting job6 doesn't care what order job4 and job7 complete in. -If job6 comes up to be re-scheduled (an hour later) while an -earlier instance is still waiting, only a single instance of job6 -will remain in the queue. -It will have all of its \[lq]waiting flags\[rq] reset: so each of -job7 and job4 (supposing again that job4 would run within the next -1h) will again have to complete before job6 will run. -.PP -If a job waits on a \@reboot or \@noauto job, the target job being -waited on will also be scheduled to run. -This technique can be used to have a common job scheduled as -\@noauto that several other jobs depend on (and so call as a -subroutine). -.PP -The command portion of a cron job is run with -\f[C]/bin/sh\ -c\ ...\f[] and may therefore contain any valid -Bourne shell command. -A common practice is to prefix your command with \f[B]exec\f[] to -keep the process table uncluttered. -It is also common to redirect job output to a file or to /dev/null. -If you do not, and the command generates output on stdout or -stderr, that output will be mailed to the local user whose crontab -the job comes from. -If you have crontabs for special users, such as uucp, who can't -receive local mail, you may want to create mail aliases for them or -adjust this behavior. -(See crond(8) for details how to adjust it.) -.PP -Whenever jobs return an exit code that's neither 0 nor 11 (EAGAIN), -that event will be logged, regardless of whether any stdout or -stderr is generated. -The job's timestamp will also be updated, and it won't be run again -until it would next be normally scheduled. -Any jobs waiting on the failed job will be canceled; they won't be -run until they're next scheduled. -.SH TODO -.PP -Ought to be able to have several crontab files for any given user, -as an organizational tool. +Just as a regular user, the superuser also has his or her own per\-user +crontab. +.SH NOTES +.PP +\f[B]crontab\f[] doesn't provide the kinds of protections that programs +like \f[B]visudo\f[] do against syntax errors and simultaneous edits. +Errors won't be detected until \f[B]crond\f[] reads the crontab file. +What \f[B]crontab\f[] does is provide a mechanism for users who may not +themselves have write privileges to the crontab folder to nonetheless +install or edit their crontabs. +It also notifies a running crond daemon of any changes to these files. +.PP +Only users who belong to the same group as the \f[B]crontab\f[] binary +will be able to install or edit crontabs. +However it'll be possible for the superuser to install crontabs even for +users who don't have the privileges to install them themselves. +(Even for users who don't have a login shell.) Only the superuser may +use the \-u or \-c switches to specify a different user and/or crontab +directory. .SH SEE ALSO .PP -\f[B]crond\f[](8) +\f[B]crontab\f[](5) \f[B]crond\f[](8) .SH AUTHORS .PP -Matthew Dillon (dillon\@apollo.backplane.com): original -developer +Matthew Dillon (dillon\@apollo.backplane.com): original developer .PD 0 .P .PD -Jim Pryor (profjim\@jimpryor.net): current -developer +James Pryor (dubiousjim\@gmail.com): current developer diff --git a/crontab.1.markdown b/crontab.1.markdown new file mode 100644 index 0000000..611c55d --- /dev/null +++ b/crontab.1.markdown @@ -0,0 +1,62 @@ +% CRONTAB(1) +% +% 20 Nov 2019 + +NAME +==== +crontab - manipulate per-user crontabs (dillon's lightweight cron daemon) + +SYNOPSIS +======== +**crontab file [-u user]** - replace crontab from file + +**crontab - [-u user]** - replace crontab from stdin + +**crontab -l [-u user]** - list crontab for user + +**crontab -e [-u user]** - edit crontab for user + +**crontab -d [-u user]** - delete crontab for user + +**crontab -c dir** - specify crontab directory + +DESCRIPTION +=========== + +The **crontab** command manipulates the per-user crontabs. If you are +looking for the format of these files, see "man 5 crontab". + +Generally the -e option is used to edit your crontab. **crontab** will use +the editor specified by your EDITOR or VISUAL environment +variable (or /usr/bin/vi) to edit the crontab: + + $ crontab -e + +Just as a regular user, the superuser also has his or her own per-user +crontab. + +NOTES +===== + +**crontab** doesn't provide the kinds of protections that programs like **visudo** do +against syntax errors and simultaneous edits. Errors won't be detected until +**crond** reads the crontab file. What **crontab** does is provide a mechanism for +users who may not themselves have write privileges to the crontab folder +to nonetheless install or edit their crontabs. It also notifies a running crond +daemon of any changes to these files. + +Only users who belong to the same group as the **crontab** binary will be able +to install or edit crontabs. However it'll be possible for the superuser to +install crontabs even for users who don't have the privileges to install them +themselves. (Even for users who don't have a login shell.) Only the superuser may use +the -u or -c switches to specify a different user and/or crontab directory. + +SEE ALSO +======== +**crontab**(5) +**crond**(8) + +AUTHORS +======= +Matthew Dillon (dillon@apollo.backplane.com): original developer +James Pryor (dubiousjim@gmail.com): current developer diff --git a/crontab.5 b/crontab.5 new file mode 100644 index 0000000..07435e6 --- /dev/null +++ b/crontab.5 @@ -0,0 +1,315 @@ +.\"t +.\" Automatically generated by Pandoc 2.3.1 +.\" +.TH "CRONTAB" "5" "20 Nov 2019" "" "" +.hy +.SH NAME +.PP +crontab \- table of cron jobs +.SH DESCRIPTION +.PP +This page describes the crontab format used by \f[B]dcron\f[]. +Where possible, the format follows the conventions of other cron +implementations, such as Vixie cron. +.PP +You may wish to skip to the comprehensive section of \f[B]EXAMPLES\f[]. +.PP +A crontab consists of one row for each job. +Blank lines in the crontab are ignored, as are lines which begin with a +hash (#) and are used for comments. +.PP +A job consists of a time specification, and command to run: +.IP +.nf +\f[C] +*\ *\ *\ *\ *\ date +\f[] +.fi +.PP +Additional attributes may be specified: +.IP +.nf +\f[C] +*\ *\ *\ *\ *\ ID=myjob\ date +\f[] +.fi +.PP +Shorthand time specifications can be given, in which case cron will +decide when the job runs: +.IP +.nf +\f[C] +\@hourly\ ID=myjob\ date +\f[] +.fi +.PP +\[lq]System\[rq] crontabs (typically in /etc/cron.d; see the section +\f[B]System crontabs\f[]) have an extended format which includes the +user to execute a job as. +.IP +.nf +\f[C] +*\ *\ *\ *\ *\ matthew\ date +\f[] +.fi +.SS Time specification +.PP +The time specification fields are, in order: +.PP +.TS +tab(@); +l l. +T{ +field +T}@T{ +values +T} +_ +T{ +minute +T}@T{ +0\-59 +T} +T{ +hour +T}@T{ +0\-23 +T} +T{ +day of month +T}@T{ +1\-31 +T} +T{ +month +T}@T{ +1\-12; jan\-dec +T} +T{ +day of week +T}@T{ +0\-7; sun\-sun +T} +.TE +.PP +For any field, ranges can be given either numerically (eg. +\[lq]1\-3\[rq]) or using the text equivalents (eg. +\[lq]mon\-wed\[rq]). +Sub\-ranges can be separated with a comma: +.IP +.nf +\f[C] +#\ run\ every\ two\ hours\ between\ 11pm\ and\ 7am,\ and\ again\ at\ 8am +0\ 23\-7/2,8\ *\ *\ *\ date +\f[] +.fi +.PP +If you specify both a day in the month and a day of week, it will be +interpreted as the Nth such day in the month. +.PP +The alternative time specifications of \@hourly, \@daily, \@weekly, +\@monthly, and \@yearly may also be used. +In this case cron will keep a log of when the job was last run, and use +this to decide when to next run the job. +This means it is necessary to set the \f[B]ID=\f[] attribute to uniquely +identify the job: +.IP +.nf +\f[C] +#\ at\ least\ one\ hour\ has\ elapsed\ since\ it\ last\ ran +\@hourly\ ID=job1\ date +\f[] +.fi +.PP +A special time specification \@reboot will begin the job on startup, and +no idenfier is required: +.IP +.nf +\f[C] +\@reboot\ date +\f[] +.fi +.SS System crontabs +.PP +\[lq]System\[rq] crontabs are files located in /etc/cron.d, and it is +normal for these files to be placed by a package manager. +.PP +The jobs run as a user specified on a per\-job basis, following the +convention of other cron implementations: +.IP +.nf +\f[C] +#\ switch\ to\ user\ "matthew"\ and\ run\ the\ command +*\ *\ *\ *\ *\ matthew\ date +\f[] +.fi +.PP +System crontabs remain are \[lq]owned\[rq] by root, which will be the +email recipient of output or errors. +.SS Fine\-grained control +.PP +There's also a format available for finer\-grained control of +frequencies: +.IP +.nf +\f[C] +#\ run\ whenever\ it\[aq]s\ between\ 2\-4\ am,\ and\ at\ least\ one\ day\ (1d) +#\ has\ elapsed\ since\ this\ job\ ran +*\ 2\-4\ *\ *\ *\ ID=job2\ FREQ=1d\ date + +#\ as\ before,\ but\ re\-try\ every\ 10\ minutes\ (10m)\ if\ my_command +#\ exits\ with\ code\ 11\ (EAGAIN) +*\ 2\-4\ *\ *\ *\ ID=job3\ FREQ=1d/10m\ my_command +\f[] +.fi +.PP +These formats also update timestamp files, and so also require their +jobs to be assigned IDs. +.PP +Notice the technique used in the second example: jobs can exit with code +11 to indicate they lacked the resources to run (for example, no network +was available), and so should be tried again after a brief delay. +This works for jobs using either \@freq or FREQ=\&... formats; but the +FREQ=\&.../10m syntax is the only way to customize the length of the +delay before re\-trying. +.SS Dependencies +.PP +Jobs can be made to \[lq]depend\[rq] on, or wait until AFTER other jobs +have successfully completed. +Consider the following crontab: +.IP +.nf +\f[C] +*\ *\ *\ *\ *\ ID=job4\ FREQ=1d\ first_command +*\ *\ *\ *\ *\ ID=job5\ FREQ=1h\ AFTER=job4/30m\ second_command +\f[] +.fi +.PP +Here, whenever job5 is up to be run, if job4 is scheduled to run within +the next 30 minutes (30m), job5 will first wait for it to successfully +complete. +.PP +(What if job4 doesn't successfully complete? +If job4 returns with exit code EAGAIN, job5 will continue to wait until +job4 is retried\[em]even if that won't be within the hour. +If job4 returns with any other non\-zero exit code, job5 will be removed +from the queue without running.) +.PP +Jobs can be told to wait for multiple other jobs, as follows: +.IP +.nf +\f[C] +10\ *\ *\ *\ *\ ID=job6\ AFTER=job4/1h,job7\ third_command +\f[] +.fi +.PP +The waiting job6 doesn't care what order job4 and job7 complete in. +If job6 comes up to be re\-scheduled (an hour later) while an earlier +instance is still waiting, only a single instance of job6 will remain in +the queue. +It will have all of its \[lq]waiting flags\[rq] reset: so each of job7 +and job4 (supposing again that job4 would run within the next 1h) will +again have to complete before job6 will run. +.PP +If a job waits on a \@reboot or \@noauto job, the target job being +waited on will also be scheduled to run. +This technique can be used to have a common job scheduled as \@noauto +that several other jobs depend on (and so call as a subroutine). +.IP +.nf +\f[C] +#\ don\[aq]t\ ever\ schedule\ this\ job\ on\ its\ own;\ only\ run\ it\ when\ it\[aq]s\ triggered +#\ as\ a\ "dependency"\ of\ another\ job\ (see\ below),\ or\ when\ the\ user\ explicitly +#\ requests\ it\ through\ the\ "cron.update"\ file\ (see\ crond(8)) +\@noauto\ ID=namedjob\ date +\f[] +.fi +.SS Command execution +.PP +The command portion of a cron job is run with \f[C]/bin/sh\ \-c\ ...\f[] +and may therefore contain any valid Bourne shell command. +A common practice is to prefix your command with \f[B]exec\f[] to keep +the process table uncluttered. +It is also common to redirect job output to a file or to /dev/null. +If you do not, and the command generates output on stdout or stderr, +that output will be mailed to the local user whose crontab the job comes +from. +If you have crontabs for special users, such as uucp, who can't receive +local mail, you may want to create mail aliases for them or adjust this +behavior. +(See crond(8) for details how to adjust it.) +.PP +Whenever jobs return an exit code that's neither 0 nor 11 (EAGAIN), that +event will be logged, regardless of whether any stdout or stderr is +generated. +The job's timestamp will also be updated, and it won't be run again +until it would next be normally scheduled. +Any jobs waiting on the failed job will be canceled; they won't be run +until they're next scheduled. +.SH EXAMPLES +.PP +Examples of regular user's crontab entries: +.IP +.nf +\f[C] +#\ MIN\ HOUR\ DAY\ MONTH\ DAYOFWEEK\ \ COMMAND + +#\ run\ `date`\ at\ 6:10\ am\ every\ day +10\ 6\ *\ *\ *\ date + +#\ run\ every\ two\ hours\ at\ the\ top\ of\ the\ hour +0\ */2\ *\ *\ *\ date + +#\ run\ every\ two\ hours\ between\ 11\ pm\ and\ 7\ am,\ and\ again\ at\ 8\ am +0\ 23\-7/2,8\ *\ *\ *\ date + +#\ run\ at\ 4:00\ am\ on\ January\ 1st +0\ 4\ 1\ jan\ *\ date + +#\ run\ every\ day\ at\ 11\ am,\ appending\ all\ output\ to\ a\ file +0\ 11\ *\ *\ *\ date\ >>\ /var/log/date\-output\ 2>&1 + +#\ schedule\ this\ job\ only\ once,\ when\ crond\ starts\ up +\@reboot\ date + +#\ at\ least\ one\ hour\ has\ elapsed\ since\ it\ last\ ran +\@hourly\ ID=job1\ date +\f[] +.fi +.PP +To request the last Monday, etc. +in a month, ask for the \[lq]5th\[rq] one. +This will always match the last Monday, etc., even if there are only +four Mondays in the month: +.IP +.nf +\f[C] +#\ run\ at\ 11\ am\ on\ the\ first\ and\ last\ Mon,\ Tue,\ Wed\ of\ each\ month +0\ 11\ 1,5\ *\ mon\-wed\ date +\f[] +.fi +.PP +When the fourth Monday in a month is the last, it will match against +both the \[lq]4th\[rq] and the \[lq]5th\[rq] (it will only run once if +both are specified). +.SH NOTES +.PP +Unlike other cron daemons, this crond/crontab package doesn't try to do +everything under the sun. +It doesn't try to keep track of user's preferred shells; that would +require special\-casing users with no login shell. +Instead, it just runs all commands using \f[C]/bin/sh\f[]. +(Commands can of course be script files written in any shell you like.) +.PP +Nor does it do any special environment handling. +A shell script is better\-suited to doing that than a cron daemon. +This cron daemon sets up only four environment variables: USER, LOGNAME, +HOME, and SHELL. +.SH SEE ALSO +.PP +\f[B]crontab\f[](1) \f[B]crond\f[](8) +.SH AUTHORS +.PP +Matthew Dillon (dillon\@apollo.backplane.com): original developer James +Pryor (dubiousjim\@gmail.com): current developer Mark Hills +(mark\@xwax.org): contributor diff --git a/crontab.markdown b/crontab.5.markdown similarity index 61% rename from crontab.markdown rename to crontab.5.markdown index 3d56338..5a0c354 100644 --- a/crontab.markdown +++ b/crontab.5.markdown @@ -1,117 +1,97 @@ -% CRONTAB(1) -% +% CRONTAB(5) +% % 20 Nov 2019 NAME ==== -crontab - manipulate per-user crontabs (dillon's lightweight cron daemon) - -SYNOPSIS -======== -**crontab file [-u user]** - replace crontab from file - -**crontab - [-u user]** - replace crontab from stdin - -**crontab -l [-u user]** - list crontab for user - -**crontab -e [-u user]** - edit crontab for user - -**crontab -d [-u user]** - delete crontab for user - -**crontab -c dir** - specify crontab directory +crontab - table of cron jobs DESCRIPTION =========== -**crontab** manipulates the per-user crontabs. +This page describes the crontab format used by **dcron**. Where +possible, the format follows the conventions of other cron +implementations, such as Vixie cron. -Generally the -e option is used to edit your crontab. **crontab** will use -the editor specified by your EDITOR or VISUAL environment -variable (or /usr/bin/vi) to edit the crontab. +You may wish to skip to the comprehensive section of **EXAMPLES**. -**crontab** doesn't provide the kinds of protections that programs like **visudo** do -against syntax errors and simultaneous edits. Errors won't be detected until -**crond** reads the crontab file. What **crontab** does is provide a mechanism for -users who may not themselves have write privileges to the crontab folder -to nonetheless install or edit their crontabs. It also notifies a running crond -daemon of any changes to these files. +A crontab consists of one row for each job. Blank lines in the crontab +are ignored, as are lines which begin with a hash (#) and are used for +comments. -Only users who belong to the same group as the **crontab** binary will be able -to install or edit crontabs. However it'll be possible for the superuser to -install crontabs even for users who don't have the privileges to install them -themselves. (Even for users who don't have a login shell.) Only the superuser may use -the -u or -c switches to specify a different user and/or crontab directory. +A job consists of a time specification, and command to run: -The superuser also has his or her own per-user crontab, saved as -/var/spool/cron/crontabs/root. + * * * * * date +Additional attributes may be specified: -Unlike other cron daemons, this crond/crontab package doesn't try to do -everything under the sun. It doesn't try to keep track of user's preferred -shells; that would require special-casing users with no login shell. Instead, -it just runs all commands using `/bin/sh`. (Commands can of course be script -files written in any shell you like.) + * * * * * ID=myjob date -Nor does it do any special environment handling. A shell script is -better-suited to doing that than a cron daemon. This cron daemon sets up only -four environment variables: USER, LOGNAME, HOME, and SHELL. +Shorthand time specifications can be given, in which case cron will +decide when the job runs: + @hourly ID=myjob date -Our crontab format is roughly similar to that used by vixiecron. Individual -fields may contain a time, a time range, a time range with a skip factor, a -symbolic range for the day of week and month in year, and additional subranges -delimited with commas. Blank lines in the crontab or lines that begin with a -hash (#) are ignored. If you specify both a day in the month and a day of week, -it will be interpreted as the Nth such day in the month. +"System" crontabs (typically in /etc/cron.d; see the section **System +crontabs**) have an extended format which includes the user to execute +a job as. -Some examples: + * * * * * matthew date - # MIN HOUR DAY MONTH DAYOFWEEK COMMAND - # run `date` at 6:10 am every day - 10 6 * * * date +Time specification +------------------ - # run every two hours at the top of the hour - 0 */2 * * * date +The time specification fields are, in order: - # run every two hours between 11 pm and 7 am, and again at 8 am - 0 23-7/2,8 * * * date +field values +------ ------- +minute 0-59 +hour 0-23 +day of month 1-31 +month 1-12; jan-dec +day of week 0-7; sun-sun - # run at 4:00 am on January 1st - 0 4 1 jan * date +For any field, ranges can be given either numerically (eg. "1-3") or +using the text equivalents (eg. "mon-wed"). Sub-ranges can be +separated with a comma: - # run every day at 11 am, appending all output to a file - 0 11 * * * date >> /var/log/date-output 2>&1 + # run every two hours between 11pm and 7am, and again at 8am + 0 23-7/2,8 * * * date -To request the last Monday, etc. in a month, ask for the "5th" one. This will always match the last Monday, etc., even if there are only four Mondays in the month: +If you specify both a day in the month and a day of week, it will be +interpreted as the Nth such day in the month. - # run at 11 am on the first and last Mon, Tue, Wed of each month - 0 11 1,5 * mon-wed date +The alternative time specifications of @hourly, @daily, @weekly, +@monthly, and @yearly may also be used. In this case cron will keep a +log of when the job was last run, and use this to decide when to next +run the job. This means it is necessary to set the **ID=** attribute +to uniquely identify the job: -When the fourth Monday in a month is the last, it will match against both the "4th" and the "5th" (it will only run once if both are specified). + # at least one hour has elapsed since it last ran + @hourly ID=job1 date -The following formats are also recognized: +A special time specification @reboot will begin the job on startup, +and no idenfier is required: - # schedule this job only once, when crond starts up @reboot date - # schedule this job whenever crond is running, and sees that at least one - # hour has elapsed since it last ran - @hourly ID=job1 date +System crontabs +--------------- -The formats @hourly, @daily, @weekly, @monthly, and @yearly need to update -timestamp files when their jobs have been run. The timestamp files are saved as -/var/spool/cron/cronstamps/user.jobname. So for all of these formats, the cron -command needs a jobname, given by prefixing the command with `ID=jobname`. -(This syntax was chosen to maximize the chance that our crontab files will be -readable by other cron daemons as well. They might just interpret the -ID=jobname as a command-line environment variable assignment.) +"System" crontabs are files located in /etc/cron.d, and it is normal +for these files to be placed by a package manager. -There's also this esoteric option, whose usefulness will be explained later: +The jobs run as a user specified on a per-job basis, following the +convention of other cron implementations: - # don't ever schedule this job on its own; only run it when it's triggered - # as a "dependency" of another job (see below), or when the user explicitly - # requests it through the "cron.update" file (see crond(8)) - @noauto ID=namedjob date + # switch to user "matthew" and run the command + * * * * * matthew date + +System crontabs remain are "owned" by root, which will be the email +recipient of output or errors. + +Fine-grained control +-------------------- There's also a format available for finer-grained control of frequencies: @@ -132,6 +112,9 @@ available), and so should be tried again after a brief delay. This works for jobs using either @freq or FREQ=... formats; but the FREQ=.../10m syntax is the only way to customize the length of the delay before re-trying. +Dependencies +------------ + Jobs can be made to "depend" on, or wait until AFTER other jobs have successfully completed. Consider the following crontab: @@ -160,6 +143,14 @@ If a job waits on a @reboot or @noauto job, the target job being waited on will also be scheduled to run. This technique can be used to have a common job scheduled as @noauto that several other jobs depend on (and so call as a subroutine). + # don't ever schedule this job on its own; only run it when it's triggered + # as a "dependency" of another job (see below), or when the user explicitly + # requests it through the "cron.update" file (see crond(8)) + @noauto ID=namedjob date + +Command execution +----------------- + The command portion of a cron job is run with `/bin/sh -c ...` and may therefore contain any valid Bourne shell command. A common practice is to prefix your command with **exec** to keep the process table uncluttered. It is @@ -176,18 +167,64 @@ timestamp will also be updated, and it won't be run again until it would next be normally scheduled. Any jobs waiting on the failed job will be canceled; they won't be run until they're next scheduled. +EXAMPLES +======== -TODO -==== -Ought to be able to have several crontab files for any given user, as -an organizational tool. +Examples of regular user's crontab entries: + # MIN HOUR DAY MONTH DAYOFWEEK COMMAND + + # run `date` at 6:10 am every day + 10 6 * * * date + + # run every two hours at the top of the hour + 0 */2 * * * date + + # run every two hours between 11 pm and 7 am, and again at 8 am + 0 23-7/2,8 * * * date + + # run at 4:00 am on January 1st + 0 4 1 jan * date + + # run every day at 11 am, appending all output to a file + 0 11 * * * date >> /var/log/date-output 2>&1 + + # schedule this job only once, when crond starts up + @reboot date + + # at least one hour has elapsed since it last ran + @hourly ID=job1 date + +To request the last Monday, etc. in a month, ask for the "5th" +one. This will always match the last Monday, etc., even if there are +only four Mondays in the month: + + # run at 11 am on the first and last Mon, Tue, Wed of each month + 0 11 1,5 * mon-wed date + +When the fourth Monday in a month is the last, it will match against +both the "4th" and the "5th" (it will only run once if both are +specified). + +NOTES +===== +Unlike other cron daemons, this crond/crontab package doesn't try to do +everything under the sun. It doesn't try to keep track of user's preferred +shells; that would require special-casing users with no login shell. Instead, +it just runs all commands using `/bin/sh`. (Commands can of course be script +files written in any shell you like.) + +Nor does it do any special environment handling. A shell script is +better-suited to doing that than a cron daemon. This cron daemon sets up only +four environment variables: USER, LOGNAME, HOME, and SHELL. SEE ALSO ======== +**crontab**(1) **crond**(8) AUTHORS ======= -Matthew Dillon (dillon@apollo.backplane.com): original developer +Matthew Dillon (dillon@apollo.backplane.com): original developer James Pryor (dubiousjim@gmail.com): current developer +Mark Hills (mark@xwax.org): contributor diff --git a/database.c b/database.c index 270ab9b..50d1de2 100644 --- a/database.c +++ b/database.c @@ -4,6 +4,7 @@ * * Copyright 1994 Matthew Dillon (dillon@apollo.backplane.com) * Copyright 2009-2019 James Pryor + * Copyright 2020 Mark Hills * May be distributed under the GNU General Public License version 2 or any later version. */ @@ -17,8 +18,8 @@ #define LAST_DOW (1 << 5) #define ALL_DOW (FIRST_DOW|SECOND_DOW|THIRD_DOW|FOURTH_DOW|FIFTH_DOW|LAST_DOW) -Prototype void CheckUpdates(const char *dpath, const char *user_override, time_t t1, time_t t2); -Prototype void SynchronizeDir(const char *dpath, const char *user_override, int initial_scan); +Prototype void CheckUpdates(const char *dpath, int is_system, time_t t1, time_t t2); +Prototype void SynchronizeDir(const char *dpath, int is_system, int initial_scan); Prototype void ReadTimestamps(const char *user); Prototype int TestJobs(time_t t1, time_t t2); Prototype int TestStartupJobs(void); @@ -26,10 +27,11 @@ Prototype int ArmJob(CronFile *file, CronLine *line, time_t t1, time_t t2); Prototype void RunJobs(void); Prototype int CheckJobs(void); -void SynchronizeFile(const char *dpath, const char *fname, const char *uname); +void SynchronizeFile(const char *dpath, const char *fname, const char *uname, int parseUser); void DeleteFile(CronFile **pfile); +void DeleteLineContent(CronLine *line); char *ParseInterval(int *interval, char *ptr); -char *ParseField(char *userName, char *ary, int modvalue, int offset, int onvalue, const char **names, char *ptr); +char *ParseField(char *ary, int modvalue, int offset, int onvalue, const char **names, char *ptr); void FixDayDow(CronLine *line); void PrintLine(CronLine *line); void PrintFile(CronFile *file, char* loc, char* fname, int line); @@ -96,12 +98,11 @@ const char *FreqAry[] = { }; /* - * Check the cron.update file in the specified directory. If user_override - * is NULL then the files in the directory belong to the user whose name is - * the file, otherwise they belong to the user_override user. + * Check the cron.update file in the specified directory. System crontabs + * are 'owned' by root but parse a username to run each job as. */ void -CheckUpdates(const char *dpath, const char *user_override, time_t t1, time_t t2) +CheckUpdates(const char *dpath, int is_system, time_t t1, time_t t2) { FILE *fi; char buf[SMALL_BUFFER]; @@ -125,12 +126,12 @@ CheckUpdates(const char *dpath, const char *user_override, time_t t1, time_t t2) */ fname = strtok_r(buf, " \t\n", &ptok); - if (user_override) - SynchronizeFile(dpath, fname, user_override); + if (is_system) + SynchronizeFile(dpath, fname, "root", 1); else if (!getpwnam(fname)) printlogf(LOG_WARNING, "ignoring %s/%s (non-existent user)\n", dpath, fname); else if (*ptok == 0 || *ptok == '\n') { - SynchronizeFile(dpath, fname, fname); + SynchronizeFile(dpath, fname, fname, 0); ReadTimestamps(fname); } else { /* if fname is followed by whitespace, we prod any following jobs */ @@ -174,7 +175,7 @@ CheckUpdates(const char *dpath, const char *user_override, time_t t1, time_t t2) } void -SynchronizeDir(const char *dpath, const char *user_override, int initial_scan) +SynchronizeDir(const char *dpath, int is_system, int initial_scan) { CronFile **pfile; CronFile *file; @@ -220,10 +221,10 @@ SynchronizeDir(const char *dpath, const char *user_override, int initial_scan) continue; if (strcmp(den->d_name, CRONUPDATE) == 0) continue; - if (user_override) { - SynchronizeFile(dpath, den->d_name, user_override); + if (is_system) { + SynchronizeFile(dpath, den->d_name, "root", 1); } else if (getpwnam(den->d_name)) { - SynchronizeFile(dpath, den->d_name, den->d_name); + SynchronizeFile(dpath, den->d_name, den->d_name, 0); } else { printlogf(LOG_WARNING, "ignoring %s/%s (non-existent user)\n", dpath, den->d_name); @@ -308,8 +309,348 @@ ReadTimestamps(const char *user) } } +/* + * parse @hourly, etc + * + * Return: NULL on parse error + */ +static char* +ParseTimeInterval(CronLine *line, char *ptr) +{ + int j; + line->cl_Delay = -1; + ptr += 1; + for (j = 0; FreqAry[j]; ++j) { + if (strncmp(ptr, FreqAry[j], strlen(FreqAry[j])) == 0) { + break; + } + } + if (FreqAry[j]) { + ptr += strlen(FreqAry[j]); + switch(j) { + case 0: + /* noauto */ + line->cl_Freq = -2; + line->cl_Delay = 0; + break; + case 1: + /* reboot */ + line->cl_Freq = -1; + line->cl_Delay = 0; + break; + case 2: + line->cl_Freq = HOURLY_FREQ; + break; + case 3: + line->cl_Freq = DAILY_FREQ; + break; + case 4: + line->cl_Freq = WEEKLY_FREQ; + break; + case 5: + line->cl_Freq = MONTHLY_FREQ; + break; + case 6: + line->cl_Freq = YEARLY_FREQ; + break; + /* else line->cl_Freq will remain 0 */ + } + } + + if (!line->cl_Freq || (*ptr != ' ' && *ptr != '\t')) + return NULL; + + if (line->cl_Delay < 0) { + /* + * delays on @daily, @hourly, etc are 1/20 of the frequency + * so they don't all start at once + * this also affects how they behave when the job returns EAGAIN + */ + line->cl_Delay = line->cl_Freq / 20; + line->cl_Delay -= line->cl_Delay % 60; + if (line->cl_Delay == 0) + line->cl_Delay = 60; + /* all minutes are permitted */ + for (j=0; j<60; ++j) + line->cl_Mins[j] = 1; + for (j=0; j<24; ++j) + line->cl_Hrs[j] = 1; + for (j=1; j<32; ++j) + /* days are numbered 1..31 */ + line->cl_Days[j] = 1; + for (j=0; j<12; ++j) + line->cl_Mons[j] = 1; + } + + while (*ptr == ' ' || *ptr == '\t') + ++ptr; + + return ptr; +} + +/* + * parse time and date fields + */ +static char* +ParseTimeSpec(CronLine *line, char *ptr) +{ + ptr = ParseField(line->cl_Mins, FIELD_MINUTES, 0, 1, NULL, ptr); + ptr = ParseField(line->cl_Hrs, FIELD_HOURS, 0, 1, NULL, ptr); + ptr = ParseField(line->cl_Days, FIELD_M_DAYS, 0, 1, NULL, ptr); + ptr = ParseField(line->cl_Mons, FIELD_MONTHS, -1, 1, MonAry, ptr); + ptr = ParseField(line->cl_Dow, FIELD_W_DAYS, 0, ALL_DOW, DowAry, ptr); + + if (!ptr) + return NULL; + + /* + * fix days and dow - if one is not * and the other + * is *, the other is set to 0, and vise-versa + */ + FixDayDow(line); + + return ptr; +} + +static char* +ParseOneAttribute(CronFile *file, CronLine *line, char *ptr, + const char *path, const char *buf) +{ + if (strncmp(ptr, ID_TAG, strlen(ID_TAG)) == 0) { + if (line->cl_JobName) { + /* only assign ID_TAG once */ + printlogf(LOG_WARNING, "%s: Multiple use of " ID_TAG " is invalid: %s\n", path, buf); + ptr = NULL; + } else { + ptr += strlen(ID_TAG); + /* + * name = strsep(&ptr, seps): + * return name = ptr, and if ptr contains sep chars, overwrite first with 0 and point ptr to next char + * else set ptr=NULL + */ + if (!(line->cl_Description = concat("job ", strsep(&ptr, " \t"), NULL))) { + errno = ENOMEM; + perror("SynchronizeFile"); + exit(1); + } + line->cl_JobName = line->cl_Description + 4; + if (!ptr) + printlogf(LOG_WARNING, "%s: Entry ends unexpectedly after " ID_TAG "%s: %s\n", path, line->cl_JobName, buf); + } + } else if (strncmp(ptr, FREQ_TAG, strlen(FREQ_TAG)) == 0) { + if (line->cl_Freq) { + /* only assign FREQ_TAG once */ + printlogf(LOG_WARNING, "%s: Multiple use of " FREQ_TAG " is invalid: %s\n", path, buf); + ptr = NULL; + } else { + ptr += strlen(FREQ_TAG); + ptr = ParseInterval(&line->cl_Freq, ptr); + if (ptr && *ptr == '/') + ptr = ParseInterval(&line->cl_Delay, ++ptr); + else + line->cl_Delay = line->cl_Freq; + if (!ptr) + printlogf(LOG_WARNING, "%s: Entry ends unexpectedly after " FREQ_TAG ": %s", path, buf); + } + } else if (strncmp(ptr, WAIT_TAG, strlen(WAIT_TAG)) == 0) { + if (line->cl_Waiters) { + /* only assign WAIT_TAG once */ + printlogf(LOG_WARNING, "%s: Multiple use of " WAIT_TAG " is invalid: %s\n", path, buf); + ptr = NULL; + } else { + short more = 1; + char *name; + ptr += strlen(WAIT_TAG); + do { + CronLine *job, **pjob; + if (strcspn(ptr,",") < strcspn(ptr," \t")) + name = strsep(&ptr, ","); + else { + more = 0; + name = strsep(&ptr, " \t"); + } + if (!ptr || *ptr == 0) { + /* unexpectedly this was the last token in buf; so abort */ + printlogf(LOG_WARNING, "%s: Entry ends unexpectedly after " WAIT_TAG "%s: %s", path, name, buf); + ptr = NULL; + } else { + int waitfor = 0; + char *w, *wsave; + if ((w = strchr(name, '/')) != NULL) { + wsave = w++; + w = ParseInterval(&waitfor, w); + if (!w || *w != 0) { + printlogf(LOG_WARNING, "%s: Could not parse interval for " WAIT_TAG "%s: %s\n", path, name, buf); + ptr = NULL; + } else + /* truncate name */ + *wsave = 0; + } + if (ptr) { + /* look for a matching CronLine */ + pjob = &file->cf_LineBase; + while ((job = *pjob) != NULL) { + if (job->cl_JobName && strcmp(job->cl_JobName, name) == 0) { + CronWaiter *waiter = malloc(sizeof(CronWaiter)); + CronNotifier *notif = malloc(sizeof(CronNotifier)); + waiter->cw_Flag = -1; + waiter->cw_MaxWait = waitfor; + waiter->cw_NotifLine = job; + waiter->cw_Notifier = notif; + waiter->cw_Next = line->cl_Waiters; /* add to head of line->cl_Waiters */ + line->cl_Waiters = waiter; + notif->cn_Waiter = waiter; + notif->cn_Next = job->cl_Notifs; /* add to head of job->cl_Notifs */ + job->cl_Notifs = notif; + break; + } else + pjob = &job->cl_Next; + } + if (!job) { + printlogf(LOG_WARNING, "%s: Ignoring unknown job: " WAIT_TAG "%s: %s\n", path, name, buf); + /* we can continue parsing this line, we just don't install any CronWaiter for the requested job */ + } + } + } + } while (ptr && more); + } + } + + return ptr; +} + +/* + * Parse as many things as we can that look like attributes + * + * Parsing stops when we encounter something that isn't identifiable + * and its assumed to be the next field. + * + * Return NULL on error + */ +static char* +ParseAttributes(CronFile *file, CronLine *line, char *ptr, + const char *path, const char *buf) +{ + for (;;) { + char *prev = ptr; + + ptr = ParseOneAttribute(file, line, ptr, path, buf); + if (!ptr) + return NULL; /* error */ + if (ptr == prev) + return ptr; /* nothing consumed; not understood */ + + while (*ptr == ' ' || *ptr == '\t') + ++ptr; + } +} + +/* + * Parse a single line in the file + * + * On completion the caller is always responsible for deallocating the + * content of "line", whether this function fully populated the line + * or not. + * + * Return: 1 if "line" was fully populated, otherwise 0 + */ +int +ParseLine(CronFile *file, const char *userName, int parseUser, CronLine *line, char *buf, time_t tnow, const char *path) +{ + char *ptr = buf; + int len; + + /* Always return with the parsed line in a defined state */ + memset(line, 0, sizeof(*line)); + + while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') + ++ptr; + + len = strlen(ptr); + if (len && ptr[len-1] == '\n') + ptr[--len] = 0; + + if (*ptr == 0 || *ptr == '#') + return 0; + + if (DebugOpt) + printlogf(LOG_DEBUG, "%s as %s: %s\n", path, userName, buf); + + if (*ptr == '@') { + ptr = ParseTimeInterval(line, ptr); + if (!ptr) { + printlogf(LOG_WARNING, "%s: Failed parsing '@' interval: %s\n", path, buf); + return 0; + } + } else { + ptr = ParseTimeSpec(line, ptr); + if (!ptr) { + printlogf(LOG_WARNING, "%s: Failed to parse date/time specification: %s\n", path, buf); + return 0; + } + } + + /* check for ID=... and AFTER=... and FREQ=... */ + ptr = ParseAttributes(file, line, ptr, path, buf); + if (!ptr) + return 0; /* error already logged */ + + /* + * Filter out a job name which is the empty string + */ + if (line->cl_JobName && line->cl_JobName[0] == '\0') { + free(line->cl_Description); + line->cl_Description = NULL; + line->cl_JobName = NULL; + } + + if (line->cl_Delay > 0 && !line->cl_JobName) { + printlogf(LOG_WARNING, "%s: Writing timestamp requries job to be named: %s\n", path, buf); + return 0; + } + + /* + * system crontabs (cron.d) have an extra field for username + */ + if (parseUser) + line->cl_UserName = strdup(strsep(&ptr, " \t")); + else + line->cl_UserName = strdup(userName); + + if (!ptr) { + printlogf(LOG_WARNING, "%s: Could not parse; a username and command is expected in a system crontab: %s\n", path, buf); + return 0; + } + + /* + * copy command string + */ + line->cl_Shell = strdup(ptr); + + if (line->cl_Delay > 0) { + if (!(line->cl_Timestamp = concat(TSDir, "/", userName, ".", line->cl_JobName, NULL))) { + errno = ENOMEM; + perror("SynchronizeFile"); + exit(1); + } + line->cl_NotUntil = tnow + line->cl_Delay; + } + + if (line->cl_JobName) { + if (DebugOpt) + printlogf(LOG_DEBUG, " Command %s Job %s\n\n", line->cl_Shell, line->cl_JobName); + } else { + /* when cl_JobName is NULL, we point cl_Description to cl_Shell */ + line->cl_Description = line->cl_Shell; + if (DebugOpt) + printlogf(LOG_DEBUG, " Command %s\n\n", line->cl_Shell); + } + + return 1; +} + void -SynchronizeFile(const char *dpath, const char *fileName, const char *userName) +SynchronizeFile(const char *dpath, const char *fileName, const char *userName, int parseUser) { CronFile **pfile; CronFile *file; @@ -364,293 +705,14 @@ SynchronizeFile(const char *dpath, const char *fileName, const char *userName) /* fgets reads at most size-1 chars until \n or EOF, then adds a\0; \n if present is stored in buf */ while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) { CronLine line; - char *ptr = buf; - int len; - - while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') - ++ptr; - - len = strlen(ptr); - if (len && ptr[len-1] == '\n') - ptr[--len] = 0; - - if (*ptr == 0 || *ptr == '#') - continue; if (--maxEntries == 0) break; - memset(&line, 0, sizeof(line)); - - if (DebugOpt) - printlogf(LOG_DEBUG, "User %s Entry %s\n", userName, buf); - - if (*ptr == '@') { - /* - * parse @hourly, etc - */ - int j; - line.cl_Delay = -1; - ptr += 1; - for (j = 0; FreqAry[j]; ++j) { - if (strncmp(ptr, FreqAry[j], strlen(FreqAry[j])) == 0) { - break; - } - } - if (FreqAry[j]) { - ptr += strlen(FreqAry[j]); - switch(j) { - case 0: - /* noauto */ - line.cl_Freq = -2; - line.cl_Delay = 0; - break; - case 1: - /* reboot */ - line.cl_Freq = -1; - line.cl_Delay = 0; - break; - case 2: - line.cl_Freq = HOURLY_FREQ; - break; - case 3: - line.cl_Freq = DAILY_FREQ; - break; - case 4: - line.cl_Freq = WEEKLY_FREQ; - break; - case 5: - line.cl_Freq = MONTHLY_FREQ; - break; - case 6: - line.cl_Freq = YEARLY_FREQ; - break; - /* else line.cl_Freq will remain 0 */ - } - } - - if (!line.cl_Freq || (*ptr != ' ' && *ptr != '\t')) { - printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s\n", userName, buf); - continue; - } - - if (line.cl_Delay < 0) { - /* - * delays on @daily, @hourly, etc are 1/20 of the frequency - * so they don't all start at once - * this also affects how they behave when the job returns EAGAIN - */ - line.cl_Delay = line.cl_Freq / 20; - line.cl_Delay -= line.cl_Delay % 60; - if (line.cl_Delay == 0) - line.cl_Delay = 60; - /* all minutes are permitted */ - for (j=0; j<60; ++j) - line.cl_Mins[j] = 1; - for (j=0; j<24; ++j) - line.cl_Hrs[j] = 1; - for (j=1; j<32; ++j) - /* days are numbered 1..31 */ - line.cl_Days[j] = 1; - for (j=0; j<12; ++j) - line.cl_Mons[j] = 1; - } - - while (*ptr == ' ' || *ptr == '\t') - ++ptr; - - } else { - /* - * parse date ranges - */ - - ptr = ParseField(file->cf_UserName, line.cl_Mins, FIELD_MINUTES, 0, 1, - NULL, ptr); - ptr = ParseField(file->cf_UserName, line.cl_Hrs, FIELD_HOURS, 0, 1, - NULL, ptr); - ptr = ParseField(file->cf_UserName, line.cl_Days, FIELD_M_DAYS, 0, 1, - NULL, ptr); - ptr = ParseField(file->cf_UserName, line.cl_Mons, FIELD_MONTHS, -1, 1, - MonAry, ptr); - ptr = ParseField(file->cf_UserName, line.cl_Dow, FIELD_W_DAYS, 0, ALL_DOW, - DowAry, ptr); - /* - * check failure - */ - - if (ptr == NULL) - continue; - - /* - * fix days and dow - if one is not * and the other - * is *, the other is set to 0, and vise-versa - */ - - FixDayDow(&line); - } - - /* check for ID=... and AFTER=... and FREQ=... */ - do { - if (strncmp(ptr, ID_TAG, strlen(ID_TAG)) == 0) { - if (line.cl_JobName) { - /* only assign ID_TAG once */ - printlogf(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr); - ptr = NULL; - } else { - ptr += strlen(ID_TAG); - /* - * name = strsep(&ptr, seps): - * return name = ptr, and if ptr contains sep chars, overwrite first with 0 and point ptr to next char - * else set ptr=NULL - */ - if (!(line.cl_Description = concat("job ", strsep(&ptr, " \t"), NULL))) { - errno = ENOMEM; - perror("SynchronizeFile"); - exit(1); - } - line.cl_JobName = line.cl_Description + 4; - if (!ptr) - printlogf(LOG_WARNING, "failed parsing crontab for user %s: no command after %s%s\n", userName, ID_TAG, line.cl_JobName); - } - } else if (strncmp(ptr, FREQ_TAG, strlen(FREQ_TAG)) == 0) { - if (line.cl_Freq) { - /* only assign FREQ_TAG once */ - printlogf(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr); - ptr = NULL; - } else { - char *base = ptr; - ptr += strlen(FREQ_TAG); - ptr = ParseInterval(&line.cl_Freq, ptr); - if (ptr && *ptr == '/') - ptr = ParseInterval(&line.cl_Delay, ++ptr); - else - line.cl_Delay = line.cl_Freq; - if (!ptr) { - printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s\n", userName, base); - } else if (*ptr != ' ' && *ptr != '\t') { - printlogf(LOG_WARNING, "failed parsing crontab for user %s: no command after %s\n", userName, base); - ptr = NULL; - } - } - } else if (strncmp(ptr, WAIT_TAG, strlen(WAIT_TAG)) == 0) { - if (line.cl_Waiters) { - /* only assign WAIT_TAG once */ - printlogf(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr); - ptr = NULL; - } else { - short more = 1; - char *name; - ptr += strlen(WAIT_TAG); - do { - CronLine *job, **pjob; - if (strcspn(ptr,",") < strcspn(ptr," \t")) - name = strsep(&ptr, ","); - else { - more = 0; - name = strsep(&ptr, " \t"); - } - if (!ptr || *ptr == 0) { - /* unexpectedly this was the last token in buf; so abort */ - printlogf(LOG_WARNING, "failed parsing crontab for user %s: no command after %s%s\n", userName, WAIT_TAG, name); - ptr = NULL; - } else { - int waitfor = 0; - char *w, *wsave; - if ((w = strchr(name, '/')) != NULL) { - wsave = w++; - w = ParseInterval(&waitfor, w); - if (!w || *w != 0) { - printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s%s\n", userName, WAIT_TAG, name); - ptr = NULL; - } else - /* truncate name */ - *wsave = 0; - } - if (ptr) { - /* look for a matching CronLine */ - pjob = &file->cf_LineBase; - while ((job = *pjob) != NULL) { - if (job->cl_JobName && strcmp(job->cl_JobName, name) == 0) { - CronWaiter *waiter = malloc(sizeof(CronWaiter)); - CronNotifier *notif = malloc(sizeof(CronNotifier)); - waiter->cw_Flag = -1; - waiter->cw_MaxWait = waitfor; - waiter->cw_NotifLine = job; - waiter->cw_Notifier = notif; - waiter->cw_Next = line.cl_Waiters; /* add to head of line.cl_Waiters */ - line.cl_Waiters = waiter; - notif->cn_Waiter = waiter; - notif->cn_Next = job->cl_Notifs; /* add to head of job->cl_Notifs */ - job->cl_Notifs = notif; - break; - } else - pjob = &job->cl_Next; - } - if (!job) { - printlogf(LOG_WARNING, "failed parsing crontab for user %s: unknown job %s\n", userName, name); - /* we can continue parsing this line, we just don't install any CronWaiter for the requested job */ - } - } - } - } while (ptr && more); - } - } else - break; - if (!ptr) - break; - while (*ptr == ' ' || *ptr == '\t') - ++ptr; - } while (!line.cl_JobName || !line.cl_Waiters || !line.cl_Freq); - - if (line.cl_JobName && (!ptr || *line.cl_JobName == 0)) { - /* we're aborting, or ID= was empty */ - free(line.cl_Description); - line.cl_Description = NULL; - line.cl_JobName = NULL; - } - if (ptr && line.cl_Delay > 0 && !line.cl_JobName) { - printlogf(LOG_WARNING, "failed parsing crontab for user %s: writing timestamp requires job %s to be named\n", userName, ptr); - ptr = NULL; - } - if (!ptr) { - /* couldn't parse so we abort; free any cl_Waiters */ - if (line.cl_Waiters) { - CronWaiter **pwaiters, *waiters; - pwaiters = &line.cl_Waiters; - while ((waiters = *pwaiters) != NULL) { - *pwaiters = waiters->cw_Next; - /* leave the Notifier allocated but disabled */ - waiters->cw_Notifier->cn_Waiter = NULL; - free(waiters); - } - } + if (!ParseLine(file, userName, parseUser, &line, buf, tnow, path)) { + DeleteLineContent(&line); continue; } - /* now we've added any ID=... or AFTER=... */ - - /* - * copy command string - */ - line.cl_Shell = strdup(ptr); - - if (line.cl_Delay > 0) { - if (!(line.cl_Timestamp = concat(TSDir, "/", userName, ".", line.cl_JobName, NULL))) { - errno = ENOMEM; - perror("SynchronizeFile"); - exit(1); - } - line.cl_NotUntil = tnow + line.cl_Delay; - } - - if (line.cl_JobName) { - if (DebugOpt) - printlogf(LOG_DEBUG, " Command %s Job %s\n\n", line.cl_Shell, line.cl_JobName); - } else { - /* when cl_JobName is NULL, we point cl_Description to cl_Shell */ - line.cl_Description = line.cl_Shell; - if (DebugOpt) - printlogf(LOG_DEBUG, " Command %s\n\n", line.cl_Shell); - } *pline = calloc(1, sizeof(CronLine)); /* copy working CronLine to newly allocated one */ @@ -665,7 +727,7 @@ SynchronizeFile(const char *dpath, const char *fileName, const char *userName) FileBase = file; if (maxLines == 0 || maxEntries == 0) - printlogf(LOG_WARNING, "maximum number of lines reached for user %s\n", userName); + printlogf(LOG_WARNING, "%s: Limit on number of entries has been reached\n", path); } fclose(fi); } @@ -701,7 +763,7 @@ ParseInterval(int *interval, char *ptr) } char * -ParseField(char *user, char *ary, int modvalue, int offset, int onvalue, const char **names, char *ptr) +ParseField(char *ary, int modvalue, int offset, int onvalue, const char **names, char *ptr) { char *base = ptr; int n1 = -1; @@ -750,10 +812,8 @@ ParseField(char *user, char *ary, int modvalue, int offset, int onvalue, const c * handle optional range '-' */ - if (skip == 0) { - printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base); + if (skip == 0) return(NULL); - } if (*ptr == '-' && n2 < 0) { ++ptr; continue; @@ -791,10 +851,8 @@ ParseField(char *user, char *ary, int modvalue, int offset, int onvalue, const c } } while (n1 != n2 && --failsafe); - if (failsafe == 0) { - printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base); + if (failsafe == 0) return(NULL); - } } if (*ptr != ',') break; @@ -803,10 +861,8 @@ ParseField(char *user, char *ary, int modvalue, int offset, int onvalue, const c n2 = -1; } - if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { - printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base); + if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') return(NULL); - } while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') ++ptr; @@ -900,8 +956,6 @@ DeleteFile(CronFile **pfile) CronFile *file = *pfile; CronLine **pline = &file->cf_LineBase; CronLine *line; - CronWaiter **pwaiters, *waiters; - CronNotifier **pnotifs, *notifs; file->cf_Running = 0; file->cf_Deleted = 1; @@ -912,33 +966,7 @@ DeleteFile(CronFile **pfile) pline = &line->cl_Next; } else { *pline = line->cl_Next; - free(line->cl_Shell); - - if (line->cl_JobName) - /* this frees both cl_Description and cl_JobName - * if cl_JobName is NULL, Description pointed to ch_Shell, which was already freed - */ - free(line->cl_Description); - if (line->cl_Timestamp) - free(line->cl_Timestamp); - - pnotifs = &line->cl_Notifs; - while ((notifs = *pnotifs) != NULL) { - *pnotifs = notifs->cn_Next; - if (notifs->cn_Waiter) { - notifs->cn_Waiter->cw_NotifLine = NULL; - notifs->cn_Waiter->cw_Notifier = NULL; - } - free(notifs); - } - pwaiters = &line->cl_Waiters; - while ((waiters = *pwaiters) != NULL) { - *pwaiters = waiters->cw_Next; - if (waiters->cw_Notifier) - waiters->cw_Notifier->cn_Waiter = NULL; - free(waiters); - } - + DeleteLineContent(line); free(line); } } @@ -951,6 +979,42 @@ DeleteFile(CronFile **pfile) } } +/* + * Free resources associated with the given line, which may be incomplete. + */ +void DeleteLineContent(CronLine *line) +{ + CronWaiter **pwaiters, *waiters; + CronNotifier **pnotifs, *notifs; + + free(line->cl_Shell); + free(line->cl_UserName); + + if (line->cl_JobName) + /* this frees both cl_Description and cl_JobName + * if cl_JobName is NULL, Description pointed to ch_Shell, which was already freed + */ + free(line->cl_Description); + if (line->cl_Timestamp) + free(line->cl_Timestamp); + + pnotifs = &line->cl_Notifs; + while ((notifs = *pnotifs) != NULL) { + *pnotifs = notifs->cn_Next; + if (notifs->cn_Waiter) { + notifs->cn_Waiter->cw_NotifLine = NULL; + notifs->cn_Waiter->cw_Notifier = NULL; + } + free(notifs); + } + pwaiters = &line->cl_Waiters; + while ((waiters = *pwaiters) != NULL) { + *pwaiters = waiters->cw_Next; + if (waiters->cw_Notifier) + waiters->cw_Notifier->cn_Waiter = NULL; + free(waiters); + } +} /* * TestJobs() @@ -1281,6 +1345,7 @@ PrintLine(CronLine *line) printlogf(LOG_DEBUG, "CronLine:\n------------\n"); printlogf(LOG_DEBUG, " Command: %s\n", line->cl_Shell); + printlogf(LOG_DEBUG, " User: %s\n", line->cl_UserName); //printlogf(LOG_DEBUG, " Desc: %s\n", line->cl_Description); printlogf(LOG_DEBUG, " Freq: %s\n", (line->cl_Freq ? (line->cl_Freq == -1 ? "(noauto)" : "(startup") : "(use arrays)")); diff --git a/defs.h b/defs.h index 5903e6e..90f37a4 100644 --- a/defs.h +++ b/defs.h @@ -137,6 +137,7 @@ typedef struct CronFile { typedef struct CronLine { struct CronLine *cl_Next; char *cl_Shell; /* shell command */ + char *cl_UserName; /* to execute as */ char *cl_Description; /* either "" or "job " */ char *cl_JobName; /* job name, if any */ char *cl_Timestamp; /* path to timestamp file, if cl_Freq defined */ diff --git a/job.c b/job.c index a0201e7..0893e66 100644 --- a/job.c +++ b/job.c @@ -62,9 +62,9 @@ RunJob(CronFile *file, CronLine *line) * Change running state to the user in question */ - if (ChangeUser(file->cf_UserName, TempDir) < 0) { + if (ChangeUser(line->cl_UserName, TempDir) < 0) { printlogf(LOG_ERR, "unable to ChangeUser (user %s %s)\n", - file->cf_UserName, + line->cl_UserName, line->cl_Description ); exit(0); @@ -112,7 +112,7 @@ RunJob(CronFile *file, CronLine *line) * Complain to our log (now associated with fd 8) */ fdprintlogf(LOG_ERR, 8, "unable to exec (user %s cmd /bin/sh -c %s)\n", - file->cf_UserName, + line->cl_UserName, line->cl_Shell ); /* @@ -128,7 +128,7 @@ RunJob(CronFile *file, CronLine *line) * Complain to log (with regular fd 2) */ printlogf(LOG_ERR, "unable to fork (user %s %s)\n", - file->cf_UserName, + line->cl_UserName, line->cl_Description ); line->cl_Pid = 0; diff --git a/main.c b/main.c index 1fc5cd6..aa95533 100644 --- a/main.c +++ b/main.c @@ -290,8 +290,8 @@ main(int ac, char **av) */ printlogf(LOG_NOTICE,"%s " VERSION " dillon's cron daemon, started with loglevel %s\n", av[0], LevelAry[LogLevel]); - SynchronizeDir(CDir, NULL, 1); - SynchronizeDir(SCDir, "root", 1); + SynchronizeDir(CDir, 0, 1); + SynchronizeDir(SCDir, 1, 1); ReadTimestamps(NULL); TestStartupJobs(); /* @startup jobs only run when crond is started, not when their crontab is loaded */ @@ -334,14 +334,14 @@ main(int ac, char **av) rescan = 1; } else { rescan = 60; - SynchronizeDir(CDir, NULL, 0); - SynchronizeDir(SCDir, "root", 0); + SynchronizeDir(CDir, 0, 0); + SynchronizeDir(SCDir, 1, 0); ReadTimestamps(NULL); } } if (rescan < 60) { - CheckUpdates(CDir, NULL, t1, t2); - CheckUpdates(SCDir, "root", t1, t2); + CheckUpdates(CDir, 0, t1, t2); + CheckUpdates(SCDir, 1, t1, t2); } if (DebugOpt) printlogf(LOG_DEBUG, "Wakeup dt=%d\n", dt);