1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
#!/bin/sh
# Copyright 1999-2015 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
#
# this script looks into /etc/cron.[hourly|daily|weekly|monthly]
# for scripts to be executed. The info about last run is stored in
# /var/spool/cron/lastrun
LOCKDIR="/var/lock"
CRONSPOOLDIR="/var/spool/cron"
LASTRUNDIR="${CRONSPOOLDIR}/lastrun"
# This is the legacy lockfile that we need to clean up.
GLOBAL_LOCKFILE="${LASTRUNDIR}/lock"
# Usage: log <level> <args to logger>
# Log a message via syslog.
log() {
local level="$1"
shift
logger -i -p "cron.${level}" -t run-crons "$@"
}
# Usage: grab_lock <class>
# Grab the lock for <class> to make sure we are the only instance.
grab_lock() {
local i cronpid cmdline1 cmdline2
local lockfile
# Free whatever previous lock (if any) we held.
free_lock
# For the legacy global lock, don't try to create a full path.
case $1 in
/*) lockfile=$1 ;;
*) lockfile="${LOCKDIR}/cron.$1" ;;
esac
# Try twice to lock, otherwise give up.
i=0
while [ $(( i += 1 )) -le 2 ] ; do
# Normally we should be able to grab the lock and get out of here fast.
if ln -sn $$ "${lockfile}" 2>/dev/null ; then
break
fi
# Locking failed, so check for a running process.
# Handle both old- and new-style locking.
# Delete the cat logic when GLOBAL_LOCKFILE is purged.
# Note: Does not handle PID namespaces ...
if ! cronpid=$(readlink "${lockfile}" 2>/dev/null) ; then
if ! cronpid=$(cat "${lockfile}" 2>/dev/null) ; then
# The lockfile disappeared? Try the whole thing again ...
continue
fi
fi
# This is better than kill -0 because we can verify that it's really
# another run-crons process.
if diff -qs /proc/{${cronpid},$$}/cmdline > /dev/null 2>&1; then
# Whoa, another run-crons is really running.
return 1
fi
# The lockfile is pointing to a dead process so break it.
# TODO: This is still racy if we're running more than one run-crons.
rm -f "${lockfile}"
done
# Check to make sure locking was successful.
if [ ! -L "${lockfile}" ] ; then
echo "Can't create or read existing ${lockfile}, giving up"
exit 1
fi
# Set the lock file for free_lock to clean up.
_LOCKFILE="${lockfile}"
return 0
}
# Prevent random env vars from messing with us.
_LOCKFILE=
# Set a trap to release the lockfile when we're finished.
trap 'free_lock' EXIT HUP INT QUIT TERM
# Usage: free_lock
# Release the lock that we last grabbed. This does not nest!
free_lock() {
if [ -n "${_LOCKFILE}" ] ; then
rm -f "${_LOCKFILE}"
# Only break the lock once.
_LOCKFILE=
fi
}
EXIT_STATUS=0
# Grab the legacy global lock to smoothly handle upgrades.
# We should drop this after like Dec 2016.
if [ -L "${GLOBAL_LOCKFILE}" -o -f "${GLOBAL_LOCKFILE}" ] ; then
if ! grab_lock "${GLOBAL_LOCKFILE}" ; then
# An old process is still running -- abort.
exit 0
fi
# Now release the lock since we no longer care about it.
free_lock
fi
for BASE in hourly daily weekly monthly ; do
CRONDIR=/etc/cron.${BASE}
test -d $CRONDIR || continue
# Grab the lock for this specific dir.
if ! grab_lock "${BASE}" ; then
# Someone else is processing this dir, so skip it.
continue
fi
# Blow away stale states for this particular dir.
lastrunfile="${LASTRUNDIR}/cron.${BASE}"
if [ -e "${lastrunfile}" ] ; then
case $BASE in
hourly)
#>= 1 hour, 5 min -=> +65 min
TIME="-cmin +65" ;;
daily)
#>= 1 day, 5 min -=> +1445 min
TIME="-cmin +1445" ;;
weekly)
#>= 1 week, 5 min -=> +10085 min
TIME="-cmin +10085" ;;
monthly)
#>= 31 days, 5 min -=> +44645 min
TIME="-cmin +44645" ;;
esac
find "${LASTRUNDIR}/" -name cron.$BASE $TIME -exec rm {} \; 2>/dev/null || :
fi
# if there is no state file, make one, then run the scripts.
if [ ! -e "${lastrunfile}" ] ; then
touch "${lastrunfile}"
set +e
for SCRIPT in $CRONDIR/* ; do
if [ -x "${SCRIPT}" ] && [ ! -d "${SCRIPT}" ] ; then
# Filter out files people do not expect to be executed.
case ${SCRIPT} in
.*|*~) continue ;;
esac
log info "($(whoami)) CMD (${SCRIPT})"
$SCRIPT
ret=$?
if [ ${ret} -ne 0 ] ; then
log err "CMD (${SCRIPT}) failed with exit status ${ret}"
EXIT_STATUS=1
fi
fi
done
fi
done
# Clean out bogus state files with future times.
touch "${LASTRUNDIR}"
find "${LASTRUNDIR}/" -newer "${LASTRUNDIR}" -exec /bin/rm -f {} \; 2>/dev/null || :
exit ${EXIT_STATUS}
|