From: Marcela Mašláňová Date: Mon, 13 Jul 2009 13:36:53 +0000 (+0200) Subject: Initial upload of anacron-2.3 which should be optimized for better X-Git-Tag: cronie1.4~15 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=55f40574a94185a73df8043c4f71f016703c1009;p=cronie Initial upload of anacron-2.3 which should be optimized for better cooperation with cronie. However, cronie should be working with or without anacron, which should be configurable. --- diff --git a/anacron/COPYING b/anacron/COPYING new file mode 100644 index 0000000..60549be --- /dev/null +++ b/anacron/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/anacron/ChangeLog b/anacron/ChangeLog new file mode 100644 index 0000000..39a18fb --- /dev/null +++ b/anacron/ChangeLog @@ -0,0 +1,34 @@ + Changes in Anacron 2.3 + ---------------------- +* anacron can now read an arbitrary anacrontab file, use the -t option + + + Changes in Anacron 2.1/2.2 + -------------------------- +* Sean 'Shaleh' Perry is now maintainer +* if timestamp is from the future, re-run job +* ansi cleanup / code cleaning + + + Changes in Anacron 2.0.1 + ------------------------ +* Minor cosmetic changes to log messages. +* Jobs are now started with "/" as their working directory. This is + more compatible with older Anacron versions, avoids annoying errors on + some systems, and generally seems to make more sense. + + + Summary of major changes in Anacron 2.0 + --------------------------------------- +* Complete rewrite in C. Should be backwards compatible with existing + Anacron installations. +* First release as a "generic" Linux package (was a Debian package). +* No longer needs special lock-files. Locking is done on the timestamp + files. +* Sends log messages to syslogd. There's no log file now. +* Output of jobs, if any, is mailed to the user. +* Added command line options: -s -f -n -d -q -u -V -h. See the manpage. +* Specific jobs can now be selected on the command line. +* Added SIGUSR1 handling, to cleanly stop execution. +* Jobs will now be started with their current directory set to the home + of the user running Anacron (usually root). diff --git a/anacron/Makefile b/anacron/Makefile new file mode 100644 index 0000000..555e524 --- /dev/null +++ b/anacron/Makefile @@ -0,0 +1,95 @@ +# Anacron - run commands periodically +# Copyright (C) 1998 Itai Tzur +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# The GNU General Public License can also be found in the file +# `COPYING' that comes with the Anacron source distribution. + + +PREFIX = +BINDIR = $(PREFIX)/usr/sbin +MANDIR = $(PREFIX)/usr/man +CFLAGS = -Wall -pedantic -O2 +#CFLAGS = -Wall -O2 -g -DDEBUG + +# If you change these, please update the man-pages too +# Only absolute paths here, please +SPOOLDIR = /var/spool/anacron +ANACRONTAB = /etc/anacrontab + +RELEASE = 2.3 +package_name = anacron-$(RELEASE) +distfiles = ChangeLog COPYING README TODO anacron.8 anacrontab.5 Makefile *.h *.c + +SHELL = /bin/sh +INSTALL = install +INSTALL_PROGRAM = $(INSTALL) +INSTALL_DATA = $(INSTALL) +INSTALL_DIR = $(INSTALL) -d +GZIP = gzip -9 -f +ALL_CPPFLAGS = -DSPOOLDIR=\"$(SPOOLDIR)\" -DRELEASE=\"$(RELEASE)\" \ + -DANACRONTAB=\"$(ANACRONTAB)\" $(CPPFLAGS) + +csources := $(wildcard *.c) +objects = $(csources:.c=.o) + +.PHONY: all +all: anacron + +# This makefile generates header file dependencies auto-magically +%.d: %.c + $(SHELL) -ec "$(CC) -MM $(ALL_CPPFLAGS) $< \ + | sed '1s/^\(.*\)\.o[ :]*/\1.d &/1' > $@" + +include $(csources:.c=.d) + +anacron: $(objects) + $(CC) $(LDFLAGS) $^ $(LOADLIBES) -o $@ + +%.o : %.c + $(CC) -c $(ALL_CPPFLAGS) $(CFLAGS) $< -o $@ + +.PHONY: installdirs +installdirs: + $(INSTALL_DIR) $(BINDIR) $(PREFIX)$(SPOOLDIR) \ + $(MANDIR)/man5 $(MANDIR)/man8 + +.PHONY: install +install: installdirs + $(INSTALL_PROGRAM) anacron $(BINDIR)/anacron + $(INSTALL_DATA) anacrontab.5 $(MANDIR)/man5/anacrontab.5 + $(INSTALL_DATA) anacron.8 $(MANDIR)/man8/anacron.8 + +.PHONY: clean +clean: + rm -f *.o *.d anacron + +distclean: clean + rm -f *~ + +.PHONY: dist +dist: $(package_name).tar.gz + +$(package_name).tar.gz: $(distfiles) + mkdir $(package_name) + ln $(distfiles) $(package_name) + chmod 0644 $(package_name)/* + chmod 0755 $(package_name) + tar cf $(package_name).tar $(package_name) + $(GZIP) $(package_name).tar + rm -r $(package_name) + +release: distclean $(package_name).tar.gz diff --git a/anacron/README b/anacron/README new file mode 100644 index 0000000..eb53947 --- /dev/null +++ b/anacron/README @@ -0,0 +1,142 @@ + + What is Anacron ? + ----------------- + + Anacron is a periodic command scheduler. It executes commands at +intervals specified in days. Unlike cron, it does not assume that the +system is running continuously. It can therefore be used to control +the execution of daily, weekly and monthly jobs (or anything with a +period of n days), on systems that don't run 24 hours a day. When +installed and configured properly, Anacron will make sure that the +commands are run at the specified intervals as closely as +machine-uptime permits. + + Every time Anacron is run, it reads a configuration file that +specifies the jobs Anacron controls, and their periods in days. If a +job wasn't executed in the last n days, where n is the period of that +job, Anacron executes it. Anacron then records the date in a special +timestamp file that it keeps for each job, so it can know when to run +it again. When all the executed commands terminate, Anacron exits. + + It is recommended to run Anacron from the system boot-scripts. +This way the jobs "whose time has come" will be run shortly after the +machine boots. A delay can be specified for each job so that the +machine isn't overloaded at boot time. + + In addition to running Anacron from the boot-scripts, it is also +recommended to schedule it as a daily cron-job (usually at an early +morning hour), so that if the machine is kept running for a night, +jobs for the next day will still be executed. + + + Why this may be useful ? + ------------------------ + + Most Unix-like systems have daily, weekly and monthly scripts that +take care of various "housekeeping chores" such as log-rotation, +updating the "locate" and "man" databases, etc. Daily scripts are +usually scheduled as cron-jobs to execute around 1-7 AM. Weekly +scripts are scheduled to run on Sundays. On machines that are turned +off for the night or for the weekend, these scripts rarely get run. + + Anacron solves this problem. These jobs can simply be scheduled as +Anacron-jobs with periods of 1, 7 and 30 days. + + + What Anacron is not ? + --------------------- + + Anacron is not an attempt to make cron redundant. It cannot +currently be used to schedule commands at intervals smaller than days. +It also does not guarantee that the commands will be executed at any +specific day or hour. + + It isn't a full-time daemon. It has to be executed from boot +scripts, from cron-jobs, or explicitly. + + + For more details, see the anacron(8) manpage. + + + Requirements + ------------ + + - A Linux system. (maybe other *NIX systems) + - A functioning syslog daemon. + - A functioning /usr/lib/sendmail command. (all MTAs should have + that). + + + Compilation and Installation + ---------------------------- + + - Untar the source package. + + - Check the Makefile. Edit as required. + + - Check the top of "global.h". You may want to change the syslog + facility and priorities, and the path to your MTA's sendmail + compatible command (/usr/lib/sendmail). + + - cd to the directory. + + - Type "make". + You can safely ignore warnings of the form: "*.d: No such file or + directory" + + - Become root. Type "make install". + + + Setup + ----- + +1. Locate your system's daily, weekly and monthly cron-jobs. + See your cron documentation for more details. + +2. Decide which of these jobs should be controlled by Anacron. + Remember that Anacron does not guarantee execution at any specific + day of the month, day of the week, or time of day. Jobs for which + the timing is critical should probably not be controlled by + Anacron. + +3. Comment these jobs out of their crontab files. (You may have to + use the "crontab" command for this. See the cron documentation.) + +4. Put them in /etc/anacrontab. Note that the format is not the same + as the crontab entries. See the anacrontab(5) manpage. Here's an + example from a typical Debian system: + +-----Cut +# /etc/anacrontab example +SHELL=/bin/sh +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +# format: period delay job-identifier command +1 5 cron.daily run-parts /etc/cron.daily +7 10 cron.weekly run-parts /etc/cron.weekly +30 15 cron.monthly run-parts /etc/cron.monthly +-----Cut + +5. Put the command "anacron -s" somewhere in your boot-scripts. + Make sure that syslogd is started before this command. + +6. Schedule the command "anacron -s" as a daily cron-job (preferably + at some early morning hour). This will make sure that jobs are run + when the systems is left running for a night. + +That's it. + +It is a good idea to check what your daily, weekly and monthly scripts +actually do, and disable any parts that may be irrelevant for your +system. + + + Credits + ------- + +Anacron was originally conceived and implemented by Christian Schwarz +. + +The current implementation is a complete rewrite by Itai Tzur +. + +Current code base maintained by Sean 'Shaleh' Perry . diff --git a/anacron/TODO b/anacron/TODO new file mode 100644 index 0000000..1354254 --- /dev/null +++ b/anacron/TODO @@ -0,0 +1,6 @@ +anacron runs jobs twice in a 31 day month +add hostname to emails sent to admin +allow user anacrontabs +make manpages match #defines automagically --> sed fu +full ANSI / POSIX compliance +code cleaning diff --git a/anacron/anacron.8 b/anacron/anacron.8 new file mode 100644 index 0000000..a3561be --- /dev/null +++ b/anacron/anacron.8 @@ -0,0 +1,147 @@ +.TH ANACRON 8 2000-06-22 "Sean 'Shaleh' Perry" "Anacron Users' Manual" +.SH NAME +anacron \- runs commands periodically +.SH SYNOPSIS +.B anacron \fR[\fB-s\fR] [\fB-f\fR] [\fB-n\fR] [\fB-d\fR] [\fB-q\fR] +[\fB-t anacrontab\fR] [\fIjob\fR] ... +.br +.B anacron -u [\fB-t anacrontab\fR] \fR[\fIjob\fR] ... +.br +.B anacron \fR[\fB-V\fR|\fB-h\fR] +.SH DESCRIPTION +Anacron +can be used to execute commands periodically, with a +frequency specified in days. Unlike \fBcron(8)\fR, +it does not assume that the machine is running continuously. Hence, +it can be used on machines that aren't running 24 hours a day, +to control daily, weekly, and monthly jobs that are +usually controlled by \fBcron\fR. +.PP +When executed, Anacron reads a list of jobs from a configuration file, normally +.I /etc/anacrontab +(see \fBanacrontab(5)\fR). This file +contains the list of jobs that Anacron controls. Each +job entry specifies a period in days, +a delay in minutes, a unique +job identifier, and a shell command. +.PP +For each job, Anacron checks whether +this job has been executed in the last n days, where n is the period specified +for that job. If not, Anacron runs the job's shell command, after waiting +for the number of minutes specified as the delay parameter. +.PP +After the command exits, Anacron records the date in a special +timestamp file for that job, so it can know when to execute it again. Only +the date is used for the time +calculations. The hour is not used. +.PP +When there are no more jobs to be run, Anacron exits. +.PP +Anacron only considers jobs whose identifier, as +specified in the \fIanacrontab\fR matches any of +the +.I job +command-line arguments. The +.I job +arguments can be shell wildcard patterns (be sure to protect them from +your shell with adequate quoting). Specifying no +.I job +arguments, is equivalent to specifying "*" (That is, all jobs will be +considered). +.PP +Unless the \fB-d\fR option is given (see below), Anacron forks to the +background when it starts, and the parent process exits +immediately. +.PP +Unless the \fB-s\fR or \fB-n\fR options are given, Anacron starts jobs +immediately when their delay is over. The execution of different jobs is +completely independent. +.PP +If a job generates any output on its standard output or standard error, +the output is mailed to the user running Anacron (usually root). +.PP +Informative messages about what Anacron is doing are sent to \fBsyslogd(8)\fR +under facility \fBcron\fR, priority \fBnotice\fR. Error messages are sent at +priority \fBerror\fR. +.PP +"Active" jobs (i.e. jobs that Anacron already decided +to run and now wait for their delay to pass, and jobs that are currently +being executed by +Anacron), are "locked", so that other copies of Anacron won't run them +at the same time. +.SH OPTIONS +.TP +.B -f +Force execution of the jobs, ignoring the timestamps. +.TP +.B -u +Only update the timestamps of the jobs, to the current date, but +don't run anything. +.TP +.B -s +Serialize execution of jobs. Anacron will not start a new job before the +previous one finished. +.TP +.B -n +Run jobs now. Ignore the delay specifications in the +.I /etc/anacrontab +file. This options implies \fB-s\fR. +.TP +.B -d +Don't fork to the background. In this mode, Anacron will output informational +messages to standard error, as well as to syslog. The output of jobs +is mailed as usual. +.TP +.B -q +Suppress messages to standard error. Only applicable with \fB-d\fR. +.TP +.B -t anacrontab +Use specified anacrontab, rather than the default +.TP +.B -V +Print version information, and exit. +.TP +.B -h +Print short usage message, and exit. +.SH SIGNALS +After receiving a \fBSIGUSR1\fR signal, Anacron waits for running +jobs, if any, to finish and then exits. This can be used to stop +Anacron cleanly. +.SH NOTES +Make sure that the time-zone is set correctly before Anacron is +started. (The time-zone affects the date). This is usually accomplished +by setting the TZ environment variable, or by installing a +.I /usr/lib/zoneinfo/localtime +file. See +.B tzset(3) +for more information. +.SH FILES +.TP +.I /etc/anacrontab +Contains specifications of jobs. See \fBanacrontab(5)\fR for a complete +description. +.TP +.I /var/spool/anacron +This directory is used by Anacron for storing timestamp files. +.SH "SEE ALSO" +.B anacrontab(5), cron(8), tzset(3) +.PP +The Anacron +.I README +file. +.SH BUGS +Anacron never removes timestamp files. Remove unused files manually. +.PP +Anacron +uses up to two file descriptors for each active job. It may run out of +descriptors if there are more than about 125 active jobs (on normal kernels). +.PP +Mail comments, suggestions and bug reports to Sean 'Shaleh' Perry . +.SH AUTHOR +Anacron was originally conceived and implemented by Christian Schwarz +. +.PP +The current implementation is a complete rewrite by Itai Tzur +. +.PP +The code base is currently maintained by Sean 'Shaleh' Perry . diff --git a/anacron/anacrontab.5 b/anacron/anacrontab.5 new file mode 100644 index 0000000..93a67a0 --- /dev/null +++ b/anacron/anacrontab.5 @@ -0,0 +1,48 @@ +.TH ANACRONTAB 5 1998-02-02 "Itai Tzur" "Anacron Users' Manual" +.SH NAME +/etc/anacrontab \- configuration file for anacron +.SH DESCRIPTION +The file +.I /etc/anacrontab +describes the jobs controlled by \fBanacron(8)\fR. Its lines can be of +three kinds: job-description lines, environment +assignments, or empty lines. +.PP +Job-description lines are of the form: +.PP + period delay job-identifier command +.PP +The +.I period +is specified in days, the +.I delay +in minutes. The +.I job-identifier +can contain any non-blank character, except slashes. It is used to identify +the job in Anacron messages, +and as the name for the job's timestamp file. The +.I command +can be any shell command. +.PP +Environment assignment lines are of the form: +.PP + VAR = VALUE +.PP +Spaces around +.I VAR +are removed. No spaces around +.I VALUE +are allowed (unless you want them to be part of the value). The assignment +takes effect from the next line to the end of the file, or to the next +assignment of the same variable. +.PP +Empty lines are either blank lines, line containing white-space only, or +lines with white-space followed by a '#' followed by an arbitrary comment. +.SH "SEE ALSO" +.B anacron(8) +.PP +The Anacron +.I README +file. +.SH AUTHOR +Itai Tzur diff --git a/anacron/global.h b/anacron/global.h new file mode 100644 index 0000000..4812c1c --- /dev/null +++ b/anacron/global.h @@ -0,0 +1,137 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + +#ifndef _ANACRON_GLOBAL_H +#define _ANACRON_GLOBAL_H + +/* Syslog facility and priorities messages will be logged to (see syslog(3)). + * If you change these, please update the man page. */ +#define SYSLOG_FACILITY LOG_CRON +#define EXPLAIN_LEVEL LOG_NOTICE /* informational messages */ +#define COMPLAIN_LEVEL LOG_ERR /* error messages */ +#define DEBUG_LEVEL LOG_DEBUG /* only used when DEBUG is defined */ + +/* Mail interface. (All MTAs should supply this command) */ +#define SENDMAIL "/usr/sbin/sendmail" + +/* End of user-configurable section */ + + +#define FAILURE_EXIT 1 +#define MAX_MSG 150 + +#include + +/* Some declarations */ + +struct env_rec1 { + char *assign; + + struct env_rec1 *next; +}; +typedef struct env_rec1 env_rec; + +struct job_rec1 { + int period; + int delay; + char *ident; + char *command; + + int tab_line; + int arg_num; + int timestamp_fd; + int output_fd; + int mail_header_size; + pid_t job_pid; + pid_t mailer_pid; + + struct job_rec1 *next; + env_rec *prev_env_rec; +}; +typedef struct job_rec1 job_rec; + +/* Global variables */ + +extern pid_t primary_pid; +extern char *program_name; +extern char *anacrontab; +extern int old_umask; +extern sigset_t old_sigmask; +extern int serialize,force,update_only,now,no_daemon,quiet; +extern int day_now; +extern int year,month,day_of_month; +extern int in_background; + +extern job_rec *first_job_rec; +extern env_rec *first_env_rec; + +extern char **args; +extern int nargs; + +extern int njobs; +extern job_rec **job_array; + +extern int running_jobs,running_mailers; + + +/* Function prototypes */ + +/* main.c */ +int xopen(int fd, const char *file_name, int flags); +void xclose(int fd); +pid_t xfork(); + +/* log.c */ +void explain(const char *fmt, ...); +void explain_e(const char *fmt, ...); +void complain(const char *fmt, ...); +void complain_e(const char *fmt, ...); +void die(const char *fmt, ...); +void die_e(const char *fmt, ...); +void xdebug(const char *fmt, ...); +void xdebug_e(const char *fmt, ...); +void xcloselog(); + +#ifdef DEBUG +#define Debug(args) xdebug args +#define Debug_e(args) xdebug_e args +#else /* not DEBUG */ +#define Debug(args) (void)(0) +#define Debug_e(args) (void)(0) +#endif /* not DEBUG */ + +/* readtab.c */ +void read_tab(); +void arrange_jobs(); + +/* lock.c */ +int consider_job(job_rec *jr); +void unlock(job_rec *jr); +void update_timestamp(job_rec *jr); +void fake_job(job_rec *jr); + +/* runjob.c */ +void tend_children(); +void launch_job(job_rec *jr); + +#endif diff --git a/anacron/gregor.c b/anacron/gregor.c new file mode 100644 index 0000000..1464398 --- /dev/null +++ b/anacron/gregor.c @@ -0,0 +1,116 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +#include +#include "gregor.h" + +const static int +days_in_month[] = { + 31, /* Jan */ + 28, /* Feb (non-leap) */ + 31, /* Mar */ + 30, /* Apr */ + 31, /* May */ + 30, /* Jun */ + 31, /* Jul */ + 31, /* Aug */ + 30, /* Sep */ + 31, /* Oct */ + 30, /* Nov */ + 31 /* Dec */ +}; + +static int leap(int year); + +int +day_num(int year, int month, int day) +/* Return the "day number" of the date year-month-day according to the + * "proleptic Gregorian calendar". + * If the given date is invalid, return -1. + * + * Here, "day number" is defined as the number of days since December 31, + * 1 B.C. (Gregorian). (January 1, 1 A.D. is day number 1 etc...) + * + * The Gregorian calendar was instituted by Pope Gregory XIII in 1582, + * and has gradually spread to become the international standard calendar. + * The proleptic Gregorian calendar is formed by projecting the date system + * of the Gregorian calendar to dates before its adoption. + * + * For more details, see: + * http://astro.nmsu.edu/~lhuber/leaphist.html + * http://www.magnet.ch/serendipity/hermetic/cal_stud/cal_art.htm + * and your local library. + */ +{ + int dn; + int i; + const int isleap; /* save three calls to leap() */ + + /* Some validity checks */ + + /* we don't deal with B.C. years here */ + if (year < 1) return - 1; + /* conservative overflow estimate */ + if (year > (INT_MAX / 366)) return - 1; + if (month > 12 || month < 1) return - 1; + if (day < 1) return - 1; + + isleap = leap(year); + + if (month != 2) { + if(day > days_in_month[month - 1]) return - 1; + } + else if ((isleap && day > 29) || (!isleap && day > 28)) + return - 1; + + /* First calculate the day number of December 31 last year */ + + /* save us from doing (year - 1) over and over */ + i = year - 1; + /* 365 days in a "regular" year + number of leap days */ + dn = (i * 365) + ((i / 4) - (i / 100) + (i / 400)); + + /* Now, day number of the last day of the previous month */ + + for (i = month - 1; i > 0; --i) + dn += days_in_month[i - 1]; + /* Add 29 February ? */ + if (month > 2 && isleap) ++dn; + + /* How many days into month are we */ + + dn += day; + + return dn; +} + +static int +leap(int year) +/* Is this a leap year ? */ +{ + /* every year exactly divisible by 4 is "leap" */ + /* unless it is exactly divisible by 100 */ + /* but not by 400 */ + return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); +} diff --git a/anacron/gregor.h b/anacron/gregor.h new file mode 100644 index 0000000..3f2d436 --- /dev/null +++ b/anacron/gregor.h @@ -0,0 +1,25 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +int day_num(int year, int month, int day); diff --git a/anacron/lock.c b/anacron/lock.c new file mode 100644 index 0000000..9284be1 --- /dev/null +++ b/anacron/lock.c @@ -0,0 +1,158 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +/* Lock and timestamp management + */ + +#include +#include +#include +#include +#include +#include +#include +#include "global.h" +#include "gregor.h" + +static void +open_tsfile(job_rec *jr) +/* Open the timestamp file for job jr */ +{ + jr->timestamp_fd = open(jr->ident, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (jr->timestamp_fd == -1) + die_e("Can't open timestamp file for job %s", jr->ident); + fcntl(jr->timestamp_fd, F_SETFD, 1); /* set close-on-exec flag */ + /* We want to own this file, and set its mode to 0600. This is necessary + * in order to prevent other users from putting locks on it. */ + if (fchown(jr->timestamp_fd, getuid(), getgid())) + die_e("Can't chown timestamp file %s", jr->ident); + if (fchmod(jr->timestamp_fd, S_IRUSR | S_IWUSR)) + die_e("Can't chmod timestamp file %s", jr->ident); +} + +static int +lock_file(int fd) +/* Attempt to put an exclusive fcntl() lock on file "fd" + * Return 1 on success, 0 on failure. + */ +{ + int r; + struct flock sfl; + + sfl.l_type = F_WRLCK; + sfl.l_start = 0; + sfl.l_whence = SEEK_SET; + sfl.l_len = 0; /* we lock all the file */ + errno = 0; + r = fcntl(fd, F_SETLK, &sfl); + if (r != -1) return 1; + if (errno != EACCES && errno != EAGAIN) + die_e("fcntl() error"); + return 0; +} + +int +consider_job(job_rec *jr) +/* Check the timestamp of the job. If "its time has come", lock the job + * and return 1, if it's too early, or we can't get the lock, return 0. + */ +{ + char timestamp[9]; + int ts_year, ts_month, ts_day, dn; + ssize_t b; + + open_tsfile(jr); + + /* read timestamp */ + b = read(jr->timestamp_fd, timestamp, 8); + if (b == -1) die_e("Error reading timestamp file %s", jr->ident); + timestamp[8] = 0; + + /* is it too early? */ + if (!force && b == 8) + { + int day_delta; + if (sscanf(timestamp, "%4d%2d%2d", &ts_year, &ts_month, &ts_day) == 3) + dn = day_num(ts_year, ts_month, ts_day); + else + dn = 0; + + day_delta = day_now - dn; + + /* + * if day_delta is negative, we assume there was a clock skew + * and re-run any affected jobs + * otherwise we check if the job's time has come + */ + if (day_delta >= 0 && day_delta < jr->period) + { + /* yes, skip job */ + xclose(jr->timestamp_fd); + return 0; + } + } + + /* no! try to grab the lock */ + if (lock_file(jr->timestamp_fd)) return 1; /* success */ + + /* didn't get lock */ + xclose(jr->timestamp_fd); + explain("Job `%s' locked by another anacron - skipping", jr->ident); + return 0; +} + +void +unlock(job_rec *jr) +{ + xclose(jr->timestamp_fd); +} + +void +update_timestamp(job_rec *jr) +/* We write the date "now". "Now" can be either the time when anacron + * started, or the time when the job finished. + * I'm not quite sure which is more "right", but I've decided on the first + * option. + * Note that this is not the way it was with anacron 1.0.3 to 1.0.7. + */ +{ + char stamp[10]; + + snprintf(stamp, 10, "%04d%02d%02d\n", year, month, day_of_month); + if (lseek(jr->timestamp_fd, 0, SEEK_SET)) + die_e("Can't lseek timestamp file for job %s", jr->ident); + if (write(jr->timestamp_fd, stamp, 9) != 9) + die_e("Can't write timestamp file for job %s", jr->ident); + if (ftruncate(jr->timestamp_fd, 9)) + die_e("ftruncate error"); +} + +void +fake_job(job_rec *jr) +/* We don't bother with any locking here. There's no point. */ +{ + open_tsfile(jr); + update_timestamp(jr); + xclose(jr->timestamp_fd); +} diff --git a/anacron/log.c b/anacron/log.c new file mode 100644 index 0000000..452f819 --- /dev/null +++ b/anacron/log.c @@ -0,0 +1,216 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +/* Error logging + * + * We have two levels of logging (plus debugging if DEBUG is defined): + * "explain" level for informational messages, and "complain" level for errors. + * + * We log everything to syslog, see the top of global.h for relevant + * definitions. + * + * Stderr gets "complain" messages when we're in the foreground, + * and "explain" messages when we're in the foreground, and not "quiet". + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "global.h" + +static char truncated[] = " (truncated)"; +static char msg[MAX_MSG + 1]; +static int log_open = 0; + +static void +xopenlog() +{ + if (!log_open) + { + openlog(program_name, LOG_PID, SYSLOG_FACILITY); + log_open = 1; + } +} + +void +xcloselog() +{ + if (log_open) closelog(); + log_open = 0; +} + +static void +make_msg(const char *fmt, va_list args) +/* Construct the message string from its parts */ +{ + int len; + + /* There's some confusion in the documentation about what vsnprintf + * returns when the buffer overflows. Hmmm... */ + len = vsnprintf(msg, sizeof(msg), fmt, args); + if (len >= sizeof(msg) - 1) + strcpy(msg + sizeof(msg) - sizeof(truncated), truncated); +} + +static void +log(int priority, const char *fmt, va_list args) +/* Log a message, described by "fmt" and "args", with the specified + * "priority". */ +{ + make_msg(fmt, args); + xopenlog(); + syslog(priority, "%s", msg); + if (!in_background) + { + if (priority == EXPLAIN_LEVEL && !quiet) + fprintf(stderr, "%s\n", msg); + else if (priority == COMPLAIN_LEVEL) + fprintf(stderr, "%s: %s\n", program_name, msg); + } +} + +static void +log_e(int priority, const char *fmt, va_list args) +/* Same as log(), but also appends an error description corresponding + * to "errno". */ +{ + int saved_errno; + + saved_errno = errno; + make_msg(fmt, args); + xopenlog(); + syslog(priority, "%s: %s", msg, strerror(saved_errno)); + if (!in_background) + { + if (priority == EXPLAIN_LEVEL && !quiet) + fprintf(stderr, "%s: %s\n", msg, strerror(saved_errno)); + else if (priority == COMPLAIN_LEVEL) + fprintf(stderr, "%s: %s: %s\n", + program_name, msg, strerror(saved_errno)); + } +} + +void +explain(const char *fmt, ...) +/* Log an "explain" level message */ +{ + va_list args; + + va_start(args, fmt); + log(EXPLAIN_LEVEL, fmt, args); + va_end(args); +} + +void +explain_e(const char *fmt, ...) +/* Log an "explain" level message, with an error description */ +{ + va_list args; + + va_start(args, fmt); + log_e(EXPLAIN_LEVEL, fmt, args); + va_end(args); +} + +void +complain(const char *fmt, ...) +/* Log a "complain" level message */ +{ + va_list args; + + va_start(args, fmt); + log(COMPLAIN_LEVEL, fmt, args); + va_end(args); +} + +void +complain_e(const char *fmt, ...) +/* Log a "complain" level message, with an error description */ +{ + va_list args; + + va_start(args, fmt); + log_e(COMPLAIN_LEVEL, fmt, args); + va_end(args); +} + +void +die(const char *fmt, ...) +/* Log a "complain" level message, and exit */ +{ + va_list args; + + va_start(args, fmt); + log(COMPLAIN_LEVEL, fmt, args); + va_end(args); + if (getpid() == primary_pid) complain("Aborted"); + + exit(FAILURE_EXIT); +} + +void +die_e(const char *fmt, ...) +/* Log a "complain" level message, with an error description, and exit */ +{ + va_list args; + + va_start(args, fmt); + log_e(COMPLAIN_LEVEL, fmt, args); + va_end(args); + if (getpid() == primary_pid) complain("Aborted"); + + exit(FAILURE_EXIT); +} + +#ifdef DEBUG + +/* These are called through the Debug() and Debug_e() macros, defined + * in global.h */ + +void +xdebug(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + log(DEBUG_LEVEL, fmt, args); + va_end(args); +} + +void +xdebug_e(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + log_e(DEBUG_LEVEL, fmt, args); + va_end(args); +} + +#endif /* DEBUG */ diff --git a/anacron/main.c b/anacron/main.c new file mode 100644 index 0000000..949ecb2 --- /dev/null +++ b/anacron/main.c @@ -0,0 +1,467 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "global.h" +#include "gregor.h" + +pid_t primary_pid; +int day_now; +int year, month, day_of_month; /* date anacron started */ + +char *program_name; +char *anacrontab; +int serialize, force, update_only, now, + no_daemon, quiet; /* command-line options */ +char **args; /* vector of "job" command-line arguments */ +int nargs; /* number of these */ +char *defarg = "*"; +int in_background; /* are we in the background? */ +int old_umask; /* umask when started */ +sigset_t old_sigmask; /* signal mask when started */ + +job_rec *first_job_rec; +env_rec *first_env_rec; + +static time_t start_sec; /* time anacron started */ +static volatile int got_sigalrm, got_sigchld, got_sigusr1; +int running_jobs, running_mailers; /* , number of */ + +static void +print_version() +{ + printf("Anacron " RELEASE "\n" + "Copyright (C) 1998 Itai Tzur \n" + "Copyright (C) 1999 Sean 'Shaleh' Perry \n" + "\n" + "Mail comments, suggestions and bug reports to ." + "\n\n"); +} + +static void +print_usage() +{ + printf("Usage: anacron [-s] [-f] [-n] [-d] [-q] [-t anacrontab] [job] ...\n" + " anacron -u [job] ...\n" + " anacron [-V|-h]\n" + "\n" + " -s Serialize execution of jobs\n" + " -f Force execution of jobs, even before their time\n" + " -n Run jobs with no delay, implies -s\n" + " -d Don't fork to the background\n" + " -q Suppress stderr messages, only applicable with -d\n" + " -u Update the timestamps without actually running anything\n" + " -t Use this anacrontab\n" + " -V Print version information\n" + " -h Print this message\n" + "\n" + "See the manpage for more details.\n" + "\n"); +} + +static void +parse_opts(int argc, char *argv[]) +/* Parse command-line options */ +{ + int opt; + + quiet = no_daemon = serialize = force = update_only = now = 0; + opterr = 0; + while ((opt = getopt(argc, argv, "sfundqt:Vh")) != EOF) + { + switch (opt) + { + case 's': + serialize = 1; + break; + case 'f': + force = 1; + break; + case 'u': + update_only = 1; + break; + case 'n': + now = serialize = 1; + break; + case 'd': + no_daemon = 1; + break; + case 'q': + quiet = 1; + break; + case 't': + anacrontab = strdup(optarg); + break; + case 'V': + print_version(); + exit(0); + case 'h': + print_usage(); + exit(0); + case '?': + fprintf(stderr, "%s: invalid option: %c\n", + program_name, optopt); + fprintf(stderr, "type: `%s -h' for more information\n", + program_name); + exit(FAILURE_EXIT); + } + } + if (optind == argc) + { + /* no arguments. Equivalent to: `*' */ + nargs = 1; + args = &defarg; + } + else + { + nargs = argc - optind; + args = argv + optind; + } +} + +pid_t +xfork() +/* Like fork(), only never returns on failure */ +{ + pid_t pid; + + pid = fork(); + if (pid == -1) die_e("Can't fork"); + return pid; +} + +int +xopen(int fd, const char *file_name, int flags) +/* Like open, only it: + * a) never returns on failure, and + * b) if "fd" is non-negative, expect the file to open + * on file-descriptor "fd". + */ +{ + int rfd; + + rfd = open(file_name, flags); + if (fd >= 0 && rfd != fd) + die_e("Can't open %s on file-descriptor %d", file_name, fd); + else if (rfd < 0) + die_e("Can't open %s", file_name); + return rfd; +} + +void +xclose(int fd) +/* Like close(), only doesn't return on failure */ +{ + if (close(fd)) die_e("Can't close file descriptor %d", fd); +} + +static void +go_background() +/* Become a daemon. The foreground process exits successfully. */ +{ + pid_t pid; + + /* stdin is already closed */ + + if (fclose(stdout)) die_e("Can't close stdout"); + xopen(1, "/dev/null", O_WRONLY); + + if (fclose(stderr)) die_e("Can't close stderr"); + xopen(2, "/dev/null", O_WRONLY); + + pid = xfork(); + if (pid != 0) + { + /* parent */ + exit(0); + } + else + { + /* child */ + primary_pid = getpid(); + if (setsid() == -1) die_e("setsid() error"); + in_background = 1; + } +} + +void +handle_sigalrm() +{ + got_sigalrm = 1; +} + +void +handle_sigchld() +{ + got_sigchld = 1; +} + +void +handle_sigusr1() +{ + got_sigusr1 = 1; +} + +static void +set_signal_handling() +/* We only use SIGALRM, SIGCHLD and SIGUSR1, and we unblock them only + * in wait_signal(). + */ +{ + sigset_t ss; + struct sigaction sa; + + got_sigalrm = got_sigchld = got_sigusr1 = 0; + + /* block SIGALRM, SIGCHLD and SIGUSR1 */ + if (sigemptyset(&ss) || + sigaddset(&ss, SIGALRM) || + sigaddset(&ss, SIGCHLD) || + sigaddset(&ss, SIGUSR1)) die_e("sigset error"); + if (sigprocmask(SIG_BLOCK, &ss, NULL)) die_e ("sigprocmask error"); + + /* setup SIGALRM handler */ + sa.sa_handler = handle_sigalrm; + sa.sa_mask = ss; + sa.sa_flags = 0; + if (sigaction(SIGALRM, &sa, NULL)) die_e("sigaction error"); + + /* setup SIGCHLD handler */ + sa.sa_handler = handle_sigchld; + sa.sa_mask = ss; + sa.sa_flags = SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sa, NULL)) die_e("sigaction error"); + + /* setup SIGUSR1 handler */ + sa.sa_handler = handle_sigusr1; + sa.sa_mask = ss; + sa.sa_flags = 0; + if (sigaction(SIGUSR1, &sa, NULL)) die_e("sigaction error"); +} + +static void +wait_signal() +/* Return after a signal is caught */ +{ + sigset_t ss; + + if (sigprocmask(0, NULL, &ss)) die_e("sigprocmask error"); + if (sigdelset(&ss, SIGALRM) || + sigdelset(&ss, SIGCHLD) || + sigdelset(&ss, SIGUSR1)) die_e("sigset error"); + sigsuspend(&ss); +} + +static void +wait_children() +/* Wait until we have no more children (of any kind) */ +{ + while (running_jobs > 0 || running_mailers > 0) + { + wait_signal(); + if (got_sigchld) tend_children(); + got_sigchld = 0; + if (got_sigusr1) explain("Received SIGUSR1"); + got_sigusr1 = 0; + } +} + +static void +orderly_termination() +/* Execution is diverted here, when we get SIGUSR1 */ +{ + explain("Received SIGUSR1"); + got_sigusr1 = 0; + wait_children(); + explain("Exited"); + exit(0); +} + +static void +xsleep(unsigned int n) +/* Sleep for n seconds, servicing SIGCHLDs and SIGUSR1s in the meantime. + * If n=0, return immediately. + */ +{ + if (n == 0) return; + alarm(n); + do + { + wait_signal(); + if (got_sigchld) tend_children(); + got_sigchld = 0; + if (got_sigusr1) orderly_termination(); + } + while (!got_sigalrm); + got_sigalrm = 0; +} + +static void +wait_jobs() +/* Wait until there are no running jobs, + * servicing SIGCHLDs and SIGUSR1s in the meantime. + */ +{ + while (running_jobs > 0) + { + wait_signal(); + if (got_sigchld) tend_children(); + got_sigchld = 0; + if (got_sigusr1) orderly_termination(); + } +} + +static void +record_start_time() +{ + struct tm *tm_now; + + start_sec = time(NULL); + tm_now = localtime(&start_sec); + year = tm_now->tm_year + 1900; + month = tm_now->tm_mon + 1; + day_of_month = tm_now->tm_mday; + day_now = day_num(year, month, day_of_month); + if (day_now == -1) die("Invalid date (this is really embarrassing)"); + if (!update_only) + explain("Anacron " RELEASE " started on %04d-%02d-%02d", + year, month, day_of_month); +} + +static int +time_till(job_rec *jr) +/* Return the number of seconds that we have to wait until it's time + * to start job jr. + */ +{ + unsigned int tj, tn; + + if (now) return 0; + tn = time(NULL); + tj = start_sec + jr->delay * 60; + if (tj < tn) return 0; + return tj - tn; +} + +static void +fake_jobs() +{ + int j; + + j = 0; + while (j < njobs) + { + fake_job(job_array[j]); + explain("Updated timestamp for job `%s' to %04d-%02d-%02d", + job_array[j]->ident, year, month, day_of_month); + j++; + } +} + +static void +explain_intentions() +{ + int j; + + j = 0; + while (j < njobs) + { + if (now) + { + explain("Will run job `%s'", job_array[j]->ident); + } + else + { + explain("Will run job `%s' in %d min.", + job_array[j]->ident, job_array[j]->delay); + } + j++; + } + if (serialize && njobs > 0) + explain("Jobs will be executed sequentially"); +} + +int +main(int argc, char *argv[]) +{ + int j; + + anacrontab = NULL; + + if((program_name = strrchr(argv[0], '/')) == NULL) + program_name = argv[0]; + else + ++program_name; /* move pointer to char after '/' */ + + parse_opts(argc, argv); + + if (anacrontab == NULL) + anacrontab = strdup(ANACRONTAB); + + in_background = 0; + + if (chdir(SPOOLDIR)) die_e("Can't chdir to " SPOOLDIR); + + old_umask = umask(0); + + if (sigprocmask(0, NULL, &old_sigmask)) die_e("sigset error"); + + if (fclose(stdin)) die_e("Can't close stdin"); + xopen(0, "/dev/null", O_RDONLY); + + if (!no_daemon) + go_background(); + else + primary_pid = getpid(); + + record_start_time(); + read_tab(); + arrange_jobs(); + + if (update_only) + { + fake_jobs(); + exit(0); + } + + explain_intentions(); + set_signal_handling(); + running_jobs = running_mailers = 0; + for(j = 0; j < njobs; ++j) + { + xsleep(time_till(job_array[j])); + if (serialize) wait_jobs(); + launch_job(job_array[j]); + } + wait_children(); + explain("Normal exit (%d jobs run)", njobs); + exit(0); +} diff --git a/anacron/matchrx.c b/anacron/matchrx.c new file mode 100644 index 0000000..a3e940c --- /dev/null +++ b/anacron/matchrx.c @@ -0,0 +1,74 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +#include +#include +#include +#include +#include "matchrx.h" + +int +match_rx(const char *rx, char *string, int n_sub, /* char **substrings */...) +/* Return 1 if the regular expression "*rx" matches the string "*string", + * 0 if not, -1 on error. + * "Extended" regular expressions are used. + * Additionally, there should be "n_sub" "substrings" arguments. These, + * if not NULL, and if the match succeeds are set to point to the + * corresponding substrings of the regexp. + * The original string is changed, and the substrings must not overlap, + * or even be directly adjacent. + * This is not the most efficient, or elegant way of doing this. + */ +{ + int r, n; + regex_t crx; + va_list va; + char **substring; + regmatch_t *sub_offsets; + sub_offsets = malloc(sizeof(regmatch_t) * (n_sub + 1)); + memset(sub_offsets, 0, sizeof(regmatch_t) * (n_sub + 1)); + + if (regcomp(&crx, rx, REG_EXTENDED)) return - 1; + r = regexec(&crx, string, n_sub + 1, sub_offsets, 0); + if (r != 0 && r != REG_NOMATCH) return - 1; + regfree(&crx); + if (r == REG_NOMATCH) return 0; + + va_start(va, n_sub); + n = 1; + while (n <= n_sub) + { + substring = va_arg(va, char**); + if (substring != NULL) + { + if (sub_offsets[n].rm_so == -1) return - 1; + *substring = string + sub_offsets[n].rm_so; + *(string + sub_offsets[n].rm_eo) = 0; + } + n++; + } + va_end(va); + free(sub_offsets); + return 1; +} diff --git a/anacron/matchrx.h b/anacron/matchrx.h new file mode 100644 index 0000000..3ce8350 --- /dev/null +++ b/anacron/matchrx.h @@ -0,0 +1,26 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +int match_rx(const char *rx, char *string, + int n_sub, /* char **substrings */...); diff --git a/anacron/readtab.c b/anacron/readtab.c new file mode 100644 index 0000000..4157ebc --- /dev/null +++ b/anacron/readtab.c @@ -0,0 +1,286 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +/* /etc/anacrontab parsing, and job sorting + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "global.h" +#include "matchrx.h" + +static struct obstack input_o; /* holds input line */ +static struct obstack tab_o; /* holds processed data read from anacrontab */ +static FILE *tab; +job_rec **job_array; +int njobs; /* number of jobs to run */ +static int jobs_read; /* number of jobs read */ +static int line_num; /* current line in anacrontab */ +static job_rec *last_job_rec; /* last job stored in memory, at the moment */ +static env_rec *last_env_rec; /* last environment assignment stored */ + +/* some definitions for the obstack macros */ +#define obstack_chunk_alloc xmalloc +#define obstack_chunk_free free + +static void * +xmalloc (size_t size) +/* Just like standard malloc(), only never returns NULL. */ +{ + void * ptr; + + ptr = malloc(size); + if (ptr == NULL) + die("Memory exhausted"); + return ptr; +} + +static int +conv2int(const char *s) +/* Return the int or -1 on over/under-flow + */ +{ + long l; + + errno = 0; + l = strtol(s, NULL, 10); + /* we use negative as error, so I am really returning unsigned int */ + if (errno == ERANGE || l < 0 || l > INT_MAX) return - 1; + return l; +} + +static char * +read_tab_line () +/* Read one line and return a pointer to it. +Return NULL if no more lines. + */ +{ + int c; + + if (feof(tab)) return NULL; + while ((c = getc(tab)) != EOF && c != '\n') + obstack_1grow(&input_o, c); + if (ferror(tab)) die_e("Error reading %s", anacrontab); + obstack_1grow(&input_o, '\0'); + return obstack_finish(&input_o); +} + +static int +job_arg_num(const char *ident) +/* Return the command-line-argument number refering to this job-identifier. + * If it isn't specified, return -1. + */ +{ + int i, r; + + for (i = 0; i < nargs; i++) + { + r = fnmatch(args[i], ident, 0); + if (r == 0) return i; + if (r != FNM_NOMATCH) die("fnmatch() error"); + } + return - 1; +} + +static void +register_env(const char *env_var, const char *value) +/* Store the environment assignment "env_var"="value" */ +{ + env_rec *er; + int var_len, val_len; + + var_len = strlen(env_var); + val_len = strlen(value); + er = obstack_alloc(&tab_o, sizeof(env_rec)); + er->assign = obstack_alloc(&tab_o, var_len + 1 + val_len + 1); + strcpy(er->assign, env_var); + er->assign[var_len] = '='; + strcpy(er->assign + var_len + 1, value); + er->assign[var_len + 1 + val_len] = 0; + if (last_env_rec != NULL) last_env_rec->next = er; + else first_env_rec = er; + last_env_rec = er; + Debug(("on line %d: %s", line_num, er->assign)); +} + +static void +register_job(const char *periods, const char *delays, + const char *ident, char *command) +/* Store a job definition */ +{ + int period, delay; + job_rec *jr; + int ident_len, command_len; + + ident_len = strlen(ident); + command_len = strlen(command); + jobs_read++; + period = conv2int(periods); + delay = conv2int(delays); + if (period < 0 || delay < 0) + { + complain("%s: number out of range on line %d, skipping", + anacrontab, line_num); + return; + } + jr = obstack_alloc(&tab_o, sizeof(job_rec)); + jr->period = period; + jr->delay = delay; + jr->tab_line = line_num; + jr->ident = obstack_alloc(&tab_o, ident_len + 1); + strcpy(jr->ident, ident); + jr->arg_num = job_arg_num(ident); + jr->command = obstack_alloc(&tab_o, command_len + 1); + strcpy(jr->command, command); + jr->job_pid = jr->mailer_pid = 0; + if (last_job_rec != NULL) last_job_rec->next = jr; + else first_job_rec = jr; + last_job_rec = jr; + jr->prev_env_rec = last_env_rec; + jr->next = NULL; + Debug(("Read job - period=%d, delay=%d, ident=%s, command=%s", + jr->period, jr->delay, jr->ident, jr->command)); +} + +static void +parse_tab_line(char *line) +{ + int r; + char *env_var; + char *value; + char *periods; + char *delays; + char *ident; + char *command; + + /* an empty line? */ + r = match_rx("^[ \t]*($|#)", line, 0); + if (r == -1) goto reg_err; + if (r) + { + Debug(("line %d empty", line_num)); + return; + } + + /* an environment assignment? */ + r = match_rx("^[ \t]*([^ \t=]+)[ \t]*=(.*)$", line, 2, + &env_var, &value); + if (r == -1) goto reg_err; + if (r) + { + register_env(env_var, value); + return; + } + + /* a job? */ + r = match_rx("^[ \t]*([[:digit:]]+)[ \t]+([[:digit:]]+)[ \t]+" + "([^ \t/]+)[ \t]+([^ \t].*)$", + line, 4, &periods, &delays, &ident, &command); + if (r == -1) goto reg_err; + if (r) + { + register_job(periods, delays, ident, command); + return; + } + complain("Invalid syntax in %s on line %d - skipping this line", + anacrontab, line_num); + return; + + reg_err: + die("Regex error reading %s", anacrontab); +} + +void +read_tab() +/* Read the anacrontab file into memory */ +{ + char *tab_line; + + first_job_rec = last_job_rec = NULL; + first_env_rec = last_env_rec = NULL; + jobs_read = 0; + line_num = 0; + /* Open the anacrontab file */ + tab = fopen(anacrontab, "r"); + if (tab == NULL) die_e("Error opening %s", anacrontab); + /* Initialize the obstacks */ + obstack_init(&input_o); + obstack_init(&tab_o); + while ((tab_line = read_tab_line()) != NULL) + { + line_num++; + parse_tab_line(tab_line); + obstack_free(&input_o, tab_line); + } + if (fclose(tab)) die_e("Error closing %s", anacrontab); +} + +static int +execution_order(const job_rec **job1, const job_rec **job2) +/* Comparison function for sorting the jobs. + */ +{ + int d; + + d = (*job1)->arg_num - (*job2)->arg_num; + if (d != 0 && now) return d; + d = (*job1)->delay - (*job2)->delay; + if (d != 0) return d; + d = (*job1)->tab_line - (*job2)->tab_line; + return d; +} + +void +arrange_jobs() +/* Make an array of pointers to jobs that are going to be executed, + * and arrange them in the order of execution. + * Also lock these jobs. + */ +{ + job_rec *j; + + j = first_job_rec; + njobs = 0; + while (j != NULL) + { + if (j->arg_num != -1 && (update_only || consider_job(j))) + { + njobs++; + obstack_grow(&tab_o, &j, sizeof(j)); + } + j = j->next; + } + job_array = obstack_finish(&tab_o); + + /* sort the jobs */ + qsort(job_array, njobs, sizeof(*job_array), + (int (*)(const void *, const void *))execution_order); +} diff --git a/anacron/runjob.c b/anacron/runjob.c new file mode 100644 index 0000000..4c8a2c9 --- /dev/null +++ b/anacron/runjob.c @@ -0,0 +1,289 @@ +/* + Anacron - run commands periodically + Copyright (C) 1998 Itai Tzur + Copyright (C) 1999 Sean 'Shaleh' Perry + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + The GNU General Public License can also be found in the file + `COPYING' that comes with the Anacron source distribution. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "global.h" + +static int +temp_file() +/* Open a temporary file and return its file descriptor */ +{ + const int max_retries = 50; + char *name; + int fd, i; + + i = 0; + name = NULL; + do + { + i++; + free(name); + name = tempnam(NULL, NULL); + if (name == NULL) die("Can't find a unique temporary filename"); + fd = open(name, O_RDWR | O_CREAT | O_EXCL | O_APPEND, + S_IRUSR | S_IWUSR); + /* I'm not sure we actually need to be so persistent here */ + } while (fd == -1 && errno == EEXIST && i < max_retries); + + if (fd == -1) die_e("Can't open temporary file"); + if (unlink(name)) die_e("Can't unlink temporary file"); + free(name); + fcntl(fd, F_SETFD, 1); /* set close-on-exec flag */ + return fd; +} + +static off_t +file_size(int fd) +/* Return the size of temporary file fd */ +{ + struct stat st; + + if (fstat(fd, &st)) die_e("Can't fstat temporary file"); + return st.st_size; +} + +static char * +username() +{ + struct passwd *ps; + + ps = getpwuid(geteuid()); + if (ps == NULL) die_e("getpwuid() error"); + return ps->pw_name; +} + +static void +xputenv(const char *s) +{ + if (putenv(s)) die_e("Can't set the environment"); +} + +static void +setup_env(const job_rec *jr) +/* Setup the environment for the job according to /etc/anacrontab */ +{ + env_rec *er; + + er = first_env_rec; + if (er == NULL || jr->prev_env_rec == NULL) return; + xputenv(er->assign); + while (er != jr->prev_env_rec) + { + er = er->next; + xputenv(er->assign); + } +} + +static void +run_job(const job_rec *jr) +/* This is called to start the job, after the fork */ +{ + setup_env(jr); + /* setup stdout and stderr */ + xclose(1); + xclose(2); + if (dup2(jr->output_fd, 1) != 1 || dup2(jr->output_fd, 2) != 2) + die_e("dup2() error"); /* dup2 also clears close-on-exec flag */ + in_background = 0; /* now, errors will be mailed to the user */ + if (chdir("/")) die_e("Can't chdir to '/'"); + + umask(old_umask); + if (sigprocmask(SIG_SETMASK, &old_sigmask, NULL)) + die_e("sigprocmask error"); + xcloselog(); + execl("/bin/sh", "/bin/sh", "-c", jr->command, (char *)NULL); + die_e("execl() error"); +} + +static void +xwrite(int fd, const char *string) +/* Write (using write()) the string "string" to temporary file "fd". + * Don't return on failure */ +{ + if (write(fd, string, strlen(string)) == -1) + die_e("Can't write to temporary file"); +} + +static int +xwait(pid_t pid , int *status) +/* Check if child process "pid" has finished. If it has, return 1 and its + * exit status in "*status". If not, return 0. + */ +{ + pid_t r; + + r = waitpid(pid, status, WNOHANG); + if (r == -1) die_e("waitpid() error"); + if (r == 0) return 0; + return 1; +} + +static void +launch_mailer(job_rec *jr) +{ + pid_t pid; + + pid = xfork(); + if (pid == 0) + { + /* child */ + in_background = 1; + /* set stdin to the job's output */ + xclose(0); + if (dup2(jr->output_fd, 0) != 0) die_e("Can't dup2()"); + if (lseek(0, 0, SEEK_SET) != 0) die_e("Can't lseek()"); + umask(old_umask); + if (sigprocmask(SIG_SETMASK, &old_sigmask, NULL)) + die_e("sigprocmask error"); + xcloselog(); + + /* Here, I basically mirrored the way /usr/sbin/sendmail is called + * by cron on a Debian system, except for the "-oem" and "-or0s" + * options, which don't seem to be appropriate here. + * Hopefully, this will keep all the MTAs happy. */ + execl(SENDMAIL, SENDMAIL, "-FAnacron", "-odi", + username(), (char *)NULL); + die_e("Can't exec " SENDMAIL); + } + /* parent */ + /* record mailer pid */ + jr->mailer_pid = pid; + running_mailers++; +} + +static void +tend_mailer(job_rec *jr, int status) +{ + if (WIFEXITED(status) && WEXITSTATUS(status) != 0) + complain("Tried to mail output of job `%s', " + "but mailer process (" SENDMAIL ") exited with ststus %d", + jr->ident, WEXITSTATUS(status)); + else if (!WIFEXITED(status) && WIFSIGNALED(status)) + complain("Tried to mail output of job `%s', " + "but mailer process (" SENDMAIL ") got signal %d", + jr->ident, WTERMSIG(status)); + else if (!WIFEXITED(status) && !WIFSIGNALED(status)) + complain("Tried to mail output of job `%s', " + "but mailer process (" SENDMAIL ") terminated abnormally" + , jr->ident); + + jr->mailer_pid = 0; + running_mailers--; +} + +void +launch_job(job_rec *jr) +{ + pid_t pid; + int fd; + + /* create temporary file for stdout and stderr of the job */ + fd = jr->output_fd = temp_file(); + /* write mail header */ + xwrite(fd, "From: "); + xwrite(fd, username()); + xwrite(fd, " (Anacron)\n"); + xwrite(fd, "To: "); + xwrite(fd, username()); + xwrite(fd, "\n"); + xwrite(fd, "Subject: Anacron job '"); + xwrite(fd, jr->ident); + xwrite(fd, "'\n\n"); + jr->mail_header_size = file_size(fd); + + pid = xfork(); + if (pid == 0) + { + /* child */ + in_background = 1; + run_job(jr); + /* execution never gets here */ + } + /* parent */ + explain("Job `%s' started", jr->ident); + jr->job_pid = pid; + running_jobs++; +} + +static void +tend_job(job_rec *jr, int status) +/* Take care of a finished job */ +{ + int mail_output; + char *m; + + update_timestamp(jr); + unlock(jr); + if (file_size(jr->output_fd) > jr->mail_header_size) mail_output = 1; + else mail_output = 0; + + m = mail_output ? " (mailing output)" : ""; + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) + explain("Job `%s' terminated%s", jr->ident, m); + else if (WIFEXITED(status)) + explain("Job `%s' terminated (exit status: %d)%s", + jr->ident, WEXITSTATUS(status), m); + else if (WIFSIGNALED(status)) + complain("Job `%s' terminated due to signal %d%s", + jr->ident, WTERMSIG(status), m); + else /* is this possible? */ + complain("Job `%s' terminated abnormally%s", jr->ident, m); + + jr->job_pid = 0; + running_jobs--; + if (mail_output) launch_mailer(jr); + xclose(jr->output_fd); +} + +void +tend_children() +/* This is called whenever we get a SIGCHLD. + * Takes care of zombie children. + */ +{ + int j; + int status; + + j = 0; + while (j < njobs) + { + if (job_array[j]->mailer_pid != 0 && + xwait(job_array[j]->mailer_pid, &status)) + tend_mailer(job_array[j], status); + if (job_array[j]->job_pid != 0 && + xwait(job_array[j]->job_pid, &status)) + tend_job(job_array[j], status); + j++; + } +}