slicd - simple lightweight Linux cron daemon

Latest version: 0.2.0

The latest version of slicd can be downloaded here. For Arch Linux users, the AUR has a PKGBUILD.

You can find the source code on this GitHub, where you can also report bugs, send suggestions or other (constructive) feedback.

What is slicd ?

slicd is a Linux cron daemon aiming to be small, simple and lightweight. It doesn't try to support every possible feature under the sun, and while not a requirement is perfectly fitted to run under a supervision suite.

It comes as a few modules :

  • slicd-parser : to parse crontabs into one "compiled" file

    The parser's job is to process all system & user crontabs and compile them into a single file, in a ready-to-use format for the scheduler.

  • slicd-sched : the scheduler, aka the actual cron daemon

    The scheduler simply loads the compiled crontabs, determines the next time a job needs to run and simply waits for it. It doesn't need to wake up every minute (as most cron daemons do) and handles time changes (manual, NTP, DST...) fine.

    It also doesn't actually run anything, but simply prints on its stdout one line for each job to run, in the form USERNAME:COMMAND LINE

  • slicd-exec : the exec daemon, to actually run jobs

    The scheduler's stdout is aimed to be piped into this daemon's stdin, which will handle forking and executing the command line. It will report all forks & reaped children on its stdout, as well as anything printed on a child's stdout or stderr.

    It is important to note that it doesn't do any drop of privileges or environment changes, instead you're supposed to do this making sure it execs into the right tools; such as one to drop privileges to a specific user, one to set up the correct environment, etc

In addition, a few extra tools are provided, meant to be used alongside slicd-exec :

  • setuid : drop privileges to the specified user

  • miniexec : minimal parsing & execing of command-line

slicd-parser - Parse crontabs and "compile" them

slicd-parser(1) is a small tool that will read & parse the specified crontabs, and "compile" them into a single file in a ready-to-use format, that will be used by slicd-sched(1).

The format of the crontabs is mostly compatible with that of other cron daemons, with notable exception that no environment setting are supported.

