Cover V13, i10

Article
Listing 1

oct2004.tar

Crontrolling Your Crontabs

Matthew Hoskins

Crontab files were designed to be modified from within an editor. This poses a problem if you want to automate or script crontab manipulations. Most initial operating system installs come with a basic set of housekeeping cron jobs scheduled to run at various times. As a systems administrator, you probably replace many of these built-in jobs or augment them with homegrown programs. This can easily be accomplished during the operating system install by replacing the standard crontab file, wherever it may be. But after installing the OS, you may also install applications and services. Unless all your systems do exactly the same thing, you will most likely need to install different crontab entries on different systems and update them over the life of a system.

In recent times, cron daemons have added features where cron jobs can be created as files in a directory (i.e., cron.d, cron.daily, cron.weekly, etc.). These features were added to allow packaged software to more easily create and modify jobs, but these methods are platform dependent. The only relatively independent method is using the crontab command to list and replace a user's crontab.

My Solution: Crontrol

Cool name, eh? As I stated previously, the only platform-independent methodology for handling crontabs on most systems is using the "list" and "replace" functionality of the crontab command. To automate this process, we list, backup, modify, then save back the modified crontab file. My goal in this project was to automate and simplify crontab job management across many systems, which meant filling in some missing features. I wanted a scriptable, command-line, self-documenting, portable utility. Other requirements for this utility included:

  • Cron jobs are assigned a short unique textual ID.
  • Maintain a library of standard jobs for easy installation on multiple hosts.
  • Ability to enable/disable jobs (commenting/un-commenting).
  • Allow entries to contain textual comments.
  • Must not hinder or break manual editing of crontab files.
  • Automatically backup entire crontab before modification.
  • Create/delete/modify crontab entries from command line/scripts.
  • Must not disturb existing entries or comments.
  • Cross-platform -- must work on any OS with the crontab command.
  • Maintain a "library" of common cron jobs for easy installation.

Perl is my sledgehammer of choice, so any platform with a crontab command and Perl should be able to run crontrol. It has been tested on various flavors of Linux, Solaris, and OpenBSD. Other platforms may require minor modification; please send me feedback regarding any modifications you make.

How Crontrol Works

Listing 1 shows a summary of options and major modes of operation for the crontrol command; invoke with -h to see this on your own systems.

Let's look at some examples. Suppose we wanted our systems to say "Hello World!" every 10 minutes during the hours of 11, 12, 16, and 18. We would formulate a command line similar to:

$ crontrol -a hello.world -T 'Say hello via cron' -H 11,12,16,18 \
  -M 0,10,20,30,40,50 -C 'echo "Hello World!"' 
Sure, some systems support the "*/10" notation to denote "every 10 minutes", but the name of the game here is portability. Any time fields that are not specified will default to "*". The previous command results in the following being appended to the end of the crontab file:

##################################################################
# The following entries were added by CRONTROL
# Entries can be edited by hand provided you preserve the format
# of the metadata stored in the comment.
##################################################################
#  %CRONTROL-COMMENT% 
0,10,20,30,40,50 11,12,16,18 * * * echo "Hello World!" \
  #%CRONTROL%; ID=hello.world; COMMENT=Say hello via cron
This shows that crontrol uses comments for both documentation and for storing its metadata. The newly created entry is uniquely identified by the textual id "hello.world", which is used as the id for all future modification of this entry. The COMMENT= field is purely for humans and is a place to remind yourself of exactly what you were trying to accomplish with this job. Here are some more quick examples:

# Comment out/disable the hello.world job
$ crontrol -c hello.world
# Un-comment/re-enable the hello.world job
$ crontrol -u hello.world
# Modify the existing job to only run during hours 9 and 10
$ crontrol -m hello.world -H 9,10
# Modify the command to say Goodbye
$ crontrol -m hello.world -C 'echo "Goodbye world!"'
# Delete the entry completely
$ crontrol -d hello.world
# List all crontab entries with some snazzy formatting
$ crontrol -l
Using Libraries
The key to organized and easy systems administration is uniformity. To that end, we sys admins usually collect libraries of tools and procedures that are proven to work well. Crontrol handles this by keeping a library of commonly used jobs for easy installation on any system. The library is kept either in the top of the crontrol script itself, or included with a Perl require() statement. Both formats are documented in the crontrol script file. Here is an example of an entry to describe our "hello.world" job:

