@reboot - explaining simple cron magic
In a conversation with Stuart the subject of cron timings came up, and after a brief discussion the ugly head of @reboot reared. While most people know that you can use the special ‘event’ syntax to trigger cronjobs at specific times I’d guess a very small number of them actually know how it works. For example does cron rerun @reboot jobs when the service is restarted? (hint - no it doesn’t.)
After a quick discussion on how cron knew the machine hadn’t really rebooted we had a short list on how it was doing it - tracking uptime, watching run level states, calling the init script only on certain levels… the only problem is that all of those had obvious issues that stopped them being a good choice. So I dug a little deeper.
First I needed a canary cronjob that would show me when @reboot was actually triggered successfully and a cronjob to run it -
$ vi /home/dwilson/log-cron
#!/bin/bash
logger "Cron ran me"
$ chmod a+rx /home/dwilson/log-cron
# and then the crontab
$ sudo vi /etc/cron.d/logme
@reboot dwilson /home/dwilson/log-cron
Once I had this in place I ran through the possible triggers, changing run levels, stopping and starting the script and changing the uptime were the big three - and none of them worked. In the logs were a number of ‘Added a cronjob and got - (CRON) INFO (Skipping @reboot jobs – not system startup) in syslog when I restarted.’ lines instead.
After a quick run under strace
I gave up under the sheer
weight of output and decided to look at the code. As my test machine was
Debian I added a source line for apt and pulled down the packages
source.
echo "deb-src http://ftp.uk.debian.org/debian etch main contrib non-free " > /etc/apt/sources.list.d/source.list
apt-get update
mkdir cron-src
cd cron-src
apt-get source cron
Now it was time to do some digging and get some line numbers to look at. In the cron directory I ran some greps to get an overview of possible code locations:
cd cron-3.0pl1/
grep -n -i reboot -r .
grep -i -r WHEN_REBOOT *
grep -n -i '@reboot' -r .
// ... snip ... //
cron.c:284:#define REBOOT_FILE "/var/run/crond.reboot"
cron.c:286: if (access(REBOOT_FILE, F_OK) == 0) {
cron.c:293: if ((rbfd = creat(REBOOT_FILE, S_IRUSR&S_IWUSR)) < 0) {
// ... snip ... //
# ls -alh /var/run/crond.reboot
---------- 1 root root 0 2008-11-07 11:07 /var/run/crond.reboot
Looking at the three interesting lines above we see how cron, on Debian at least, knows if it’s been a real reboot. It uses the access function to check REBOOT_FILE. Nosing around a little more I also found the creat line and saw that the file had no permissions. The delving was nearly over but there was one thing I didn’t get - how did the file get removed?
A quick look at the /var/run Filesystem Hierarchy Standard page cleared this up - ‘Files under this directory must be cleared (removed or truncated as appropriate) at the beginning of the boot process.’. Which Debian does in /etc/init.d/bootclean Why is it done on system boot? So that if the system failed it still gets cleaned out.
With a much better idea how this should work, and just to double check, I stopped crond, deleted the /var/run/crond.reboot file by hand and turned crond back on. And my cronjob logged a little line. Not much feedback for all those commands but it was oddly worth it.