Any line starting with a pound sign (#) is ignored, to allow comments. Comments are not supported elsewhere, e.g. at the end of a line (any pound sign will then be part of the command line).

Each line is made of 5 time-and-date fields, followed by a username field for system crontabs, then the command line.

The time-and-date fields are, in order:

field               allowed values
-----               --------------
minute              0-59
hour                0-23
day                 1-31
month               1-12 (or names, see below)
day of the week     0-6 (0=Sunday, 1=Monday, etc; or names, see below)

Range of values are allowed, separating two values with a hyphen (-). The specified range is inclusive, and must always have the lower value first.

A field can contain either a single value, a single range, or a list. A list is a set of values or ranges, separated by commas.

A field may also contain an asterisk to mean all allowed values (i.e. same as "first-last" range).

A step value can be used with ranges (or asterisk). Following the range (or asterisk) with "/_n_" where _n_ is the number to move forward in the range. The lower value will always be matched, though the higher one might not.

For example, using "10-20/2" would be the same as "10,12,14,16,18,20" and "10-20/3" be the same as "10,13,16,19" (note that 20 isn't a match).

Note that unlike other daemons, B does not wake every minute to check if a job matches the current time, instead it calculates the time for the next job to run, and waits until there. This is why any out of range value will be detected and reported as a parsing error.

As indicated in the table above, for the 'month' and 'day of the week' fields names can also be used. Names are always the first 3 letters of the month/day (case does not matter).

Names can be used in place of a single value or within ranges. You can for example use "jun-aug", "6-Aug" or "6-7,Aug" indifferently for the 'month' field.

Each field can have its value prefixed with an exclamation point (!) in order to invert it. For example, using "!15,20" as value for days will mean every day but the 15th and 20th; the invertion being applied to the entire list of values.

The hour field can also have its value prefixed with either the less than sign (<), the greater than sign (>), or both (<>), to enable the special DST mode for the job only on deactivation, only on activation, or both respectively. See slicd-sched(1) for more. Note that when also using the invertion flag (!), the DST flag(s) should come first.

System crontabs have an extra field for the username to run under.

The final field, or rest of the line, will be used as command line. See slicd-exec(1) for more on how it will be parsed/processed.

Special case: Using both 'day' & 'day of the week' fields

If a restriction is set on the 'day of the week' field, i.e. the whole range wasn't included, the state of the first 6 days (1-6 in the field 'day') will be checked:

  • if all are set, the field 'day' will be treated as if an asterisk was used (i.e. any restriction above will be ignored)

  • if none are set, this will result in a parsing error

  • if only some are set, it will be processed to mean the Nth of the 'day of the week'. For example, with the 'day' field set to "2,4" and 'day of the week' set to "Wed,Fri" then the job will run the 2nd Wednesday, 2nd Friday, 4th Wednesday and 4th Friday of the month.

    In addition to 1-5 for the first to fifth of the month, you can use 6 to mean the last of the month. For example, with "2,6" as 'day' and "Mon" as 'day of the week' the job would run the second and last Mondays.

    Note that using e.g. "4,6" when if the fourth is also the last will only have the job run once, on the fourth (or last) of the month, as expected.

slicd-sched - Scheduler, aka the cron daemon

slicd-sched(1) is the actual cron daemon. It must be started with the path to a file of "compiled" crontabs, previously generated via slicd-parser(1)

It will load the compiled crontabs in memory, calculate the next runtime for every job, process them as needed and wait until the next time a job needs to run.

Processing a job simply means that it will print on its stdout a line in the format USERNAME:COMMAND LINE corresponding to the job to run. Its stdout is obviously meant to be redirected into slicd-exec(1)'s stdin, which will take care of forking and executing the command line.

Unlike most cron daemons, slicd-sched(1) doesn't wake up every minute to check if a job definition matches & must be run; Instead, it calculate the next runtime for each job, and waits until the closest one.

It does so via a timer set on the real-time system clock, which means any time changes (manual, NTP, etc) is handled properly.

Specifically, if the time jumps backward, nothing happens (jobs are not re-run), as slicd-sched(1) still waits for the time previously calculated. If after setting the time backward you want jobs to be processed, you'll need to have slicd-sched(1) re-process jobs at the (new) current minute, which can be done simply by sending it a signal USR1.

If the time jumps forward and the timer has expired, it will process every job that should run in the current minute, and again set a new timer for the closest next run. (There's no attempt to run "missed" jobs.)

Daylight Saving Time

Time changes due to DST are also handled properly, though differently since they are another beast.

Specifically, when time jumps backward then jobs that run during the "repeated" interval will indeed run again. For example, if a job runs every hour at minute 30, and the clock jumps from 02:59 (DST activated) to 02:00 (DST deactivated), then the job will run at 02:30 (DST on), then an hour later at 02:30 (DST off).

When time jumps forward then jobs that would have run during the "missed" interval will simply not run, since that time didn't happen; there's no attempt to run "missed" jobs either. For example our job set to run every hour at minute 30, when the clock goes from 01:59 (DST off) to 03:00 (DST on), would simply run at 01:30 (DST off) then an hour later at 03:30 (DST on).

Special DST mode

A special DST handling mode can be activated on a per-job basis, in which case time changes due to DST are handled differently than described above. It can be enabled only on deactivation (time jumps backward), only on activation (time jumps forward), or both.

When time jumps backward, jobs will only run during the "first pass" - or when DST was activated. When the time "repeats" but with DST deactivated, they will not run. So using the same example, our job would run at 02:30 (DST on), and then only two hours later, at 03:30 (DST off).

When time jumps forward, jobs that would have run at least once during the "missed" interval will run at the first minute of DST. Again, with our usual example it would have the job run at 01:30 (DST off), then at 03:00 (DST on) - because of a "missed run" at the non-existing time of 02:30 - and back as usual at 03:30 (DST off).

User privileges

Since it only prints to its stdout when a job needs to run, slicd-sched(1) can run as any user, it doesn't require root privileges. Only slicd-exec(1) does, so the forked children can drop privileges as needed.

slicd-exec - Daemon to execute command-lines

slicd-exec(1) is a daemon that will read lines on its stdin, then fork & execute the specified command line. It is meant to be used alongside slicd-sched(1) to actually run the cron jobs.

The argument on its command-line are the ones that will be used to create a command line to execute into for each job.

slicd-exec(1) expect to read lines in the form USERNAME:COMMAND LINE and for each one, will fork a new child and execute into a new command line. The stdout & stderr of the new child will be pipes and anything printed there will end up on slicd-exec(1)'s stdout.

It is important to note that it does not do anything else, specifically does not drop privileges or set up the environment. If this is needed, this should be done by execing into the right tools.

Command-line construction

When a new line is read, it is split in two : a username, and a command-line. slicd-exec(1) will then fork & execute into a new command line, as specified on its own command line, with one single treatment: Any percent sign followed by another one (%%) will be replaced with a single percent sign (%), and any percent sign followed by a lowercase 'u' (%u) will be replaced with the username, as read from stdin.

The command line (or argv) to execute into is then the resulting arguments, with one extra argument: the command-line as read from stdin.

For example, a classic way to run slicd-exec(1) would be:

slicd-exec setuid %u sh -c

With this, for a line "bob:echo 'hello world'" read on its stdin, it will fork a new process and execute into a command-line made of 5 arguments:

setuid
bob
sh
-c
echo 'hello world'

setuid(1) is a small tool that simply drops privileges to that of the username specified as first argument, then executes into the rest of its command line.

In this case, into the shell, which will then parse & execute the command-line from the crontab.

If you don't need a full shell to run your cron jobs, you can use miniexec(1), a small tool that will simply parse/split its arguments then execute into the resulting command line.

Notes

slicd-exec(1) does not actually require root privileges to run. In fact, it could run as any user just fine, except that then every job will run as that user as well, and the one from the cronjob (i.e. read on stdin) has to be ignored.

setuid - Execute a program as another user

setuid(1) is a small tool that will set its real and effective uid and gid to those of the specified user, as well as the list of supplementaty groups to the one of said user, then execute into the rest of its command line.

setuid(1) obviously requires to run as root in order to be able to drop privileges.

miniexec - Parse and execute command line

miniexec(1) is a small tool to parse its arguments and execute into the resulting command line. It is very simple, and aimed to be used from slicd-exec(1) (after setuid(1)) to avoid running a whole shell just to execute a simple command line.

miniexec(1) will construct the command line to execute into by parsing each of its argument in the following manner (similar to single-quote shell escaping) :

  • skip leading & ending blanks (spaces & tabulations)

  • anything in between single quotes is left as-in (the single quotes being removed, obviously). A single quote cannot be used within, you need to escape it outside, e.g:

    'This is how it'\''s done.'

  • any backslash followed by another backslash will be replaced with a single backslash

  • any backslash followed by a single quote will be replaced with a single quote

  • split on blanks

Example

For example, imagine you ran slicd-exec(1) as such:

slicd-exec setuid %u miniexec

Processing a line "bob:echo 'hello world'" read on its stdin, it would fork a new process and execute into a command-line made of 4 arguments:

setuid
bob
miniexec
echo 'hello world'

Which in turn would have setuid(1) drop privileges, then execute into:

miniexec
echo 'hello world'

miniexec(1) would then parse its arguments, and execute into the expected command line:

echo
hello world

Free Software

slicd - Copyright (C) 2016 Olivier Brunel <jjk@jjacky.com>

slicd 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.

slicd 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.

Read more about slicd

You can check out the following blog posts.

Top of Page