@LIBRARY=(@LIBRARY,
        {
                ID      =>      'hello.world',
                MINUTES =>      '0,10,20,30,40,50',
                HOURS   =>      '11,12,16,18',
                COMMENT =>      'Say hello via cron',
                COMMAND =>      'echo "Hello World!"'
        },
);   
This is fairly self-explanatory; the library entries are stored in a list of associative arrays. Any Perl syntax for accomplishing that can be used. Some basic examples are included in the distributed version of the crontrol script file, so you don't really need to be a Perl programmer to use it. Other time specification fields are: DOW (day of week), DOM (days of month), and MONTHS. All are numeric and follow the standard crontab format. Any time specification that is omitted becomes "*" by default.

Why keep the configuration inside the script? Some of you are saying, "Use a configuration file!". Simply, I wanted this utility to have as few dependencies as possible. By putting the config and library inside the script, I eliminated two possible extra files. You can simply copy the script to a destination system and use it. This keeps the program always in sync with the config and library.

If you want to store your library in a separate file, you have a couple of options. First, a file named ~/.crontrol will automatically be read upon startup. You can add library entries using the above syntax. Second, if you want a global library for all users, create a require('/some/dir/your.crontrol.global.lib') entry at the top of the crontrol program. See examples given inside the config section of the crontrol script.

Adding an entry from the library of your standard jobs is as simple as a single non-interactive command line. To add the "hello.world" job to a crontab from the library, you would execute the following command:

$ crontrol  -L hello.world
To list all entries in the library, you would execute:

$ crontrol  -B
==================================================================
The library of TEMPLATES contains: 
==================================================================
0,10,20,30,40,50   11,12,16,18   *     *     *     echo "Hello World!"
        ID: hello.world
   Comment: Say hello via cron
After a job entry has been added, you can use any of the previously mentioned crontrol commands to modify it. If crontrol notices that an entry in the crontab no longer matches the definition inside the library, it will let you know with a message similar to the following whenever crontrol is invoked:

*** NOTICE: MISMATCH between library and crontab for entry ID: [hello.world]
  -> HOURS: Library says: [11,12,16,18]  but crontab is: [11,12]
  -> MINUTES: Library says: [0,10,20,30,40,50] but crontab is: [0,1,10]
There are two ways to correct these mismatches -- either change crontab or update the library entry. If you want to keep the changes but stop the complaining, simply change the ID of the installed entry. Use a command similar to the following:

$ crontrol -m hello.world -I hello.world.local
This would rename an entry with the ID "hello.world" to "hello.world.local". Crontrol will no longer complain. Alternatively, if the library represents what you want installed, simply delete and re-add the entry. Keeping the library up to date will reduce these situations.

But, what happens if someone modifies an entry manually with an editor? Well, first and foremost, nothing bad! One of the design goals was not to complicate manual editing in any way. You just need to preserve the format after the "#" in each entry.

Conclusion

Since I have been using crontrol, I have collected nearly 200 standard job entries in my library to support various system- and application-level automation processes. Crontrol allows me to snap these jobs into systems automatically and non-interactively, which is especially useful when setting up a new system.

You can download the Perl script for crontrol from:

http://www.njit.edu/~matt/crontrol/
Then, copy it to a directory in your path (like /usr/local/bin), make it executable, and start using it. You may need to adjust the path to your Perl installation on the first line of the script. The script will create a ~/.archive/ directory, which will be used to save backup copies of the crontab before each change. Crontrol is released under GPL. Enjoy!

Matthew Hoskins is a Unix Systems Administrator for The New Jersey Institute of Technology where he maintains many of the corporate administrative systems. He enjoys trying to get wildly different systems and software working together, usually with a thin layer of Perl (Locally known as "MattGlue"). When not hacking systems, he can often be found hacking in the kitchen. Matt can be contacted at: matt@njit.edu.