diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..c94ea0c --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,10 @@ +;; Per-directory local variables for GNU Emacs 23 and later. + +((nil . ((fill-column . 78) + (tab-width . 8))) + (c-mode . ((c-file-style . "gnu") + (indent-tabs-mode . nil))) + (scheme-mode + . + ((indent-tabs-mode . nil) + (eval . (put 'mcron-error 'scheme-indent-function 1))))) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9f5414 --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +*.[oa] +*.go +*.log +*.trs +*~ +.deps +.dirstamp +/bin/cron +/bin/crontab +/bin/mcron +/build-aux/ar-lib +/build-aux/compile +/build-aux/config.guess +/build-aux/config.sub +/build-aux/depcomp +/build-aux/install-sh +/build-aux/mdate-sh +/build-aux/missing +/build-aux/test-driver +/build-aux/texinfo.tex +/doc/config.texi +/doc/cron.8 +/doc/crontab.1 +/doc/mcron.1 +/doc/mcron.info +/doc/stamp-vti +/doc/version.texi +/mdate-sh +INSTALL +Makefile +Makefile.in +aclocal.m4 +autom4te.cache +compile +config.cache +config.h +config.h.in +config.log +config.scm +config.status +configure +depcomp +install-sh +missing +pre-inst-env +stamp-h1 +texinfo.tex diff --git a/.prev-version b/.prev-version new file mode 100644 index 0000000..524cb55 --- /dev/null +++ b/.prev-version @@ -0,0 +1 @@ +1.1.1 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..5657aa1 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,6 @@ +Dale Mellor +Mathieu Lirzin +Sergey Poznyakoff +Ludovic Courtès +宋文武 +Efraim Flashner diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. 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 +them 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 prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. 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. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state 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) + + 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 3 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program 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, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU 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 Lesser General +Public License instead of this License. But first, please read +. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..fe5459d --- /dev/null +++ b/ChangeLog @@ -0,0 +1,4 @@ +Normally a ChangeLog is generated at "make dist" time and available in +source tarballs. + +If not, see the Git commit log at . diff --git a/ChangeLog.old b/ChangeLog.old new file mode 100644 index 0000000..09c4147 --- /dev/null +++ b/ChangeLog.old @@ -0,0 +1,147 @@ +2014-05-25 Dale Mellor + + * Juggled build infrastructure so that we can make the minimal man + page in the proper autotools way. + + * configure.ac: version to 1.0.8. + +2014-04-28 Dale Mellor + + * We now run against, and require, guile-2.0. + + * configure.ac: version to 1.0.7. + +2012-02-04 Dale Mellor + + * main.scm: added search for initial files in + $XDG_CONFIG_HOME/cron directory, defaulting to ~/.config/cron if + the environment variable is not set) as well as in ~/.cron + directory (this is in line with the current FreeDesktop.org + standards). + +2010-06-13 Dale Mellor + + * configure.ac: added --enable-no-vixie-clobber argument to + configure so that the root user can avoid overwriting a legacy + cron installation. + + * mcron.1: added simple, minimal man page using help2man (the + texinfo file is still the primary documentation source). + + * makefile.am: replaced use of mkinstalldirs with install; the + former is not supplied with the latest automake (1.11). + +2008-02-21 Dale Mellor + + * ALL FILES: Replaced version 2 GPL notices with version 3 ones. + + * makefile.am: Do not remove COPYING file with make + maintainer-clean; if you do it will eventually get replaced with + the old version 2 GPL by a subsequent automake. + + * configure.ac: Bumped version to 1.0.4. + +2008-01-25 Dale Mellor + + * main.scm (command-type): Files which are listed on the command + line are assumed to be guile configurations if they do not end in + .guile or .vixie (previously they were silently ignored). + + * main.scm: Argument to --schedule is no longer optional (the + options system goes really screwy with optional values, usually + pulling the first non-option argument as a value if one was not + intended!) + + * makefile.am: Moved target-specific CFLAGS and LDFLAGS to global + AM_* variables, to remove problem with automake requiring + AM_PROGS_CC_C_O in configure.ac (!) + + * Version is currently at 1.0.3. + +2005-09-02 Dale Mellor + + * makefile.am, mcron.c.template (main): Modified install-exec-hook + so that a proper installation of a Vixie-compatible cron only + takes place if we are root - otherwise only mcron is installed as + a user-owned program. The guile modules are now installed under + mcron's shared data directory, not guile's global directories. + + * mcron-core.scm: Removed job:advance-time, put the code inline + where it was called, and changed the instance in the main loop to + compute the new time based on the current-time, rather than the + previous job time (this makes things behave more reasonably when a + laptop awakes from suspend mode). + + * Bumped version to 1.0.2. + +2004-05-15 Dale Mellor + + * Modified all auxiliary files to reflect that the package is now + properly homed at www.gnu.org. + + * Bumped version to 1.0.1. + +2003-12-11 Dale Mellor + + * Modified all auxiliary files to reflect that we are now a GNU + package. + + * Bumped version to 1.0.0. + +2003-12-07 Dale Mellor + + * configure.ac: Added switches for files and directories used by + mcron: --spool-dir, --socket-file, --allow-file, --deny-file, + --pid-file and --tmp-dir. All the code has been modified to use + these configure options (including the source for the texinfo + manual). + +2003-12-05 Dale Mellor + + * configure.ac: Added test for guile version >= 1.6.4. + + * bumped version to 0.99.4. + +2003-08-03 Dale Mellor + + * Third cut, fully functional, modular, production quality, still + needs testing... + + * Pulled all functionality into modules, so it can be incorporated + into other programs. + + * Bumped version to 0.99.3. + +2003-07-20 Dale Mellor + + * Second cut, now _really_ fully functional (100% Vixie + compatible), production quality code, still needs lots of testing + doing... + + * Converted from SIGUP-/var/cron/update to select-/var/cron/socket + method of communication between crontab and cron. + + * Added implicit job which checks every minute for updates to + /etc/crontab. + + * Removed --enable-vixie configuration option - the Vixie programs + are built and installed by default now. + + * Bumped version to 0.99.2. + +2003-06-28 Dale Mellor + + * First cut, fully functional, production quality code, just needs + testing... + + * Broken/incomplete Guile prevents vixie compatibility from + working - this has been disabled by default in the configuration. + + * Version set at 0.99.1 + +________________________________________________________________________________ +Copyright (C) 2003, 2005, 2006, 2014, 2015 Dale Mellor + +Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. diff --git a/HACKING b/HACKING new file mode 100644 index 0000000..a2f0d13 --- /dev/null +++ b/HACKING @@ -0,0 +1,90 @@ +These notes intend to help people working on the checked-out sources. +These requirements do not apply when building from a distribution tarball. + +* First Git checkout + +You can get a copy of the source repository like this: + + $ git clone git://git.sv.gnu.org/mcron + $ cd mcron + +The next step is to get and check other files needed to build, which are +extracted from other source packages: + + $ ./bootstrap + +And there you are! Just + + $ ./configure + $ make + +At this point, there should be no difference between your local copy, and the +Git master copy: + + $ git diff + +should output no difference. + +Enjoy! + +* Submitting patches + +If you develop a fix or a new feature, please send it to the appropriate +bug-reporting address as reported by the --help option of each program. One +way to do this is to use vc-dwim ), as +follows. + + Run the command "vc-dwim --help", copy its definition of the + "git-changelog-symlink-init" function into your shell, and then run this + function at the top-level directory of the package. + + Edit the (empty) ChangeLog file that this command creates, creating a + properly-formatted entry according to the GNU coding standards + . + + Make your changes. + + Run the command "vc-dwim" and make sure its output (the diff of all your + changes) looks good. + + Run "vc-dwim --commit". + + Run the command "git format-patch --stdout -1", and email its output in, + using the output's subject line. + +* Updating auxilary scripts + + Fetch new versions of the files that are maintained in other GNU + repositories by running "make fetch". In case any file in the + Mcron repository has been updated, commit and re-run the testsuite. + +* Code coverage + + Assuming 'lcov' is installed, it is possible to check the actual code + coverage achieved by the test suite by running the following commands: + + $ make check SCM_LOG_DRIVER_FLAGS="--coverage=yes" + $ genhtml tests/*.info --output-directory out + +----- + +Copyright © 2002-2017 Free Software Foundation, Inc. +Copyright © 2017, 2018 Mathieu Lirzin + +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 3 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, see . + +Local Variables: +mode: outline +fill-column: 78 +End: diff --git a/Makefile.am b/Makefile.am new file mode 100755 index 0000000..ddfad07 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,254 @@ +## Process this file with automake to produce Makefile.in. +# Copyright © 2003 Dale Mellor +# Copyright © 2015, 2016, 2017, 2018, 2020 Mathieu Lirzin +# +# This file is part of GNU Mcron. +# +# GNU Mcron is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GNU Mcron is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Mcron. If not, see . + +## ---------- ## +## Programs. ## +## ---------- ## + +bin_SCRIPTS = bin/mcron +noinst_SCRIPTS = + +if MULTI_USER +bin_SCRIPTS += bin/crontab +sbin_SCRIPTS = bin/cron +else +noinst_SCRIPTS += bin/cron bin/crontab +endif + +# wrapper to be used in the build environment and for running tests. +noinst_SCRIPTS += pre-inst-env + +## --------------- ## +## Guile modules. ## +## --------------- ## + +# Root directory used for installing Guile modules. +guilesitedir = $(datarootdir)/guile/site/$(GUILE_EFFECTIVE_VERSION) +# Root directory used for installing Guile compiled modules. +guilesitegodir = $(libdir)/guile/$(GUILE_EFFECTIVE_VERSION)/site-ccache + +pkgmoduledir = $(guilesitedir)/$(PACKAGE) +pkgmodule_DATA = src/mcron/config.scm +dist_pkgmodule_DATA = \ + src/mcron/base.scm \ + src/mcron/environment.scm \ + src/mcron/job-specifier.scm \ + src/mcron/redirect.scm \ + src/mcron/utils.scm \ + src/mcron/vixie-specification.scm \ + src/mcron/vixie-time.scm + +# Alias for 'src/mcron/base.scm' kept for backward compatibility. +dist_pkgmodule_DATA += src/mcron/core.scm + +pkgmodulegodir = $(guilesitegodir)/$(PACKAGE) +pkgmodulego_DATA = \ + $(dist_pkgmodule_DATA:.scm=.go) \ + src/mcron/config.go + +pkgscriptdir = $(pkgmoduledir)/scripts +dist_pkgscript_DATA = \ + src/mcron/scripts/cron.scm \ + src/mcron/scripts/crontab.scm \ + src/mcron/scripts/mcron.scm + +pkgscriptgodir = $(pkgmodulegodir)/scripts +pkgscriptgo_DATA = $(dist_pkgscript_DATA:.scm=.go) + +compiled_modules = \ + $(pkgmodulego_DATA) \ + $(pkgscriptgo_DATA) + +CLEANFILES = $(compiled_modules) bin/crontab bin/cron bin/mcron +DISTCLEANFILES = src/mcron/config.scm + +# Unset 'GUILE_LOAD_COMPILED_PATH' altogether while compiling. Otherwise, if +# $GUILE_LOAD_COMPILED_PATH contains $(pkgmoduledir), we may find .go files +# in there that are newer than the local .scm files (for instance because the +# user ran 'make install' recently). When that happens, we end up loading +# those previously-installed .go files, which may be stale, thereby breaking +# the whole thing. Set GUILE_AUTO_COMPILE to 0 to avoid auto-compiling guild +# as a consequence of the previous hack. +# +# XXX: Use the C locale for when Guile lacks +# . +.scm.go: + $(guilec_verbose)$(MKDIR_P) `dirname "$@"`; \ + export GUILE_AUTO_COMPILE=0; unset GUILE_LOAD_COMPILED_PATH; \ + LC_ALL=C \ + $(top_builddir)/pre-inst-env $(GUILD) compile \ + --load-path="$(builddir)/src" \ + --load-path="$(srcdir)/src" \ + --warn=format --warn=unbound-variable --warn=arity-mismatch \ + --target="$(host)" --output="$@" "$<" $(devnull_verbose) + + +bin/% : src/%.in Makefile + $(AM_V_GEN)$(MKDIR_P) bin ; \ + sed -e 's,%PREFIX%,${prefix},g' \ + -e 's,%modsrcdir%,${guilesitedir},g' \ + -e 's,%modbuilddir%,${guilesitegodir},g' \ + -e 's,%localstatedir%,${localstatedir},g' \ + -e 's,%pkglibdir%,${pkglibdir},g' \ + -e 's,%sysconfdir%,${sysconfdir},g' \ + -e 's,%localedir%,${localedir},g' \ + -e 's,%VERSION%,@VERSION@,g' \ + -e 's,%PACKAGE_BUGREPORT%,@PACKAGE_BUGREPORT@,g' \ + -e 's,%PACKAGE_NAME%,@PACKAGE_NAME@,g' \ + -e 's,%PACKAGE_URL%,@PACKAGE_URL@,g' \ + -e 's,%GUILE%,$(GUILE),g' \ + $< > $@ ; \ + chmod a+x $@ + + +## ------------ ## +## Test suite. ## +## ------------ ## + +TEST_EXTENSIONS = .scm .sh +AM_TESTS_ENVIRONMENT = env GUILE_AUTO_COMPILE='0' + +SH_LOG_COMPILER = ./pre-inst-env $(SHELL) + +SCM_LOG_DRIVER = \ + $(builddir)/pre-inst-env $(GUILE) \ + $(srcdir)/build-aux/test-driver.scm + +TESTS = \ + tests/basic.sh \ + tests/schedule.sh \ + tests/schedule-2.sh \ + tests/base.scm \ + tests/environment.scm \ + tests/job-specifier.scm \ + tests/redirect.scm \ + tests/utils.scm \ + tests/vixie-specification.scm \ + tests/vixie-time.scm + +## -------------- ## +## Distribution. ## +## -------------- ## + +EXTRA_DIST = \ + bootstrap \ + build-aux/guix.scm \ + HACKING \ + src/cron.in \ + src/crontab.in \ + src/mcron.in \ + tests/init.sh \ + $(TESTS) + +## -------------- ## +## Installation. ## +## -------------- ## + +# Sed command for Transforming program names. +transform_exe = s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/ + +install-exec-hook: +if MULTI_USER + tcrontab=`echo crontab | sed '$(transform_exe)'`; \ + chmod u+s $(DESTDIR)$(bindir)/$${tcrontab} + tcron=`echo cron | sed '$(transform_exe)'`; \ + chmod u+s $(DESTDIR)$(sbindir)/$${tcron} +endif + tmcron=`echo mcron | sed '$(transform_exe)'`; + +installcheck-local: +## Check that only expected programs are installed and configured + tmcron=`echo mcron | sed '$(transform_exe)'`; \ + test -e $(DESTDIR)$(bindir)/$${tmcron} +if MULTI_USER + tcrontab=`echo crontab | sed '$(transform_exe)'`; \ + test -u $(DESTDIR)$(bindir)/$${tcrontab} + tcron=`echo cron | sed '$(transform_exe)'`; \ + test -e $(DESTDIR)$(sbindir)/$${tcron} +else !MULTI_USER + tcrontab=`echo crontab | sed '$(transform_exe)'`; \ + test ! -u $(DESTDIR)$(bindir)/$${tcrontab} + tcron=`echo cron | sed '$(transform_exe)'`; \ + test ! -f $(DESTDIR)$(sbindir)/$${tcron} +endif !MULTI_USER + +## --------------- ## +## Documentation. ## +## --------------- ## + +info_TEXINFOS = doc/mcron.texi +doc_mcron_TEXINFOS = doc/fdl.texi +nodist_doc_mcron_TEXINFOS = doc/config.texi +dist_man_MANS = $(srcdir)/doc/mcron.1 +extra_mans = \ + $(srcdir)/doc/crontab.1 \ + $(srcdir)/doc/cron.8 + +if MULTI_USER +dist_man_MANS += $(extra_mans) +else +# Build, distribute, but do not install the extra man pages. +all-local: $(extra_mans) +EXTRA_DIST += $(extra_mans) +endif + +# XXX: Allow the inclusion of 'doc/fdl.texi' and 'doc/config.texi' inside +# 'doc/mcron.texi' for 'dvi' and 'pdf' targets. +TEXI2DVI = texi2dvi -I doc + +# The 'case' ensures the man pages are only generated if the corresponding +# source script (the first prerequisite) has been changed. The second +# prerequisites is solely meant to force these docs to be made only after +# executables have been compiled. +gen_man = \ + case '$?' in \ + *$<*) $(AM_V_P) && set -x || echo " HELP2MAN $@"; \ + LANGUAGE= $(top_builddir)/pre-inst-env $(HELP2MAN) \ + -s $$man_section -S GNU -p $(PACKAGE_TARNAME) -o $@ $$prog;; \ + *) : ;; \ + esac + +$(srcdir)/doc/mcron.1: src/mcron/scripts/mcron.scm bin/mcron + -@prog="bin/mcron"; man_section=1; $(gen_man) + +$(srcdir)/doc/crontab.1: src/mcron/scripts/crontab.scm bin/crontab + -@prog="bin/crontab"; man_section=1; $(gen_man) + +$(srcdir)/doc/cron.8: src/mcron/scripts/cron.scm bin/cron + -@prog="cron"; man_section=8; $(gen_man) + +MAINTAINERCLEANFILES = $(dist_man_MANS) $(extra_mans) + +## -------------- ## +## Silent rules. ## +## -------------- ## + +guilec_verbose = $(guilec_verbose_@AM_V@) +guilec_verbose_ = $(guilec_verbose_@AM_DEFAULT_V@) +guilec_verbose_0 = @echo " GUILEC " $@; + +devnull_verbose = $(devnull_verbose_@AM_V@) +devnull_verbose_ = $(devnull_verbose_@AM_DEFAULT_V@) +devnull_verbose_0 = >/dev/null + +## ------------- ## +## Maintenance. ## +## ------------- ## + +@MAINT_MAKEFILE@ diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..c5dc2b9 --- /dev/null +++ b/NEWS @@ -0,0 +1,196 @@ +GNU Mcron NEWS -*- outline -*- + +* Noteworthy changes in release 1.2.0 (2020-04-22) [stable] + +** Improvements + C code removed, mcron becomes 100% Guile. + Make doc/mcron.texi gender neutral. + Have src/mcron/scripts/mcron.scm (process-user-file): use read and eval + instead of load. + New tests added for extra checks. + +* Noteworthy changes in release 1.1.4 (2020-04-12) [stable] + +** Improvements + Added missing #include directives + Support Guile 3.0 + Call 'child-cleanup' when 'select' returns an empty set + Avoid 'call-with-current-continuation' + Date changes for Copyrights changed for 2020 + Email updates in documentation + +* Noteworthy changes in release 1.1.3 (2019-11-17) [stable] + +** Improvements + Package contains configure script by default + Authors file change (addition) + Doc fix for 'every second sunday' + guix.scm update + +* Noteworthy changes in release 1.1.2 (2018-11-26) [stable] + +** Improvements + + The "--with-sendmail" configure variable has been added to allow the usage + of a different Mail Transfert Agent (MTA) than 'sendmail -t'. The MTA must + be able to guess the actual recipients from the 'To:' message header. + +* Noteworthy changes in release 1.1.1 (2018-04-08) [stable] + +** Bug fixes + + The "--disable-multi-user" configure variable is not reversed anymore. + 'cron' and 'crontab' are now installed unless this option is used. + + The programs now sets the GUILE_LOAD_PATH and GUILE_LOAD_COMPILED_PATH + environment variables with the location of the installed Guile modules. + + 'next-year-from', 'next-year', 'next-month-from', 'next-month', + 'next-day-from', 'next-day', 'next-hour-from', 'next-hour', + 'next-minute-from', 'next-minute', 'next-second-from', and 'next-second' no + longer crashes when passing an optional argument. + [bug introduced in mcron-1.1] + +** Improvements + + Some basic tests for the installed programs can be run after 'make install' + with 'make installcheck'. + + The configuration files are now processed using a deterministic order. + + The test suite code coverage for mcron modules is now at 66.8% in term of + number of lines (mcron-1.1 was at 23.7%). + +* Noteworthy changes in release 1.1 (2018-03-19) [stable] + +** New features + + The 'job' procedure has now a '#:user' keyword argument which allows + specifying a different user that will run it. + + Additional man pages for 'cron(8)' and 'crontab(1)' are now generated using + GNU Help2man. + +** Bug fixes + + Child process created when executing a job are now properly cleaned even + when execution fails by using 'dynamic-wind' construct. + +** Improvements + + GNU Guile 2.2 is now supported. + + Some procedures are now written using functional style and include a + docstring. 'def-macro' usages are now replaced with hygienic macros. + + Compilation is now done using a non-recursive Makefile, supports out of tree + builds, and use silent rules by default. + + Guile object files creation don't rely on auto-compilation anymore and are + installed in 'site-ccache' directory. + + Jobs are now internally represented using SRFI-9 records instead of vectors. + + Changelog are generated from Git logs when generating the tarball using + Gnulib gitlog-to-changelog script. + + A test suite is now available and can be run with 'make check'. + +** Changes in behavior + + The "--enable-debug" configure variable has been removed and replaced with + MCRON_DEBUG environment variable. + + The "--disable-multi-user" configure variable is now used to not build and + install the 'cron' and 'crontab' programs. It has replaced the + "--enable-no-vixie-clobber" which had similar effect. + + (mcron core) module is now deprecated and has been superseeded by + (mcron base). + +* Noteworthy changes in release 1.0.8 (2014-04-28) [stable] + + Man page is now generated with GNU Help2man before installation and + distributed in the tarball. + +* Noteworthy changes in release 1.0.7 (2012-02-04) [stable] + + Mcron is now compatible with Guile 2.0. + + FreeDesktop.org's standard user configuration directories are now used to + find the user script files. + +* Noteworthy changes in release 1.0.6 (2010-06-20) [stable] + + The copyright notices are now standardized on all auxiliary files. This + follows the example set by the GNU hello program. + + immutable end texts from the texinfo document are now removed, to + accomodate with Debian requirements. + +* Noteworthy changes in release 1.0.5 (2010-06-13) [stable] + + Some technical changes to the build system has been made to help the + distribution to Debian. + + The Git repository has been completely re-hashed, and now represents a + complete and faithful history of the package's development since its + inception. + +* Noteworthy changes in release 1.0.4 (2008-02-21) [stable] + + The source code is now held in a Git repository, which can be checked-out at + . + + The code is now covered by the GPLv3 license. + +* Noteworthy changes in release 1.0.3 (2006-04-16) [stable] + + daylight savings time shifts are now properly handled + + Parsing Vixie-style input files has been improved. + + Crontab entries can now be corrected instead of just wiping out the file. + + Mcron is now compatible with Guile 1.8. + + The manual is now licensed under the GNU Free Documentation License (GFDL) + +* Noteworthy changes in release 1.0.2 (2006-01-02) [stable] + +* Noteworthy changes in release 1.0.1 (2004-05-15) [stable] + + The mailing list has been set-up. + +* Noteworthy changes in release 1.0 (2003-12-12) [stable] + + Mcron is now officially a GNU program. + +* Noteworthy changes in release 0.99.3 (2003-08-05) [stable] + + The code is now splitted into modules. + +* Noteworthy changes in release 0.99.2 (2003-07-20) [stable] + + The implementation is now really 100% Vixie compatible. + + Some Guile limitations such as the absence of POSIX threads and signals has + been worked around. + +* Noteworthy changes in release 0.99.1 (2003-07-05) [stable] + + Installation of cron and crontab is now disabled by default (suspect problems + with Guile internals are preventing these from working properly). + + The project is now managed on Savannah. A CVS repository and web page have been + created. + +======================================================================== + +Copyright © 2003, 2005, 2006 Dale Mellor +Copyright © 2017, 2018 Mathieu Lirzin +Copyright © 2018 宋文武 + +Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. diff --git a/README b/README new file mode 100644 index 0000000..7fe59f0 --- /dev/null +++ b/README @@ -0,0 +1,69 @@ +This is GNU Mcron, a tool to run jobs at scheduled times. It is a complete +replacement for Vixie cron. Besides supporting the traditional Vixie syntax +for its configuration files, GNU Mcron offers the possibility to define jobs +using the Scheme language. + +See the INSTALL file for generic information about how to configure and +install GNU Mcron. If this file is not present, see HACKING for +preliminary build instructions. + +---------------------------------------------------------------------- +IMPORTANT NOTICES + +Do not (yet) install this software on a machine which relies for its +functioning on its current set of crontabs. + +To not replace the cron daemon on a system, the package must be installed +with the --disable-multi-user configure option. + +Before installing this package for the first time, it is necessary to terminate +any running cron daemons on your system. If your old cron is not Vixie or +accurately Vixie compatible (files in /var/cron/tabs*, /var/cron/allow, +/var/cron/deny, /etc/crontab, /var/run/cron.pid) then you will need to clear out +all old crontabs and make new ones afresh - or else look very carefully at the +options you pass to the package configure script, as follows. + +It is often the case that GNU/Linux distributions and other Unices hacked the +cron daemon to use different directories to those above. You can use configure +options --spool-dir, --socket-file, --allow-file, --deny-file, --pid-file and +--tmp-dir to make mcron behave similarly. Note that, with the exception of +tmp-dir, none of these files or directories should be accessible by ordinary +(non-root) users. + +If your old cron is Vixie, or very similar, mcron should fall right into place +where your old cron was (the binaries cron and crontab will be replaced, but if +your existing system has a binary called crond, you should make this a link +to mcron), and you should be able to continue to use your existing crontabs +without noticing any changes. + +If you don't want to clobber your existing cron executables, you can specify +the --program-prefix option to configure with a prefix ending in a +non-alphabetic character, for example "m.", and then run the programs as +m.mcron, m.cron (or m.crond) and m.crontab. +---------------------------------------------------------------------- + + +After compilation, read the info file for full instructions for use (typing +'info -f doc/mcron.info' at the command line should suffice). Notes for end +users, sysadmins, and developers who wish to incorporate mcron into their own +programs are included here. + +Features which might be implemented sometime sooner or later are noted in the +TODO file. + +Please send all other bug reports to bug-mcron@gnu.org. + +Mcron is free software. See the file COPYING for copying conditions. + +The mcron development home page is at http://www.gnu.org/software/mcron, and it +can be obtained from ftp://ftp.gnu.org/pub/gnu/mcron. + +----- + +Copyright © 2003, 2005, 2006, 2012, 2014 Dale Mellor +Copyright © 2018 Mathieu Lirzin + +Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. diff --git a/TODO b/TODO new file mode 100644 index 0000000..2b7329f --- /dev/null +++ b/TODO @@ -0,0 +1,54 @@ +GNU mcron --- TODO -*-text-*- + + Copyright (C) 2015, 2016 Mathieu Lirzin + Copyright (C) 2003, 2005, 2006, 2014 Dale Mellor + + Copying and distribution of this file, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. + + + +Maybe in the near future... + + * Logging. + + * Check POSIX compliance (should be okay if Vixie cron was okay). + + * Work out how to give each user his own closure (or environment or module + or continuation) for his configuration files so that he can't mess the + core or other users' files up. Then allow scheme code in the system + crontabs. + + * Provide a test suite using SRFI-64 API. + . + + * Internationalize Mcron using GNU Gettext and ask the Translation + Project to handle the localization. + + +There are no plans to actually do the following any time soon... + + * Develop at and batch modes of operation. + + * Make compatibilities with other crons (BSD, SYSV, Solaris, Dillon's, ...) + + * Port to BSD, other operating systems. + + * Full security audit for Vixie mode. + + + +May happen if version 2.0 ever materializes... + + * UNIX or TCP socket will allow interrogation and control of a running + daemon (unrelated to, or maybe a major enhancement of, socket used for + communication from crontab process). + + * Add anacron functionality (run missed jobs if the daemon is stopped, for + example if a personal computer does not run 24 hours a day). + + * TCP socket to allow control via HTTP (web browser interface). Or maybe + crontab-like CGI personality. + + * GTK+/d-bus/Gnome3 interface. diff --git a/bootstrap b/bootstrap new file mode 100755 index 0000000..8d2758f --- /dev/null +++ b/bootstrap @@ -0,0 +1,5 @@ +#!/bin/sh +# Initialize the build system. + +set -e -x +exec autoreconf -vfi diff --git a/build-aux/announce-gen b/build-aux/announce-gen new file mode 100644 index 0000000..eeb9071 --- /dev/null +++ b/build-aux/announce-gen @@ -0,0 +1,557 @@ +eval '(exit $?0)' && eval 'exec perl -wS "$0" "$@"' + & eval 'exec perl -wS "$0" $argv:q' + if 0; +# Generate a release announcement message. + +my $VERSION = '2018-03-07 03:46'; # UTC +# The definition above must lie within the first 8 lines in order +# for the Emacs time-stamp write hook (at end) to update it. +# If you change this file with Emacs, please let the write hook +# do its job. Otherwise, update this string manually. + +# Copyright (C) 2002-2018 Free Software Foundation, Inc. + +# 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 3 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, see . + +# Written by Jim Meyering + +use strict; + +use Getopt::Long; +use POSIX qw(strftime); + +(my $ME = $0) =~ s|.*/||; + +my %valid_release_types = map {$_ => 1} qw (alpha beta stable); +my @archive_suffixes = ('tar.gz', 'tar.bz2', 'tar.lzma', 'tar.xz'); +my %digest_classes = + ( + 'md5' => (eval { require Digest::MD5; } and 'Digest::MD5'), + 'sha1' => ((eval { require Digest::SHA; } and 'Digest::SHA') + or (eval { require Digest::SHA1; } and 'Digest::SHA1')) + ); +my $srcdir = '.'; + +sub usage ($) +{ + my ($exit_code) = @_; + my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR); + if ($exit_code != 0) + { + print $STREAM "Try '$ME --help' for more information.\n"; + } + else + { + my @types = sort keys %valid_release_types; + print $STREAM < = C + +Compute the sizes of the C<@file> and return them as a hash. Return +C if one of the computation failed. + +=cut + +sub sizes (@) +{ + my (@file) = @_; + + my $fail = 0; + my %res; + foreach my $f (@file) + { + my $cmd = "du -h $f"; + my $t = `$cmd`; + # FIXME-someday: give a better diagnostic, a la $PROCESS_STATUS + $@ + and (warn "command failed: '$cmd'\n"), $fail = 1; + chomp $t; + $t =~ s/^\s*([\d.]+[MkK]).*/${1}B/; + $res{$f} = $t; + } + return $fail ? undef : %res; +} + +=item C dedicated to the list of <@file>, which +sizes are stored in C<%size>, and which are available from the C<@url>. + +=cut + +sub print_locations ($\@\%@) +{ + my ($title, $url, $size, @file) = @_; + print "Here are the $title:\n"; + foreach my $url (@{$url}) + { + for my $file (@file) + { + print " $url/$file"; + print " (", $$size{$file}, ")" + if exists $$size{$file}; + print "\n"; + } + } + print "\n"; +} + +=item C. + +=cut + +sub print_checksums (@) +{ + my (@file) = @_; + + print "Here are the MD5 and SHA1 checksums:\n"; + print "\n"; + + foreach my $meth (qw (md5 sha1)) + { + my $class = $digest_classes{$meth} or next; + foreach my $f (@file) + { + open IN, '<', $f + or die "$ME: $f: cannot open for reading: $!\n"; + binmode IN; + my $dig = $class->new->addfile(*IN)->hexdigest; + close IN; + print "$dig $f\n"; + } + } + print "\n"; +} + +=item C addressing changes +between versions C<$prev_version> and C<$curr_version>. + +=cut + +sub print_news_deltas ($$$) +{ + my ($news_file, $prev_version, $curr_version) = @_; + + my $news_name = $news_file; + $news_name =~ s|^\Q$srcdir\E/||; + + print "\n$news_name\n\n"; + + # Print all lines from $news_file, starting with the first one + # that mentions $curr_version up to but not including + # the first occurrence of $prev_version. + my $in_items; + + my $re_prefix = qr/(?:\* )?(?:Noteworthy c|Major c|C)(?i:hanges)/; + + my $found_news; + open NEWS, '<', $news_file + or die "$ME: $news_file: cannot open for reading: $!\n"; + while (defined (my $line = )) + { + if ( ! $in_items) + { + # Match lines like these: + # * Major changes in release 5.0.1: + # * Noteworthy changes in release 6.6 (2006-11-22) [stable] + $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$curr_version\E(?:[^\d.]|$)/o + or next; + $in_items = 1; + print $line; + } + else + { + # This regexp must not match version numbers in NEWS items. + # For example, they might well say "introduced in 4.5.5", + # and we don't want that to match. + $line =~ /^$re_prefix.*(?:[^\d.]|$)\Q$prev_version\E(?:[^\d.]|$)/o + and last; + print $line; + $line =~ /\S/ + and $found_news = 1; + } + } + close NEWS; + + $in_items + or die "$ME: $news_file: no matching lines for '$curr_version'\n"; + $found_news + or die "$ME: $news_file: no news item found for '$curr_version'\n"; +} + +sub print_changelog_deltas ($$) +{ + my ($package_name, $prev_version) = @_; + + # Print new ChangeLog entries. + + # First find all CVS-controlled ChangeLog files. + use File::Find; + my @changelog; + find ({wanted => sub {$_ eq 'ChangeLog' && -d 'CVS' + and push @changelog, $File::Find::name}}, + '.'); + + # If there are no ChangeLog files, we're done. + @changelog + or return; + my %changelog = map {$_ => 1} @changelog; + + # Reorder the list of files so that if there are ChangeLog + # files in the specified directories, they're listed first, + # in this order: + my @dir = qw ( . src lib m4 config doc ); + + # A typical @changelog array might look like this: + # ./ChangeLog + # ./po/ChangeLog + # ./m4/ChangeLog + # ./lib/ChangeLog + # ./doc/ChangeLog + # ./config/ChangeLog + my @reordered; + foreach my $d (@dir) + { + my $dot_slash = $d eq '.' ? $d : "./$d"; + my $target = "$dot_slash/ChangeLog"; + delete $changelog{$target} + and push @reordered, $target; + } + + # Append any remaining ChangeLog files. + push @reordered, sort keys %changelog; + + # Remove leading './'. + @reordered = map { s!^\./!!; $_ } @reordered; + + print "\nChangeLog entries:\n\n"; + # print join ("\n", @reordered), "\n"; + + $prev_version =~ s/\./_/g; + my $prev_cvs_tag = "\U$package_name\E-$prev_version"; + + my $cmd = "cvs -n diff -u -r$prev_cvs_tag -rHEAD @reordered"; + open DIFF, '-|', $cmd + or die "$ME: cannot run '$cmd': $!\n"; + # Print two types of lines, making minor changes: + # Lines starting with '+++ ', e.g., + # +++ ChangeLog 22 Feb 2003 16:52:51 -0000 1.247 + # and those starting with '+'. + # Don't print the others. + my $prev_printed_line_empty = 1; + while (defined (my $line = )) + { + if ($line =~ /^\+\+\+ /) + { + my $separator = "*"x70 ."\n"; + $line =~ s///; + $line =~ s/\s.*//; + $prev_printed_line_empty + or print "\n"; + print $separator, $line, $separator; + } + elsif ($line =~ /^\+/) + { + $line =~ s///; + print $line; + $prev_printed_line_empty = ($line =~ /^$/); + } + } + close DIFF; + + # The exit code should be 1. + # Allow in case there are no modified ChangeLog entries. + $? == 256 || $? == 128 + or warn "warning: '$cmd' had unexpected exit code or signal ($?)\n"; +} + +sub get_tool_versions ($$) +{ + my ($tool_list, $gnulib_version) = @_; + @$tool_list + or return (); + + my $fail; + my @tool_version_pair; + foreach my $t (@$tool_list) + { + if ($t eq 'gnulib') + { + push @tool_version_pair, ucfirst $t . ' ' . $gnulib_version; + next; + } + # Assume that the last "word" on the first line of + # 'tool --version' output is the version string. + my ($first_line, undef) = split ("\n", `$t --version`); + if ($first_line =~ /.* (\d[\w.-]+)$/) + { + $t = ucfirst $t; + push @tool_version_pair, "$t $1"; + } + else + { + defined $first_line + and $first_line = ''; + warn "$t: unexpected --version output\n:$first_line"; + $fail = 1; + } + } + + $fail + and exit 1; + + return @tool_version_pair; +} + +{ + # Neutralize the locale, so that, for instance, "du" does not + # issue "1,2" instead of "1.2", what confuses our regexps. + $ENV{LC_ALL} = "C"; + + my $mail_headers; + my $release_type; + my $package_name; + my $prev_version; + my $curr_version; + my $gpg_key_id; + my @url_dir_list; + my @news_file; + my $bootstrap_tools; + my $gnulib_version; + my $print_checksums_p = 1; + + # Reformat the warnings before displaying them. + local $SIG{__WARN__} = sub + { + my ($msg) = @_; + # Warnings from GetOptions. + $msg =~ s/Option (\w)/option --$1/; + warn "$ME: $msg"; + }; + + GetOptions + ( + 'mail-headers=s' => \$mail_headers, + 'release-type=s' => \$release_type, + 'package-name=s' => \$package_name, + 'previous-version=s' => \$prev_version, + 'current-version=s' => \$curr_version, + 'gpg-key-id=s' => \$gpg_key_id, + 'url-directory=s' => \@url_dir_list, + 'news=s' => \@news_file, + 'srcdir=s' => \$srcdir, + 'bootstrap-tools=s' => \$bootstrap_tools, + 'gnulib-version=s' => \$gnulib_version, + 'print-checksums!' => \$print_checksums_p, + 'archive-suffix=s' => \@archive_suffixes, + + help => sub { usage 0 }, + version => sub { print "$ME version $VERSION\n"; exit }, + ) or usage 1; + + my $fail = 0; + # Ensure that each required option is specified. + $release_type + or (warn "release type not specified\n"), $fail = 1; + $package_name + or (warn "package name not specified\n"), $fail = 1; + $prev_version + or (warn "previous version string not specified\n"), $fail = 1; + $curr_version + or (warn "current version string not specified\n"), $fail = 1; + $gpg_key_id + or (warn "GnuPG key ID not specified\n"), $fail = 1; + @url_dir_list + or (warn "URL directory name(s) not specified\n"), $fail = 1; + + my @tool_list = split ',', $bootstrap_tools + if $bootstrap_tools; + + grep (/^gnulib$/, @tool_list) ^ defined $gnulib_version + and (warn "when specifying gnulib as a tool, you must also specify\n" + . "--gnulib-version=V, where V is the result of running git describe\n" + . "in the gnulib source directory.\n"), $fail = 1; + + !$release_type || exists $valid_release_types{$release_type} + or (warn "'$release_type': invalid release type\n"), $fail = 1; + + @ARGV + and (warn "too many arguments:\n", join ("\n", @ARGV), "\n"), + $fail = 1; + $fail + and usage 1; + + my $my_distdir = "$package_name-$curr_version"; + + my $xd = "$package_name-$prev_version-$curr_version.xdelta"; + + my @candidates = map { "$my_distdir.$_" } @archive_suffixes; + my @tarballs = grep {-f $_} @candidates; + + @tarballs + or die "$ME: none of " . join(', ', @candidates) . " were found\n"; + my @sizable = @tarballs; + -f $xd + and push @sizable, $xd; + my %size = sizes (@sizable); + %size + or exit 1; + + my $headers = ''; + if (defined $mail_headers) + { + ($headers = $mail_headers) =~ s/\s+(\S+:)/\n$1/g; + $headers .= "\n"; + } + + # The markup is escaped as <\# so that when this script is sent by + # mail (or part of a diff), Gnus is not triggered. + print < + +FIXME: put comments here + +EOF + + if (@url_dir_list == 1 && @tarballs == 1) + { + # When there's only one tarball and one URL, use a more concise form. + my $m = "$url_dir_list[0]/$tarballs[0]"; + print "Here are the compressed sources and a GPG detached signature[*]:\n" + . " $m\n" + . " $m.sig\n\n"; + } + else + { + print_locations ("compressed sources", @url_dir_list, %size, @tarballs); + -f $xd + and print_locations ("xdelta diffs (useful? if so, " + . "please tell bug-gnulib\@gnu.org)", + @url_dir_list, %size, $xd); + my @sig_files = map { "$_.sig" } @tarballs; + print_locations ("GPG detached signatures[*]", @url_dir_list, %size, + @sig_files); + } + + if ($url_dir_list[0] =~ "gnu\.org") + { + print "Use a mirror for higher download bandwidth:\n"; + if (@tarballs == 1 && $url_dir_list[0] =~ m!https://ftp\.gnu\.org/gnu/!) + { + (my $m = "$url_dir_list[0]/$tarballs[0]") + =~ s!https://ftp\.gnu\.org/gnu/!https://ftpmirror\.gnu\.org/!; + print " $m\n" + . " $m.sig\n\n"; + + } + else + { + print " https://www.gnu.org/order/ftp.html\n\n"; + } + } + + $print_checksums_p + and print_checksums (@sizable); + + print <. + +# Written by Jim Meyering + +ME=$(basename "$0") +warn() { printf '%s: %s\n' "$ME" "$*" >&2; } +die() { warn "$*"; exit 1; } + +help() +{ + cat <. +EOF + exit +} + +version() +{ + year=$(echo "$VERSION" | sed 's/[^0-9].*//') + cat < +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. +EOF + exit +} + +## ------ ## +## Main. ## +## ------ ## + +# Constants. +noteworthy='* Noteworthy changes in release' +noteworthy_stub="$noteworthy ?.? (????-??-??) [?]" + +# Variables. +branch=$(git branch | sed -ne '/^\* /{s///;p;q;}') +builddir=. + +while test $# != 0 +do + # Handle --option=value by splitting apart and putting back on argv. + case $1 in + --*=*) + opt=$(echo "$1" | sed -e 's/=.*//') + val=$(echo "$1" | sed -e 's/[^=]*=//') + shift + set dummy "$opt" "$val" "$@"; shift + ;; + esac + + case $1 in + --help|--version) ${1#--};; + --branch) shift; branch=$1; shift ;; + -C|--builddir) shift; builddir=$1; shift ;; + --*) die "unrecognized option: $1";; + *) break;; + esac +done + +test $# = 2 \ + || die "Usage: $ME [OPTION...] VERSION TYPE" + +ver=$1 +type=$2 + + +## ---------------------- ## +## First, sanity checks. ## +## ---------------------- ## + +# Verify that $ver looks like a version number, and... +echo "$ver"|grep -E '^[0-9][0-9.]*[0-9]$' > /dev/null \ + || die "invalid version: $ver" +prev_ver=$(cat .prev-version) \ + || die 'failed to determine previous version number from .prev-version' + +# Verify that $ver is sensible (> .prev-version). +case $(printf "$prev_ver\n$ver\n"|sort -V -u|tr '\n' ':') in + "$prev_ver:$ver:") ;; + *) die "invalid version: $ver (<= $prev_ver)";; +esac + +case $type in + alpha|beta|stable) ;; + *) die "invalid release type: $type";; +esac + +# No local modifications allowed. +case $(git diff-index --name-only HEAD) in + '') ;; + *) die 'this tree is dirty; commit your changes first';; +esac + +# Ensure the current branch name is correct: +curr_br=$(git rev-parse --symbolic-full-name HEAD) +test "$curr_br" = "refs/heads/$branch" || die not on branch $branch + +# Extract package name from Makefile. +Makefile=$builddir/Makefile +pkg=$(sed -n 's/^PACKAGE = \(.*\)/\1/p' "$Makefile") \ + || die "failed to determine package name from $Makefile" + +# Check that line 3 of NEWS is the stub line about to be replaced. +test "$(sed -n 3p NEWS)" = "$noteworthy_stub" \ + || die "line 3 of NEWS must be exactly '$noteworthy_stub'" + +## --------------- ## +## Then, changes. ## +## --------------- ## + +# Update NEWS to have today's date, plus desired version number and $type. +perl -MPOSIX -ni -e 'my $today = strftime "%F", localtime time;' \ + -e 'my ($type, $ver) = qw('"$type $ver"');' \ + -e 'my $pfx = "'"$noteworthy"'";' \ + -e 'print $.==3 ? "$pfx $ver ($today) [$type]\n" : $_' \ + NEWS || die 'failed to update NEWS' + +printf "version $ver\n\n* NEWS: Record release date.\n" \ + | git commit -F - -a || die 'git commit failed' +git tag -s -m "$pkg $ver" v$ver HEAD || die 'git tag failed' + +# Local variables: +# indent-tabs-mode: nil +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "VERSION=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: " # UTC" +# End: diff --git a/build-aux/gnu-web-doc-update b/build-aux/gnu-web-doc-update new file mode 100644 index 0000000..356c561 --- /dev/null +++ b/build-aux/gnu-web-doc-update @@ -0,0 +1,210 @@ +#!/bin/sh +# Run this after each non-alpha release, to update the web documentation at +# https://www.gnu.org/software/$pkg/manual/ + +VERSION=2018-03-07.03; # UTC + +# Copyright (C) 2009-2018 Free Software Foundation, Inc. + +# 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 3 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, see . + +ME=$(basename "$0") +warn() { printf '%s: %s\n' "$ME" "$*" >&2; } +die() { warn "$*"; exit 1; } + +help() +{ + cat <. +EOF + exit +} + +version() +{ + year=$(echo "$VERSION" | sed 's/[^0-9].*//') + cat < +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. +EOF + exit +} + +# find_tool ENVVAR NAMES... +# ------------------------- +# Search for a required program. Use the value of ENVVAR, if set, +# otherwise find the first of the NAMES that can be run (i.e., +# supports --version). If found, set ENVVAR to the program name, +# die otherwise. +# +# FIXME: code duplication, see also bootstrap. +find_tool () +{ + find_tool_envvar=$1 + shift + find_tool_names=$@ + eval "find_tool_res=\$$find_tool_envvar" + if test x"$find_tool_res" = x; then + for i + do + if ($i --version /dev/null 2>&1; then + find_tool_res=$i + break + fi + done + else + find_tool_error_prefix="\$$find_tool_envvar: " + fi + test x"$find_tool_res" != x \ + || die "one of these is required: $find_tool_names" + ($find_tool_res --version /dev/null 2>&1 \ + || die "${find_tool_error_prefix}cannot run $find_tool_res --version" + eval "$find_tool_envvar=\$find_tool_res" + eval "export $find_tool_envvar" +} + +## ------ ## +## Main. ## +## ------ ## + +# Requirements: everything required to bootstrap your package, plus +# these. +find_tool CVS cvs +find_tool GIT git +find_tool RSYNC rsync +find_tool XARGS gxargs xargs + +builddir=. +dryrun= +rm_stale='echo' +while test $# != 0 +do + # Handle --option=value by splitting apart and putting back on argv. + case $1 in + --*=*) + opt=$(echo "$1" | sed -e 's/=.*//') + val=$(echo "$1" | sed -e 's/[^=]*=//') + shift + set dummy "$opt" "$val" "$@"; shift + ;; + esac + + case $1 in + --help|--version) ${1#--};; + -C|--builddir) shift; builddir=$1; shift ;; + -n|--dry-run) dryrun=echo; shift;; + -m|--mirror) rm_stale=''; shift;; + --*) die "unrecognized option: $1";; + *) break;; + esac +done + +test $# = 0 \ + || die "too many arguments" + +prev=.prev-version +version=$(cat $prev) || die "no $prev file?" +pkg=$(sed -n 's/^PACKAGE = \(.*\)/\1/p' $builddir/Makefile) \ + || die "no Makefile?" +tmp_branch=web-doc-$version-$$ +current_branch=$($GIT branch | sed -ne '/^\* /{s///;p;q;}') + +cleanup() +{ + __st=$? + $dryrun rm -rf "$tmp" + $GIT checkout "$current_branch" + $GIT submodule update --recursive + $GIT branch -d $tmp_branch + exit $__st +} +trap cleanup 0 +trap 'exit $?' 1 2 13 15 + +# We must build using sources for which --version reports the +# just-released version number, not some string like 7.6.18-20761. +# That version string propagates into all documentation. +set -e +$GIT checkout -b $tmp_branch v$version +$GIT submodule update --recursive +./bootstrap +srcdir=$(pwd) +cd "$builddir" +builddir=$(pwd) + ./config.status --recheck + ./config.status + make + make web-manual +cd "$srcdir" +set +e + +tmp=$(mktemp -d web-doc-update.XXXXXX) || exit 1 +( cd $tmp \ + && $CVS -d $USER@cvs.sv.gnu.org:/webcvs/$pkg co $pkg ) +$RSYNC -avP "$builddir"/doc/manual/ $tmp/$pkg/manual + +( + cd $tmp/$pkg/manual + + # Add all the files. This is simpler than trying to add only the + # new ones because of new directories + # First add non empty dirs individually + find . -name CVS -prune -o -type d \! -empty -print \ + | $XARGS -n1 --no-run-if-empty -- $dryrun $CVS add -ko + # Now add all files + find . -name CVS -prune -o -type f -print \ + | $XARGS --no-run-if-empty -- $dryrun $CVS add -ko + + # Report/Remove stale files + # excluding doc server specific files like CVS/* and .symlinks + if test -n "$rm_stale"; then + echo 'Consider the --mirror option if all of the manual is generated,' >&2 + echo 'which will run `cvs remove` to remove stale files.' >&2 + fi + { find . \( -name CVS -o -type f -name '.*' \) -prune -o -type f -print + (cd "$builddir"/doc/manual/ && find . -type f -print | sed p) + } | sort | uniq -u \ + | $XARGS --no-run-if-empty -- ${rm_stale:-$dryrun} $CVS remove -f + + $dryrun $CVS ci -m $version +) + +# Local variables: +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "VERSION=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/build-aux/gnupload b/build-aux/gnupload new file mode 100644 index 0000000..2a0bfa3 --- /dev/null +++ b/build-aux/gnupload @@ -0,0 +1,440 @@ +#!/bin/sh +# Sign files and upload them. + +scriptversion=2018-03-07.03; # UTC + +# Copyright (C) 2004-2018 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Originally written by Alexandre Duret-Lutz . +# The master copy of this file is maintained in the gnulib Git repository. +# Please send bug reports and feature requests to bug-gnulib@gnu.org. + +set -e + +GPG='gpg --batch --no-tty' +conffile=.gnuploadrc +to= +dry_run=false +replace= +symlink_files= +delete_files= +delete_symlinks= +collect_var= +dbg= +nl=' +' + +usage="Usage: $0 [OPTION]... [CMD] FILE... [[CMD] FILE...] + +Sign all FILES, and process them at the destinations specified with --to. +If CMD is not given, it defaults to uploading. See examples below. + +Commands: + --delete delete FILES from destination + --symlink create symbolic links + --rmsymlink remove symbolic links + -- treat the remaining arguments as files to upload + +Options: + --to DEST specify a destination DEST for FILES + (multiple --to options are allowed) + --user NAME sign with key NAME + --replace allow replacements of existing files + --symlink-regex[=EXPR] use sed script EXPR to compute symbolic link names + --dry-run do nothing, show what would have been done + (including the constructed directive file) + --version output version information and exit + --help print this help text and exit + +If --symlink-regex is given without EXPR, then the link target name +is created by replacing the version information with '-latest', e.g.: + foo-1.3.4.tar.gz -> foo-latest.tar.gz + +Recognized destinations are: + alpha.gnu.org:DIRECTORY + savannah.gnu.org:DIRECTORY + savannah.nongnu.org:DIRECTORY + ftp.gnu.org:DIRECTORY + build directive files and upload files by FTP + download.gnu.org.ua:{alpha|ftp}/DIRECTORY + build directive files and upload files by SFTP + [user@]host:DIRECTORY upload files with scp + +Options and commands are applied in order. If the file $conffile exists +in the current working directory, its contents are prepended to the +actual command line options. Use this to keep your defaults. Comments +(#) and empty lines in $conffile are allowed. + + +gives some further background. + +Examples: +1. Upload foobar-1.0.tar.gz to ftp.gnu.org: + gnupload --to ftp.gnu.org:foobar foobar-1.0.tar.gz + +2. Upload foobar-1.0.tar.gz and foobar-1.0.tar.xz to ftp.gnu.org: + gnupload --to ftp.gnu.org:foobar foobar-1.0.tar.gz foobar-1.0.tar.xz + +3. Same as above, and also create symbolic links to foobar-latest.tar.*: + gnupload --to ftp.gnu.org:foobar \\ + --symlink-regex \\ + foobar-1.0.tar.gz foobar-1.0.tar.xz + +4. Upload foobar-0.9.90.tar.gz to two sites: + gnupload --to alpha.gnu.org:foobar \\ + --to sources.redhat.com:~ftp/pub/foobar \\ + foobar-0.9.90.tar.gz + +5. Delete oopsbar-0.9.91.tar.gz and upload foobar-0.9.91.tar.gz + (the -- terminates the list of files to delete): + gnupload --to alpha.gnu.org:foobar \\ + --to sources.redhat.com:~ftp/pub/foobar \\ + --delete oopsbar-0.9.91.tar.gz \\ + -- foobar-0.9.91.tar.gz + +gnupload executes a program ncftpput to do the transfers; if you don't +happen to have an ncftp package installed, the ncftpput-ftp script in +the build-aux/ directory of the gnulib package +(https://savannah.gnu.org/projects/gnulib) may serve as a replacement. + +Send patches and bug reports to ." + +# Read local configuration file +if test -r "$conffile"; then + echo "$0: Reading configuration file $conffile" + conf=`sed 's/#.*$//;/^$/d' "$conffile" | tr "\015$nl" ' '` + eval set x "$conf \"\$@\"" + shift +fi + +while test -n "$1"; do + case $1 in + -*) + collect_var= + case $1 in + --help) + echo "$usage" + exit $? + ;; + --to) + if test -z "$2"; then + echo "$0: Missing argument for --to" 1>&2 + exit 1 + elif echo "$2" | grep 'ftp-upload\.gnu\.org' >/dev/null; then + echo "$0: Use ftp.gnu.org:PKGNAME or alpha.gnu.org:PKGNAME" >&2 + echo "$0: for the destination, not ftp-upload.gnu.org (which" >&2 + echo "$0: is used for direct ftp uploads, not with gnupload)." >&2 + echo "$0: See --help and its examples if need be." >&2 + exit 1 + else + to="$to $2" + shift + fi + ;; + --user) + if test -z "$2"; then + echo "$0: Missing argument for --user" 1>&2 + exit 1 + else + GPG="$GPG --local-user $2" + shift + fi + ;; + --delete) + collect_var=delete_files + ;; + --replace) + replace="replace: true" + ;; + --rmsymlink) + collect_var=delete_symlinks + ;; + --symlink-regex=*) + symlink_expr=`expr "$1" : '[^=]*=\(.*\)'` + ;; + --symlink-regex) + symlink_expr='s|-[0-9][0-9\.]*\(-[0-9][0-9]*\)\{0,1\}\.|-latest.|' + ;; + --symlink) + collect_var=symlink_files + ;; + --dry-run|-n) + dry_run=: + ;; + --version) + echo "gnupload $scriptversion" + exit $? + ;; + --) + shift + break + ;; + -*) + echo "$0: Unknown option '$1', try '$0 --help'" 1>&2 + exit 1 + ;; + esac + ;; + *) + if test -z "$collect_var"; then + break + else + eval "$collect_var=\"\$$collect_var $1\"" + fi + ;; + esac + shift +done + +dprint() +{ + echo "Running $* ..." +} + +if $dry_run; then + dbg=dprint +fi + +if test -z "$to"; then + echo "$0: Missing destination sites" >&2 + exit 1 +fi + +if test -n "$symlink_files"; then + x=`echo "$symlink_files" | sed 's/[^ ]//g;s/ //g'` + if test -n "$x"; then + echo "$0: Odd number of symlink arguments" >&2 + exit 1 + fi +fi + +if test $# = 0; then + if test -z "${symlink_files}${delete_files}${delete_symlinks}"; then + echo "$0: No file to upload" 1>&2 + exit 1 + fi +else + # Make sure all files exist. We don't want to ask + # for the passphrase if the script will fail. + for file + do + if test ! -f $file; then + echo "$0: Cannot find '$file'" 1>&2 + exit 1 + elif test -n "$symlink_expr"; then + linkname=`echo $file | sed "$symlink_expr"` + if test -z "$linkname"; then + echo "$0: symlink expression produces empty results" >&2 + exit 1 + elif test "$linkname" = $file; then + echo "$0: symlink expression does not alter file name" >&2 + exit 1 + fi + fi + done +fi + +# Make sure passphrase is not exported in the environment. +unset passphrase +unset passphrase_fd_0 +GNUPGHOME=${GNUPGHOME:-$HOME/.gnupg} + +# Reset PATH to be sure that echo is a built-in. We will later use +# 'echo $passphrase' to output the passphrase, so it is important that +# it is a built-in (third-party programs tend to appear in 'ps' +# listings with their arguments...). +# Remember this script runs with 'set -e', so if echo is not built-in +# it will exit now. +if $dry_run || grep -q "^use-agent" $GNUPGHOME/gpg.conf; then :; else + PATH=/empty echo -n "Enter GPG passphrase: " + stty -echo + read -r passphrase + stty echo + echo + passphrase_fd_0="--passphrase-fd 0" +fi + +if test $# -ne 0; then + for file + do + echo "Signing $file ..." + rm -f $file.sig + echo "$passphrase" | $dbg $GPG $passphrase_fd_0 -ba -o $file.sig $file + done +fi + + +# mkdirective DESTDIR BASE FILE STMT +# Arguments: See upload, below +mkdirective () +{ + stmt="$4" + if test -n "$3"; then + stmt=" +filename: $3$stmt" + fi + + cat >${2}.directive<&2 + fi + $dbg ncftpput savannah.gnu.org /incoming/savannah/$destdir $files + ;; + savannah.nongnu.org:*) + if test -z "$files"; then + echo "$0: warning: standalone directives not applicable for $dest" >&2 + fi + $dbg ncftpput savannah.nongnu.org /incoming/savannah/$destdir $files + ;; + download.gnu.org.ua:alpha/*|download.gnu.org.ua:ftp/*) + destdir_p1=`echo "$destdir" | sed 's,^[^/]*/,,'` + destdir_topdir=`echo "$destdir" | sed 's,/.*,,'` + mkdirective "$destdir_p1" "$base" "$file" "$stmt" + echo "$passphrase" | $dbg $GPG $passphrase_fd_0 --clearsign $base.directive + for f in $files $base.directive.asc + do + echo put $f + done | $dbg sftp -b - puszcza.gnu.org.ua:/incoming/$destdir_topdir + ;; + /*) + dest_host=`echo "$dest" | sed 's,:.*,,'` + mkdirective "$destdir" "$base" "$file" "$stmt" + echo "$passphrase" | $dbg $GPG $passphrase_fd_0 --clearsign $base.directive + $dbg cp $files $base.directive.asc $dest_host + ;; + *) + if test -z "$files"; then + echo "$0: warning: standalone directives not applicable for $dest" >&2 + fi + $dbg scp $files $dest + ;; + esac + rm -f $base.directive $base.directive.asc +} + +##### +# Process any standalone directives +stmt= +if test -n "$symlink_files"; then + stmt="$stmt +`mksymlink $symlink_files`" +fi + +for file in $delete_files +do + stmt="$stmt +archive: $file" +done + +for file in $delete_symlinks +do + stmt="$stmt +rmsymlink: $file" +done + +if test -n "$stmt"; then + for dest in $to + do + destdir=`echo $dest | sed 's/[^:]*://'` + upload "$dest" "$destdir" "`hostname`-$$" "" "$stmt" + done +fi + +# Process actual uploads +for dest in $to +do + for file + do + echo "Uploading $file to $dest ..." + stmt= + # + # allowing file replacement is all or nothing. + if test -n "$replace"; then stmt="$stmt +$replace" + fi + # + files="$file $file.sig" + destdir=`echo $dest | sed 's/[^:]*://'` + if test -n "$symlink_expr"; then + linkname=`echo $file | sed "$symlink_expr"` + stmt="$stmt +symlink: $file $linkname +symlink: $file.sig $linkname.sig" + fi + upload "$dest" "$destdir" "$file" "$file" "$stmt" "$files" + done +done + +exit 0 + +# Local variables: +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/build-aux/guix.scm b/build-aux/guix.scm new file mode 100644 index 0000000..25a281f --- /dev/null +++ b/build-aux/guix.scm @@ -0,0 +1,55 @@ +;;;; guix.scm -- Guix package definition +;;; Copyright © 2016 Mathieu Lirzin +;;; Copyright © 2018 宋文武 +;;; Copyright © 2019 Efraim Flashner +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +(use-modules (ice-9 popen) + (ice-9 rdelim) + (gnu) + (guix) + (srfi srfi-1)) + +(define (keep-mcron-file? file stat) + ;; Return #t if FILE in Mcron repository must be kept, #f otherwise. FILE + ;; is an absolute file name and STAT is the result of 'lstat' applied to + ;; FILE. + (not (or (any (λ (str) (string-contains file str)) + '(".git" "autom4te" "Makefile.in" ".go" ".log" + "stamp-vti" ".dirstamp")) + (any (λ (str) (string-suffix? str file)) + '("trs" "configure" "Makefile" "config.status" "pre-inst-env" + "aclocal.m4" "bin/cron" "bin/mcron" "bin/crontab" + "config.cache" "guix.scm"))))) + +(define %srcdir + (or (current-source-directory) ".")) + +(package + (inherit (specification->package "mcron")) + (version "1.2.0") + (source (local-file (dirname %srcdir) #:recursive? #t + #:select? keep-mcron-file?)) + (inputs + `(("guile" ,(specification->package "guile@2.2")))) + (native-inputs + `(("autoconf" ,(specification->package "autoconf")) + ("automake" ,(specification->package "automake")) + ("help2man" ,(specification->package "help2man")) + ("pkg-config" ,(specification->package "pkg-config")) + ("texinfo" ,(specification->package "texinfo")) + ("tzdata" ,(specification->package "tzdata"))))) diff --git a/build-aux/pre-inst-env.in b/build-aux/pre-inst-env.in new file mode 100644 index 0000000..aefe33e --- /dev/null +++ b/build-aux/pre-inst-env.in @@ -0,0 +1,38 @@ +#!/bin/sh + +# Copyright © 2015, 2016 Mathieu Lirzin +# +# This file is part of GNU Mcron. +# +# GNU Mcron is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GNU Mcron is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Mcron. If not, see . + +abs_top_srcdir="`cd "@abs_top_srcdir@" > /dev/null; pwd`" +abs_top_builddir="`cd "@abs_top_builddir@" > /dev/null; pwd`" + +GUILE_LOAD_COMPILED_PATH="$abs_top_builddir/src${GUILE_LOAD_COMPILED_PATH:+:}$GUILE_LOAD_COMPILED_PATH" +GUILE_LOAD_PATH="$abs_top_builddir/src:$abs_top_srcdir/src${GUILE_LOAD_PATH:+:}:$GUILE_LOAD_PATH" +export GUILE_LOAD_COMPILED_PATH GUILE_LOAD_PATH + +PATH="$abs_top_builddir/bin:$PATH" +export PATH + +# Define $MCRON_UNINSTALLED to prevent 'mcron' from prepending @moduledir@ to +# the Guile load paths. +MCRON_UNINSTALLED=1 +export MCRON_UNINSTALLED + +srcdir="@srcdir@" +export srcdir + +exec "$@" diff --git a/build-aux/test-driver.scm b/build-aux/test-driver.scm new file mode 100644 index 0000000..e264bea --- /dev/null +++ b/build-aux/test-driver.scm @@ -0,0 +1,232 @@ +;;;; test-driver.scm - Guile test driver for Automake testsuite harness + +(define script-version "2018-03-25.05") ;UTC + +;;; Copyright © 2015-2018 Free Software Foundation, Inc. +;;; +;;; 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 3 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, see . + +;;;; Commentary: +;;; +;;; This script provides a Guile test driver using the SRFI-64 Scheme API for +;;; test suites. SRFI-64 is distributed with Guile since version 2.0.9. +;;; +;;; To use it, you have to manually copy this file in the ‘build-aux’ +;;; directory of your package, then adapt the following snippets to your +;;; actual needs: +;;; +;;; configure.ac: +;;; AC_CONFIG_AUX_DIR([build-aux]) +;;; AC_REQUIRE_AUX_FILE([test-driver.scm]) +;;; AC_PATH_PROG([GUILE], [guile]) +;;; +;;; Makefile.am +;;; TEST_LOG_DRIVER = $(GUILE) $(top_srcdir)/build-aux/test-driver.scm +;;; AM_TESTS_ENVIRONMENT = env GUILE_AUTO_COMPILE='0' +;;; TESTS = foo.test +;;; EXTRA_DIST = $(TESTS) +;;; +;;; foo.test +;;; (use-modules (srfi srfi-64)) +;;; (test-begin "foo") +;;; (test-assert "assertion example" #t) +;;; (test-end "foo") +;;; +;;; See for general +;;; information about SRFI-64 usage. +;;; +;;;; Code: + +(use-modules (ice-9 getopt-long) + (ice-9 match) + (ice-9 pretty-print) + (srfi srfi-11) + (srfi srfi-26) + (srfi srfi-64) + (system vm coverage) + (system vm vm)) + +(define (show-help) + (display "Usage: + test-driver --test-name=NAME --log-file=PATH --trs-file=PATH + [--expect-failure={yes|no}] [--color-tests={yes|no}] + [--enable-hard-errors={yes|no}] [--brief={yes|no}}] + [--coverage={yes|no}] [--] + TEST-SCRIPT [TEST-SCRIPT-ARGUMENTS] +The '--test-name', '--log-file' and '--trs-file' options are mandatory.\n")) + +(define %options + '((test-name (value #t)) + (log-file (value #t)) + (trs-file (value #t)) + (color-tests (value #t)) + (expect-failure (value #t)) ;XXX: not implemented yet + (enable-hard-errors (value #t)) ;not implemented in SRFI-64 + (coverage (value #t)) + (brief (value #t)) + (help (single-char #\h) (value #f)) + (version (single-char #\V) (value #f)))) + +(define (option->boolean options key) + "Return #t if the value associated with KEY in OPTIONS is \"yes\"." + (and=> (option-ref options key #f) (cut string=? <> "yes"))) + +(define* (test-display field value #:optional (port (current-output-port)) + #:key pretty?) + "Display \"FIELD: VALUE\\n\" on PORT." + (if pretty? + (begin + (format port "~A:~%" field) + (pretty-print value port #:per-line-prefix "+ ")) + (format port "~A: ~S~%" field value))) + +(define* (result->string symbol #:key colorize?) + "Return SYMBOL as an upper case string. Use colors when COLORIZE is #t." + (let ((result (string-upcase (symbol->string symbol)))) + (if colorize? + (string-append (case symbol + ((pass) "") ;green + ((xfail) "") ;light green + ((skip) "") ;blue + ((fail xpass) "") ;red + ((error) "")) ;magenta + result + "") ;no color + result))) + +(define* (test-runner-gnu test-name #:key color? brief? out-port trs-port) + "Return an custom SRFI-64 test runner. TEST-NAME is a string specifying the +file name of the current the test. COLOR? specifies whether to use colors, +and BRIEF?, well, you know. OUT-PORT and TRS-PORT must be output ports. The +current output port is supposed to be redirected to a '.log' file." + + (define (test-on-test-begin-gnu runner) + ;; Procedure called at the start of an individual test case, before the + ;; test expression (and expected value) are evaluated. + (let ((result (cute assq-ref (test-result-alist runner) <>))) + (format #t "test-name: ~A~%" (result 'test-name)) + (format #t "location: ~A~%" + (string-append (result 'source-file) ":" + (number->string (result 'source-line)))) + (test-display "source" (result 'source-form) #:pretty? #t))) + + (define (test-on-test-end-gnu runner) + ;; Procedure called at the end of an individual test case, when the result + ;; of the test is available. + (let* ((results (test-result-alist runner)) + (result? (cut assq <> results)) + (result (cut assq-ref results <>))) + (unless brief? + ;; Display the result of each test case on the console. + (format out-port "~A: ~A - ~A~%" + (result->string (test-result-kind runner) #:colorize? color?) + test-name (test-runner-test-name runner))) + (when (result? 'expected-value) + (test-display "expected-value" (result 'expected-value))) + (when (result? 'expected-error) + (test-display "expected-error" (result 'expected-error) #:pretty? #t)) + (when (result? 'actual-value) + (test-display "actual-value" (result 'actual-value))) + (when (result? 'actual-error) + (test-display "actual-error" (result 'actual-error) #:pretty? #t)) + (format #t "result: ~a~%" (result->string (result 'result-kind))) + (newline) + (format trs-port ":test-result: ~A ~A~%" + (result->string (test-result-kind runner)) + (test-runner-test-name runner)))) + + (define (test-on-group-end-gnu runner) + ;; Procedure called by a 'test-end', including at the end of a test-group. + (let ((fail (or (positive? (test-runner-fail-count runner)) + (positive? (test-runner-xpass-count runner)))) + (skip (or (positive? (test-runner-skip-count runner)) + (positive? (test-runner-xfail-count runner))))) + ;; XXX: The global results need some refinements for XPASS. + (format trs-port ":global-test-result: ~A~%" + (if fail "FAIL" (if skip "SKIP" "PASS"))) + (format trs-port ":recheck: ~A~%" + (if fail "yes" "no")) + (format trs-port ":copy-in-global-log: ~A~%" + (if (or fail skip) "yes" "no")) + (when brief? + ;; Display the global test group result on the console. + (format out-port "~A: ~A~%" + (result->string (if fail 'fail (if skip 'skip 'pass)) + #:colorize? color?) + test-name)) + #f)) + + (let ((runner (test-runner-null))) + (test-runner-on-test-begin! runner test-on-test-begin-gnu) + (test-runner-on-test-end! runner test-on-test-end-gnu) + (test-runner-on-group-end! runner test-on-group-end-gnu) + (test-runner-on-bad-end-name! runner test-on-bad-end-name-simple) + runner)) + + +;;; +;;; Entry point. +;;; + +(let* ((opts (getopt-long (command-line) %options)) + (option (cut option-ref opts <> <>))) + (cond + ((option 'help #f) (show-help)) + ((option 'version #f) (format #t "test-driver.scm ~A" script-version)) + (else + (match (option '() '()) + (() + (display "missing test script argument\n" (current-error-port)) + (exit 1)) + ((script . args) + (let ((log (open-file (option 'log-file "") "w0")) + (trs (open-file (option 'trs-file "") "wl")) + (out (duplicate-port (current-output-port) "wl"))) + (define (check) + (test-with-runner + (test-runner-gnu (option 'test-name #f) + #:color? (option->boolean opts 'color-tests) + #:brief? (option->boolean opts 'brief) + #:out-port out #:trs-port trs) + (primitive-load script))) + + (redirect-port log (current-output-port)) + (redirect-port log (current-warning-port)) + (redirect-port log (current-error-port)) + + (if (not (option->boolean opts 'coverage)) + (check) + (begin + ;; The debug engine is required for tracing coverage data. + (set-vm-engine! 'debug) + (let-values (((data result) (with-code-coverage check))) + (let* ((file (string-append (option 'test-name #f) ".info")) + (port (open-output-file file))) + (coverage-data->lcov data port) + (close port))))) + + (close-port log) + (close-port trs) + (close-port out)))))) + (exit 0)) + +;;; Local Variables: +;;; eval: (add-hook 'before-save-hook 'time-stamp) +;;; time-stamp-start: "(define script-version \"" +;;; time-stamp-format: "%:y-%02m-%02d.%02H" +;;; time-stamp-time-zone: "UTC0" +;;; time-stamp-end: "\") ;UTC" +;;; End: + +;;;; test-driver.scm ends here. diff --git a/configure.ac b/configure.ac new file mode 100755 index 0000000..8948c9b --- /dev/null +++ b/configure.ac @@ -0,0 +1,135 @@ +## Process this file with autoconf to produce a configure script. +# +# Copyright © 2003, 2005, 2012, 2014 Dale Mellor +# Copyright © 2015, 2016, 2017, 2018, 2020 Mathieu Lirzin +# Copyright © 2018 宋文武 +# +# This file is part of GNU Mcron. +# +# GNU Mcron is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GNU Mcron is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Mcron. If not, see . + +AC_PREREQ(2.61) +AC_INIT([GNU Mcron], [1.2.0+dmbcs], [bug-mcron@gnu.org]) +AC_CONFIG_SRCDIR([src/mcron/scripts/mcron.scm]) +AC_CONFIG_AUX_DIR([build-aux]) +AC_REQUIRE_AUX_FILE([test-driver.scm]) + +dnl We're fine with GNU make constructs, hence '-Wno-portability'. +AM_INIT_AUTOMAKE([1.11 gnu silent-rules subdir-objects color-tests + -Wall -Wno-override -Wno-portability std-options]) + +AM_SILENT_RULES([yes]) # Enables silent rules by default. + +AC_CANONICAL_HOST + +dnl We require pkg.m4 (from pkg-config) and guile.m4 (from Guile.) +dnl Make sure they are available when generating the configure script. +m4_pattern_forbid([^PKG_PROG]) +m4_pattern_forbid([^PKG_CHECK]) +m4_pattern_forbid([^GUILE_P]) +m4_pattern_allow([^GUILE_PKG_ERRORS]) + +# Check for Guile development files. +GUILE_PKG([3.0 2.2 2.0]) + +# Checks for programs. +GUILE_PROGS + +AM_MISSING_PROG(HELP2MAN, help2man, $missing_dir) + +# Let users choose the Mail Transfert Agent (MTA) of their choice. Default to +# a non-absolute program name to make it a loose dependency resolved at +# runtime. +AC_ARG_WITH([sendmail], + [AS_HELP_STRING([--with-sendmail=COMMAND], + [command to read an email message from standard input, and send it])], + [SENDMAIL="$withval"], + [SENDMAIL="sendmail -t"]) +AC_SUBST([SENDMAIL]) + +AC_ARG_ENABLE([multi-user], + [AS_HELP_STRING([--disable-multi-user], + [Don't Install legacy cron and crontab programs])], + [enable_multi_user="$enableval"], + [enable_multi_user="yes"]) +AM_CONDITIONAL([MULTI_USER], [test "x$enable_multi_user" = xyes]) + +# Configure the various files that mcron uses at runtime. + +AC_MSG_CHECKING([which spool directory to use]) +AC_ARG_WITH(spool-dir, + AC_HELP_STRING([--with-spool-dir], + [the crontab spool directory (/var/cron/tabs)]), + CONFIG_SPOOL_DIR=$withval, + CONFIG_SPOOL_DIR=[/var/cron/tabs]) +AC_MSG_RESULT($CONFIG_SPOOL_DIR) +AC_SUBST(CONFIG_SPOOL_DIR) + +AC_MSG_CHECKING([name of socket]) +AC_ARG_WITH(socket-file, + AC_HELP_STRING([--with-socket-file], + [unix pathname for cron socket (/var/cron/socket)]), + CONFIG_SOCKET_FILE=$withval, + CONFIG_SOCKET_FILE=[/var/cron/socket]) +AC_MSG_RESULT($CONFIG_SOCKET_FILE) +AC_SUBST(CONFIG_SOCKET_FILE) + +AC_MSG_CHECKING([name of allow file]) +AC_ARG_WITH(allow-file, + AC_HELP_STRING([--with-allow-file], + [the file of allowed users (/var/cron/allow)]), + CONFIG_ALLOW_FILE=$withval, + CONFIG_ALLOW_FILE=[/var/cron/allow]) +AC_MSG_RESULT($CONFIG_ALLOW_FILE) +AC_SUBST(CONFIG_ALLOW_FILE) + +AC_MSG_CHECKING([name of deny file]) +AC_ARG_WITH(deny-file, + AC_HELP_STRING([--with-deny-file], + [the file of barred users (/var/cron/deny)]), + CONFIG_DENY_FILE=$withval, + CONFIG_DENY_FILE=[/var/cron/deny]) +AC_MSG_RESULT($CONFIG_DENY_FILE) +AC_SUBST(CONFIG_DENY_FILE) + +AC_MSG_CHECKING([name of PID file]) +AC_ARG_WITH(pid-file, + AC_HELP_STRING([--with-pid-file], + [the file to record cron's PID (/var/run/cron.pid)]), + CONFIG_PID_FILE=$withval, + CONFIG_PID_FILE=[/var/run/cron.pid]) +AC_MSG_RESULT($CONFIG_PID_FILE) +AC_SUBST(CONFIG_PID_FILE) + +AC_MSG_CHECKING([directory to hold temporary files]) +AC_ARG_WITH(tmp-dir, + AC_HELP_STRING([--with-tmp-dir], + [directory to hold temporary files (/tmp)]), + CONFIG_TMP_DIR=$withval, + CONFIG_TMP_DIR=[/tmp]) +AC_MSG_RESULT($CONFIG_TMP_DIR) +AC_SUBST(CONFIG_TMP_DIR) + +# Include the Maintainer's Makefile fragment, if it's here. +MAINT_MAKEFILE=/dev/null +AS_IF([test -r "$srcdir/maint.mk"], + [MAINT_MAKEFILE="$srcdir/maint.mk"]) +AC_SUBST_FILE([MAINT_MAKEFILE]) + +AC_CONFIG_FILES([pre-inst-env:build-aux/pre-inst-env.in], + [chmod +x pre-inst-env]) +AC_CONFIG_FILES([doc/config.texi + Makefile + src/mcron/config.scm]) +AC_OUTPUT diff --git a/dale.key b/dale.key deleted file mode 100644 index 3110d69..0000000 Binary files a/dale.key and /dev/null differ diff --git a/doc/config.texi.in b/doc/config.texi.in new file mode 100644 index 0000000..50d9a18 --- /dev/null +++ b/doc/config.texi.in @@ -0,0 +1,5 @@ +@set CONFIG_SOCKET_FILE @CONFIG_SOCKET_FILE@ +@set CONFIG_SPOOL_DIR @CONFIG_SPOOL_DIR@ +@set CONFIG_PID_FILE @CONFIG_PID_FILE@ +@set CONFIG_ALLOW_FILE @CONFIG_ALLOW_FILE@ +@set CONFIG_DENY_FILE @CONFIG_DENY_FILE@ diff --git a/doc/fdl.texi b/doc/fdl.texi new file mode 100644 index 0000000..9c3bbe5 --- /dev/null +++ b/doc/fdl.texi @@ -0,0 +1,505 @@ +@c The GNU Free Documentation License. +@center Version 1.3, 3 November 2008 + +@c This file is intended to be included within another document, +@c hence no sectioning command or @node. + +@display +Copyright @copyright{} 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. +@uref{http://fsf.org/} + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. +@end display + +@enumerate 0 +@item +PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document @dfn{free} in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of ``copyleft'', which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + +@item +APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The ``Document'', below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as ``you''. You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A ``Modified Version'' of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A ``Secondary Section'' is a named appendix or a front-matter section +of the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall +subject (or to related matters) and contains nothing that could fall +directly within that overall subject. (Thus, if the Document is in +part a textbook of mathematics, a Secondary Section may not explain +any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The ``Invariant Sections'' are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The ``Cover Texts'' are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A ``Transparent'' copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not ``Transparent'' is called ``Opaque''. + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, La@TeX{} input +format, SGML or XML using a publicly available +DTD, and standard-conforming simple HTML, +PostScript or PDF designed for human modification. Examples +of transparent image formats include PNG, XCF and +JPG@. Opaque formats include proprietary formats that can be +read and edited only by proprietary word processors, SGML or +XML for which the DTD and/or processing tools are +not generally available, and the machine-generated HTML, +PostScript or PDF produced by some word processors for +output purposes only. + +The ``Title Page'' means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, ``Title Page'' means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +The ``publisher'' means any person or entity that distributes copies +of the Document to the public. + +A section ``Entitled XYZ'' means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as ``Acknowledgements'', +``Dedications'', ``Endorsements'', or ``History''.) To ``Preserve the Title'' +of such a section when you modify the Document means that it remains a +section ``Entitled XYZ'' according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + +@item +VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + +@item +COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + +@item +MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +@enumerate A +@item +Use in the Title Page (and on the covers, if any) a title distinct +from that of the Document, and from those of previous versions +(which should, if there were any, be listed in the History section +of the Document). You may use the same title as a previous version +if the original publisher of that version gives permission. + +@item +List on the Title Page, as authors, one or more persons or entities +responsible for authorship of the modifications in the Modified +Version, together with at least five of the principal authors of the +Document (all of its principal authors, if it has fewer than five), +unless they release you from this requirement. + +@item +State on the Title page the name of the publisher of the +Modified Version, as the publisher. + +@item +Preserve all the copyright notices of the Document. + +@item +Add an appropriate copyright notice for your modifications +adjacent to the other copyright notices. + +@item +Include, immediately after the copyright notices, a license notice +giving the public permission to use the Modified Version under the +terms of this License, in the form shown in the Addendum below. + +@item +Preserve in that license notice the full lists of Invariant Sections +and required Cover Texts given in the Document's license notice. + +@item +Include an unaltered copy of this License. + +@item +Preserve the section Entitled ``History'', Preserve its Title, and add +to it an item stating at least the title, year, new authors, and +publisher of the Modified Version as given on the Title Page. If +there is no section Entitled ``History'' in the Document, create one +stating the title, year, authors, and publisher of the Document as +given on its Title Page, then add an item describing the Modified +Version as stated in the previous sentence. + +@item +Preserve the network location, if any, given in the Document for +public access to a Transparent copy of the Document, and likewise +the network locations given in the Document for previous versions +it was based on. These may be placed in the ``History'' section. +You may omit a network location for a work that was published at +least four years before the Document itself, or if the original +publisher of the version it refers to gives permission. + +@item +For any section Entitled ``Acknowledgements'' or ``Dedications'', Preserve +the Title of the section, and preserve in the section all the +substance and tone of each of the contributor acknowledgements and/or +dedications given therein. + +@item +Preserve all the Invariant Sections of the Document, +unaltered in their text and in their titles. Section numbers +or the equivalent are not considered part of the section titles. + +@item +Delete any section Entitled ``Endorsements''. Such a section +may not be included in the Modified Version. + +@item +Do not retitle any existing section to be Entitled ``Endorsements'' or +to conflict in title with any Invariant Section. + +@item +Preserve any Warranty Disclaimers. +@end enumerate + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled ``Endorsements'', provided it contains +nothing but endorsements of your Modified Version by various +parties---for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + +@item +COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled ``History'' +in the various original documents, forming one section Entitled +``History''; likewise combine any sections Entitled ``Acknowledgements'', +and any sections Entitled ``Dedications''. You must delete all +sections Entitled ``Endorsements.'' + +@item +COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + +@item +AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an ``aggregate'' if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + +@item +TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled ``Acknowledgements'', +``Dedications'', or ``History'', the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + +@item +TERMINATION + +You may not copy, modify, sublicense, or distribute the Document +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense, or distribute it is void, and +will automatically terminate your rights under this License. + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, receipt of a copy of some or all of the same material does +not give you any rights to use it. + +@item +FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation 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. See +@uref{http://www.gnu.org/copyleft/}. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License ``or any later version'' applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. If the Document +specifies that a proxy can decide which future versions of this +License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the +Document. + +@item +RELICENSING + +``Massive Multiauthor Collaboration Site'' (or ``MMC Site'') means any +World Wide Web server that publishes copyrightable works and also +provides prominent facilities for anybody to edit those works. A +public wiki that anybody can edit is an example of such a server. A +``Massive Multiauthor Collaboration'' (or ``MMC'') contained in the +site means any set of copyrightable works thus published on the MMC +site. + +``CC-BY-SA'' means the Creative Commons Attribution-Share Alike 3.0 +license published by Creative Commons Corporation, a not-for-profit +corporation with a principal place of business in San Francisco, +California, as well as future copyleft versions of that license +published by that same organization. + +``Incorporate'' means to publish or republish a Document, in whole or +in part, as part of another Document. + +An MMC is ``eligible for relicensing'' if it is licensed under this +License, and if all works that were first published under this License +somewhere other than this MMC, and subsequently incorporated in whole +or in part into the MMC, (1) had no cover texts or invariant sections, +and (2) were thus incorporated prior to November 1, 2008. + +The operator of an MMC Site may republish an MMC contained in the site +under CC-BY-SA on the same site at any time before August 1, 2009, +provided the MMC is eligible for relicensing. + +@end enumerate + +@page +@heading ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + +@smallexample +@group + Copyright (C) @var{year} @var{your name}. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover + Texts. A copy of the license is included in the section entitled ``GNU + Free Documentation License''. +@end group +@end smallexample + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the ``with@dots{}Texts.''@: line with this: + +@smallexample +@group + with the Invariant Sections being @var{list their titles}, with + the Front-Cover Texts being @var{list}, and with the Back-Cover Texts + being @var{list}. +@end group +@end smallexample + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. + +@c Local Variables: +@c ispell-local-pdict: "ispell-dict" +@c End: diff --git a/doc/mcron.texi b/doc/mcron.texi new file mode 100644 index 0000000..70ab38c --- /dev/null +++ b/doc/mcron.texi @@ -0,0 +1,1352 @@ +\input texinfo +@c %**start of header +@setfilename mcron.info +@include config.texi +@include version.texi +@settitle mcron @value{VERSION} +@c %**end of header + +@syncodeindex fn cp + +@copying This manual is for GNU mcron (version @value{VERSION}), which is a +program for running jobs at scheduled times. + +Copyright @copyright{} 2003, 2005, 2006, 2012, 2014 Dale Mellor +Copyright @copyright{} 2018 Mathieu Lirzin + +@quotation +Permission is granted to copy, distribute and/or modify this +document under the terms of the GNU Free Documentation License, +Version 1.3 or any later version published by the Free Software +Foundation; with no Invariant Sections, no Front-Cover Texts and +no Back-Cover Texts. A copy of the license is included in the +section entitled ``GNU Free Documentation License''. +@end quotation +@end copying + + +@ifinfo + +@dircategory Individual utilities + +@direntry +* mcron: (mcron). Run jobs at scheduled times. +@end direntry + +@end ifinfo + + +@titlepage +@title mcron - Mellor's cron daemon +@author Dale Mellor + +@page +@vskip 0pt plus 1fill +@c @insertcopying + +@end titlepage + +@contents + +@ifnottex +@node Top, Introduction, (dir), (dir) +@top mcron + +This file documents the @code{mcron} command (Mellor's cron) for +running jobs at scheduled times. + +@c @insertcopying +@end ifnottex + +@menu +* Introduction:: Introducing mcron. +* Simple examples:: How to use mcron 99.9% of the time. +* Syntax:: All the possibilities for configuring cron jobs. +* Invoking:: What happens when you run the mcron command. +* Guile modules:: Incorporating mcron into another Guile program. +* GNU Free Documentation License:: The license of this manual. +* Index:: The complete index. + +@detailmenu + --- The Detailed Node Listing --- + +Simple examples + +* Guile Simple Examples:: +* Vixie Simple Examples:: + +Full available syntax + +* Guile Syntax:: +* Extended Guile examples:: +* Vixie Syntax:: + +Extended Guile examples + +* AT commands:: +* Every second Sunday:: +* Two hours every day:: +* Missing the first appointment:: +* Penultimate day of every month:: + +Vixie + +* Paul Vixie's copyright:: +* Crontab file:: +* Incompatibilities with old Unices:: + +Detailed invoking + +* Invoking mcron:: +* Invoking cron or crond:: +* Invoking crontab:: +* Behaviour on laptops:: +* Exit codes:: + +Guile modules + +* The base module:: The job list and execution loop. +* The redirect module:: Sending output of jobs to a mail box. +* The vixie-time module:: Parsing vixie-style time specifications. +* The job-specifier module:: All commands for scheme configuration files. +* The vixie-specification module:: Commands for reading vixie-style crontabs. + +@end detailmenu +@end menu + +@node Introduction, Simple examples, Top, Top +@chapter Introducing mcron +@cindex introduction +@cindex mcron +The mcron program represents a complete re-think of the cron concept +originally found in the Berkeley and AT&T unices, and subsequently +rationalized by Paul Vixie. The original idea was to have a daemon +that wakes up every minute, scans a set of files under a special +directory, and determines from those files if any shell commands +should be executed in this minute. + +The new idea is to read the required command instructions, work out +which command needs to be executed next, and then sleep until the +inferred time has arrived. On waking the commands are run, and the +time of the next command is computed. Furthermore, the specifications +are written in scheme, allowing at the same time simple command +execution instructions and very much more flexible ones to be composed +than the original Vixie format. This has several useful advantages +over the original idea. (Changes to user crontabs are signalled +directly to mcron by the crontab program; cron must still scan the +/etc/crontab file once every minute, although use of this file is +highly discouraged and this behaviour can be turned off). + +@cindex advantages of mcron +@itemize @bullet +@item +Does not consume CPU resources when not needed. Many cron daemons only +run jobs once an hour, or even just once a day. +@item +Can easily allow for finer time-points to be specified, +i.e. seconds. In principle this could be extended to microseconds, but +this is not implemented. +@item +Times can be more or less regular. For example, a job that runs +every 17 hours can be specified, or a job that runs on the first +Sunday of every month. +@item +Times can be dynamic. Arbitrary Guile (scheme) code can be provided to +compute the next time that a command needs to be run. This could, for +example, take the system load into consideration. +@item +Turns out to be easy to provide complete backwards compatibility with +Vixie cron. +@item +Each user looks after their own files in their own directory. They can use +more than one to break up complicated cron specifications. +@item +Each user can run their own daemon. This removes the need for suid +programs to manipulate the crontabs, and eliminates many security +concerns that surround all existing cron programs. +@item +The user can obtain an advance schedule of all the jobs that are due +to run. +@item +Vixie cron is implemented in 4500 lines of C code; mcron is 2000 lines +of scheme, despite the fact that it offers many more features and much +more flexibility, and complete compatibility with Vixie cron. +@end itemize + +A full discussion of the design and philosophy of mcron can be found +in the white paper at +@url{http://www.gnu.org/software/mcron/design.html}. + + +@node Simple examples, Syntax, Introduction, Top +@chapter Simple examples +The vast majority of uses of cron are sublimely simple: run a program +every hour, or every day. With this in mind the design of mcron has +been to allow such simple specifications to be made easily. The +examples show how to create the command descriptions, and subsequently +how to run mcron to make them happen. +@menu +* Guile Simple Examples:: +* Vixie Simple Examples:: +@end menu + +@node Guile Simple Examples, Vixie Simple Examples, Simple examples, Simple examples +@section Guile +@cindex guile examples +@cindex examples, guile +@cindex example, run a program every hour +You have an executable @code{my-program} in your home directory, which +you want to run every hour. Create a file @code{job.guile} in +directory @code{~/.config/cron} (this path may be altered by the +@code{$XDG_CONFIG_HOME} environment variable) with the following +contents + +@example +(job '(next-hour) "my-program") +@end example + +then run the command @code{mcron}. + +Want the program to run fifteen minutes past the hour, every two +hours? Edit the file to read + +@example +(job + '(next-minute-from + (next-hour (range 0 24 2)) + 15) + "my-program") +@end example + +and run the command @code{mcron}. + +Or, if you are not comfortable with Scheme, you could use (and see +also the next section) + +@example +(job "15 */2 * * *" "my-program") +@end example + +and run the @code{mcron} command. + +If you want to run other jobs, you can either add more lines to this +file, or you can create other files in your @code{.config/cron} directory +with the @code{.guile} extension. Alternatively, you can use any file +you want and pass it as an argument to @code{mcron}, or even pipe the +commands into the standard input. + + +@node Vixie Simple Examples, , Guile Simple Examples, Simple examples +@section Vixie +@cindex examples +@cindex examples, vixie +@cindex vixie examples +You have an executable @code{my-program} in your home directory, which +you want to run every hour. Create a file @code{job.vixie} in directory +@code{~/.cron} with the following contents + +@example +0 * * * * my-program +@end example + +then run the command @code{mcron}. + +@cindex vixie compatibility +@cindex compatibility +Alternatively (full compatibility with Vixie cron), set your +environment variable @code{EDITOR} to your favorite editor, run +@code{crontab -e}, put the above line into the edit buffer, save and +exit. For this to work the @code{cron} daemon must be already running +on your system, as root. + +@node Syntax, Invoking, Simple examples, Top +@chapter Full available syntax +@menu +* Guile Syntax:: +* Extended Guile examples:: +* Vixie Syntax:: +@end menu +@node Guile Syntax, Extended Guile examples, Syntax, Syntax +@section Guile Syntax +@subsection Job specification +@cindex guile syntax +@cindex syntax, guile +@findex job +In Guile-formatted configuration files each command that needs executing is +introduced with the @code{job} function. This function always takes two +arguments, the first a time specification, and the second a command +specification. An optional third argument may contain a string to display +when this job is listed in a schedule. Additionally a @var{user} keyword +argument can be supplied to use a different user than the one defined in +@code{configuration-user} global variable. + +@cindex time specification, procedure +@cindex procedure time specification +The first argument can be a procedure, a list, or a string. If a +function is supplied, it must take exactly one argument, which will be +the ``current'' time in UNIX format, and the return value of the +function must be the time in UNIX format when this action should next +be run. The following functions are available to facilitate the +computation: + +@findex next-second-from +@code{(next-second-from time . args)} without arguments this +returns the second after the current one. With the extra arguments, +these form a list of seconds in the minute when the action should run, +and the function will return the time of the next allowed second +(which may be in the next minute of the hour). @footnote{Note that +while commands can be scheduled to run at any second, it is unlikely +that they will be executed then but some time shortly thereafter, +depending on the load on the system and the number of jobs that mcron +has to start at the same time.} + +@findex next-minute-from +@findex next-hour-from +@findex next-day-from +@findex next-week-from +@findex next-month-from +@findex next-year-from +Similarly to @code{next-second-from}, there are also +@code{next-minute-from}, @code{next-hour-from}, @code{next-day-from}, +@code{next-week-from}, @code{next-month-from}, @code{next-year-from}. + +@findex range +Furthermore, the optional argument can be fulfilled by the function +@code{(range start end . step)}, which will provide a list of values +from start to (but not including) end, with the step if given. For +example @code{(range 0 10 2)} will yield the list @code{'(0 2 4 6 8)}. + +@findex next-second +@findex next-minute +@findex next-hour +@findex next-day +@findex next-week +@findex next-month +@findex next-year +@cindex time specification, list +@cindex list time specification +If the first argument to the @code{job} function is a list, it is +taken to be program code made up of the functions @code{(next-second +. args)}, @code{(next-minute...)}, etc, where the optional arguments +can be supplied with the @code{(range)} function above (these +functions are analogous to the ones above except that they implicitly +assume the current time; it is supplied by the mcron base when the +list is eval'd). + +@cindex time specification +@cindex time specification, string +@cindex string time specification +@cindex time specification, vixie-style +@cindex vixie-style time specification +If the first argument to the @code{job} function is a string, it is +expected to be a Vixie cron-style time specification. See the section +on Vixie syntax for this. + +@cindex job execution +@cindex command execution +@cindex execution +The second argument to the @code{(job)} function can be either a string, a +list, or a function. The command is executed in the home directory and with +the UID of @var{user}. If a string is passed, it is assumed to be shell +script and is executed with the user's default shell. If a list is passed it +is assumed to be scheme code and is eval'd as such. A supplied function +should take exactly zero arguments, and will be called at the pertinent times. + +@subsection Sending output as e-mail +@cindex email output +@cindex email from guile script +@cindex standard input to commands +@findex with-mail-out +When jobs are specified in a vixie-style configuration, the command is +broken at a percentage sign, and the stuff that comes after this is +sent into the command's standard input. Furthermore, any output from +the command is mailed to the user. This functionality is provided for +compatibility with Vixie cron, but it is also available to scheme +configuration files. The command (with-mail-out action . user) can be +used to direct output from the action (which may be a procedure, list, +or string) into an e-mail to the user. + +In the case that the action is a string, then percentage signs are +processed as per the vixie specifications, and information is piped to +the shell command's standard input. + +@subsection Setting environment variables +@cindex environment variables in scheme +@cindex setting environment variables +@findex append-environment-mods +Also for compatibility with Vixie cron, mcron has the ability to set +environment variables in configuration files. To access this +functionality from a scheme configuration file, use the command +(append-environment-mods name value), where name is the name of an +environment variable, and value is the value put to it. A value of #f +will remove the variable from the environment. + +Note that environment modifications are accumulated as the +configuration file is processed, so when a job actually runs, its +environment will be modified according to the modifications specified +before the job specification in the configuration file. + + +@node Extended Guile examples, Vixie Syntax, Guile Syntax, Syntax +@section Extended Guile examples +@cindex examples, extended guile +@cindex extended guile examples +While Guile gives you flexibility to do anything, and the power to +represent complex requirements succinctly, things are not always as +they seem. The following examples illustrate some pitfalls, and +demonstrate how to code around them. + +@menu +* AT commands:: +* Every second Sunday:: +* Two hours every day:: +* Missing the first appointment:: +* Penultimate day of every month:: +@end menu + +@node AT commands, Every second Sunday, Extended Guile examples, Extended Guile examples +@subsection Synthesizing ``at'' commands +@cindex at command +The current implementation of mcron does not provide for an at command +(a command-line program that allows the user to specify that a job +runs exactly once at a certain time). This can, however, be achieved. + +Suppose the program @code{my-program} needs to be run at midnight +tonight. A Guile script like the following would work (but a printed +schedule, obtained with the @code{--schedule} option, will show +superfluous entries). + +@example +(job '(next-day) + (lambda () (system "my-program") + (kill (getppid) SIGINT))) +@end example + +@node Every second Sunday, Two hours every day, AT commands, Extended Guile examples +@subsection Every second Sunday +@cindex examples, every second sunday +To run @code{my-program} on the second Sunday of every month, a Guile +script like the following should suffice (it is left as an exercise to +the student to understand how this works!). + +@example +(job (lambda (current-time) + (let* ((next-month (next-month-from current-time)) + (first-day (tm:wday (localtime next-month))) + (second-sunday (if (eqv? first-day 0) + 7 + (- 14 first-day)))) + (+ next-month (* 24 60 60 second-sunday)))) + "my-program") +@end example + + +@node Two hours every day, Missing the first appointment, Every second Sunday, Extended Guile examples +@subsection Two hours every day +@cindex examples, two hours every day +@cindex pitfalls, two hours every day +Surprisingly perhaps, the following will @strong{not} have the desired +effect. + +@example +(job '(next-hour-from (next-day) '(1 2)) + "my-program") +@end example + +Rather than running the my-program program at one o'clock and two +o'clock every day, it will only run it at one o'clock. This is because +each time mcron has to compute the next time to run the command, it +first obtains the next day, and then finds the earliest hour in that +day to run at. Thus, after running the command at one o'clock, the +program first skips forwards to the next midnight (missing the two +o'clock appointment), and then finds the next one o'clock schedule. + +The following simple command is the correct way to specify this +behaviour. + +@example +(job '(next-hour '(1 2)) "my-program") +@end example + + +@node Missing the first appointment, Penultimate day of every month, Two hours every day, Extended Guile examples +@subsection Missing the first appointment +@cindex examples, missing the first appointment +@cindex pitfalls, missing the first appointment +The command + +@example +(job '(next-hour-from (next-day) '(16)) + "my-program") +@end example + +will run @code{my-program} every day at four o'clock in the +afternoon. However, if mcron is started with this script at midday, +the first time the command will run will be four o'clock tomorrow; +today's appointment will be missed (one time only). + +The correct way to specify this requirement is simply + +@example +(job '(next-hour '(16)) + "my-program") +@end example + + +@node Penultimate day of every month, , Missing the first appointment, Extended Guile examples +@subsection Penultimate day of every month +@cindex examples, penultimate day of every month +The following will run the @code{my-program} program on the +second-to-last day of every month. + +@example +(job '(- (next-month-from (next-month)) (* 48 3600)) + "my-program") +@end example + + + +@node Vixie Syntax, , Extended Guile examples, Syntax +@section Vixie +@cindex syntax, vixie +@cindex vixie syntax +@cindex vixie definition +@cindex vixie compatibility +@cindex compatibility, vixie +@emph{NOTE} that this section is definitive. If there is a difference in +behaviour between the mcron program and this part of the manual, then +there is a bug in the program. This section is also copied verbatim +from Paul Vixie's documentation for their cron program, and their +copyright notice is duly reproduced below. + +There are three problems with this specification. + +@cindex zero'th day of month +@cindex 0'th day of month +1. It is allowed to specify days of the month in the range 0-31. What +does it mean to specify day 0? Looking at the Vixie source code, it +seems that if this date appears as part of a list, it has no +effect. However, if it appears on its own, the effect is to say +``don't run on any particular day of the month, only take the week-day +specification into account.'' Mcron has been coded to mimic this +behaviour as a special case (unmodified mcron logic implies that this +date specification would cause jobs to run on the last day of the +previous month). + +@cindex thirteenth month of year +@cindex 13th month of year +2. Similarly to the above (but different), months of the year can be +specified in the range 0-12. In the case of mcron (don't know what +Vixie cron did) month 12 will cause the program to wait until January +of the following year (but don't rely on this). + +@cindex shell +@cindex environment variables, shell +@cindex /etc/passwd +3. Somewhere it says that cron sets the SHELL environment variable to +/bin/sh, and elsewhere it implies that the default behaviour is for +the user's default shell to be used to execute commands. Mcron sets +the variable and runs the command in the user's default shell, as +advertised by the /etc/passwd file. + +@menu +* Paul Vixie's copyright:: +* Crontab file:: +* Incompatibilities with old Unices:: +@end menu + + +@node Paul Vixie's copyright, Crontab file, Vixie Syntax, Vixie Syntax +@subsection Paul Vixie's copyright +@cindex copyright, Paul Vixie's +@cindex Paul Vixie's copyright +@quotation +Copyright 1988,1990,1993,1994 by Paul Vixie +All rights reserved + +Distribute freely, except: don't remove my name from the source or +documentation (don't take credit for my work), mark your changes (don't +get me blamed for your possible bugs), don't alter or remove this +notice. May be sold if buildable source is provided to buyer. No +warrantee of any kind, express or implied, is included with this +software; use at your own risk, responsibility for damages (if any) to +anyone resulting from the use of this software rests entirely with the +user. +@end quotation + + + + +@node Crontab file, Incompatibilities with old Unices, Paul Vixie's copyright, Vixie Syntax +@subsection Crontab files +@cindex crontab file +@cindex vixie crontab file +A @code{crontab} file contains instructions to the @code{cron} daemon +of the general form: ``run this command at this time on this date''. +Each user has their own crontab, and commands in any given crontab +will be executed as the user who owns the crontab. Uucp and News will +usually have their own crontabs, eliminating the need for explicitly +running @code{su} as part of a cron command. + +@cindex comments, vixie-style +Blank lines and leading spaces and tabs are ignored. Lines whose first +non-space character is a pound-sign (#) are comments, and are ignored. +Note that comments are not allowed on the same line as cron commands, since +they will be taken to be part of the command. Similarly, comments are not +allowed on the same line as environment variable settings. + +An active line in a crontab will be either an environment setting or a cron +command. An environment setting is of the form, + +@cindex environment setting, vixie-style +@example +name = value +@end example + +where the spaces around the equal-sign (=) are optional, and any +subsequent non-leading spaces in @code{value} will be part of the +value assigned to @code{name}. The @code{value} string may be placed +in quotes (single or double, but matching) to preserve leading or +trailing blanks. + +@cindex environment variables, SHELL +@cindex environment variables, LOGNAME +@cindex environment variables, HOME +@cindex SHELL environment variable +@cindex LOGNAME environment variable +@cindex HOME environment variable +@cindex /etc/passwd +Several environment variables are set up automatically by the +@code{cron} daemon. SHELL is set to /bin/sh, and LOGNAME and HOME are +set from the /etc/passwd line of the crontab's owner. HOME and SHELL +may be overridden by settings in the crontab; LOGNAME may not. + +@cindex environment variables, USER +@cindex USER environment variable +@cindex BSD +(Another note: the LOGNAME variable is sometimes called USER on BSD systems... +on these systems, USER will be set also.) @footnote{mcron has not been +ported to BSD, so these notes are not relevant.} + +@cindex environment variables, MAILTO +@cindex MAILTO environment variable +In addition to LOGNAME, HOME, and SHELL, @code{cron} will look at +MAILTO if it has any reason to send mail as a result of running +commands in ``this'' crontab. If MAILTO is defined (and non-empty), +mail is sent to the user so named. If MAILTO is defined but empty +(MAILTO=""), no mail will be sent. Otherwise mail is sent to the +owner of the crontab. This option is useful if you decide on +/bin/mail instead of /usr/lib/sendmail as your mailer when you install +cron -- /bin/mail doesn't do aliasing, and UUCP usually doesn't read +its mail. + +The format of a cron command is very much the V7 standard, with a number of +upward-compatible extensions. Each line has five time and date fields, +followed by a user name if this is the system crontab file, +followed by a command. Commands are executed by @code{cron} +when the minute, hour, and month of year fields match the current +time, @strong{and} when at least one of the two day fields (day of month, or day of week) +match the current time (see ``Note'' below). @code{cron} examines cron entries once every minute. +The time and date fields are: + +@cindex vixie time specification fields +@cindex fields, vixie time specification +@multitable @columnfractions .2 .5 +@item Field @tab Allowed values +@item ----- @tab -------------- +@item minute @tab 0-59 +@item hour @tab 0-23 +@item day of month @tab 0-31 +@item month @tab 0-12 (or names, see below) +@item day of week @tab 0-7 (0 or 7 is Sun, or use names) +@end multitable + +A field may be an asterisk (*), which always stands for ``first-last''. + +@cindex ranges in vixie time specifications +Ranges of numbers are allowed. Ranges are two numbers separated +with a hyphen. The specified range is inclusive. For example, +8-11 for an ``hours'' entry specifies execution at hours 8, 9, 10 +and 11. + +@cindex lists in vixie time specifications +Lists are allowed. A list is a set of numbers (or ranges) +separated by commas. Examples: ``1,2,5,9'', ``0-4,8-12''. + +@cindex steps in vixie time specifications +Step values can be used in conjunction with ranges. Following +a range with ``/'' specifies skips of the number's value +through the range. For example, ``0-23/2'' can be used in the hours +field to specify command execution every other hour (the alternative +in the V7 standard is ``0,2,4,6,8,10,12,14,16,18,20,22''). Steps are +also permitted after an asterisk, so if you want to say ``every two +hours'', just use ``*/2''. + +@cindex names in vixie-style time specifications +Names can also be used for the ``month'' and ``day of week'' +fields. Use the first three letters of the particular +day or month (case doesn't matter). Ranges or +lists of names are not allowed. @footnote{Mcron allows any alphabetic +characters after a name, so full names of days or months are also valid.} + +@cindex % character on vixie-style commands +@cindex standard input, vixie-style +The ``sixth'' field (the rest of the line) specifies the command to be +run. +The entire command portion of the line, up to a newline or % +character, will be executed by /bin/sh or by the shell +specified in the SHELL variable of the cronfile. +Percent-signs (%) in the command, unless escaped with backslash +(\\), will be changed into newline characters, and all data +after the first % will be sent to the command as standard +input. + +@cindex day specification, vixie-style +@cindex vixie-style day specification +Note: The day of a command's execution can be specified by two +fields -- day of month, and day of week. If both fields are +restricted (ie, aren't *), the command will be run when +@emph{either} +field matches the current time. For example, + +``30 4 1,15 * 5'' + +would cause a command to be run at 4:30 am on the 1st and 15th of each +month, plus every Friday. + +EXAMPLE CRON FILE + +@example +# use /bin/sh to run commands, no matter what /etc/passwd says +SHELL=/bin/sh +# mail any output to `paul', no matter whose crontab this is +MAILTO=paul +# +# run five minutes after midnight, every day +5 0 * * * $HOME/bin/daily.job >> $HOME/tmp/out 2>&1 +# run at 2:15pm on the first of every month -- output mailed to paul +15 14 1 * * $HOME/bin/monthly +# run at 10 pm on weekdays, annoy Joe +0 22 * * 1-5 mail -s "It's 10pm" joe%Joe,%%Where are your kids?% +23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday" +5 4 * * sun echo "run at 5 after 4 every sunday" +@end example + +@node Incompatibilities with old Unices, , Crontab file, Vixie Syntax +@subsection Extensions and incompatibilities +@cindex incompatibilities with old Unices +@cindex extensions, vixie over old Unices +This section lists differences between Paul Vixie's cron and the +olde-worlde BSD and AT&T programs, for the benefit of system +administrators and users who are upgrading all the way. + +@itemize @bullet +@item +@cindex day 7 +When specifying day of week, both day 0 and day 7 will be considered Sunday. +BSD and AT&T seem to disagree about this. + +@item +Lists and ranges are allowed to co-exist in the same field. "1-3,7-9" would +be rejected by AT&T or BSD cron -- they want to see "1-3" or "7,8,9" ONLY. + +@item +Ranges can include "steps", so "1-9/2" is the same as "1,3,5,7,9". + +@item +Names of months or days of the week can be specified by name. + +@item +Environment variables can be set in the crontab. In BSD or AT&T, the +environment handed to child processes is basically the one from /etc/rc. + +@item +Command output is mailed to the crontab owner (BSD can't do this), can be +mailed to a person other than the crontab owner (SysV can't do this), or the +feature can be turned off and no mail will be sent at all (SysV can't do this +either). + +@end itemize + + +@node Invoking, Guile modules, Syntax, Top +@chapter Detailed invoking +@cindex invoking +@cindex personality +@cindex mcron program +@cindex cron program +@cindex crond program +@cindex crontab program +The program adopts one of three different personalities depending on +the name used to invoke it. In a standard installation, the program is +installed in the system under the names mcron, cron and crontab +(installed SUID). + +The recommended way to invoke the program is via the mcron personality +described in the next section. The program can also be run as cron by +root, and by the SUID program crontab by individual users to gain +backwards compatibility with Vixie cron. However, due to the fact that +this daemon process is shared by, and under control of, all the users +of the system it is possible (though very unlikely) that it may become +unusable, hence the recommendation to use the mcron personality. + +@cindex deprecated, vixie personality +Furthermore, the Vixie personality is considered deprecated by this +author (it offers not a single advantage over the mcron personality, +and bloats the code by a factor of three). It is unlikely that this +personality will ever actually go away, but the program may in future +be split into two distinct parts, and new developments will only take +place in the part which implements the mcron personality. + + + +@menu +* Invoking mcron:: +* Invoking cron or crond:: +* Invoking crontab:: +* Behaviour on laptops:: +* Exit codes:: +@end menu + +@node Invoking mcron, Invoking cron or crond, Invoking, Invoking +@section Invoking mcron +@cindex invoking mcron +@cindex mcron options +@cindex mcron arguments +@cindex command line, mcron +@cindex mcron command line +Mcron should be run by the user who wants to schedule their jobs. It +may be made a background job using the facilities of the shell. The +basic command is @code{mcron [OPTION ...] [file ...]} which has the +effect of reading all the configuration files specified (subject to +the options) and then waiting until it is time to execute some +command. If no files are given on the command line, then mcron will +look in the user's cron configuration directories: these are ~/.cron +(deprecated), the directory indicated by the @code{XDG_CONFIG_HOME} +environment variable, or ~/.config/cron if this variable is not set. +In any case, files which end in the extension .vixie or .vix will be +assumed to contain Vixie-style crontabs, and files ending .guile or +.gle will be assumed to contain scheme code and will be executed as +such; ANY OTHER FILES WILL BE IGNORED - specify a file name of ``-'' +and then pipe the files into the standard input if you really want to +read them, possibly using the @code{stdin} option to specify the type +of file. + +The program accepts the following options. + +@table @option +@item -s count +@itemx --schedule=count +@cindex printout of jobs schedule +@cindex schedule of jobs, listing +@cindex options, schedule +@cindex options, -s +@cindex -s option +@cindex --schedule option +With this option specified no commands are run. Instead, the program +computes the times the commands would be run and prints the +information to the screen, and then immediately exits. + +The count indicates the number of commands to display. + +@cindex daemon option +@cindex options, daemon +@cindex options, -d +@cindex -d option +@cindex --daemon option +@item -d +@itemx --daemon +With this option the program will detach itself from the controlling +terminal and run as a daemon process. + +@cindex stdin option +@cindex options, stdin +@cindex options, -i +@cindex -i option +@cindex --stdin option +@cindex standard input, configuring from +@cindex configuring from standard input +@item -i (vixie|guile) +@itemx --stdin=(vixie|guile) +This option is used to indicate whether the configuration information +being passed on the standard input is in Vixie format or Guile +format. Guile is the default. + +@cindex -v option +@cindex --version option +@cindex options, -v +@cindex options, version +@item -v +@itemx --version +This option causes a message to be printed on the standard output with +information about the version and copyright for the current program. + +@cindex -h option +@cindex --help option +@cindex options, -h +@cindex options, --help +@item -h +@itemx --help +This causes a short but complete usage message to be displayed on +standard output. + +@end table + +@node Invoking cron or crond, Invoking crontab, Invoking mcron, Invoking +@section Invoking cron or crond +@cindex cron, invokation +@cindex invoking cron +@cindex crond, invokation +@cindex invoking crond +@cindex @value{CONFIG_SPOOL_DIR} +@cindex @value{CONFIG_SOCKET_FILE} +NOTE THAT THIS SECTION ONLY APPLIES IF THE @code{cron} or +@code{crond}, and @code{crontab} PROGRAMS HAVE BEEN INSTALLED BY THE +SYSTEM ADMINISTRATOR. + +If the program runs by the name of @code{cron} or @code{crond}, then +it will read all the files in @code{@value{CONFIG_SPOOL_DIR}} (which +should only be readable by root) and the file @code{/etc/crontab}, and +then detaches itself from the terminal to live forever as a daemon +process. Additionally, it creates a UNIX socket at +@code{@value{CONFIG_SOCKET_FILE}}, and listens for messages sent to +that socket consisting of a user name whose crontabs have been +changed. In this case, the program will re-read that user's crontab. +This is for correct functioning with the crontab program. + +Further, unless the @code{--noetc} option is used, a job is scheduled to run +every minute to check if @code{/etc/crontab} has been modified. If so, this +file will also be re-read. + +The options which may be used with this program are as follows. + +@table @option + +@cindex -v option +@cindex --version option +@cindex options, -v +@cindex options, version +@item -v +@itemx --version +This option causes a message to be printed on the standard output with +information about the version and copyright for the current program. + +@cindex -h option +@cindex --help option +@cindex options, -h +@cindex options, --help +@item -h +@itemx --help +This causes a short but complete usage message to be displayed on +standard output. + +@item -s [count] +@itemx --schedule[=count] +@cindex printout of jobs schedule +@cindex schedule of jobs, listing +@cindex options, schedule +@cindex options, -s +@cindex -s option +@cindex --schedule option +With this option specified no commands are run. Instead, the program +computes the times the commands would be run and prints the +information to the screen, and then immediately exits. + +The count, if supplied, indicates the number of commands to +display. The default value is 8. + +@cindex -n option +@cindex --noetc option +@cindex options, -n +@cindex options, --noetc +@item -n +@itemx --noetc +This tells cron not to add a job to the system which wakes up every +minute to check for modifications to @code{/etc/crontab}. It is +recommended that this option be used (and further that the +@code{/etc/crontab} file be taken off the system altogether!) + +@end table + +@node Invoking crontab, Behaviour on laptops, Invoking cron or crond, Invoking +@section Invoking crontab +@cindex crontab, invoking +@cindex invoking crontab +This program is run by individual users to inspect or modify their +crontab files. If a change is made to the file, then the root daemon +process will be given a kick, and will immediately read the new +configuration. A warning will be issued to standard output if it +appears that a cron daemon is not running. + +The command is used as + +@code{crontab [-u user] file} + +or + +@code{crontab [-u user] ( -l | -e | -r )} + +Only the root user can use the -u option, to specify the manipulation +of another user's crontab file. In the first instance, the entire +crontab file of the user is replaced with the contents of the +specified file, or standard input if the file is ``-''. + +In the latter case, the program behaves according to which of the +(mutually exclusive) options was given (note that the long options are +an mcron extension). + +@table @option + +@cindex -l option +@cindex list option, crontab +@cindex options, -l +@cindex options, --list +@cindex viewing a crontab +@cindex listing a crontab +@item -l +@itemx --list +Print the user's crontab file to the standard output, and exit. + +@cindex -r option +@cindex remove option +@cindex options, -r +@cindex options, --remove +@cindex deleting a crontab +@cindex removing a crontab +@item -r +@item --remove +Delete the user's crontab file, and exit. + +@cindex -e option +@cindex edit option +@cindex options, -e +@cindex options, --edit +@cindex editing a crontab +@cindex creating a crontab +@item -e +@item --edit +Using the editor specified in the user's VISUAL or EDITOR environment +variables, allow the user to edit their crontab. Once the user exits the +editor, the crontab is checked for parseability, and if it is okay +then it is installed as the user's new crontab and the daemon is +notified that a change has taken place, so that the new file will +become immediately effective. + +@end table + + +@node Behaviour on laptops, Exit codes, Invoking crontab, Invoking +@section Behaviour on laptops +@cindex laptops +@cindex power suspend +While mcron has not been designed to work anachronistically, the behaviour of +mcron when a laptop emerges from a suspended state is well defined, and the +following description explains what happens in this situation. + +When a laptop awakes from a suspended state, all jobs which would have run while +the laptop was suspended will run exactly once immediately (and simultaneously) +when the laptop awakes, and then the next time that those jobs run will be +computed based on the time the laptop was awoken. Any jobs which would not have +run during the suspense period will be unaffected, and will still run at their +proper times. + + +@node Exit codes, , Behaviour on laptops, Invoking +@section Exit codes +@cindex exit codes +@cindex error conditions +@cindex errors +The following are the status codes returned to the operating system +when the program terminates. + +@table @asis +@item 0 +No problems. + +@item 1 +An attempt has been made to start cron but there is already a +@value{CONFIG_PID_FILE} file. If there really is no other cron daemon +running (this does not include invokations of mcron) then you should +remove this file before attempting to run cron. + +@item 2 +In parsing a guile configuration file, a @code{job} command has been +seen but the second argument is neither a procedure, list or +string. This argument is the job's action, and needs to be specified +in one of these forms. + +@item 3 +In parsing a guile configuration file, a @code{job} command has been +seen but the first argument is neither a procedure, list or +string. This argument is the job's next-time specification, and needs +to be specified in one of these forms. + +@item 4 +An attempt to run cron has been made by a user who does not have +permission to access the crontabs in @value{CONFIG_SPOOL_DIR}. These +files should be readable only by root, and the cron daemon must be run +as root. + +@item 5 +An attempt to run mcron has been made, but there are no jobs to +schedule! + +@item 6 +The system administrator has blocked this user from using crontab with +the files @value{CONFIG_ALLOW_FILE} and @value{CONFIG_DENY_FILE}. + +@item 7 +Crontab has been run with more than one of the arguments @code{-l}, +@code{-r}, @code{-e}. These are mutually exclusive options. + +@item 8 +Crontab has been run with the -u option by a user other than +root. Only root is allowed to use this option. + +@item 9 +An invalid vixie-style time specification has been supplied. + +@item 10 +An invalid vixie-style job specification has been supplied. + +@item 11 +A bad line has been seen in /etc/crontab. + +@item 12 +The last component of the name of the program was not one of +@code{mcron}, @code{cron}, @code{crond} or @code{crontab}. + +@item 13 +Either none of the user's configuration directories exist, or there is a problem +reading the files there. The configuration directories are ~/.cron +and the directory pointed to by the @code{XDG_CONFIG_HOME} environment +variable, or ~/.config/cron if this is not set. + +@c @item 14 +@c There is a problem writing to /var/cron/update. This is probably +@c because the crontab program is not installed SUID root, as it should +@c be. + +@item 15 +Crontab has been run without any arguments at all. There is no default +behaviour in this case. + +@item 16 +Cron has been run by a user other than root. + +@end table + + + +@node Guile modules, Index, Invoking, Top +@chapter Guile modules +Some of the key parts of mcron are implemented as modules so they can +be incorporated into other Guile programs, or even into C-sourced +programs if they are linked against libguile. + +It may be, for example, that a program needs to perform house-keeping +functions at certain times of the day, in which case it can spawn +(either fork or thread) a sub-process which uses a built-in +mcron. Another example may be a program which must sleep until some +non-absolute time specified on the Gregorian calendar (the first day +of next week, for example). Finally, it may be the wish of the user to +provide a program with the functionality of mcron plus a bit extra. + +The base module maintains mcron's internal job lists, and provides the +main wait-run-wait loop that is mcron's main function. It also +introduces the facilities for accumulating a set of environment +modifiers, which take effect when jobs run. + +@menu +* The base module:: The job list and execution loop. +* The redirect module:: Sending output of jobs to a mail box. +* The vixie-time module:: Parsing vixie-style time specifications. +* The job-specifier module:: All commands for scheme configuration files. +* The vixie-specification module:: Commands for reading vixie-style crontabs. +@end menu + +@node The base module, The redirect module, Guile modules, Guile modules +@section The base module +@cindex guile module +@cindex base module +@cindex modules, base + +This module may be used by including @code{(use-modules (mcron base))} +in a program. The main functions are @code{add-job} and +@code{run-job-loop}, which allow a program to create a list of job +specifications to run, and then to initiate the wait-run-wait loop +firing the jobs off at the requisite times. However, before they are +introduced two functions which manipulate the environment that takes +effect when a job runs are defined. + +@cindex environment +The environment is a set of name-value pairs which is built up +incrementally. Each time the @code{add-job} function is called, the +environment modifiers that have been accumulated up to that point are +stored with the new job specification, and when the job actually runs +these name-value pairs are used to modify the run-time environment in +effect. + +@deffn{Scheme procedure} append-environment-mods name value +When a job is run make sure the environment variable @var{name} has +the value @var{value}. +@end deffn + +@deffn{Scheme procedure} clear-environment-mods +This procedure causes all the environment modifiers that have been +specified so far to be forgotten. +@end deffn + +@deffn{Scheme procedure} add-job time-proc action displayable @ + configuration-time configuration-user @ + [#:schedule @var{%global-schedule}] +This procedure adds a job specification to the list of all jobs to +run. @var{time-proc} should be a procedure taking exactly one argument +which will be a UNIX time. This procedure must compute the next time +that the job should run, and return the result. @var{action} should be +a procedure taking no arguments, and contains the instructions that +actually get executed whenever the job is scheduled to +run. @var{displayable} should be a string, and is only for the use of +humans; it can be anything which identifies or simply gives a clue as +to the purpose or function of this job. @var{configuration-time} is +the time from which the first invokation of this job should be +computed. Finally, @var{configuration-user} should be the passwd entry +for the user under whose personality the job is to run. +@end deffn + +@deffn{Scheme procedure} run-job-loop @var{fd-list} @ + [#:schedule @var{%global-schedule}] +@cindex file descriptors +@cindex interrupting the mcron loop +This procedure returns only under exceptional circumstances, but +usually loops forever waiting for the next time to arrive when a job +needs to run, running that job, recomputing the next run time, and +then waiting again. However, the wait can be interrupted by data +becoming available for reading on one of the file descriptors in the +fd-list, if supplied. Only in this case will the procedure return to +the calling program, which may then make modifications to the job list +before calling the @code{run-job-loop} procedure again to resume execution of +the mcron base. +@end deffn + +@deffn{Scheme procedure} remove-user-jobs user @ + [#:schedule @var{%global-schedule}] +The argument @var{user} should be a string naming a user (their +login name), or an integer UID, or an object representing the user's passwd +entry. All jobs on the current job list that are scheduled to be run +under this personality are removed from the job list. +@end deffn + +@deffn{Scheme procedure} display-schedule @var{count} [@var{port}] @ + [#:schedule @var{%global-schedule}] +@cindex schedule of jobs +This procedure is used to display a textual list of the next COUNT jobs +to run. + +The argument @var{count} must be an integer value giving the number +of time-points in the future to report that jobs will run as. Note +that this procedure is disruptive; if @code{run-job-loop} is called +after this procedure, the first job to run will be the one after the +last job that was reported in the schedule report. The report itself +is returned to the calling program as a string. +@end deffn + +@node The redirect module, The vixie-time module, The base module, Guile modules +@section The redirect module +@cindex redirect module +@cindex modules, redirect + +This module is introduced to a program with the command +@code{(use-modules (mcron redirect))}. + +This module provides the @code{with-mail-out} function, described +fully in @ref{Guile Syntax}. + +@node The vixie-time module, The job-specifier module, The redirect module, Guile modules +@section The vixie-time module +@cindex vixie-time module +@cindex modules, vixie-time + +This module is introduced to a program by @code{(use-modules (mcron +vixie-time))}. + +This module provides a single method for converting a vixie-style time +specification into a procedure which can be used as the +@code{next-time-function} to the base @code{add-job} procedure, or to +the @code{job-specifier} @code{job} procedure. See @ref{Vixie Syntax} +for full details of the allowed format for the time string. + +@deffn{Scheme procedure} parse-vixie-time time-string +The single argument @var{time-string} should be a string containing a +vixie-style time specification, and the return value is the required +procedure. +@end deffn + + +@node The job-specifier module, The vixie-specification module, The vixie-time module, Guile modules +@section The job-specifier module +@cindex job-specifier module +@cindex modules, job-specifier + +This module is introduced to a program by @code{(use-modules (mcron +job-specifier))}. + +This module provides all the functions available to user's Guile +configuration files, namely @code{range}, @code{next-year-from}, +@code{next-year}, @code{next-month-from}, @code{next-month}, +@code{next-day-from}, @code{next-day}, @code{next-hour-from}, +@code{next-hour}, @code{next-minute-from}, @code{next-minute}, +@code{next-second-from}, @code{next-second}, + and last but not least, @code{job}. See @ref{Guile Syntax} for full + details. + +Once this module is loaded, a scheme configuration file can be used to +put jobs onto the job list simply by @code{load}ing the file. + +@node The vixie-specification module, , The job-specifier module, Guile modules +@section The vixie-specification module +@cindex vixie-specification module +@cindex modules, vixie-specification + +To use this module, put the command @code{(use-modules (mcron +vixie-specification))} into your program. + +This module exports a couple of functions for adding jobs to the +internal job list according to a Vixie-style crontab file. + +@deffn{Scheme procedure} read-vixie-port port . parse-line + +This procedure reads a crontab from the given port, and adds jobs to +the job list accordingly, taking care of environment specifications +and comments which may appear in such a file. + +@var{parse-line} should not normally be used, except that if you are +parsing a (deprecated) @code{/etc/crontab} file with a slightly +modified syntax, you may pass the value @var{parse-system-vixie-line} +as the optional argument. + +@end deffn + +@deffn{Scheme procedure} read-vixie-file name . parse-line + +This procedure attempts to open the named file, and if it fails will +return silently. Otherwise, the behaviour is identical to +@code{read-vixie-port} above. + +@end deffn + +Once this module has been declared in a program, a crontab file can be +used to augment the current job list with a call to +@code{read-vixie-file}. + +@node GNU Free Documentation License +@appendix GNU Free Documentation License + +@include fdl.texi + +@node Index, , Guile modules, Top +@unnumbered Index + +@printindex cp + +@bye diff --git a/maint.mk b/maint.mk new file mode 100755 index 0000000..3a41816 --- /dev/null +++ b/maint.mk @@ -0,0 +1,125 @@ +## Maintainer-only Makefile fragment +# Copyright © 2018 Mathieu Lirzin +# +# This file is part of GNU Mcron. +# +# GNU Mcron is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GNU Mcron is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Mcron. If not, see . + +# Rebuild Makefile.in if this file is modifed. +Makefile.in: maint.mk + +## -------------------- ## +## Third-party files. ## +## ---------------------## + +WGET = wget + +# Git repositories on Savannah. +git_sv_host = git.savannah.gnu.org + +# Some repositories we sync files from. +sv_git_am = 'https://$(git_sv_host)/gitweb/?p=automake.git;a=blob_plain;hb=HEAD;f=' +sv_git_gl = 'https://$(git_sv_host)/gitweb/?p=gnulib.git;a=blob_plain;hb=HEAD;f=' + +# Files that we fetch and which we compare against. +# Note that the 'lib/COPYING' file must still be synced by hand. +fetchfiles = \ + $(sv_git_am)contrib/test-driver.scm \ + $(sv_git_gl)build-aux/do-release-commit-and-tag \ + ${sv_git_gl}build-aux/gnu-web-doc-update \ + $(sv_git_gl)build-aux/gnupload + +# Fetch the latest versions of few scripts and files we care about. +# A retrieval failure or a copying failure usually mean serious problems, +# so we'll just bail out if 'wget' or 'cp' fail. +fetch: + $(AM_V_at)rm -rf Fetchdir + $(AM_V_at)mkdir Fetchdir + $(AM_V_GEN)set -e; \ + if $(AM_V_P); then wget_opts=; else wget_opts=-nv; fi; \ + for url in $(fetchfiles); do \ + file=`printf '%s\n' "$$url" | sed 's|^.*/||; s|^.*=||'`; \ + $(WGET) $$wget_opts "$$url" -O Fetchdir/$$file || exit 1; \ + if cmp Fetchdir/$$file $(srcdir)/build-aux/$$file >/dev/null; then \ + : Nothing to do; \ + else \ + echo "$@: updating file $$file"; \ + cp Fetchdir/$$file $(srcdir)/build-aux/$$file || exit 1; \ + fi; \ + done + $(AM_V_at)rm -rf Fetchdir +.PHONY: fetch + +# If it's not already specified, derive the GPG key ID from +# the signed tag we've just applied to mark this release. +gpg_key_ID = \ + $$(cd $(srcdir) \ + && git cat-file tag v$(VERSION) \ + | gpgv --status-fd 1 --keyring /dev/null - - 2>/dev/null \ + | awk '/^\[GNUPG:\] ERRSIG / {print $$3; exit}') + +# Use alpha.gnu.org for alpha and beta releases. +# Use ftp.gnu.org for stable releases. +gnu_ftp_host-alpha = alpha.gnu.org +gnu_ftp_host-beta = alpha.gnu.org +gnu_ftp_host-stable = ftp.gnu.org +gnu_rel_host = $(gnu_ftp_host-$(release-type)) + +noteworthy_changes = * Noteworthy changes in release ?.? (????-??-??) [?] + +.PHONY: release +release: + cd $(srcdir) && rm -rf autom4te.cache && ./bootstrap && ./configure + $(AM_V_at)$(MAKE) Makefile + $(AM_V_at)$(srcdir)/build-aux/announce-gen \ + --mail-headers='To: ??? Mail-Followup-To: $(PACKAGE_BUGREPORT)' \ + --release-type=$(release-type) \ + --package=$(PACKAGE) \ + --prev=`cat .prev-version` \ + --curr=$(VERSION) \ + --gpg-key-id=$(gpg_key_ID) \ + --srcdir=$(srcdir) \ + --news=$(srcdir)/NEWS \ + --bootstrap-tools=autoconf,automake,help2man \ + --no-print-checksums \ + --url-dir=https://ftp.gnu.org/gnu/$(PACKAGE) \ + > ~/announce-$(PACKAGE)-$(VERSION) + $(AM_V_at)echo $(VERSION) > .prev-version + $(AM_V_at)perl -pi \ + -e '$$. == 3 and print "$(noteworthy_changes)\n\n\n"' \ + $(srcdir)/NEWS + $(AM_V_at)msg=`printf '%s\n' 'maint: Post-release administrivia' '' \ + '* NEWS: Add header line for next release.' \ + '* .prev-version: Record previous version.'` || exit 1; \ + git commit -m "$$msg" -a + +.PHONY: upload +upload: + $(srcdir)/build-aux/gnupload $(GNUPLOADFLAGS) \ + --to $(gnu_rel_host):$(PACKAGE) \ + $(DIST_ARCHIVES) + +.PHONY: web-manual +web-manual: + $(AM_V_at)cd '$(srcdir)/doc'; \ + $(SHELL) ../build-aux/gendocs.sh \ + -o '$(abs_builddir)/doc/manual' \ + --email $(PACKAGE_BUGREPORT) $(PACKAGE) \ + "$(PACKAGE_STRING) Reference Manual" + $(AM_V_at)echo " *** Upload the doc/manual directory to web-cvs." + +.PHONY: web-manual-update +web-manual-update: + $(AM_V_GEN)cd $(srcdir) \ + && build-aux/gnu-web-doc-update -C $(abs_builddir) diff --git a/src/cron.in b/src/cron.in new file mode 100644 index 0000000..c22b701 --- /dev/null +++ b/src/cron.in @@ -0,0 +1,53 @@ +#!%GUILE% --no-auto-compile +-*- scheme -*- +!# + +;;;; cron -- run jobs at scheduled times +;;; Copyright © 2003, 2012, 2020 Dale Mellor +;;; Copyright © 2015, 2016, 2018 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + + +(unless (getenv "MCRON_UNINSTALLED") + (set! %load-path (cons "%modsrcdir%" %load-path)) + (set! %load-compiled-path (cons "%modbuilddir%" %load-compiled-path))) + +(use-modules (mcron scripts cron) + (ice-9 command-line-processor)) + +(process-command-line (command-line) + application "cron" + version "%VERSION%" + usage "[OPTIONS]" + help-preamble + "Unless an option is specified, run a cron daemon as a detached process," + "reading all the information in the usersʼ crontabs and in /etc/crontab." + option (--schedule=8 -s string->number + "display the next N (or 8) jobs that will be" + "run, and exit") + option (--noetc -n "do not check /etc/crontab for updates (use" + "of this option is HIGHLY RECOMMENDED)") + help-postamble + "Mandatory or optional arguments to long options are also mandatory or " + "optional for any corresponding short options." + bug-address "%PACKAGE_BUGREPORT%" + copyright + "2003, 2012, 2015, 2016, 2018, 2020 Free Software Foundation, Inc." + license GPLv3) + + +(main --schedule --noetc) diff --git a/src/crontab.in b/src/crontab.in new file mode 100644 index 0000000..f203a98 --- /dev/null +++ b/src/crontab.in @@ -0,0 +1,45 @@ +#!%GUILE% --no-auto-compile +-*- scheme -*- +!# + +;;;; crontab -- run jobs at scheduled times +;;; Copyright © 2003, 2020 Dale Mellor +;;; Copyright © 2015, 2016, 2018 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + + +(unless (getenv "MCRON_UNINSTALLED") + (set! %load-path (cons "%modsrcdir%" %load-path)) + (set! %load-compiled-path (cons "%modbuilddir%" %load-compiled-path))) + +(use-modules (mcron scripts crontab) + (ice-9 command-line-processor)) + +(process-command-line (command-line) + application "crontab" + version "%VERSION%" + usage "[-u user] { -e | -l | -r }" + help-preamble "the default operation is to replace, per 1003.2" + option (--user= -u "the user whose files are to be manipulated") + option (--edit -e "edit this userʼs crontab") + option (--list -l "list this userʼs crontab") + option (--remove -r "delete the userʼs crontab") + bug-address "%PACKAGE_BUGREPORT%" + copyright "2003, 2016, 2020 Free Software Foundation, Inc." + license GPLv3) + +((@ (mcron scripts crontab) main) --user --edit --list --remove --!) diff --git a/src/mcron.in b/src/mcron.in new file mode 100644 index 0000000..43dc326 --- /dev/null +++ b/src/mcron.in @@ -0,0 +1,56 @@ +#!%GUILE% --no-auto-compile +-*- scheme -*- +!# + +;;;; mcron -- run jobs at scheduled times +;;; Copyright © 2003, 2012, 2020 Dale Mellor +;;; Copyright © 2015, 2016, 2018 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + + +(unless (getenv "MCRON_UNINSTALLED") + (set! %load-path (cons "%modsrcdir%" %load-path)) + (set! %load-compiled-path (cons "%modbuilddir%" %load-compiled-path))) + +(use-modules (mcron scripts mcron) + (ice-9 command-line-processor)) + +(process-command-line (command-line) + application "mcron" + version "%VERSION%" + usage "[OPTIONS ...] [FILES ...]" + help-preamble + "Run unattended jobs according to instructions in the FILES... " + "(`-' for standard input), or use all the files in ~/.config/cron " + "(or the deprecated ~/.cron) with .guile or .vixie extensions.\n" + "Note that --daemon and --schedule are mutually exclusive." + option (--daemon -d "run as a daemon process") + option (--schedule=8 -s string->number + "display the next N (or 8) jobs that will be run," + "and then exit") + option (--stdin=guile short-i (λ (in) (or (string=? in "guile") + (string=? in "vixie"))) + "format of data passed as standard input or file " + "arguments, 'guile' or 'vixie' (default guile)") + help-postamble + "Mandatory or optional arguments to long options are also mandatory or " + "optional for any corresponding short options." + bug-address "%PACKAGE_BUGREPORT%" + copyright "2003, 2006, 2014, 2020 Free Software Foundation, Inc." + license GPLv3) + +(main --daemon --schedule --stdin --!) diff --git a/src/mcron/base.scm b/src/mcron/base.scm new file mode 100644 index 0000000..f7b727d --- /dev/null +++ b/src/mcron/base.scm @@ -0,0 +1,248 @@ +;;;; base.scm -- core procedures +;;; Copyright © 2003 Dale Mellor +;;; Copyright © 2015, 2016, 2018 Mathieu Lirzin +;;; Copyright © 2016, 2020 Ludovic Courtès +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +;;;; Commentary: +;;; +;;; This module provides the core data structures for scheduling jobs and the +;;; procedures for running those jobs. +;;; +;;;; Code: + +(define-module (mcron base) + #:use-module (ice-9 match) + #:use-module (ice-9 control) + #:use-module (mcron environment) + #:use-module (mcron utils) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-2) + #:use-module (srfi srfi-9) + #:use-module (srfi srfi-111) + #:export (add-job + remove-user-jobs + display-schedule + run-job-loop + ;; Deprecated and undocumented procedures. + use-system-job-list + use-user-job-list + clear-system-jobs) + #:re-export (clear-environment-mods + append-environment-mods)) + +;; A cron job. +(define-record-type + (make-job user time-proc action environment displayable next-time) + job? + (user job:user) ;object : passwd entry + (time-proc job:next-time-function) ;proc : with one 'time' parameter + (action job:action) ;thunk : user's code + ;; Environment variables that need to be set before the ACTION is run. + (environment job:environment) ;alist : environment variables + (displayable job:displayable) ;string : visible in schedule + (next-time job:next-time ;number : time in UNIX format + job:next-time-set!)) + +;; A schedule of cron jobs. +(define-record-type + ;; The schedule is composed of a 'user' and 'system' schedule. This makes + ;; removing all the jobs belonging to one group easy, which is required for + ;; full vixie compatibility. + (make-schedule user system current) + schedule? + ;; list for jobs that may be placed in '/etc/crontab'. + (system schedule-system set-schedule-system!) ;list of + ;; list for all other jobs. + (user schedule-user set-schedule-user!) ;list of + (current schedule-current set-schedule-current!)) ;symbol 'user or 'system + +(define %global-schedule + ;; Global schedule used by 'mcron' and 'cron'. + (make-schedule '() '() 'user)) + +(define* (use-system-job-list #:key (schedule %global-schedule)) + "Mutate '%global-schedule' to use system jobs. +This procedure is deprecated." + (set-schedule-current! schedule 'system)) + +(define* (use-user-job-list #:key (schedule %global-schedule)) + "Mutate '%global-schedule' to use user jobs. +This procedure is deprecated." + (set-schedule-current! schedule 'user)) + +(define* (remove-user-jobs user #:key (schedule %global-schedule)) + "Remove user jobs from SCHEDULE belonging to USER. USER must be either a +username, a UID, or a passwd entry." + (let ((user* (get-user user))) + (set-schedule-user! schedule + (filter (lambda (job) + (not (eqv? (passwd:uid user*) + (passwd:uid (job:user job))))) + (schedule-user schedule))))) + +(define* (clear-system-jobs #:key (schedule %global-schedule)) + "Remove all the system jobs from SCHEDULE." + (set-schedule-system! schedule '())) + +(define* (add-job time-proc action displayable configuration-time + configuration-user + #:key (schedule %global-schedule)) + "Add a new job with the given specifications to the current job set in +SCHEDULE." + (let ((entry (make-job configuration-user + time-proc + action + (get-current-environment-mods-copy) + displayable + (time-proc configuration-time)))) + (if (eq? (schedule-current schedule) 'user) + (set-schedule-user! schedule (cons entry (schedule-user schedule))) + (set-schedule-system! schedule + (cons entry (schedule-system schedule)))))) + +(define* (find-next-jobs #:key (schedule %global-schedule)) + "Locate the jobs in SCHEDULE with the lowest (soonest) next-times. Return a +list where the head is the next scheduled time and the rest are all the job +entries that are to run at this time. When SCHEDULE is empty next time is +'#f'." + (let loop ((jobs + (append (schedule-system schedule) (schedule-user schedule))) + (next-time (inf)) + (next-jobs '())) + (match jobs + (() + (cons (and (finite? next-time) next-time) next-jobs)) + ((job . rest) + (let ((this-time (job:next-time job))) + (cond ((< this-time next-time) + (loop rest this-time (list job))) + ((= this-time next-time) + (loop rest next-time (cons job next-jobs))) + (else + (loop rest next-time next-jobs)))))))) + +(define* (display-schedule count #:optional (port (current-output-port)) + #:key (schedule %global-schedule)) + "Display on PORT a textual list of the next COUNT jobs to run. This +simulates the run of the job loop to display the requested information. +Since calling this procedure has the effect of mutating the job timings, the +program must exit after. Otherwise the internal data state will be left +unusable." + (unless (<= count 0) + (match (find-next-jobs #:schedule schedule) + ((#f . jobs) + #f) + ((time . jobs) + (let ((date-string (strftime "%c %z\n" (localtime time)))) + (for-each (lambda (job) + (display date-string port) + (display (job:displayable job) port) + (newline port) + (newline port) + (job:next-time-set! job ((job:next-time-function job) + (job:next-time job)))) + jobs)))) + (display-schedule (- count 1) port #:schedule schedule))) + +;;; +;;; Running jobs +;;; + +(define number-children + ;; For proper housekeeping, it is necessary to keep a record of the number + ;; of child processes we fork off to run the jobs. + (box 0)) + +(define (update-number-children! proc) + ;; Apply PROC to the value stored in 'number-children'. + (set-box! number-children (proc (unbox number-children)))) + +(define (run-job job) + "Run JOB in a separate process. The process is run as JOB user with the +environment properly set. Update the NEXT-TIME field of JOB by computing its +next value." + (if (= (primitive-fork) 0) + (dynamic-wind ;child + (const #t) + (λ () + (setgid (passwd:gid (job:user job))) + (setuid (passwd:uid (job:user job))) + (chdir (passwd:dir (job:user job))) + (modify-environment (job:environment job) (job:user job)) + ((job:action job))) + (λ () + (primitive-exit 0))) + (begin ;parent + (update-number-children! 1+) + (job:next-time-set! job ((job:next-time-function job) + (current-time)))))) + +(define (child-cleanup) + ;; Give any zombie children a chance to die, and decrease the number known + ;; to exist. + (unless (or (<= (unbox number-children) 0) + (= (car (waitpid WAIT_ANY WNOHANG)) 0)) + (update-number-children! 1-) + (child-cleanup))) + +(define* (run-job-loop #:optional fd-list #:key (schedule %global-schedule)) + ;; Loop over all job specifications, get a list of the next ones to run (may + ;; be more than one). Set an alarm and go to sleep. When we wake, run the + ;; jobs and reap any children (old jobs) that have completed. Repeat ad + ;; infinitum. + ;; + ;; Note that, if we wake ahead of time, it can only mean that a signal has + ;; been sent by a crontab job to tell us to re-read a crontab file. In this + ;; case we break out of the loop here, and let the main procedure deal with + ;; the situation (it will eventually re-call this function, thus maintaining + ;; the loop). + (cond-expand + ((or guile-3.0 guile-2.2) ;2.2 and 3.0 + (define select* select)) + (else + ;; On Guile 2.0, 'select' could throw upon EINTR or EAGAIN. + (define (select* read write except time) + (catch 'system-error + (lambda () + (select read write except time)) + (lambda args + (if (member (system-error-errno args) (list EAGAIN EINTR)) + '(() () ()) + (apply throw args))))))) + + (let/ec break + (let loop () + (match (find-next-jobs #:schedule schedule) + ((next-time . next-jobs-lst) + (let ((sleep-time (if next-time + (- next-time (current-time)) + 2000000000))) + (when (> sleep-time 0) + (match (select* fd-list '() '() sleep-time) + ((() () ()) + ;; 'select' returned an empty set, perhaps because it got + ;; EINTR or EAGAIN. It's a good time to wait for child + ;; processes. + (child-cleanup)) + (((lst ...) () ()) + ;; There's some activity so leave the loop. + (break)))) + + (for-each run-job next-jobs-lst) + (child-cleanup) + (loop))))))) diff --git a/src/mcron/config.scm.in b/src/mcron/config.scm.in new file mode 100644 index 0000000..e6a0e07 --- /dev/null +++ b/src/mcron/config.scm.in @@ -0,0 +1,42 @@ +;;;; config.scm -- variables defined at configure time +;;; Copyright © 2003 Dale Mellor +;;; Copyright © 2015, 2016 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +(define-module (mcron config)) + +(define-public config-package-name "@PACKAGE_NAME@") +(define-public config-package-version "@PACKAGE_VERSION@") +(define-public config-package-string "@PACKAGE_STRING@") +(define-public config-package-bugreport "@PACKAGE_BUGREPORT@") +(define-public config-package-url "@PACKAGE_URL@") +(define-public config-sendmail "@SENDMAIL@") + +(define-public config-spool-dir "@CONFIG_SPOOL_DIR@") +(define-public config-socket-file "@CONFIG_SOCKET_FILE@") +(define-public config-allow-file "@CONFIG_ALLOW_FILE@") +(define-public config-deny-file "@CONFIG_DENY_FILE@") +(define-public config-pid-file "@CONFIG_PID_FILE@") +(define-public config-tmp-dir "@CONFIG_TMP_DIR@") + +;;; +;;; Runtime configuration +;;; + +(define-public config-debug + ;; Trigger the display of Guile stack traces on errors. + (getenv "MCRON_DEBUG")) diff --git a/src/mcron/core.scm b/src/mcron/core.scm new file mode 100644 index 0000000..987881d --- /dev/null +++ b/src/mcron/core.scm @@ -0,0 +1,37 @@ +;;;; core.scm -- alias for (mcron base) kept for backward compatibility +;;; Copyright © 2017 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +;;; TODO: Deprecate this alias in next major version. + +(define-module (mcron core) + #:use-module (mcron base) + #:export (;; Deprecated + get-schedule) + #:re-export (add-job + remove-user-jobs + run-job-loop + clear-environment-mods + append-environment-mods + ;; Deprecated and undocumented procedures. + use-system-job-list + use-user-job-list + clear-system-jobs)) + +(define (get-schedule count) + (with-output-to-string + (lambda () (display-schedule count)))) diff --git a/src/mcron/environment.scm b/src/mcron/environment.scm new file mode 100644 index 0000000..469f6f5 --- /dev/null +++ b/src/mcron/environment.scm @@ -0,0 +1,100 @@ +;;;; environment.scm -- interact with the job process environment +;;; Copyright © 2003 Dale Mellor +;;; Copyright © 2015, 2016, 2018 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +;;;; Commentary: +;;; +;;; Define the variable current-environment-mods, and the procedures +;;; append-environment-mods (which is available to user configuration files), +;;; clear-environment-mods and modify-environment. The idea is that the +;;; current-environment-mods is a list of pairs of environment names and +;;; values, and represents the cumulated environment settings in a +;;; configuration file. When a job definition is seen in a configuration file, +;;; the current-environment-mods are copied into the internal job description, +;;; and when the job actually runs these environment modifications are applied +;;; to the UNIX environment in which the job runs. +;;; +;;;; Code: + +(define-module (mcron environment) + #:use-module (srfi srfi-111) + #:export (modify-environment + clear-environment-mods + append-environment-mods + get-current-environment-mods-copy)) + +;;; +;;; Configuration files +;;; + +(define %current-environment-mods + ;; Global variable containing an alist of environment variables populated as + ;; we parse configuration files. + (box '())) + +(define* (get-current-environment-mods-copy + #:key (environ %current-environment-mods)) + "Return a snapshot of the current environment modifications from ENVIRON. +This snapshot is a copy of the environment so that modifying it doesn't +impact ENVIRON." + ;; Each time a job is registered we should call this procedure. + (list-copy (unbox environ))) + +(define* (clear-environment-mods #:key (environ %current-environment-mods)) + "Remove all entries in the ENVIRON environment." + ;; When we start to parse a new configuration file, we want to start with a + ;; fresh environment (actually an umodified version of the pervading mcron + ;; environment) by calling this procedure. + (set-box! environ '())) + +(define* (append-environment-mods name value + #:key (environ %current-environment-mods)) + "Set NAME to VALUE in the ENVIRON environment. If VALUES is #f then NAME is +considered unset." + ;; This procedure is used implicitly by the Vixie parser, and can be used + ;; directly by users in scheme configuration files. + (set-box! environ (append (unbox environ) `((,name . ,value)))) + ;; XXX: The return value is purely for the convenience of the + ;; '(@ (mcron vixie-specification) parse-vixie-environment)'. + #t) + +;;; +;;; Job runtime +;;; + +(define (modify-environment env passwd-entry) + "Modify the environment (in the UNIX sense) by setting the variables from +ENV and some default ones which are modulated by PASSWD-ENTRY. \"LOGNAME\" +and \"USER\" environment variables can't be overided by ENV. ENV must be an +alist which associate environment variables to their value. PASSWD-ENTRY must +be an object representing user information which corresponds to a valid entry +in /etc/passwd. The return value is not specified." + (for-each (lambda (pair) (setenv (car pair) (cdr pair))) + (let ((home-dir (passwd:dir passwd-entry)) + (user-name (passwd:name passwd-entry))) + (append + ;; Default environment variables which can be overided by ENV. + `(("HOME" . ,home-dir) + ("CWD" . ,home-dir) + ("SHELL" . ,(passwd:shell passwd-entry)) + ("TERM" . #f) + ("TERMCAP" . #f)) + env + ;; Environment variables with imposed values. + `(("LOGNAME" . ,user-name) + ("USER" . ,user-name)))))) diff --git a/src/mcron/job-specifier.scm b/src/mcron/job-specifier.scm new file mode 100644 index 0000000..7eb2304 --- /dev/null +++ b/src/mcron/job-specifier.scm @@ -0,0 +1,258 @@ +;;;; job-specifier.scm -- public interface for defining jobs +;;; Copyright © 2003 Dale Mellor +;;; Copyright © 2016, 2017, 2018 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +;;;; Commentary: +;;; +;;; Define all the functions that can be used by scheme Mcron configuration +;;; files, namely the procedures for working out next times, the job procedure +;;; for registering new jobs (actually a wrapper around the base add-job +;;; function), and the procedure for declaring environment modifications. +;;; +;;;; Code: + +(define-module (mcron job-specifier) + #:use-module (ice-9 match) + #:use-module (mcron base) + #:use-module (mcron environment) + #:use-module (mcron utils) + #:use-module (mcron vixie-time) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-111) + #:re-export (append-environment-mods) + #:export (range + next-year-from next-year + next-month-from next-month + next-day-from next-day + next-hour-from next-hour + next-minute-from next-minute + next-second-from next-second + set-configuration-user + set-configuration-time + job)) + +(define* (range start end #:optional (step 1)) + "Produces a list of values from START up to (but not including) END. An +optional STEP may be supplied, and (if positive) only every step'th value will +go into the list. For example, (range 1 6 2) returns '(1 3 5)." + (let ((step* (max step 1))) + (unfold (λ (i) (>= i end)) ;predicate + identity ;value + (λ (i) (+ step* i)) ;next seed + start))) ;seed + +(define (%find-best-next current next-list) + ;; Takes a value and a list of possible next values. It returns a pair + ;; consisting of the smallest element of the NEXT-LIST, and the smallest + ;; element larger than the CURRENT value. If an example of the latter + ;; cannot be found, +INF.0 will be returned. + (define (exact-min a b) + ;; A binary implementation of 'min' which preserves the exactness of its + ;; arguments. + (if (< a b) a b)) + + (let loop ((smallest (inf)) (closest+ (inf)) (lst next-list)) + (match lst + (() (cons smallest closest+)) + ((time . rest) + (loop (exact-min time smallest) + (if (> time current) (exact-min time closest+) closest+) + rest))))) + +(define (bump-time time value-list component higher-component + set-component! set-higher-component!) + ;; Return the time corresponding to some near future hour. If hour-list is + ;; not supplied, the time returned corresponds to the start of the next hour + ;; of the day. + ;; + ;; If the hour-list is supplied the time returned corresponds to the first + ;; hour of the day in the future which is contained in the list. If all the + ;; values in the list are less than the current hour, then the time returned + ;; will correspond to the first hour in the list *on the following day*. + ;; + ;; ... except that the function is actually generalized to deal with + ;; seconds, minutes, etc., in an obvious way :-) + (if (null? value-list) + (set-component! time (1+ (component time))) + (match (%find-best-next (component time) value-list) + ((smallest . closest+) + (cond ((inf? closest+) + (set-higher-component! time (1+ (higher-component time))) + (set-component! time smallest)) + (else + (set-component! time closest+)))))) + (first (mktime time))) + +;; Set of configuration methods which use the above general function to bump +;; specific components of time to the next legitimate value. In each case, all +;; the components smaller than that of interest are taken to zero, so that for +;; example the time of the next year will be the time at which the next year +;; actually starts. + +(define* (next-year-from current-time #:optional (year-list '())) + (let ((time (localtime current-time))) + (set-tm:mon time 0) + (set-tm:mday time 1) + (set-tm:hour time 0) + (set-tm:min time 0) + (set-tm:sec time 0) + (bump-time time year-list tm:year tm:year set-tm:year set-tm:year))) + +(define* (next-month-from current-time #:optional (month-list '())) + (let ((time (localtime current-time))) + (set-tm:mday time 1) + (set-tm:hour time 0) + (set-tm:min time 0) + (set-tm:sec time 0) + (bump-time time month-list tm:mon tm:year set-tm:mon set-tm:year))) + +(define* (next-day-from current-time #:optional (day-list '())) + (let ((time (localtime current-time))) + (set-tm:hour time 0) + (set-tm:min time 0) + (set-tm:sec time 0) + (bump-time time day-list tm:mday tm:mon set-tm:mday set-tm:mon))) + +(define* (next-hour-from current-time #:optional (hour-list '())) + (let ((time (localtime current-time))) + (set-tm:min time 0) + (set-tm:sec time 0) + (bump-time time hour-list tm:hour tm:mday set-tm:hour set-tm:mday))) + +(define* (next-minute-from current-time #:optional (minute-list '())) + (let ((time (localtime current-time))) + (set-tm:sec time 0) + (bump-time time minute-list tm:min tm:hour set-tm:min set-tm:hour))) + +(define* (next-second-from current-time #:optional (second-list '())) + (let ((time (localtime current-time))) + (bump-time time second-list tm:sec tm:min set-tm:sec set-tm:min))) + +;;; The following procedures are convenient for configuration files. They are +;;; wrappers for the next-X-from functions above, by implicitly using +;;; %CURRENT-ACTION-TIME as the time argument. + +(define %current-action-time + ;; The time a job was last run, the time from which the next time to run a + ;; job must be computed. (When the program is first run, this time is set to + ;; the configuration time so that jobs run from that moment forwards.) Once + ;; we have this, we supply versions of the time computation commands above + ;; which implicitly assume this value. + (make-parameter 0)) + +(define* (next-year #:optional (args '())) + "Compute the next year from %CURRENT-ACTION-TIME parameter object." + (next-year-from (%current-action-time) args)) + +(define* (next-month #:optional (args '())) + "Compute the next month from %CURRENT-ACTION-TIME parameter object." + (next-month-from (%current-action-time) args)) + +(define* (next-day #:optional (args '())) + "Compute the next day from %CURRENT-ACTION-TIME parameter object." + (next-day-from (%current-action-time) args)) + +(define* (next-hour #:optional (args '())) + "Compute the next hour from %CURRENT-ACTION-TIME parameter object." + (next-hour-from (%current-action-time) args)) + +(define* (next-minute #:optional (args '())) + "Compute the next minute from %CURRENT-ACTION-TIME parameter object." + (next-minute-from (%current-action-time) args)) + +(define* (next-second #:optional (args '())) + "Compute the next second from %CURRENT-ACTION-TIME parameter object." + (next-second-from (%current-action-time) args)) + +;; The default user for running jobs is the current one (who invoked this +;; program). There are exceptions: when cron parses /etc/crontab the user is +;; specified on each individual line; when cron parses /var/cron/tabs/* the user +;; is derived from the filename of the crontab. These cases are dealt with by +;; mutating this variable. Note that the variable is only used at configuration +;; time; a UID is stored with each job and it is that which takes effect when +;; the job actually runs. + +(define configuration-user (box (getpw (getuid)))) + +(define configuration-time + ;; Use SOURCE_DATE_EPOCH environment variable to support reproducible tests. + (if (getenv "SOURCE_DATE_EPOCH") 0 (current-time))) + +(define (set-configuration-user user) + (set-box! configuration-user (get-user user))) +(define (set-configuration-time time) (set! configuration-time time)) + + + +;; The job function, available to configuration files for adding a job rule to +;; the system. +;; +;; Here we must 'normalize' the next-time-function so that it is always a +;; lambda function which takes one argument (the last time the job ran) and +;; returns a single value (the next time the job should run). If the input +;; value is a string this is parsed as a Vixie-style time specification, and +;; if it is a list then we arrange to eval it (but note that such lists are +;; expected to ignore the function parameter - the last run time is always +;; read from the %CURRENT-ACTION-TIME parameter object). A similar +;; normalization is applied to the action. +;; +;; Here we also compute the first time that the job is supposed to run, by +;; finding the next legitimate time from the current configuration time (set +;; right at the top of this program). + +(define* (job time-proc action #:optional displayable + #:key (user (unbox configuration-user))) + (let ((action (cond ((procedure? action) action) + ((list? action) (lambda () (primitive-eval action))) + ((string? action) (lambda () (system action))) + (else + (throw 'mcron-error 2 + "job: invalid second argument (action; should be lambda " + "function, string or list)")))) + + (time-proc + (cond ((procedure? time-proc) time-proc) + ((string? time-proc) (parse-vixie-time time-proc)) + ((list? time-proc) (lambda (current-time) + (eval time-proc + (resolve-module '(mcron job-specifier))))) + (else + (throw 'mcron-error 3 + "job: invalid first argument (next-time-function; " + "should be function, string or list)")))) + (displayable + (cond (displayable displayable) + ((procedure? action) "Lambda function") + ((string? action) action) + ((list? action) (simple-format #f "~A" action)))) + (user* (get-user user))) + (add-job (lambda (current-time) + (parameterize ((%current-action-time current-time)) + ;; Allow for daylight savings time changes. + (let* ((next (time-proc current-time)) + (gmtoff (tm:gmtoff (localtime next))) + (d (+ next + (- gmtoff + (tm:gmtoff (localtime current-time)))))) + (if (eqv? (tm:gmtoff (localtime d)) gmtoff) + d + next)))) + action + displayable + configuration-time + user*))) diff --git a/src/mcron/redirect.scm b/src/mcron/redirect.scm new file mode 100644 index 0000000..8374552 --- /dev/null +++ b/src/mcron/redirect.scm @@ -0,0 +1,194 @@ +;;;; redirect.scm -- modify job outputs +;;; Copyright © 2003 Dale Mellor +;;; Copyright © 2020 Mathieu Lirzin +;;; Copyright © 2018 宋文武 +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +;;;; Commentary: +;;; +;;; Provide the (with-mail-out action . user) procedure. This procedure runs +;;; the action in a child process, allowing the user control over the input +;;; and output (including standard error). The input is governed (only in the +;;; case of a string action) by the placing of percentage signs in the string; +;;; the first delimits the true action from the standard input, and subsequent +;;; ones denote newlines to be placed into the input. The output (if there +;;; actually is any) is controlled by the MAILTO environment variable. If +;;; this is not defined, output is e-mailed to the user passed as argument, if +;;; any, or else the owner of the action; if defined but empty then any output +;;; is sunk to /dev/null; otherwise output is e-mailed to the address held in +;;; the MAILTO variable. +;;; +;;;; Code: + +(define-module (mcron redirect) + #:use-module (ice-9 popen) + #:use-module (ice-9 regex) + #:use-module (mcron config) + #:use-module (mcron vixie-time) + #:export (with-mail-out)) + +;; An action string consists of a sequence of characters forming a command +;; executable by the shell, possibly followed by an non-escaped percentage +;; sign. The text after the percentage sign is to be fed to the command's +;; standard input, with further unescaped percents being substituted with +;; newlines. The escape character can itself be escaped. +;; +;; This regexp separates the two halves of the string, and indeed determines if +;; the second part is present. + +(define action-string-regexp (make-regexp "((\\\\%|[^%])*)%(.*)$")) + + + +;; This regexp identifies an escaped percentage sign. + +(define e-percent (make-regexp "\\\\%")) + + +;; Function to execute some action (this may be a shell command, lamdba function +;; or list of scheme procedures) in a forked process, with the input coming from +;; the string, and output (including the error output) being sent to a pipe +;; opened on a mail transport. + +(define* (with-mail-out action #:optional user #:key + (hostname (gethostname)) + (out (lambda () + (open-output-pipe config-sendmail)))) + + ;; Determine the name of the user who is to recieve the mail, looking for a + ;; name in the optional user argument, then in the MAILTO environment + ;; variable, and finally in the LOGNAME environment variable. (The case + ;; MAILTO="" is dealt with specially below.) + + (let* ((mailto (getenv "MAILTO")) + (user (cond (mailto mailto) + (user user) + (else (getenv "LOGNAME")))) + (parent->child (pipe)) + (child->parent (pipe)) + (child-pid (primitive-fork))) + + + ;; The child process. Close redundant ends of pipes, remap the standard + ;; streams, and run the action, taking care to chop off the input part of an + ;; action string. + + (if (eqv? child-pid 0) + (begin + (close (cdr parent->child)) + (close (car child->parent)) + + (dup2 (port->fdes (car parent->child)) 0) + (close (car parent->child)) + (dup2 (port->fdes (cdr child->parent)) 1) + (close (cdr child->parent)) + (dup2 1 2) + + (cond ((string? action) + (let ((match (regexp-exec action-string-regexp action))) + (system (if match + (let ((action (match:substring match 1))) + (do ((match (regexp-exec e-percent action) + (regexp-exec e-percent action))) + ((not match)) + (set! action (string-append + (match:prefix match) + "%" + (match:suffix match)))) + action) + action)))) + + ((procedure? action) (action)) + ((list? action) (primitive-eval action))) + + (primitive-exit 0))) + + + ;; The parent process. Get rid of redundant pipe ends. + + (close (car parent->child)) + (close (cdr child->parent)) + + + ;; Put stuff to child from after '%' in command line, replacing + ;; other %'s with newlines. Ugly or what? + + (if (string? action) + (let ((port (cdr parent->child)) + (match (regexp-exec action-string-regexp action))) + (if (and match + (match:substring match 3)) + (with-input-from-string (match:substring match 3) + (lambda () + (let loop () + (let ((next-char (read-char))) + (if (not (eof-object? next-char)) + (cond + ((char=? next-char #\%) + (newline port) + (loop)) + ((char=? next-char #\\) + (let ((escape (read-char))) + (if (eof-object? escape) + (display #\\ port) + (if (char=? escape #\%) + (begin + (display #\% port) + (loop)) + (begin + (display #\\ port) + (display escape port) + (loop)))))) + (else + (display next-char port) + (loop))))))))))) + + + ;; So the child process doesn't hang on to its input expecting more stuff. + + (close (cdr parent->child)) + + + ;; That's got streaming into the child's input out of the way, now we stream + ;; the child's output to a mail sink, but only if there is something there + ;; in the first place. + + (if (eof-object? (peek-char (car child->parent))) + + (read-char (car child->parent)) + + (begin + (set-current-output-port (if (and (string? mailto) + (string=? mailto "")) + (open-output-file "/dev/null") + ;; The sendmail command should read + ;; recipients from the message header. + (out))) + (set-current-input-port (car child->parent)) + (display "To: ") (display user) (newline) + (display "From: mcron") (newline) + (display (string-append "Subject: " user "@" hostname)) + (newline) + (newline) + + (do ((next-char (read-char) (read-char))) + ((eof-object? next-char)) + (display next-char)))) + + (close (car child->parent)) + + (waitpid child-pid))) diff --git a/src/mcron/scripts/cron.scm b/src/mcron/scripts/cron.scm new file mode 100644 index 0000000..b8463f6 --- /dev/null +++ b/src/mcron/scripts/cron.scm @@ -0,0 +1,162 @@ +;;;; cron -- daemon for running jobs at scheduled times +;;; Copyright © 2003, 2012 Dale Mellor +;;; Copyright © 2015, 2016, 2018 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + + +(define-module (mcron scripts cron) + #:use-module (ice-9 ftw) + #:use-module (mcron base) + #:use-module (mcron config) + #:use-module (mcron job-specifier) + #:use-module (mcron utils) + #:use-module (mcron vixie-specification) + #:use-module (srfi srfi-2) + #:export (main)) + + + +(define (delete-run-file) + "Remove the /var/run/cron.pid file so that crontab and other invocations of +cron don't get the wrong idea that a daemon is currently running. This +procedure is called from the C front-end whenever a terminal signal is +received." + (catch #t + (λ () + (delete-file config-pid-file) + (delete-file config-socket-file)) + noop) + (quit)) + + + +(define (cron-file-descriptors) + "Establish a socket to listen for updates from a crontab program, and return +a list containing the file descriptors correponding to the files read by +crontab. This requires that command-type is 'cron." + (catch #t + (λ () + (let ((sock (socket AF_UNIX SOCK_STREAM 0))) + (bind sock AF_UNIX config-socket-file) + (listen sock 5) + (list sock))) + (λ (key . args) + (delete-file config-pid-file) + (mcron-error 1 "Cannot bind to UNIX socket " config-socket-file)))) + + + +(define (process-files-in-system-directory) + "Process all the files in the crontab directory. When the job procedure is +run on behalf of the configuration files, the jobs are registered on the +system with the appropriate user. Only root should be able to perform this +operation. The permissions on the /var/cron/tabs directory enforce this." + + (define (user-entry name) + ;; Return the user database entry if NAME is valid, otherwise #f. + (false-if-exception (getpwnam name))) + + (catch #t + (λ () + (for-each + (λ (user) + (and-let* ((entry (user-entry user))) ;crontab without user? + (set-configuration-user entry) + (catch-mcron-error + (read-vixie-file (string-append config-spool-dir "/" user))))) + (scandir config-spool-dir))) + (λ (key . args) + (mcron-error 4 + "You do not have permission to access the system crontabs.")))) + +(define (%process-files noetc?) + ;; Clear MAILTO so that outputs are sent to the various users. + (setenv "MAILTO" #f) + ;; Having defined all the necessary procedures for scanning various sets of + ;; files, we perform the actual configuration of the program depending on + ;; the personality we are running as. If it is mcron, we either scan the + ;; files passed on the command line, or else all the ones in the user's + ;; .config/cron (or .cron) directory. If we are running under the cron + ;; personality, we read the /var/cron/tabs directory and also the + ;; /etc/crontab file. + (process-files-in-system-directory) + (use-system-job-list) + (catch-mcron-error + (read-vixie-file "/etc/crontab" parse-system-vixie-line)) + (use-user-job-list) + (unless noetc? + (display "\ +WARNING: cron will check for updates to /etc/crontab EVERY MINUTE. If you do +not use this file, or you are prepared to manually restart cron whenever you +make a change, then it is HIGHLY RECOMMENDED that you use the --noetc +option.\n") + (set-configuration-user "root") + (job '(- (next-minute-from (next-minute)) 6) + check-system-crontab + "/etc/crontab update checker."))) + + +;;; +;;; Entry point. +;;; + +(define (main --schedule --noetc) + (when config-debug (debug-enable 'backtrace)) + + (cond ((not (zero? (getuid))) + (mcron-error 16 + "This program must be run by the root user (and should" + " have been installed as such).")) + ((access? config-pid-file F_OK) + (mcron-error 1 + "A cron daemon is already running.\n (If you are sure" + " this is not true, remove the file\n " + config-pid-file ".)")) + (else + (cond (--schedule + => (λ (count) + (display-schedule (max 1 (string->number count))) + (exit 0)))) + (%process-files --noetc))) + + ;; Daemonize ourself. + (unless (eq? 0 (primitive-fork)) (exit 0)) + (setsid) + + ;; Set up process signal handlers, as signals are the only way to terminate + ;; the daemon and we MUST be graceful in defeat. + (for-each (λ (x) (sigaction x + (λ (sig) (catch #t + (λ () + (delete-file config-pid-file) + (delete-file config-socket-file)) + noop) + (exit EXIT_FAILURE)))) + '(SIGTERM SIGINT SIGQUIT SIGHUP)) + + ;; We can now write the PID file. + (with-output-to-file config-pid-file + (λ () (display (getpid)) (newline))) + + ;; Forever execute the 'run-job-loop', and when it drops out (can + ;; only be because a message has come in on the socket) we + ;; process the socket request before restarting the loop again. + (catch-mcron-error + (let ((fdes-list (cron-file-descriptors))) + (while #t + (run-job-loop fdes-list) + (unless (null? fdes-list) (process-update-request fdes-list)))))) diff --git a/src/mcron/scripts/crontab.scm b/src/mcron/scripts/crontab.scm new file mode 100644 index 0000000..0c5c47f --- /dev/null +++ b/src/mcron/scripts/crontab.scm @@ -0,0 +1,196 @@ +;;;; crontab -- edit user's cron tabs +;;; Copyright © 2003, 2004 Dale Mellor <> +;;; Copyright © 2016 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +(define-module (mcron scripts crontab) + #:use-module (ice-9 rdelim) + #:use-module (mcron config) + #:use-module (mcron utils) + #:use-module (mcron vixie-specification) + #:export (main)) + +(define (hit-server user-name) + "Tell the running cron daemon that the user corresponding to +USER-NAME has modified his crontab. USER-NAME is written to the +'/var/cron/socket' UNIX socket." + (catch #t + (lambda () + (let ((socket (socket AF_UNIX SOCK_STREAM 0))) + (connect socket AF_UNIX config-socket-file) + (display user-name socket) + (close socket))) + (lambda (key . args) + (display "Warning: a cron daemon is not running.\n")))) + + + +;; Display the prompt and wait for user to type his choice. Return #t if the +;; answer begins with 'y' or 'Y', return #f if it begins with 'n' or 'N', +;; otherwise ask again. +(define (get-yes-no prompt . re-prompt) + (unless (null? re-prompt) + (display "Please answer y or n.\n")) + (display (string-append prompt " ")) + (let ((r (read-line))) + (if (not (string-null? r)) + (case (string-ref r 0) + ((#\y #\Y) #t) + ((#\n #\N) #f) + (else (get-yes-no prompt #t))) + (get-yes-no prompt #t)))) + + + +(define (in-access-file? file name) + "Scan FILE which should contain one user name per line (such as +'/var/cron/allow' and '/var/cron/deny'). Return #t if NAME is in there, and +#f otherwise. if FILE cannot be opened, a error is signaled." + (catch #t + (lambda () + (with-input-from-file file + (lambda () + (let loop ((input (read-line))) + (cond ((eof-object? input) + #f) + ((string=? input name) + #t) + (else + (loop (read-line)))))))) + (const '()))) + + +;;; +;;; Entry point. +;;; + +(define (main --user --edit --list --remove files) + (when config-debug (debug-enable 'backtrace)) + (let ((crontab-real-user + ;; This program should have been installed SUID root. Here we get + ;; the passwd entry for the real user who is running this program. + (passwd:name (getpw (getuid))))) + + ;; If the real user is not allowed to use crontab due to the + ;; /var/cron/allow and/or /var/cron/deny files, bomb out now. + (if (or (eq? (in-access-file? config-allow-file crontab-real-user) #f) + (eq? (in-access-file? config-deny-file crontab-real-user) #t)) + (mcron-error 6 "Access denied by system operator.")) + + ;; Check that no more than one of the mutually exclusive options are + ;; being used. + (when (< 1 (+ (if --edit 1 0) (if --list 1 0) (if --remove 1 0))) + (mcron-error 7 "Only one of options -e, -l or -r can be used.")) + + ;; Check that a non-root user is trying to read someone else's files. + (when (and (not (zero? (getuid))) --user) + (mcron-error 8 "Only root can use the -u option.")) + + (letrec* (;; Iff the --user option is given, the crontab-user may be + ;; different from the real user. + (crontab-user (or --user crontab-real-user)) + ;; So now we know which crontab file we will be manipulating. + (crontab-file + (string-append config-spool-dir "/" crontab-user))) + ;; There are four possible sub-personalities to the crontab + ;; personality: list, remove, edit and replace (when the user uses no + ;; options but supplies file names on the command line). + (cond + ;; In the list personality, we simply open the crontab and copy it + ;; character-by-character to the standard output. If anything goes + ;; wrong, it can only mean that this user does not have a crontab + ;; file. + (--list + (catch #t + (λ () + (with-input-from-file crontab-file + (λ () + (do ((input (read-char) (read-char))) + ((eof-object? input)) + (display input))))) + (λ (key . args) + (display (string-append "No crontab for " + crontab-user + " exists.\n"))))) + + ;; In the edit personality, we determine the name of a temporary file + ;; and an editor command, copy an existing crontab file (if it is + ;; there) to the temporary file, making sure the ownership is set so + ;; the real user can edit it; once the editor returns we try to read + ;; the file to check that it is parseable (but do nothing more with + ;; the configuration), and if it is okay (this program is still + ;; running!) we move the temporary file to the real crontab, wake the + ;; cron daemon up, and remove the temporary file. If the parse fails, + ;; we give user a choice of editing the file again or quitting the + ;; program and losing all changes made. + (--edit + (let ((temp-file (string-append config-tmp-dir + "/crontab." + (number->string (getpid))))) + (catch #t + (λ () (copy-file crontab-file temp-file)) + (λ (key . args) (with-output-to-file temp-file noop))) + (chown temp-file (getuid) (getgid)) + (let retry () + (system (string-append + (or (getenv "VISUAL") (getenv "EDITOR") "vi") + " " + temp-file)) + (catch 'mcron-error + (λ () (read-vixie-file temp-file)) + (λ (key exit-code . msg) + (apply mcron-error 0 msg) + (if (get-yes-no "Edit again?") + (retry) + (begin + (mcron-error 0 "Crontab not changed") + (primitive-exit 0)))))) + (copy-file temp-file crontab-file) + (delete-file temp-file) + (hit-server crontab-user))) + + ;; In the remove personality we simply make an effort to delete the + ;; crontab and wake the daemon. No worries if this fails. + (--remove (catch #t (λ () (delete-file crontab-file) + (hit-server crontab-user)) + noop)) + + ;; XXX: This comment is wrong. + ;; In the case of the replace personality we loop over all the + ;; arguments on the command line, and for each one parse the file to + ;; make sure it is parseable (but subsequently ignore the + ;; configuration), and all being well we copy it to the crontab + ;; location; we deal with the standard input in the same way but + ;; different. :-) In either case the server is woken so that it will + ;; read the newly installed crontab. + ((not (null? files)) + (let ((input-file (car files))) + (catch-mcron-error + (if (string=? input-file "-") + (let ((input-string (read-string))) + (read-vixie-port (open-input-string input-string)) + (with-output-to-file crontab-file + (λ () (display input-string)))) + (begin + (read-vixie-file input-file) + (copy-file input-file crontab-file)))) + (hit-server crontab-user))) + + ;; The user is being silly. The message here is identical to the one + ;; Vixie cron used to put out, for total compatibility. + (else (mcron-error 15 + "usage error: file name must be specified for replace.")))))) diff --git a/src/mcron/scripts/mcron.scm b/src/mcron/scripts/mcron.scm new file mode 100644 index 0000000..48efae5 --- /dev/null +++ b/src/mcron/scripts/mcron.scm @@ -0,0 +1,109 @@ +;;;; mcron -- run jobs at scheduled times +;;; Copyright © 2003, 2012, 2020 Dale Mellor <> +;;; Copyright © 2015, 2016, 2018 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +(define-module (mcron scripts mcron) + #:use-module (ice-9 ftw) + #:use-module (ice-9 local-eval) + #:use-module (ice-9 rdelim) + #:use-module (mcron base) + #:use-module (mcron config) + #:use-module (mcron job-specifier) ; For user/system files. + #:use-module (mcron utils) + #:use-module (mcron vixie-specification) + #:export (main)) + + + +(define process-user-file + (let ((guile-regexp (make-regexp "\\.gui(le)?$")) + (vixie-regexp (make-regexp "\\.vix(ie)?$"))) + (lambda* (file-name #:optional guile-syntax? #:key (input "guile")) + "Process FILE-NAME according its extension. When GUILE-SYNTAX? is TRUE, +force guile syntax usage. If FILE-NAME format is not recognized, it is +silently ignored." + (cond ((string=? "-" file-name) + (if (string=? input "vixie") + (read-vixie-port (current-input-port)) + (eval-string (read-string) + (resolve-module '(mcron job-specifier))))) + ((or guile-syntax? (regexp-exec guile-regexp file-name)) + (eval-string (read-delimited "" (open-input-file file-name)) + (resolve-module '(mcron job-specifier)))) + ((regexp-exec vixie-regexp file-name) + (read-vixie-file file-name)))))) + + + +(define (process-files-in-user-directory input-type) + "Process files in $XDG_CONFIG_HOME/cron and/or ~/.cron directories (if +$XDG_CONFIG_HOME is not defined uses ~/.config/cron instead)." + (let ((errors 0) + (home-directory (passwd:dir (getpw (getuid))))) + (map (λ (dir) + (catch #t + (λ () + (for-each (λ (file) + (process-user-file (string-append dir "/" file) + #:input input-type)) + (scandir dir))) + (λ (key . args) + (set! errors (1+ errors))))) + (list (string-append home-directory "/.cron") + (string-append (or (getenv "XDG_CONFIG_HOME") + (string-append home-directory "/.config")) + "/cron"))) + (when (eq? 2 errors) + (mcron-error 13 + "Cannot read files in your ~/.config/cron (or ~/.cron) directory.")))) + + + +(define (%process-files files input-type) + (if (null? files) + (process-files-in-user-directory input-type) + (for-each (λ (file) (process-user-file file #t)) files))) + + +;;; +;;; Entry point. +;;; + +(define (main --daemon --schedule --stdin file-list) + + (when config-debug (debug-enable 'backtrace)) + (%process-files file-list (or --stdin "guile")) + (cond (--schedule + => (λ (count) + (display-schedule + (max 1 (inexact->exact (floor (string->number count))))) + (exit 0))) + (--daemon (case (primitive-fork) ((0) (setsid)) + (else (exit 0))))) + + ;; Forever execute the 'run-job-loop', and when it drops out (can only be + ;; because a message has come in on the socket) we process the socket + ;; request before restarting the loop again. + (catch-mcron-error + (let ((fdes-list '())) + (while #t + (run-job-loop fdes-list) + ;; we can also drop out of run-job-loop because of a SIGCHLD, + ;; so must test FDES-LIST. + (unless (null? fdes-list) + (process-update-request fdes-list)))))) diff --git a/src/mcron/utils.scm b/src/mcron/utils.scm new file mode 100644 index 0000000..d8b1dc7 --- /dev/null +++ b/src/mcron/utils.scm @@ -0,0 +1,104 @@ +;;;; utils.scm -- helper procedures +;;; Copyright © 2003, 2012 Dale Mellor +;;; Copyright © 2015, 2016, 2018 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +(define-module (mcron utils) + #:use-module (ice-9 rdelim) + #:use-module (mcron config) + #:use-module (mcron base) + #:use-module (mcron job-specifier) + #:use-module (mcron vixie-specification) + #:export (catch-mcron-error + mcron-error + show-version + show-package-information + process-update-request + get-user) + #:re-export (read-string)) + +(define (mcron-error exit-code . rest) + "Print an error message (made up from the parts of REST), and if the +EXIT-CODE error is fatal (present and non-zero) then exit to the system with +EXIT-CODE." + (with-output-to-port (current-error-port) + (lambda () + (for-each display (cons "mcron: " rest)) + (newline))) + (when (and exit-code (not (eq? exit-code 0))) + (primitive-exit exit-code))) + +(define-syntax-rule (catch-mcron-error exp ...) + "Evaluate EXP .... if an 'mcron-error exception occurs, print its diagnostics +and exit with its error code." + (catch 'mcron-error + (lambda () exp ...) + (lambda (key exit-code . msg) + (apply mcron-error exit-code msg)))) + +(define (show-version command) + "Display version information for COMMAND and quit." + (let* ((name config-package-name) + (short-name (cadr (string-split name #\space))) + (version config-package-version)) + (simple-format #t "~a (~a) ~a +Copyright (C) 2020 the ~a authors. +License GPLv3+: GNU GPL version 3 or later +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law.\n" + command name version short-name))) + +(define (show-package-information) + "Display where to get help and send bug reports." + (simple-format #t "\nReport bugs to: ~a. +~a home page: <~a> +General help using GNU software: \n" + config-package-bugreport + config-package-name + config-package-url)) + +(define (process-update-request fdes-list) + "Read a user name from the socket, dealing with the /etc/crontab special +case, remove all the user's jobs from the job list, and then re-read the +user's updated file. In the special case drop all the system jobs and re-read +the /etc/crontab file. This function should be called whenever a message +comes in on the above socket." + (let* ((sock (car (accept (car fdes-list)))) + (user-name (read-line sock))) + (close sock) + (set-configuration-time (current-time)) + (catch-mcron-error + (if (string=? user-name "/etc/crontab") + (begin + (clear-system-jobs) + (use-system-job-list) + (read-vixie-file "/etc/crontab" parse-system-vixie-line) + (use-user-job-list)) + (let ((user (getpw user-name))) + (remove-user-jobs user) + (set-configuration-user user) + (read-vixie-file (string-append config-spool-dir "/" user-name))))))) + +(define (get-user spec) + "Return the passwd entry corresponding to SPEC. If SPEC is passwd entry +then return it. If SPEC is not a valid specification throw an exception." + (cond ((or (string? spec) (integer? spec)) + (getpw spec)) + ((vector? spec) ;assume a user passwd entry + spec) + (else + (throw 'invalid-user-specification spec)))) diff --git a/src/mcron/vixie-specification.scm b/src/mcron/vixie-specification.scm new file mode 100644 index 0000000..cf2679a --- /dev/null +++ b/src/mcron/vixie-specification.scm @@ -0,0 +1,205 @@ +;;;; vixie-specification.scm -- read Vixie-sytle configuration file +;;; Copyright © 2003 Dale Mellor +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +;;;; Commentary: +;;; +;;; Methods for reading a complete Vixie-style configuration file, either from +;;; a real file or an already opened port. It also exposes the method for +;;; parsing the time-specification part of a Vixie string, so that these can +;;; be used to form the next-time-function of a job in a Guile configuration +;;; file. +;;; +;;;; Code: + +(define-module (mcron vixie-specification) + #:use-module (ice-9 regex) + #:use-module (ice-9 rdelim) + #:use-module (mcron base) + #:use-module (mcron config) + #:use-module (mcron job-specifier) + #:use-module (mcron redirect) + #:use-module (mcron vixie-time) + #:use-module (srfi srfi-1) + #:export (parse-user-vixie-line + parse-system-vixie-line + read-vixie-port + read-vixie-file + check-system-crontab)) + +;; A line in a Vixie-style crontab file which gives a command specification +;; carries two pieces of information: a time specification consisting of five +;; space-separated items, and a command which is also separated from the time +;; specification by a space. The line is broken into the two components, and the +;; job procedure run to add the two pieces of information to the job list (this +;; will in turn use the above function to turn the time specification into a +;; function for computing future run times of the command). + +(define parse-user-vixie-line-regexp + (make-regexp "^[[:space:]]*(([^[:space:]]+[[:space:]]+){5})(.*)$")) + +(define (parse-user-vixie-line line) + (let ((match (regexp-exec parse-user-vixie-line-regexp line))) + (if (not match) + (throw 'mcron-error 10 "Bad job line in Vixie file.")) + (job (match:substring match 1) + (lambda () (with-mail-out (match:substring match 3))) + (match:substring match 3)))) + + + +;; The case of reading a line from /etc/crontab is similar to above but the user +;; ID appears in the sixth field, before the action. + +(define parse-system-vixie-line-regexp + (make-regexp (string-append "^[[:space:]]*(([^[:space:]]+[[:space:]]+){5})" + "([[:alpha:]][[:alnum:]_]*)[[:space:]]+(.*)$"))) + +(define (parse-system-vixie-line line) + (let ((match (regexp-exec parse-system-vixie-line-regexp line))) + (if (not match) + (throw 'mcron-error 11 "Bad job line in /etc/crontab.")) + (let ((user (match:substring match 3))) + (set-configuration-user user) + (job (match:substring match 1) + (lambda () (with-mail-out (match:substring match 4) + user)) + (match:substring match 4))))) + + + +;; Procedure to act on an environment variable specification in a Vixie-style +;; configuration file, by adding an entry to the alist above. Returns #t if the +;; operation was successful, #f if the line could not be interpreted as an +;; environment specification. + +(define parse-vixie-environment-regexp1 + (make-regexp + "^[ \t]*([[:alpha:]_][[:alnum:]_]*)[ \t]*=[ \t]*\"(.*)\"[ \t]*$")) +(define parse-vixie-environment-regexp2 + (make-regexp + "^[ \t]*([[:alpha:]_][[:alnum:]_]*)[ \t]*=[ \t]*'(.*)'[ \t]*$")) +(define parse-vixie-environment-regexp3 + (make-regexp + "^[ \t]*([[:alpha:]_][[:alnum:]_]*)[ \t]*=[ \t]*(.*[^ \t])[ \t]*$")) +(define parse-vixie-environment-regexp4 + (make-regexp + "^[ \t]*([[:alpha:]_][[:alnum:]_]*)[ \t]*=[ \t]*$")) + + +(define (parse-vixie-environment string) + (let ((match (or (regexp-exec parse-vixie-environment-regexp1 string) + (regexp-exec parse-vixie-environment-regexp2 string) + (regexp-exec parse-vixie-environment-regexp3 string)))) + (if match + (append-environment-mods (match:substring match 1) + (match:substring match 2)) + (and=> (regexp-exec parse-vixie-environment-regexp4 string) + (λ (match) + (append-environment-mods (match:substring match 1) #f)))))) + +;; The next procedure reads an entire Vixie-style file. For each line in the +;; file there are three possibilities (after continuation lines have been +;; appended): the line is blank or contains only a comment, the line contains an +;; environment modifier which will be handled in the mcron environment module, +;; or the line contains a command specification in which case we use the +;; procedure above to add an entry to the internal job list. +;; +;; Note that the environment modifications are cleared, so that there is no +;; interference between crontab files (this might lead to unpredictable +;; behaviour because the order in which crontab files are processed, if there is +;; more than one, is generally undefined). + +(define read-vixie-file-comment-regexp + (make-regexp "^[[:space:]]*(#.*)?$")) + + +(define (read-vixie-port port . parse-vixie-line) + (clear-environment-mods) + (if port + (let ((parse-vixie-line + (if (null? parse-vixie-line) parse-user-vixie-line + (car parse-vixie-line)))) + (do ((line (read-line port) (read-line port)) + (line-number 1 (1+ line-number))) + ((eof-object? line)) + + (let ((report-line line-number)) + ;; If the line ends with \, append the next line. + (while (and (>= (string-length line) 1) + (char=? (string-ref line + (- (string-length line) 1)) + #\\)) + (let ((next-line (read-line port))) + (if (eof-object? next-line) + (set! next-line "")) + (set! line-number (1+ line-number)) + (set! line + (string-append + (substring line 0 (- (string-length line) 1)) + next-line)))) + + (catch 'mcron-error + (lambda () + ;; Consider the three cases mentioned in the description. + (or (regexp-exec read-vixie-file-comment-regexp line) + (parse-vixie-environment line) + (parse-vixie-line line))) + (lambda (key exit-code . msg) + (throw 'mcron-error exit-code + (apply string-append + (number->string report-line) + ": " + msg))))))))) + + + +;; If a file cannot be opened, we must silently ignore it because it may have +;; been removed by crontab. However, if the file is there it must be parseable, +;; otherwise the error must be propagated to the caller. + +(define (read-vixie-file file-path . parse-vixie-line) + (let ((port #f)) + (catch #t (lambda () (set! port (open-input-file file-path))) + (lambda (key . args) (set! port #f))) + (if port + (catch 'mcron-error + (lambda () + (if (null? parse-vixie-line) + (read-vixie-port port) + (read-vixie-port port (car parse-vixie-line))) + (close port)) + (lambda (key exit-code . msg) + (close port) + (throw 'mcron-error exit-code + (apply string-append file-path ":" msg))))))) + + +;; A procedure which determines if the /etc/crontab file has been recently +;; modified, and, if so, signals the main routine to re-read the file. We run +;; under the with-mail-to command so that the process runs as a child, +;; preventing lockup. If cron is supposed to check for updates to /etc/crontab, +;; then this procedure will be called about 5 seconds before every minute. + +(define (check-system-crontab) + (with-mail-out (lambda () + (let ((mtime (stat:mtime (stat "/etc/crontab")))) + (if (> mtime (- (current-time) 60)) + (let ((socket (socket AF_UNIX SOCK_STREAM 0))) + (connect socket AF_UNIX config-socket-file) + (display "/etc/crontab" socket) + (close socket))))))) diff --git a/src/mcron/vixie-time.scm b/src/mcron/vixie-time.scm new file mode 100644 index 0000000..0301434 --- /dev/null +++ b/src/mcron/vixie-time.scm @@ -0,0 +1,374 @@ +;;;; vixie-time.scm -- parse Vixie-style times +;;; Copyright © 2003 Dale Mellor +;;; Copyright © 2018, 2020 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +(define-module (mcron vixie-time) + #:use-module (ice-9 match) + #:use-module (ice-9 regex) + #:use-module (mcron job-specifier) + #:use-module (srfi srfi-1) + #:export (parse-vixie-time)) + +;; In Vixie-style time specifications three-letter symbols are allowed to stand +;; for the numbers corresponding to months and days of the week. We deal with +;; this by making a textual substitution early on in the processing of the +;; strings. +;; +;; We start by defining, once and for all, a list of cons cells consisting of +;; regexps which will match the symbols - which allow an arbitrary number of +;; other letters to appear after them (so that the user can optionally complete +;; the month and day names; this is an extension of Vixie) - and the value which +;; is to replace the symbol. +;; +;; The procedure then takes a string, and then for each symbol in the +;; parse-symbols list attempts to locate an instance and replace it with an +;; ASCII representation of the value it stands for. The procedure returns the +;; modified string. (Note that each symbol can appear only once, which meets the +;; Vixie specifications technically but still allows silly users to mess things +;; up). + +(define parse-symbols + (map (lambda (symbol-cell) + (cons (make-regexp (string-append (car symbol-cell) "[[:alpha:]]*") + regexp/icase) + (cdr symbol-cell))) + '(("jan" . "0") ("feb" . "1") ("mar" . "2") ("apr" . "3") + ("may" . "4") ("jun" . "5") ("jul" . "6") ("aug" . "7") + ("sep" . "8") ("oct" . "9") ("nov" . "10") ("dec" . "11") + + ("sun" . "0") ("mon" . "1") ("tue" . "2") ("wed" . "3") + ("thu" . "4") ("fri" . "5") ("sat" . "6") ))) + +(define (vixie-substitute-parse-symbols string) + (for-each (lambda (symbol-cell) + (let ((match (regexp-exec (car symbol-cell) string))) + (if match + (set! string (string-append (match:prefix match) + (cdr symbol-cell) + (match:suffix match)))))) + parse-symbols) + string) + + + +;; A Vixie time specification is made up of a space-separated list of elements, +;; and the elements consist of a comma-separated list of subelements. The +;; procedure below takes a string holding a subelement, which should have no +;; spaces or symbols (see above) in it, and returns a list of all values which +;; that subelement indicates. There are five distinct cases which must be dealt +;; with: [1] a single '*' which returns a list of all values; [2] a '*' followed +;; by a step specifier; [3] a range and step specifier; [4] a range; and [5] a +;; single number. +;; +;; To perform the computation required for the '*' cases, we need to pass the +;; limit of the allowable range for this subelement as the third argument. As +;; days of the month start at 1 while all the other time components start at 0, +;; we must pass the base of the range to deal with this case also. + +(define parse-vixie-subelement-regexp + (make-regexp "^([[:digit:]]+)(-([[:digit:]]+)(/([[:digit:]]+))?)?$")) + +(define (parse-vixie-subelement string base limit) + (if (char=? (string-ref string 0) #\*) + (range base limit (if (> (string-length string) 1) + (string->number (substring string 2)) ;; [2] + 1)) ;; [1] + (let ((match (regexp-exec parse-vixie-subelement-regexp string))) + (cond ((not match) + (throw 'mcron-error 9 + "Bad Vixie-style time specification.")) + ((match:substring match 5) + (range (string->number (match:substring match 1)) + (+ 1 (string->number (match:substring match 3))) + (string->number (match:substring match 5)))) ;; [3] + ((match:substring match 3) + (range (string->number (match:substring match 1)) + (+ 1 (string->number (match:substring match 3))))) ;; [4] + (else + (list (string->number (match:substring match 1)))))))) ;; [5] + + + +;; A Vixie element contains the entire specification, without spaces or symbols, +;; of the acceptable values for one of the time components (minutes, hours, +;; days, months, week days). Here we break the comma-separated list into +;; subelements, and process each with the procedure above. The return value is a +;; list of all the valid values of all the subcomponents. +;; +;; The second and third arguments are the base and upper limit on the values +;; that can be accepted for this time element. +;; +;; The effect of the 'apply append' is to merge a list of lists into a single +;; list. + +(define (parse-vixie-element string base limit) + (apply append + (map (lambda (sub-element) + (parse-vixie-subelement sub-element base limit)) + (string-tokenize string (char-set-complement (char-set #\,)))))) + +(define (interpolate-weekdays mday-list wday-list month year) + "Given a list of days in the month MDAY-LIST and a list of days in the week +WDAY-LIST, return an augmented list of days in the month with weekdays +accounted for." + (let ((t (localtime 0))) + (set-tm:mday t 1) + (set-tm:mon t month) + (set-tm:year t year) + (let ((first-day (tm:wday (cdr (mktime t))))) + (define (range-wday wday) + (let* ((first (- wday first-day)) + (first* (if (negative? first) (+ 7 first) first))) + (range (1+ first*) 32 7))) + (apply append mday-list (map range-wday wday-list))))) + +;; Return the number of days in a month. Fix up a tm object for the zero'th day +;; of the next month, rationalize the object and extract the day. + +(define (days-in-month month year) + (let ((t (localtime 0))) (set-tm:mday t 0) + (set-tm:mon t (+ month 1)) + (set-tm:year t year) + (tm:mday (cdr (mktime t))))) + + + +;; We will be working with a list of time-spec's, one for each element of a time +;; specification (minute, hour, ...). Each time-spec holds three pieces of +;; information: a list of acceptable values for this time component, a procedure +;; to get the component from a tm object, and a procedure to set the component +;; in a tm object. + +(define (time-spec:list time-spec) (vector-ref time-spec 0)) +(define (time-spec:getter time-spec) (vector-ref time-spec 1)) +(define (time-spec:setter time-spec) (vector-ref time-spec 2)) + + + +;; This procedure modifies the time tm object by setting the component referred +;; to by the time-spec object to its next acceptable value. If this value is not +;; greater than the original (because we have wrapped around the top of the +;; acceptable values list), then the function returns #t, otherwise it returns +;; #f. Thus, if the return value is true then it will be necessary for the +;; caller to increment the next coarser time component as well. +;; +;; The first part of the let block is a concession to humanity; the procedure is +;; simply unreadable without all of these aliases. + +(define (increment-time-component time time-spec) + (let ((time-list (time-spec:list time-spec)) + (getter (time-spec:getter time-spec)) + (setter (time-spec:setter time-spec)) + (find-best-next (@@ (mcron job-specifier) %find-best-next))) + (match (find-best-next (getter time) time-list) + ((smallest . closest+) + (let ((infinite (inf? closest+))) + (if infinite + (setter time smallest) + (setter time closest+)) + infinite))))) + +;; There now follows a set of procedures for adjusting an element of time, +;; i.e. taking it to the next acceptable value. In each case, the head of the +;; time-spec-list is expected to correspond to the component of time in +;; question. If the adjusted value wraps around its allowed range, then the next +;; biggest element of time must be adjusted, and so on. + +;; There is no specification allowed for the year component of +;; time. Therefore, if we have to make an adjustment (presumably because a +;; monthly adjustment has wrapped around the top of its range) we can simply +;; go to the next year. + +(define (nudge-year! time) + (set-tm:year time (+ (tm:year time) 1))) + + +;; We nudge the month by finding the next allowable value, and if it wraps +;; around we also nudge the year. The time-spec-list will have time-spec +;; objects for month and weekday. + +(define (nudge-month! time time-spec-list) + (and (increment-time-component time (car time-spec-list)) + (nudge-year! time))) + + +;; Try to increment the day component of the time according to the combination +;; of the mday-list and the wday-list. If this wraps around the range, or if +;; this falls outside the current month (31st February, for example), then +;; bump the month, set the day to zero, and recurse on this procedure to find +;; the next day in the new month. +;; +;; The time-spec-list will have time-spec entries for mday, month, and +;; weekday. + +(define (nudge-day! time time-spec-list) + (if (or (increment-time-component + time + (vector + (interpolate-weekdays (time-spec:list (car time-spec-list)) + (time-spec:list (caddr time-spec-list)) + (tm:mon time) + (tm:year time)) + tm:mday + set-tm:mday)) + (> (tm:mday time) (days-in-month (tm:mon time) (tm:year time)))) + (begin + (nudge-month! time (cdr time-spec-list)) + (set-tm:mday time 0) + (nudge-day! time time-spec-list)))) + + + +;; The hour is bumped to the next accceptable value, and the day is bumped if +;; the hour wraps around. +;; +;; The time-spec-list holds specifications for hour, mday, month and weekday. + +(define (nudge-hour! time time-spec-list) + (and (increment-time-component time (car time-spec-list)) + (nudge-day! time (cdr time-spec-list)))) + + + +;; The minute is bumped to the next accceptable value, and the hour is bumped +;; if the minute wraps around. +;; +;; The time-spec-list holds specifications for minute, hour, day-date, month +;; and weekday. + +(define (nudge-min! time time-spec-list) + (and (increment-time-component time (car time-spec-list)) + (nudge-hour! time (cdr time-spec-list)))) + + + + +;; This is a procedure which returns a procedure which computes the next time a +;; command should run after the current time, based on the information in the +;; Vixie-style time specification. +;; +;; We start by computing a list of time-spec objects (described above) for the +;; minute, hour, date, month, year and weekday components of the overall time +;; specification [1]. Special care is taken to produce proper values for +;; fields 2 and 4: according to Vixie specification "If both fields are +;; restricted (ie, aren't *), the command will be run when _either_ field +;; matches the current time." This implies that if one of these fields is *, +;; while the other is not, its value should be '() [0], otherwise +;; interpolate-weekdays below will produce incorrect results. + +;; When we create the return procedure, it is this list to +;; which references to a time-spec-list will be bound. It will be used by the +;; returned procedure [3] to compute the next time a function should run. Any +;; 7's in the weekday component of the list (the last one) are folded into 0's +;; (both values represent sunday) [2]. Any 0's in the month-day component of the +;; list are removed (this allows a solitary zero to be used to indicate that +;; jobs should only run on certain days of the _week_) [2.1]. +;; +;; The returned procedure itself:- +;; +;; Starts by obtaining the current broken-down time [4], and fixing it to +;; ensure that it is an acceptable value, as follows. Each component from the +;; biggest down is checked for acceptability, and if it is not acceptable it +;; is bumped to the next acceptable value (this may cause higher components to +;; also be bumped if there is range wrap-around) and all the lower components +;; are set to -1 so that it can successfully be bumped up to zero if this is +;; an allowed value. The -1 value will be bumped up subsequently to an allowed +;; value [5]. +;; +;; Once it has been asserted that the current time is acceptable, or has been +;; adjusted to one minute before the next acceptable time, the minute +;; component is then bumped to the next acceptable time, which may ripple +;; through the higher components if necessary [6]. We now have the next time +;; the command needs to run. +;; +;; The new time is then converted back into a UNIX time and returned [7]. + +(define (parse-vixie-time string) + (let ((tokens (string-tokenize (vixie-substitute-parse-symbols string)))) + (cond + ((> (length tokens) 5) + (throw 'mcron-error 9 + "Too many fields in Vixie-style time specification")) + ((< (length tokens) 5) + (throw 'mcron-error 9 + "Not enough fields in Vixie-style time specification"))) + (match (map-in-order + (λ (x) + (vector + (let* ((n (vector-ref x 0)) + (tok (list-ref tokens n))) + (cond + ((and (= n 4) + (string=? tok "*") + (not (string=? + (list-ref tokens 2) "*"))) + '()) + ((and (= n 2) + (string=? tok "*") + (not (string=? + (list-ref tokens 4) "*"))) + '()) + (else + (parse-vixie-element + tok + (vector-ref x 1) + (vector-ref x 2))))) ; [0] + (vector-ref x 3) + (vector-ref x 4))) + ;; token range-top+1 getter setter + `( #( 0 0 60 ,tm:min ,set-tm:min ) + #( 1 0 24 ,tm:hour ,set-tm:hour ) + #( 2 1 32 ,tm:mday ,set-tm:mday ) + #( 3 0 12 ,tm:mon ,set-tm:mon ) + #( 4 0 7 ,tm:wday ,set-tm:wday ))) ;; [1] + ((and time-spec-list (min hour day month wday)) + (vector-set! wday + 0 + (map (lambda (time-spec) + (if (eqv? time-spec 7) 0 time-spec)) + (vector-ref wday 0))) ;; [2] + + (vector-set! day + 0 + (remove (lambda (d) (eqv? d 0)) + (vector-ref day 0))) ;; [2.1] + + (λ (current-time) ;; [3] + (let ((time (localtime current-time))) ;; [4] + (unless (member (tm:mon time) (time-spec:list month)) + (nudge-month! time (cdddr time-spec-list)) + (set-tm:mday time 0)) + (when (or (eqv? (tm:mday time) 0) + (not (member (tm:mday time) + (interpolate-weekdays + (time-spec:list day) + (time-spec:list wday) + (tm:mon time) + (tm:year time))))) + (nudge-day! time (cddr time-spec-list)) + (set-tm:hour time -1)) + (unless (member (tm:hour time) + (time-spec:list hour)) + (nudge-hour! time (cdr time-spec-list)) + (set-tm:min time -1)) ;; [5] + + (set-tm:sec time 0) + (nudge-min! time time-spec-list) ;; [6] + (first (mktime time)))))))) ;; [7] + + diff --git a/tests/base.scm b/tests/base.scm new file mode 100644 index 0000000..eb9e11a --- /dev/null +++ b/tests/base.scm @@ -0,0 +1,215 @@ +;;;; base.scm -- tests for (mcron base) module +;;; Copyright © 2018 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +(use-modules (srfi srfi-64) + (srfi srfi-111) + (mcron base)) + +(test-begin "base") + +(setlocale LC_ALL "C") +(setenv "TZ" "UTC0") + +;;; Import private procedures. +(define make-schedule (@@ (mcron base) make-schedule)) +(define schedule-current (@@ (mcron base) schedule-current)) +(define schedule-user (@@ (mcron base) schedule-user)) +(define schedule-system (@@ (mcron base) schedule-system)) +(define make-job (@@ (mcron base) make-job)) +(define find-next-jobs (@@ (mcron base) find-next-jobs)) + +(define %user0 #("user0" "x" 0 0 "user0" "/var/empty" "/bin/sh")) +(define %user1 #("user1" "x" 1 1 "user1" "/var/empty" "/bin/sh")) + +(define* (make-dummy-job #:optional (displayable "dummy") + #:key + (user (getpw)) + (time-proc 1+) + (action (λ () "dummy action")) + (environment '()) + (next-time 0)) + (make-job user time-proc action environment displayable next-time)) + +;;; Check 'use-system-job-list' and 'use-user-job-list' effect +(let ((schdl (make-schedule '() '() 'user))) + (use-system-job-list #:schedule schdl) + (test-eq "use-system-job-list" + 'system + (schedule-current schdl)) + + (use-user-job-list #:schedule schdl) + (test-eq "use-user-job-list" + 'user + (schedule-current schdl))) + +;;; Check that 'remove-user-jobs' with only one type of user job clears the +;;; schedule. +(let* ((job (make-dummy-job #:user %user0)) + (schdl (make-schedule (list job) '() 'user))) + (remove-user-jobs %user0 #:schedule schdl) + (test-equal "remove-user-jobs: only one" + '() + (schedule-user schdl))) + +;;; Check that 'remove-user-jobs' with only two types of user jobs keep the +;;; other user jobs in the schedule. +(let* ((job0 (make-dummy-job #:user %user0)) + (job1 (make-dummy-job #:user %user1)) + (schdl (make-schedule (list job0 job1) '() 'user))) + (remove-user-jobs %user0 #:schedule schdl) + (test-equal "remove-user-jobs: keep one" + (list job1) + (schedule-user schdl))) + +;;; Check that 'clear-system-jobs' removes all system jobs and has no effect +;;; on the user jobs. +(let* ((job0 (make-dummy-job #:user %user0)) + (job1 (make-dummy-job #:user %user1)) + (schdl (make-schedule (list job0) (list job1) 'user))) + (clear-system-jobs #:schedule schdl) + (test-assert "clear-system-jobs: basic" + (and (equal? (list job0) (schedule-user schdl)) + (equal? '() (schedule-system schdl))))) + +;;; Check that 'add-job' adds a user job when the current schedule is 'user. +(let ((schdl (make-schedule '() '() 'user))) + (add-job 1+ (const #t) "job0" 0 "user" #:schedule schdl) + (test-assert "add-job: user schedule" + (and (= 1 (length (schedule-user schdl))) + (= 0 (length (schedule-system schdl)))))) + +;;; Check that 'add-job' adds a system job when the current schedule is +;;; 'system. +(let ((schdl (make-schedule '() '() 'system))) + (add-job 1+ (const #t) "job0" 0 "user" #:schedule schdl) + (test-assert "add-job: system schedule" + (and (= 0 (length (schedule-user schdl))) + (= 1 (length (schedule-system schdl)))))) + +;;; Check that 'find-next-jobs' find the soonest job. +(let* ((job0 (make-dummy-job #:user %user0 #:next-time 5)) + (job1 (make-dummy-job #:user %user1 #:next-time 10)) + (schdl (make-schedule (list job0) (list job1) 'user))) + (test-equal "find-next-jobs: basic" + (list 5 job0) + (find-next-jobs #:schedule schdl))) + +;;; Check that 'find-next-jobs' can find multiple soonest jobs. +(let* ((job0 (make-dummy-job #:user %user0 #:next-time 5)) + (job1 (make-dummy-job #:user %user1 #:next-time 5)) + (schdl (make-schedule (list job0) (list job1) 'user))) + (test-equal "find-next-jobs: two jobs" + (list 5 job0 job1) + (find-next-jobs #:schedule schdl))) + +;;; Check that 'find-next-jobs' returns #f when the schedule is empty. +(let ((schdl (make-schedule '() '() 'user))) + (test-equal "find-next-jobs: empty" + (list #f) + (find-next-jobs #:schedule schdl))) + +;;; Check output of 'display-schedule' with a basic schedule. +(test-assert "display-schedule: basic" + (and (equal? + "Thu Jan 1 00:00:05 1970 +0000\ndummy\n\n" + (let* ((job0 (make-dummy-job #:user %user0 #:next-time 5)) + (job1 (make-dummy-job #:user %user1 #:next-time 10)) + (schdl (make-schedule (list job0) (list job1) 'user))) + (with-output-to-string + (λ () (display-schedule 1 #:schedule schdl))))) + (equal? + (string-append + "Thu Jan 1 00:00:05 1970 +0000\ndummy\n\n" + "Thu Jan 1 00:00:06 1970 +0000\ndummy\n\n") + (let* ((job0 (make-dummy-job #:user %user0 #:next-time 5)) + (job1 (make-dummy-job #:user %user1 #:next-time 10)) + (schdl (make-schedule (list job0) (list job1) 'user))) + (with-output-to-string + (λ () (display-schedule 2 #:schedule schdl))))))) + +;;; Check output of 'display-schedule' with an empty schedule. +(let ((schdl (make-schedule '() '() 'user))) + (test-equal "display-schedule: empty" + "" + (with-output-to-string + (λ () (display-schedule 1 #:schedule schdl))))) + +;;; +;;; Running jobs +;;; + +;;; Import private global. +(define number-children (@@ (mcron base) number-children)) + +;;; Import private procedures. +(define update-number-children! (@@ (mcron base) update-number-children!)) +(define child-cleanup (@@ (mcron base) child-cleanup)) +(define run-job (@@ (mcron base) run-job)) + +;;; Check 'number-children' initial value. +(test-equal "number-children: init" + 0 + (unbox number-children)) + +;;; Check 'update-number-children!' incrementation. +(test-equal "update-number-children!: 1+" + 2 + (begin + (update-number-children! 1+) + (update-number-children! 1+) + (unbox number-children))) + +;;; Check 'update-number-children!' decrementation. +(test-equal "update-number-children!: 1-" + 1 + (begin + (update-number-children! 1-) + (unbox number-children))) + +;;; Check 'update-number-children!' constant value. +(test-equal "update-number-children!: set value" + 0 + (begin + (update-number-children! (const 0)) + (unbox number-children))) + +;;; Check 'run-job' and 'child-cleanup'. +;;; XXX: Having to use the filesystem for a unit test is wrong. +(let* ((filename (tmpnam)) + (action (λ () (close-port (open-output-file filename)))) + (job (make-dummy-job #:user (getpw (getuid)) #:action action))) + (dynamic-wind + (const #t) + (λ () + (sigaction SIGCHLD (const #t)) + (run-job job) + ;; Wait for the SIGCHLD signal sent when job exits. + (pause) + ;; Check 'run-job' result and if the number of children is up-to-date. + (test-equal "run-job: basic" + 1 + (and (access? filename F_OK) + (unbox number-children))) + (child-cleanup) + ;; Check that 'child-cleanup' updates the number of children. + (test-equal "child-cleanup: one" 0 (unbox number-children))) + (λ () + (and (access? filename F_OK) (delete-file filename)) + (sigaction SIGCHLD SIG_DFL)))) + +(test-end) diff --git a/tests/basic.sh b/tests/basic.sh new file mode 100644 index 0000000..7b2ca55 --- /dev/null +++ b/tests/basic.sh @@ -0,0 +1,36 @@ +# basic.sh -- basic tests for mcron +# Copyright © 2017 Mathieu Lirzin +# +# This file is part of GNU Mcron. +# +# GNU Mcron is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GNU Mcron is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Mcron. If not, see . + +source "${srcdir}/tests/init.sh" + +# Use current working directory to store mcron files +XDG_CONFIG_HOME=`pwd` +export XDG_CONFIG_HOME + +mkdir cron +cat > cron/foo.guile < "output$$" +grep -e "foo" "output$$" || fail_ "'foo.guile' job is not scheduled" + +mcron --schedule=1 > "output$$" +grep -e "foo" "output$$" || fail_ "'foo.guile' job is not scheduled" + +Exit 0 diff --git a/tests/environment.scm b/tests/environment.scm new file mode 100644 index 0000000..05ad3be --- /dev/null +++ b/tests/environment.scm @@ -0,0 +1,92 @@ +;;;; environment.scm -- tests for (mcron environment) module +;;; Copyright © 2016, 2018 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +(use-modules (srfi srfi-64) + (srfi srfi-111) + (mcron environment)) + +(test-begin "environment") + +;;; Check 'current-environment-mods' initial value which should be empty. +(test-equal "current-environment-mods: init" + '() + (unbox (@@ (mcron environment) %current-environment-mods))) + +;;; Check 'current-environment-mods-copy' with an empty environment +(test-assert "current-environment-mods-copy: empty" + (let* ((env (box '())) + (copy0 (get-current-environment-mods-copy #:environ env)) + (copy1 (get-current-environment-mods-copy #:environ env))) + (set! copy1 (assoc-set! copy1 "FOO" "BAR")) + (and (equal? '() (unbox env)) + (equal? '() copy0) + (equal? '(("FOO" . "BAR")) copy1)))) + +;;; Check 'current-environment-mods-copy' with a basic environment +(test-assert "current-environment-mods-copy: basic" + (let* ((init-env '(("a" . "1") ("b" . "2"))) + (env (box init-env)) + (copy0 (get-current-environment-mods-copy #:environ env)) + (copy1 (get-current-environment-mods-copy #:environ env))) + (set! copy1 (assoc-set! copy1 "c" "3")) + (and (equal? init-env (unbox env)) + (equal? init-env copy0) + (equal? `(("c" . "3") . ,init-env) copy1)))) + +;;; Check 'append-environment-mods' basic call +(test-equal "append-environment-mods: basic" + "BAR" + (let ((env (box '()))) + (append-environment-mods "FOO" "BAR" #:environ env) + (assoc-ref (unbox env) "FOO"))) + +;;; Check 'append-environment-mods' that when adding the same key twice the +;;; later is placed after the previous one. +(test-equal "append-environment-mods: twice" + '(("FOO" . "BAR") ("FOO" . "BAZ")) + (let ((env (box '()))) + (append-environment-mods "FOO" "BAR" #:environ env) + (append-environment-mods "FOO" "BAZ" #:environ env) + (unbox env))) + +;;; Check 'clear-environment-mods' side effect +(test-equal "clear-environment-mods: effect" + '() + (let ((env (box '()))) + (append-environment-mods "FOO" "BAR" #:environ env) + (append-environment-mods "FOO" "BAZ" #:environ env) + (clear-environment-mods #:environ env) + (unbox env))) + +;;; Check 'modify-environment' basic call +(test-assert "modifiy-environment: basic" + (begin + (modify-environment '(("FOO" . "bar")) (getpw)) + (equal? (getenv "FOO") "bar"))) + +(test-assert "modifiy-environment: user & logname" + ;; Check that USER and LOGNAME environment variables can't be changed. + (let* ((user-entry (pk (getpw))) + (user-name (passwd:name user-entry))) + (modify-environment '(("USER" . "alice")) user-entry) + (modify-environment '(("LOGNAME" . "bob")) user-entry) + (equal? user-name + (pk (getenv "USER")) + (pk (getenv "LOGNAME"))))) + +(test-end) diff --git a/tests/init.sh b/tests/init.sh new file mode 100644 index 0000000..470605c --- /dev/null +++ b/tests/init.sh @@ -0,0 +1,605 @@ +# source this file; set up for tests + +# Copyright (C) 2009-2017 Free Software Foundation, Inc. + +# 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 3 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, see . + +# Using this file in a test +# ========================= +# +# The typical skeleton of a test looks like this: +# +# #!/bin/sh +# . "${srcdir=.}/init.sh"; path_prepend_ . +# Execute some commands. +# Note that these commands are executed in a subdirectory, therefore you +# need to prepend "../" to relative filenames in the build directory. +# Note that the "path_prepend_ ." is useful only if the body of your +# test invokes programs residing in the initial directory. +# For example, if the programs you want to test are in src/, and this test +# script is named tests/test-1, then you would use "path_prepend_ ../src", +# or perhaps export PATH='$(abs_top_builddir)/src$(PATH_SEPARATOR)'"$$PATH" +# to all tests via automake's TESTS_ENVIRONMENT. +# Set the exit code 0 for success, 77 for skipped, or 1 or other for failure. +# Use the skip_ and fail_ functions to print a diagnostic and then exit +# with the corresponding exit code. +# Exit $? + +# Executing a test that uses this file +# ==================================== +# +# Running a single test: +# $ make check TESTS=test-foo.sh +# +# Running a single test, with verbose output: +# $ make check TESTS=test-foo.sh VERBOSE=yes +# +# Running a single test, keeping the temporary directory: +# $ make check TESTS=test-foo.sh KEEP=yes +# +# Running a single test, with single-stepping: +# 1. Go into a sub-shell: +# $ bash +# 2. Set relevant environment variables from TESTS_ENVIRONMENT in the +# Makefile: +# $ export srcdir=../../tests # this is an example +# 3. Execute the commands from the test, copy&pasting them one by one: +# $ . "$srcdir/init.sh"; path_prepend_ . +# ... +# 4. Finally +# $ exit + +ME_=`expr "./$0" : '.*/\(.*\)$'` + +# We use a trap below for cleanup. This requires us to go through +# hoops to get the right exit status transported through the handler. +# So use 'Exit STATUS' instead of 'exit STATUS' inside of the tests. +# Turn off errexit here so that we don't trip the bug with OSF1/Tru64 +# sh inside this function. +Exit () { set +e; (exit $1); exit $1; } + +# Print warnings (e.g., about skipped and failed tests) to this file number. +# Override by defining to say, 9, in init.cfg, and putting say, +# export ...ENVVAR_SETTINGS...; $(SHELL) 9>&2 +# in the definition of TESTS_ENVIRONMENT in your tests/Makefile.am file. +# This is useful when using automake's parallel tests mode, to print +# the reason for skip/failure to console, rather than to the .log files. +: ${stderr_fileno_=2} + +# Note that correct expansion of "$*" depends on IFS starting with ' '. +# Always write the full diagnostic to stderr. +# When stderr_fileno_ is not 2, also emit the first line of the +# diagnostic to that file descriptor. +warn_ () +{ + # If IFS does not start with ' ', set it and emit the warning in a subshell. + case $IFS in + ' '*) printf '%s\n' "$*" >&2 + test $stderr_fileno_ = 2 \ + || { printf '%s\n' "$*" | sed 1q >&$stderr_fileno_ ; } ;; + *) (IFS=' '; warn_ "$@");; + esac +} +fail_ () { warn_ "$ME_: failed test: $@"; Exit 1; } +skip_ () { warn_ "$ME_: skipped test: $@"; Exit 77; } +fatal_ () { warn_ "$ME_: hard error: $@"; Exit 99; } +framework_failure_ () { warn_ "$ME_: set-up failure: $@"; Exit 99; } + +# This is used to simplify checking of the return value +# which is useful when ensuring a command fails as desired. +# I.e., just doing `command ... &&fail=1` will not catch +# a segfault in command for example. With this helper you +# instead check an explicit exit code like +# returns_ 1 command ... || fail +returns_ () { + # Disable tracing so it doesn't interfere with stderr of the wrapped command + { set +x; } 2>/dev/null + + local exp_exit="$1" + shift + "$@" + test $? -eq $exp_exit && ret_=0 || ret_=1 + + if test "$VERBOSE" = yes && test "$gl_set_x_corrupts_stderr_" = false; then + set -x + fi + { return $ret_; } 2>/dev/null +} + +# Sanitize this shell to POSIX mode, if possible. +DUALCASE=1; export DUALCASE +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in + *posix*) set -o posix ;; + esac +fi + +# We require $(...) support unconditionally. +# We require non-surprising "local" semantics (this eliminates dash). +# This takes the admittedly draconian step of eliminating dash, because the +# assignment tab=$(printf '\t') works fine, yet preceding it with "local " +# transforms it into an assignment that sets the variable to the empty string. +# That is too counter-intuitive, and can lead to subtle run-time malfunction. +# The example below is less subtle in that with dash, it evokes the run-time +# exception "dash: 1: local: 1: bad variable name". +# We require a few additional shell features only when $EXEEXT is nonempty, +# in order to support automatic $EXEEXT emulation: +# - hyphen-containing alias names +# - we prefer to use ${var#...} substitution, rather than having +# to work around lack of support for that feature. +# The following code attempts to find a shell with support for these features. +# If the current shell passes the test, we're done. Otherwise, test other +# shells until we find one that passes. If one is found, re-exec it. +# If no acceptable shell is found, skip the current test. +# +# The "...set -x; P=1 true 2>err..." test is to disqualify any shell that +# emits "P=1" into err, as /bin/sh from SunOS 5.11 and OpenBSD 4.7 do. +# +# Use "9" to indicate success (rather than 0), in case some shell acts +# like Solaris 10's /bin/sh but exits successfully instead of with status 2. + +# Eval this code in a subshell to determine a shell's suitability. +# 10 - passes all tests; ok to use +# 9 - ok, but enabling "set -x" corrupts app stderr; prefer higher score +# ? - not ok +gl_shell_test_script_=' +test $(echo y) = y || exit 1 +f_local_() { local v=1; }; f_local_ || exit 1 +f_dash_local_fail_() { local t=$(printf " 1"); }; f_dash_local_fail_ +score_=10 +if test "$VERBOSE" = yes; then + test -n "$( (exec 3>&1; set -x; P=1 true 2>&3) 2> /dev/null)" && score_=9 +fi +test -z "$EXEEXT" && exit $score_ +shopt -s expand_aliases +alias a-b="echo zoo" +v=abx + test ${v%x} = ab \ + && test ${v#a} = bx \ + && test $(a-b) = zoo \ + && exit $score_ +' + +if test "x$1" = "x--no-reexec"; then + shift +else + # Assume a working shell. Export to subshells (setup_ needs this). + gl_set_x_corrupts_stderr_=false + export gl_set_x_corrupts_stderr_ + + # Record the first marginally acceptable shell. + marginal_= + + # Search for a shell that meets our requirements. + for re_shell_ in __current__ "${CONFIG_SHELL:-no_shell}" \ + /bin/sh bash dash zsh pdksh fail + do + test "$re_shell_" = no_shell && continue + + # If we've made it all the way to the sentinel, "fail" without + # finding even a marginal shell, skip this test. + if test "$re_shell_" = fail; then + test -z "$marginal_" && skip_ failed to find an adequate shell + re_shell_=$marginal_ + break + fi + + # When testing the current shell, simply "eval" the test code. + # Otherwise, run it via $re_shell_ -c ... + if test "$re_shell_" = __current__; then + # 'eval'ing this code makes Solaris 10's /bin/sh exit with + # $? set to 2. It does not evaluate any of the code after the + # "unexpected" first '('. Thus, we must run it in a subshell. + ( eval "$gl_shell_test_script_" ) > /dev/null 2>&1 + else + "$re_shell_" -c "$gl_shell_test_script_" 2>/dev/null + fi + + st_=$? + + # $re_shell_ works just fine. Use it. + if test $st_ = 10; then + gl_set_x_corrupts_stderr_=false + break + fi + + # If this is our first marginally acceptable shell, remember it. + if test "$st_:$marginal_" = 9: ; then + marginal_="$re_shell_" + gl_set_x_corrupts_stderr_=true + fi + done + + if test "$re_shell_" != __current__; then + # Found a usable shell. Preserve -v and -x. + case $- in + *v*x* | *x*v*) opts_=-vx ;; + *v*) opts_=-v ;; + *x*) opts_=-x ;; + *) opts_= ;; + esac + re_shell=$re_shell_ + export re_shell + exec "$re_shell_" $opts_ "$0" --no-reexec "$@" + echo "$ME_: exec failed" 1>&2 + exit 127 + fi +fi + +# If this is bash, turn off all aliases. +test -n "$BASH_VERSION" && unalias -a + +# Note that when supporting $EXEEXT (transparently mapping from PROG_NAME to +# PROG_NAME.exe), we want to support hyphen-containing names like test-acos. +# That is part of the shell-selection test above. Why use aliases rather +# than functions? Because support for hyphen-containing aliases is more +# widespread than that for hyphen-containing function names. +test -n "$EXEEXT" && shopt -s expand_aliases + +# Enable glibc's malloc-perturbing option. +# This is useful for exposing code that depends on the fact that +# malloc-related functions often return memory that is mostly zeroed. +# If you have the time and cycles, use valgrind to do an even better job. +: ${MALLOC_PERTURB_=87} +export MALLOC_PERTURB_ + +# This is a stub function that is run upon trap (upon regular exit and +# interrupt). Override it with a per-test function, e.g., to unmount +# a partition, or to undo any other global state changes. +cleanup_ () { :; } + +# Emit a header similar to that from diff -u; Print the simulated "diff" +# command so that the order of arguments is clear. Don't bother with @@ lines. +emit_diff_u_header_ () +{ + printf '%s\n' "diff -u $*" \ + "--- $1 1970-01-01" \ + "+++ $2 1970-01-01" +} + +# Arrange not to let diff or cmp operate on /dev/null, +# since on some systems (at least OSF/1 5.1), that doesn't work. +# When there are not two arguments, or no argument is /dev/null, return 2. +# When one argument is /dev/null and the other is not empty, +# cat the nonempty file to stderr and return 1. +# Otherwise, return 0. +compare_dev_null_ () +{ + test $# = 2 || return 2 + + if test "x$1" = x/dev/null; then + test -s "$2" || return 0 + emit_diff_u_header_ "$@"; sed 's/^/+/' "$2" + return 1 + fi + + if test "x$2" = x/dev/null; then + test -s "$1" || return 0 + emit_diff_u_header_ "$@"; sed 's/^/-/' "$1" + return 1 + fi + + return 2 +} + +for diff_opt_ in -u -U3 -c '' no; do + test "$diff_opt_" != no && + diff_out_=`exec 2>/dev/null; diff $diff_opt_ "$0" "$0" < /dev/null` && + break +done +if test "$diff_opt_" != no; then + if test -z "$diff_out_"; then + compare_ () { diff $diff_opt_ "$@"; } + else + compare_ () + { + # If no differences were found, AIX and HP-UX 'diff' produce output + # like "No differences encountered". Hide this output. + diff $diff_opt_ "$@" > diff.out + diff_status_=$? + test $diff_status_ -eq 0 || cat diff.out || diff_status_=2 + rm -f diff.out || diff_status_=2 + return $diff_status_ + } + fi +elif cmp -s /dev/null /dev/null 2>/dev/null; then + compare_ () { cmp -s "$@"; } +else + compare_ () { cmp "$@"; } +fi + +# Usage: compare EXPECTED ACTUAL +# +# Given compare_dev_null_'s preprocessing, defer to compare_ if 2 or more. +# Otherwise, propagate $? to caller: any diffs have already been printed. +compare () +{ + # This looks like it can be factored to use a simple "case $?" + # after unchecked compare_dev_null_ invocation, but that would + # fail in a "set -e" environment. + if compare_dev_null_ "$@"; then + return 0 + else + case $? in + 1) return 1;; + *) compare_ "$@";; + esac + fi +} + +# An arbitrary prefix to help distinguish test directories. +testdir_prefix_ () { printf gt; } + +# Run the user-overridable cleanup_ function, remove the temporary +# directory and exit with the incoming value of $?. +remove_tmp_ () +{ + __st=$? + cleanup_ + if test "$KEEP" = yes; then + echo "Not removing temporary directory $test_dir_" + else + # cd out of the directory we're about to remove + cd "$initial_cwd_" || cd / || cd /tmp + chmod -R u+rwx "$test_dir_" + # If removal fails and exit status was to be 0, then change it to 1. + rm -rf "$test_dir_" || { test $__st = 0 && __st=1; } + fi + exit $__st +} + +# Given a directory name, DIR, if every entry in it that matches *.exe +# contains only the specified bytes (see the case stmt below), then print +# a space-separated list of those names and return 0. Otherwise, don't +# print anything and return 1. Naming constraints apply also to DIR. +find_exe_basenames_ () +{ + feb_dir_=$1 + feb_fail_=0 + feb_result_= + feb_sp_= + for feb_file_ in $feb_dir_/*.exe; do + # If there was no *.exe file, or there existed a file named "*.exe" that + # was deleted between the above glob expansion and the existence test + # below, just skip it. + test "x$feb_file_" = "x$feb_dir_/*.exe" && test ! -f "$feb_file_" \ + && continue + # Exempt [.exe, since we can't create a function by that name, yet + # we can't invoke [ by PATH search anyways due to shell builtins. + test "x$feb_file_" = "x$feb_dir_/[.exe" && continue + case $feb_file_ in + *[!-a-zA-Z/0-9_.+]*) feb_fail_=1; break;; + *) # Remove leading file name components as well as the .exe suffix. + feb_file_=${feb_file_##*/} + feb_file_=${feb_file_%.exe} + feb_result_="$feb_result_$feb_sp_$feb_file_";; + esac + feb_sp_=' ' + done + test $feb_fail_ = 0 && printf %s "$feb_result_" + return $feb_fail_ +} + +# Consider the files in directory, $1. +# For each file name of the form PROG.exe, create an alias named +# PROG that simply invokes PROG.exe, then return 0. If any selected +# file name or the directory name, $1, contains an unexpected character, +# define no alias and return 1. +create_exe_shims_ () +{ + case $EXEEXT in + '') return 0 ;; + .exe) ;; + *) echo "$0: unexpected \$EXEEXT value: $EXEEXT" 1>&2; return 1 ;; + esac + + base_names_=`find_exe_basenames_ $1` \ + || { echo "$0 (exe_shim): skipping directory: $1" 1>&2; return 0; } + + if test -n "$base_names_"; then + for base_ in $base_names_; do + alias "$base_"="$base_$EXEEXT" + done + fi + + return 0 +} + +# Use this function to prepend to PATH an absolute name for each +# specified, possibly-$initial_cwd_-relative, directory. +path_prepend_ () +{ + while test $# != 0; do + path_dir_=$1 + case $path_dir_ in + '') fail_ "invalid path dir: '$1'";; + /*) abs_path_dir_=$path_dir_;; + *) abs_path_dir_=$initial_cwd_/$path_dir_;; + esac + case $abs_path_dir_ in + *:*) fail_ "invalid path dir: '$abs_path_dir_'";; + esac + PATH="$abs_path_dir_:$PATH" + + # Create an alias, FOO, for each FOO.exe in this directory. + create_exe_shims_ "$abs_path_dir_" \ + || fail_ "something failed (above): $abs_path_dir_" + shift + done + export PATH +} + +setup_ () +{ + if test "$VERBOSE" = yes; then + # Test whether set -x may cause the selected shell to corrupt an + # application's stderr. Many do, including zsh-4.3.10 and the /bin/sh + # from SunOS 5.11, OpenBSD 4.7 and Irix 5.x and 6.5. + # If enabling verbose output this way would cause trouble, simply + # issue a warning and refrain. + if $gl_set_x_corrupts_stderr_; then + warn_ "using SHELL=$SHELL with 'set -x' corrupts stderr" + else + set -x + fi + fi + + initial_cwd_=$PWD + + pfx_=`testdir_prefix_` + test_dir_=`mktempd_ "$initial_cwd_" "$pfx_-$ME_.XXXX"` \ + || fail_ "failed to create temporary directory in $initial_cwd_" + cd "$test_dir_" || fail_ "failed to cd to temporary directory" + + # As autoconf-generated configure scripts do, ensure that IFS + # is defined initially, so that saving and restoring $IFS works. + gl_init_sh_nl_=' +' + IFS=" "" $gl_init_sh_nl_" + + # This trap statement, along with a trap on 0 below, ensure that the + # temporary directory, $test_dir_, is removed upon exit as well as + # upon receipt of any of the listed signals. + for sig_ in 1 2 3 13 15; do + eval "trap 'Exit $(expr $sig_ + 128)' $sig_" + done +} + +# Create a temporary directory, much like mktemp -d does. +# Written by Jim Meyering. +# +# Usage: mktempd_ /tmp phoey.XXXXXXXXXX +# +# First, try to use the mktemp program. +# Failing that, we'll roll our own mktemp-like function: +# - try to get random bytes from /dev/urandom +# - failing that, generate output from a combination of quickly-varying +# sources and gzip. Ignore non-varying gzip header, and extract +# "random" bits from there. +# - given those bits, map to file-name bytes using tr, and try to create +# the desired directory. +# - make only $MAX_TRIES_ attempts + +# Helper function. Print $N pseudo-random bytes from a-zA-Z0-9. +rand_bytes_ () +{ + n_=$1 + + # Maybe try openssl rand -base64 $n_prime_|tr '+/=\012' abcd first? + # But if they have openssl, they probably have mktemp, too. + + chars_=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 + dev_rand_=/dev/urandom + if test -r "$dev_rand_"; then + # Note: 256-length($chars_) == 194; 3 copies of $chars_ is 186 + 8 = 194. + dd ibs=$n_ count=1 if=$dev_rand_ 2>/dev/null \ + | LC_ALL=C tr -c $chars_ 01234567$chars_$chars_$chars_ + return + fi + + n_plus_50_=`expr $n_ + 50` + cmds_='date; date +%N; free; who -a; w; ps auxww; ps ef; netstat -n' + data_=` (eval "$cmds_") 2>&1 | gzip ` + + # Ensure that $data_ has length at least 50+$n_ + while :; do + len_=`echo "$data_"|wc -c` + test $n_plus_50_ -le $len_ && break; + data_=` (echo "$data_"; eval "$cmds_") 2>&1 | gzip ` + done + + echo "$data_" \ + | dd bs=1 skip=50 count=$n_ 2>/dev/null \ + | LC_ALL=C tr -c $chars_ 01234567$chars_$chars_$chars_ +} + +mktempd_ () +{ + case $# in + 2);; + *) fail_ "Usage: mktempd_ DIR TEMPLATE";; + esac + + destdir_=$1 + template_=$2 + + MAX_TRIES_=4 + + # Disallow any trailing slash on specified destdir: + # it would subvert the post-mktemp "case"-based destdir test. + case $destdir_ in + / | //) destdir_slash_=$destdir;; + */) fail_ "invalid destination dir: remove trailing slash(es)";; + *) destdir_slash_=$destdir_/;; + esac + + case $template_ in + *XXXX) ;; + *) fail_ \ + "invalid template: $template_ (must have a suffix of at least 4 X's)";; + esac + + # First, try to use mktemp. + d=`unset TMPDIR; { mktemp -d -t -p "$destdir_" "$template_"; } 2>/dev/null` && + + # The resulting name must be in the specified directory. + case $d in "$destdir_slash_"*) :;; *) false;; esac && + + # It must have created the directory. + test -d "$d" && + + # It must have 0700 permissions. Handle sticky "S" bits. + perms=`ls -dgo "$d" 2>/dev/null` && + case $perms in drwx--[-S]---*) :;; *) false;; esac && { + echo "$d" + return + } + + # If we reach this point, we'll have to create a directory manually. + + # Get a copy of the template without its suffix of X's. + base_template_=`echo "$template_"|sed 's/XX*$//'` + + # Calculate how many X's we've just removed. + template_length_=`echo "$template_" | wc -c` + nx_=`echo "$base_template_" | wc -c` + nx_=`expr $template_length_ - $nx_` + + err_= + i_=1 + while :; do + X_=`rand_bytes_ $nx_` + candidate_dir_="$destdir_slash_$base_template_$X_" + err_=`mkdir -m 0700 "$candidate_dir_" 2>&1` \ + && { echo "$candidate_dir_"; return; } + test $MAX_TRIES_ -le $i_ && break; + i_=`expr $i_ + 1` + done + fail_ "$err_" +} + +# If you want to override the testdir_prefix_ function, +# or to add more utility functions, use this file. +test -f "$srcdir/init.cfg" \ + && . "$srcdir/init.cfg" + +setup_ "$@" +# This trap is here, rather than in the setup_ function, because some +# shells run the exit trap at shell function exit, rather than script exit. +trap remove_tmp_ 0 diff --git a/tests/job-specifier.scm b/tests/job-specifier.scm new file mode 100644 index 0000000..70dd518 --- /dev/null +++ b/tests/job-specifier.scm @@ -0,0 +1,168 @@ +;;;; job-specifier.scm -- tests for (mcron job-specifier) module +;;; Copyright © 2016 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +(use-modules (ice-9 match) + (srfi srfi-64) + (srfi srfi-111) + (mcron job-specifier)) + +(test-begin "job-specifier") + +(test-equal "range: basic" + '(0 1 2 3 4 5 6 7 8 9) + (range 0 10)) + +(test-equal "range: positive step" + '(0 2 4 6 8) + (range 0 10 2)) + +(test-assert "range: zero step" + ;; Since this behavior is undefined, only check if range doesn't crash. + (range 0 5 0)) + +(test-assert "range: negative step" + ;; Since this behavior is undefined, only check if range doesn't crash. + (range 0 5 -2)) + +(test-assert "range: reverse boundaries" + (range 10 3)) + +(define %find-best-next (@@ (mcron job-specifier) %find-best-next)) + +(test-assert "%find-best-next: exact" + ;; Ensure that '%find-best-next' preserves the exactness of the numbers + ;; inside the NEXT-LIST argument. + (match (pk 'match (%find-best-next 1 '(0 2))) + ((a . b) (and (exact? a) (exact? b))))) + +;;; +;;; Check 'next-...' procedures. +;;; + +;;; TODO: Find more meaningful date examples. + +(setenv "TZ" ":UTC") + +(test-equal "next-year" + (list 1893456000 1546300800) + (list (next-year '(130)) ;; This is the year 2030. + (next-year-from 1522095469))) + +(test-equal "next-month" + 5097600 + (next-month-from 101 '(0 2 4))) + +(test-equal "next-day" + 345600 + (next-day-from 4337 '(0 5 10))) + +(test-equal "next-hour" + 3600 + (next-hour-from 3 '(0 1 2 3 4))) + +(test-equal "next-minute" + 60 + (next-minute-from 8)) + +(test-equal "next-second" + 15 + (next-second-from 14)) + +;;; +;;; Check 'configuration-user' manipulation +;;; + +(define configuration-user (@@ (mcron job-specifier) configuration-user)) + +;;; Call 'set-configuration-user' with a valid uid. +(let ((uid (getuid))) + (test-equal "set-configuration-user: uid" + uid + (begin + (set-configuration-user uid) + (passwd:uid (unbox configuration-user))))) + +(define entry + ;; Random user entry. + (getpw)) + +;;; Call 'set-configuration-user' with a valid user name. +(let ((name (passwd:name entry))) + (test-equal "set-configuration-user: name" + name + (begin + (set-configuration-user name) + (passwd:name (unbox configuration-user))))) + +(define root-entry (getpw 0)) + +;;; Call 'set-configuration-user' with a passwd entry. +(test-equal "set-configuration-user: passwd entry" + root-entry + (begin + (set-configuration-user root-entry) + (unbox configuration-user))) + +;;; Call 'set-configuration-user' with an invalid uid. +(test-error "set-configuration-user: invalid uid" + #t + (set-configuration-user -20000)) + +;;; Call 'set-configuration-user' with an invalid spec. +(test-error "set-configuration-user: invalid spec" + #t + (set-configuration-user 'wrong)) + +;;; +;;; Check the 'job' procedure +;;; + +(test-assert "job: procedure timeproc" + (job 1+ "dummy action")) + +(test-assert "job: list timeproc" + (job '(next-hour '(0)) "dummy action")) + +(test-assert "job: string timeproc" + (job "30 4 1,15 * 5" "dummy action")) + +(test-error "job: invalid string timeproc" + 'mcron-error + (job "30 4 1,15 * WRONG" "dummy action")) + +(test-error "job: invalid timeproc" + 'mcron-error + (job 42 "dummy action")) + +(test-assert "job: procedure action" + (job 1+ (λ () (display "hello\n")))) + +(test-assert "job: list action" + (job 1+ '(display "hello\n"))) + +(test-assert "job: string action" + (job 1+ "echo hello")) + +(test-error "job: string action" + 'mcron-error + (job 1+ 42)) + +(test-assert "job: user name" + (job 1+ "dummy action" #:user (getuid))) + +(test-end) diff --git a/tests/redirect.scm b/tests/redirect.scm new file mode 100644 index 0000000..700bfb4 --- /dev/null +++ b/tests/redirect.scm @@ -0,0 +1,53 @@ +;;;; redirect.scm -- tests for (mcron redirect) module +;;; Copyright © 2020 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +(use-modules (ice-9 textual-ports) + (srfi srfi-1) + (srfi srfi-64) + (mcron redirect)) + +(setenv "TZ" "UTC0") + +(test-begin "redirect") + +(define out (mkstemp! (string-copy "foo-XXXXXX"))) + +(dynamic-wind + (const #t) + (lambda () + (with-mail-out "echo 'foo'" "user0" + #:out (lambda () out) + #:hostname "localhost") + + (flush-all-ports) + + (test-equal "mail output" + "To: user0 +From: mcron +Subject: user0@localhost + +foo +" + (call-with-input-file (port-filename out) get-string-all))) + + (lambda () + (let ((fname (port-filename out))) + (close out) + (delete-file fname)))) + +(test-end) diff --git a/tests/schedule-2.sh b/tests/schedule-2.sh new file mode 100644 index 0000000..531af54 --- /dev/null +++ b/tests/schedule-2.sh @@ -0,0 +1,81 @@ +# schedule-2.sh -- Check mcron schedule output +# Copyright © 2020 Dale Mellor +# +# This file is part of GNU Mcron. +# +# GNU Mcron is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GNU Mcron is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Mcron. If not, see . + +source "${srcdir}/tests/init.sh" + +# Use UTC and SOURCE_DATE_EPOCH to get reproducible result. + +SOURCE_DATE_EPOCH=1 +export SOURCE_DATE_EPOCH + +TZ=UTC0 +export TZ + +# Use current working directory to store mcron files +XDG_CONFIG_HOME=`pwd` +export XDG_CONFIG_HOME + +LC_ALL=C +export LC_ALL + +mkdir cron +cat > cron/foo.guile < expected < output +diff expected output \ + || skip_ 'The -s option is not fully functional; +this will be fixed with a future version of GNU Guile.' + +Exit 0 diff --git a/tests/schedule.sh b/tests/schedule.sh new file mode 100644 index 0000000..9a1836e --- /dev/null +++ b/tests/schedule.sh @@ -0,0 +1,131 @@ +# schedule.sh -- Check mcron schedule output +# Copyright © 2017, 2018 Mathieu Lirzin +# +# This file is part of GNU Mcron. +# +# GNU Mcron is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GNU Mcron is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Mcron. If not, see . + +source "${srcdir}/tests/init.sh" + +# Use UTC and SOURCE_DATE_EPOCH to get reproducible result. + +SOURCE_DATE_EPOCH=1 +export SOURCE_DATE_EPOCH + +TZ=UTC0 +export TZ + +LC_ALL=C +export LC_ALL + +# Use current working directory to store mcron files +XDG_CONFIG_HOME=`pwd` +export XDG_CONFIG_HOME + +mkdir cron +cat > cron/foo.guile < cron/bar.guile < expected < output +diff expected output || fail_ "schedule output is not correct" + +Exit 0 diff --git a/tests/utils.scm b/tests/utils.scm new file mode 100644 index 0000000..0625850 --- /dev/null +++ b/tests/utils.scm @@ -0,0 +1,111 @@ +;;;; utils.scm -- tests for (mcron utils) module +;;; Copyright © 2018 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +(use-modules (ice-9 match) + (ice-9 rdelim) + (srfi srfi-64) + (mcron config) + (mcron utils)) + +(test-begin "utils") + +;;; Check 'mcron-error' error code return value. +(test-equal "mcron-error: exit code" + 42 + (match (primitive-fork) + (0 ;child + (mcron-error 42 "exit with 42")) + ((= waitpid (pid . exit-code)) ;parent + (status:exit-val exit-code)))) + +;;; Check 'mcron-error' output with basic error code. +(test-equal "mcron-error: output" + "mcron: token" + (call-with-output-string + (λ (port) + (match (pipe) + ((in . out) + (match (primitive-fork) + (0 ;child + (close in) + (with-error-to-port out + (λ () (mcron-error 37 "token")))) + ((= waitpid (pid . exit-code)) ;parent + (close out) + (display (read-line in) port)))))))) + +;;; Check mcron-error output when error code is 0. +(test-equal "mcron-error: output no-exit" + "mcron: foobar\n" + (call-with-output-string + (λ (port) + (with-error-to-port port + (λ () + (mcron-error 0 "foo" "bar")))))) + +;;; Check that mcron-error doesn't print anything on the standard output. +(test-equal "mcron-error: only stderr" + "" + (with-output-to-string + (λ () (mcron-error 0 "foo" "bar")))) + +;;; +;;; Check user interface conformance to GNU Coding Standards +;;; + +(test-assert "show-version" + (let ((out (with-output-to-string (λ () (show-version "dummy"))))) + (and (string-contains out config-package-version) + (string-contains out config-package-name)))) + +(test-assert "show-package-information" + (let ((out (with-output-to-string (λ () (show-package-information))))) + (string-contains out config-package-bugreport))) + +;;; +;;; Check 'get-user' +;;; + +(define entry + ;; Random user entry. + (getpw)) + +;;; Call 'get-user' with a valid uid. +(let ((uid (getuid))) + (test-equal "get-user: uid" + uid + (passwd:uid (get-user uid)))) + +;;; Call 'get-user' with a valid user name. +(let ((name (passwd:name entry))) + (test-equal "get-user: name" + name + (passwd:name (get-user name)))) + +;;; Call 'get-user' with a passwd entry. +(test-equal "get-user: passwd entry" + entry + (get-user entry)) + +;;; Call 'get-user' with an invalid uid. +(test-error "get-user: invalid uid" #t (get-user -20000)) + +;;; Call 'get-user' with an invalid spec. +(test-error "get-user: invalid spec" #t (get-user 'wrong)) + +(test-end) diff --git a/tests/vixie-specification.scm b/tests/vixie-specification.scm new file mode 100644 index 0000000..78c1dad --- /dev/null +++ b/tests/vixie-specification.scm @@ -0,0 +1,144 @@ +;;;; vixie-specification.scm -- tests for (mcron vixie-specificaion) module +;;; Copyright © 2020 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +(use-modules (srfi srfi-1) + (srfi srfi-64) + (mcron vixie-specification)) + +(setenv "TZ" "UTC0") + +;;; Do not send mail +(setenv "MAILTO" "") + +(define (create-file! content) + "Construct a temporary file port containing CONTENT which must be a string." + (let ((port (mkstemp! (string-copy "file-XXXXXX")))) + (display content port) + (force-output port) + port)) + +(define (clean-temp port) + "Close and Delete a temporary file port" + (let ((fname (port-filename port))) + (close port) + (delete-file fname))) + +(define schedule (@@ (mcron base) %global-schedule)) +(define schedule-user (@@ (mcron base) schedule-user)) +(define set-schedule-user! (@@ (mcron base) set-schedule-user!)) +(define job:environment (@@ (mcron base) job:environment)) +(define job:displayable (@@ (mcron base) job:displayable)) +(define job:user (@@ (mcron base) job:user)) + +(test-begin "vixie-specification") + +;;; Parse user crontab file + +(define user-crontab-example + "# Example crontab +FOO=x +BAR=y + +# Example of job definitions: +17 * * * * cd / && run baz +47 6 * * 7 foo -x /tmp/example || bar +") + +(define user-crontab (create-file! user-crontab-example)) + +(dynamic-wind + (const #t) + (lambda () + (set-schedule-user! schedule '()) + (read-vixie-file (port-filename user-crontab)) + + (test-assert "User schedule has exactly 2 matching jobs" + (lset= string=? + '("cd / && run baz" + "foo -x /tmp/example || bar") + (map job:displayable (schedule-user schedule)))) + + (test-assert "Job environment matches configuration" + (every (lambda (j) + (lset= equal? + '(("FOO" . "x") ("BAR" . "y")) + (job:environment j))) + (schedule-user schedule)))) + + (lambda () + (clean-temp user-crontab))) + +;;; Parse system crontab file + +;;; Get two existing users from the test environment. +(setpwent) +(define user0 (getpwent)) +(define user1 (or (getpwent) user0)) +(define system-crontab-example + (string-append + "# Example crontab +BAZ=z + +17 * * * * " (passwd:name user0) " cd / && run baz +47 6 * * 7 " (passwd:name user1) " foo -x /tmp/example || bar")) + +(define sys-crontab (create-file! system-crontab-example)) + +(dynamic-wind + (const #t) + (lambda () + (set-schedule-user! schedule '()) + (read-vixie-file (port-filename sys-crontab) parse-system-vixie-line) + + (test-assert "System schedule has exactly 2 matching jobs" + (lset= equal? + `((,user0 . "cd / && run baz") + (,user1 . "foo -x /tmp/example || bar")) + (map (lambda (j) + (cons (job:user j) (job:displayable j))) + (schedule-user schedule)))) + + (test-assert "Job environment matches configuration" + (every (lambda (j) + (lset= equal? '(("BAZ" . "z")) (job:environment j))) + (schedule-user schedule)))) + + (lambda () + (clean-temp sys-crontab))) + +;;; Try to parse a user crontab in a system context + +(define wrong-system-crontab-example + " +# Example of job definitions: +17 * * * * ls") + +(define wrong-sys-crontab (create-file! wrong-system-crontab-example)) + +(dynamic-wind + (const #t) + (lambda () + (test-error "missing user" + 'mcron-error + (read-vixie-file (port-filename wrong-sys-crontab) + parse-system-vixie-line))) + + (lambda () + (clean-temp wrong-sys-crontab))) + +(test-end) diff --git a/tests/vixie-time.scm b/tests/vixie-time.scm new file mode 100644 index 0000000..e5db67a --- /dev/null +++ b/tests/vixie-time.scm @@ -0,0 +1,118 @@ +;;;; vixie-time.scm -- tests for (mcron vixie-time) module +;;; Copyright © 2018 Mathieu Lirzin +;;; +;;; This file is part of GNU Mcron. +;;; +;;; GNU Mcron is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation, either version 3 of the License, or +;;; (at your option) any later version. +;;; +;;; GNU Mcron is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Mcron. If not, see . + +(use-modules (srfi srfi-1) + (srfi srfi-64) + (mcron vixie-time)) + +(setenv "TZ" "UTC0") + +(test-begin "vixie-time") + +(define (times-equal spec times proc) + (test-equal spec + (cdr times) + (fold-right (λ (val acc) + (cons (proc val) acc)) + '() + (drop-right times 1)))) + +(times-equal + "every minute" + '(0 60 120 180 240 300 360 420) + (parse-vixie-time "* * * * *")) + +(times-equal + "every hour" + (list 0 + 3600 + (* 2 3600) + (* 3 3600) + (* 4 3600) + (* 5 3600) + (* 6 3600) + (* 7 3600)) + (parse-vixie-time "0 * * * *")) + +(times-equal + "every day" + (list 0 + (* 24 3600) + (* 2 24 3600) + (* 3 24 3600) + (* 4 24 3600) + (* 5 24 3600) + (* 6 24 3600) + (* 7 24 3600)) + (parse-vixie-time "0 0 * * *")) + +(times-equal + "every month" + (list 0 + (* 31 86400) ;jan + (* (+ 31 28) 86400) ;fev + (* (+ 31 28 31) 86400) ;mar + (* (+ 31 28 31 30) 86400) ;avr + (* (+ 31 28 31 30 31) 86400) ;may + (* (+ 31 28 31 30 31 30) 86400) ;jun + (* (+ 31 28 31 30 31 30 31) 86400)) ;july + (parse-vixie-time "0 0 1 * *")) + +(times-equal + "every year" + (list 0 + (* 365 86400) ;1971 + (* 2 365 86400) ;1972 (leap) + (* (+ (* 2 365) 366) 86400) ;1973 + (* (+ (* 3 365) 366) 86400) ;1974 + (* (+ (* 4 365) 366) 86400) ;1975 + (* (+ (* 5 365) 366) 86400) ;1976 (leap) + (* (+ (* 5 365) (* 2 366)) 86400)) ;1977 + (parse-vixie-time "0 0 1 0 *")) + +(times-equal + "30 4 1,15 * 5" + (list 0 + (+ (* 4 3600) 1800) + (+ (* 28 3600) 1800) + (+ (* 8 86400) (* 4 3600) 1800) + (+ (* 13 86400) (* 28 3600) 1800) + (+ (* 15 86400) (* 4 3600) 1800) + (+ (* 532 3600) 1800)) + (parse-vixie-time "30 4 1,15 * 5")) + +;;; +;;; Errors +;;; + +;; FIXME: infinite loop +;; (test-error "month 0" #t +;; (let ((p (parse-vixie-time "0 0 0 * *"))) +;; (p 1234))) + +(test-error + "not enough fields" + 'mcron-error + (parse-vixie-time "1 2 3 4")) + +(test-error + "too many fields" + 'mcron-error + (parse-vixie-time "1 2 3 4 5 6")) + +(test-end)