-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathawecron
executable file
·167 lines (157 loc) · 4.52 KB
/
awecron
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
#!/bin/sh
main() {
# runs through all dirs that contain `tmr` file
# the order does not matter because it all will be in parallel
for t in "$cdir"/*/tmr; do
[ -e "$t" ] || continue # verify that file exists
# creates subshell to isolate the environment and for parallelism
(
# error when using unset variables
# can't also use -e because awecron should handle errors by itself
set -u
# gets cronjob directory
d="${t%/*}"
# compares the timer with the current epoch time
tmr=$(stat -c "%Y" "$d/tmr")
time=$(awk 'BEGIN{srand(); print srand()}')
if [ $((tmr <= time)) = 1 ]; then
run
fi
) &
done
wait
}
# function that actually runs a given cronjob
run() {
# deletes timer file to disable the cronjob if it is broken
rm "$d/tmr"
# runs the binary in the background
"$d/run" &
rpid="$!"
# spawn watchdog process for timeout
(
sleep "$timeout"
# the cronjob is not trusted to properly exit
# so if timeout is reached the cronjob will be forced to exit
kill -9 "$rpid" >/dev/null 2>/dev/null
) &
wpid=$!
# wait for run process
wait "$rpid"
# save error code for later
ec="$?"
# stop watchdog process
# ignore errors because wpid might not exist; there may not be a better way to handle this
kill "$wpid" >/dev/null 2>/dev/null
# gets the cronjob name
name="${d##*/}"
# exit code checking
if [ "$ec" = "0" ]; then
log "cronjob run success"
# gets the cronjob configuration variable
cfg=$(cat "$d/cfg")
# schedule the timer for next run time
touch -d "@$((time + cfg))" "$d/tmr"
else
log "cronjob run error"
fi
}
# dynamic sleep
ds() {
# gets the soonest cronjob next run time in the $cdir
# NOTE: keep in mind that the "next run time" is stored in last modification time
for t in "$cdir"/*/tmr; do
[ -e "$t" ] || continue # verify that file exists
# oldest last modification time == soonest cronjob next run time
if [ ! "$old" ] || [ "$t" -ot "$old" ]; then
old="$t"
fi
done
# checks if there is any timer file
if [ -n "$old" ]; then
# gets the next run time
next=$(stat -c "%Y" "$old")
else
# sets to max sleep duration if no timer file found
delay="$max"
return 0
fi
# converts next run time to sleep duration and applies limits if necessary
time=$(awk 'BEGIN{srand(); print srand()}')
delay=$((next - time))
if [ $((delay > max)) = 1 ]; then
delay="$max"
fi
if [ $((delay < min)) = 1 ]; then
delay="$min"
fi
}
# error check
ercheck() {
# checks that global awecron config variables are integers
# using expr with regex because of POSIX compatibility reasons
if ! { expr "$max" : '[0-9]\+' && expr "$min" : '[0-9]\+' && expr "$timeout" : '[0-9]\+'; } >/dev/null; then
log "some awecron global config variable is incorrectly set or does not exist"
exit 1
fi
for t in "$cdir"/*/tmr; do
[ -e "$t" ] || continue # verify that file exists
# creates subshell to isolate the environment and for parallelism
(
# gets cronjob directory
d="${t%/*}"
# gets the cronjob name
name="${d##*/}"
# checks if config exists and it is a file
if [ -f "$d/cfg" ]; then
cfg=$(cat "$d/cfg")
else
log "cronjob configuration file is missing"
rm "$d/tmr"
return 1
fi
# checks that run is executable
if ! [ -x "$d/run" ]; then
log "cronjob run file is missing or not executable"
rm "$d/tmr"
return 1
fi
# checks if cfg variable is an integer
if ! expr "$cfg" : '[0-9]\+' >/dev/null; then
log "cronjob config variable is incorrectly set or does not exist"
rm "$d/tmr"
return 1
fi
) &
done
wait
}
# logging format
log() {
printf "awecron (%s) {%s} [%s]: %s\n" "${USER:-?}" "${name:-?}" "${ec:-?}" "$1"
}
# checks if various options for storing awecron config directory exist, then sets to that location
if [ -d "$XDG_CONFIG_DIR/awecron" ]; then
cdir="$XDG_CONFIG_DIR/awecron"
elif [ -d "$HOME/.config/awecron" ]; then
cdir="$HOME/.config/awecron"
elif [ -d "/etc/awecron" ]; then
cdir="/etc/awecron"
else
log "awecron config directory does not exist"
exit 1
fi
# checks if global awecron config file exists and runs it
if [ -f "$cdir/cfg" ]; then
. "$cdir/cfg"
else
log "global awecron config file does not exist"
exit 1
fi
# does an initial error check
# NOTE: this is optional and if you really want you may remove it
ercheck
while true; do
main
ds && sleep "$delay"
done