Commit | Line | Data |
---|---|---|
9f0350f9 MW |
1 | #! @BASH@ |
2 | ### | |
3 | ### Check that dumps have been made as expected. | |
4 | ### | |
5 | ### (c) 2013 Mark Wooding | |
6 | ### | |
7 | ||
8 | ###----- Licensing notice --------------------------------------------------- | |
9 | ### | |
10 | ### This file is part of the `rsync-backup' program. | |
11 | ### | |
12 | ### rsync-backup is free software; you can redistribute it and/or modify | |
13 | ### it under the terms of the GNU General Public License as published by | |
14 | ### the Free Software Foundation; either version 2 of the License, or | |
15 | ### (at your option) any later version. | |
16 | ### | |
17 | ### rsync-backup is distributed in the hope that it will be useful, | |
18 | ### but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | ### GNU General Public License for more details. | |
21 | ### | |
22 | ### You should have received a copy of the GNU General Public License | |
23 | ### along with rsync-backup; if not, write to the Free Software Foundation, | |
24 | ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
25 | ||
26 | set -e | |
27 | ||
28 | quis=${0##*/} | |
29 | . @pkgdatadir@/lib.sh | |
30 | ||
31 | ###-------------------------------------------------------------------------- | |
32 | ### Stubs for the configuration file. | |
33 | ||
34 | defhook () { :; } | |
35 | addhook () { :; } | |
36 | ||
37 | retain () { :; } | |
38 | snaptype () { :; } | |
39 | like () { :; } | |
40 | retry () { :; } | |
41 | user () { :; } | |
42 | ||
43 | ###-------------------------------------------------------------------------- | |
44 | ### Output format switch. | |
45 | ||
46 | formats=: | |
47 | defformat () { formats=$formats$1:; } | |
48 | fmtstate=begin | |
49 | ||
50 | fmt () { | |
51 | state=$1; shift | |
52 | ||
53 | while :; do | |
54 | ostate=$fmtstate | |
55 | case $fmtstate in $state) break ;; esac | |
56 | case "$fmtstate,$state" in | |
57 | begin,end) | |
58 | fmtstate=end | |
59 | ;; | |
60 | begin,*) | |
61 | ${fmt}_header "$@" | |
62 | fmtstate=hosttbl_begin | |
63 | ;; | |
64 | hosttbl_begin,hosttbl_*) | |
65 | ${fmt}_hosttbl_begin "$@" | |
66 | fmtstate=hosttbl_host | |
67 | ;; | |
68 | hosttbl_host,hosttbl_fs) | |
69 | fmtstate=hosttbl_fs | |
70 | ;; | |
71 | hosttbl_fs,hosttbl_host) | |
72 | ${fmt}_hosttbl_sep "$@" | |
73 | fmtstate=hosttbl_host | |
74 | ;; | |
75 | hosttbl_begin,* | hosttbl_end,*) | |
76 | fmtstate=logdump_begin | |
77 | ;; | |
78 | hosttbl_*,*) | |
79 | ${fmt}_hosttbl_end "$@" | |
80 | fmtstate=hosttbl_end | |
81 | ;; | |
82 | logdump_begin,logdump_*) | |
83 | fmtstate=logdump_out | |
84 | ;; | |
85 | logdump_out,logdump_info | logdump_out,logdump_file) | |
86 | fmtstate=$state | |
87 | ;; | |
88 | logdump_*,logdump_begin) | |
89 | ${fmt}_logdump_end "$@" | |
90 | fmtstate=logdump_begin | |
91 | ;; | |
92 | logdump_end,*) | |
93 | fmtstate=footer | |
94 | ;; | |
95 | logdump_*,*) | |
96 | fmtstate=logdump_end | |
97 | ;; | |
98 | footer,end) | |
99 | ${fmt}_footer "$@" | |
100 | fmtstate=end | |
101 | ;; | |
102 | esac | |
103 | case $fmtstate in | |
104 | "$ostate") | |
105 | echo >&2 "$quis: FATAL! NO PROGRESS IN FMT STATE MACHINE" | |
106 | exit 9 | |
107 | ;; | |
108 | esac | |
109 | done | |
110 | ||
111 | ${fmt}_$fmtstate "$@" | |
112 | } | |
113 | ||
114 | ###-------------------------------------------------------------------------- | |
115 | ### Plain text output. | |
116 | ||
117 | defformat txt | |
118 | ||
119 | txt_header () { :; } | |
120 | txt_hosttbl_begin () { :; } | |
121 | txt_hosttbl_host () { echo "HOST $1"; } | |
122 | txt_hosttbl_fs () { printf " %-23s %s\n" "$2" "$4"; } | |
123 | txt_hosttbl_sep () { echo; } | |
124 | txt_hosttbl_end () { :; } | |
125 | ||
126 | txt_logdump_begin () { | |
127 | cat <<EOF | |
128 | ||
129 | ----------------------------------------------------------------------------- | |
130 | Log tail for $1:$2 | |
131 | ||
132 | EOF | |
133 | } | |
134 | ||
135 | txt_logdump_info () { echo "!!! $3"; } | |
136 | txt_logdump_file () { cat "$3"; } | |
137 | txt_logdump_end () { :; } | |
138 | ||
139 | txt_footer () { :; } | |
140 | txt_end () { :; } | |
141 | ||
142 | fmt=txt | |
143 | ||
144 | ###-------------------------------------------------------------------------- | |
145 | ### HTML output. | |
146 | ||
147 | defformat html | |
148 | ||
149 | html_header () { | |
150 | cat <<EOF | |
151 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" | |
152 | "http://www.w3c.org/TR/html4/strict.dtd"> | |
153 | <html> | |
154 | <head> | |
155 | <title>$now rsync-backup report</title> | |
156 | <style type='text/css'><!-- | |
157 | body { | |
158 | background: white; | |
159 | color: black; | |
160 | margin: 1ex 2em; | |
161 | } | |
162 | ||
163 | h1, h2 { font-family: sans-serif; font-weight: bold; } | |
164 | ||
165 | h1 { | |
166 | padding-bottom: 1ex; | |
167 | border-bottom: solid medium black; | |
168 | margin-bottom: 3ex; | |
169 | font-size: 200%; | |
170 | } | |
171 | ||
172 | h2 { | |
173 | border-top: solid thin black; | |
174 | padding-top: 1ex; | |
175 | margin-top: 3ex; | |
176 | font-size: x-large; | |
177 | } | |
178 | ||
179 | h1 + h2 { | |
180 | border-top: none; | |
181 | padding-top: 0; | |
182 | } | |
183 | ||
184 | div.footer { | |
185 | border-top: solid medium black; | |
186 | margin-top: 3ex; | |
187 | padding-top: 1ex; | |
188 | font-size: small; | |
189 | clear: both; | |
190 | text-align: right; | |
191 | font-style: italic; | |
192 | } | |
193 | ||
194 | table.hosttbl { | |
195 | border-collapse: collapse; | |
196 | display: inline-table; | |
197 | margin: 1ex 1em; | |
198 | } | |
199 | table.hosttbl tr.fs td { | |
200 | border: solid thin black; | |
201 | } | |
202 | table.hosttbl td { padding: 0.5ex 0.5em; } | |
203 | table.hosttbl tr.host td { | |
204 | padding-top: 2ex; | |
205 | text-align: center; | |
206 | font-weight: bold; | |
207 | font-family: monospace; | |
208 | } | |
209 | table.hosttbl td.fs { | |
210 | font-family: monospace; | |
211 | } | |
212 | ||
213 | table.hosttbl td.winning { background: #2f4; } | |
214 | table.hosttbl td.failed { background: #f80; } | |
215 | table.hosttbl td.broken { background: #f11; } | |
216 | table.hosttbl td.never { background: #66f; } | |
217 | ||
218 | pre { | |
219 | clear: both; | |
220 | margin: 3ex 2em; | |
221 | padding: 1ex; | |
222 | background: #eee; | |
223 | border: solid thin black; | |
224 | overflow-y: auto; | |
225 | } | |
226 | ||
227 | div.logdump-info { | |
228 | margin: 3ex 2em; | |
229 | padding: 1ex; | |
230 | background: #f11; | |
231 | border: solid thin black; | |
232 | } | |
233 | --></style> | |
234 | </head> | |
235 | <body> | |
236 | ||
237 | <h1><tt>rsync-backup</tt> report: $now</tt></h1> | |
238 | EOF | |
239 | } | |
240 | ||
241 | html_hosttbl_begin () { | |
242 | cat <<EOF | |
243 | ||
244 | <h2>Overview</h2> | |
245 | EOF | |
246 | } | |
247 | ||
248 | html_hosttbl_host () { | |
249 | cat <<EOF | |
250 | <table class=hosttbl> | |
251 | <tr class=host><td colspan=2>$1</tc> | |
252 | EOF | |
253 | } | |
254 | ||
255 | html_hosttbl_fs () { | |
256 | case $3 in | |
257 | winning) link="" knil="" ;; | |
258 | *) link="<a href='#log-$host:$fs'>" knil="</a>";; | |
259 | esac | |
260 | cat <<EOF | |
261 | <tr class=fs> | |
262 | <td class=fs>$2</td> | |
263 | <td class=$3>$link$4$knil</td> | |
264 | EOF | |
265 | } | |
266 | ||
267 | html_hosttbl_sep () { | |
268 | cat <<EOF | |
269 | </table> | |
270 | EOF | |
271 | } | |
272 | ||
273 | html_hosttbl_end () { | |
274 | cat <<EOF | |
275 | </table> | |
276 | EOF | |
277 | } | |
278 | ||
279 | html_logdump_begin () { | |
280 | cat <<EOF | |
281 | ||
282 | <h2><a name='log-$1:$2'>Log tail for <tt>$1:$2</tt></a></h2> | |
283 | EOF | |
284 | } | |
285 | ||
286 | html_logdump_info () { | |
287 | cat <<EOF | |
288 | <div class=logdump-info>$3</div> | |
289 | EOF | |
290 | } | |
291 | ||
292 | html_logdump_file () { | |
293 | cat <<EOF | |
294 | <pre class=logdump> | |
295 | EOF | |
296 | cat "$3" | |
297 | cat <<EOF | |
298 | </pre> | |
299 | EOF | |
300 | } | |
301 | ||
302 | html_logdump_end () { :; } | |
303 | ||
304 | html_footer () { | |
305 | cat <<EOF | |
306 | ||
307 | <div class=footer> | |
308 | Checked at $now $now_time. | |
309 | <br><tt>rsync-backup</tt> $VERSION; © 2014 Mark Wooding | |
310 | </div> | |
311 | ||
312 | </body> | |
313 | </html> | |
314 | EOF | |
315 | } | |
316 | ||
317 | html_end () { :; } | |
318 | ||
319 | ###-------------------------------------------------------------------------- | |
320 | ### Main checking. | |
321 | ||
322 | INDEXDB=@pkglocalstatedir@/index.db | |
323 | ||
324 | host () { host=$1; } | |
325 | ||
326 | patterns= | |
327 | showhost= | |
328 | failures= | |
329 | ||
330 | backup () { | |
331 | for fs in "$@"; do | |
332 | case $fs in *:*) fs=${fs%%:*} ;; esac | |
333 | matchp=nil | |
334 | for p in "${patterns[@]}"; do | |
335 | case $host:$fs in $p) matchp=t; break ;; esac | |
336 | done | |
337 | case $matchp in nil) return ;; esac | |
338 | when=$(sqlite3 $INDEXDB \ | |
339 | "SELECT MAX(date) FROM idx WHERE host = '$host' AND fs = '$fs';") | |
340 | case $when in | |
341 | "") | |
342 | class=never | |
343 | info="NEVER" | |
344 | show=t | |
345 | win=nil | |
346 | ;; | |
347 | "$now") | |
348 | class=winning | |
349 | info="today" | |
350 | show=$verbose | |
351 | win=t | |
352 | ;; | |
353 | *) | |
354 | jdn=$(julian "$when") | |
355 | ago=$(( $now_jdn - $jdn )) | |
356 | case $ago in 1 | -1) days=day ;; *) days=days ;; esac | |
357 | case $ago in | |
358 | -*) class=future; info="${ago#-} $days in the FUTURE" ;; | |
359 | 1 | 2) class=failed; info="$ago $days ago" ;; | |
360 | *) class=broken; info="$ago $days ago" ;; | |
361 | esac | |
362 | show=t | |
363 | win=nil | |
364 | ;; | |
365 | esac | |
366 | case $show in | |
367 | t) | |
368 | case $showhost in | |
369 | "$host") ;; | |
370 | *) | |
371 | fmt hosttbl_host $host | |
372 | showhost=$host | |
373 | ;; | |
374 | esac | |
375 | fmt hosttbl_fs $host $fs $class "$info" | |
376 | case $win in | |
377 | nil) failures="$failures $host:$fs" ;; | |
378 | esac | |
379 | ;; | |
380 | esac | |
381 | done | |
382 | } | |
383 | ||
384 | failure_logs () { | |
385 | for fail in $failures; do | |
386 | host=${fail%:*} fs=${fail##*:} | |
387 | any=nil | |
388 | for i in "$logdir/$host/$fs.$now#"*; do | |
389 | if [ -f "$i" ]; then log=$i; any=t; fi | |
390 | done | |
391 | fmt logdump_begin $host $fs | |
392 | case $any in | |
393 | t) fmt logdump_file $host $fs "$log" ;; | |
394 | nil) fmt logdump_info $host $fs "No log! No backup attempted." ;; | |
395 | esac | |
396 | done | |
397 | } | |
398 | ||
399 | ###-------------------------------------------------------------------------- | |
400 | ### Read the configuration and we're done. | |
401 | ||
402 | usage () { | |
403 | echo "usage: $quis [-v] [-c CONF] [-f FORMAT] [PATTERNS...]" | |
404 | } | |
405 | ||
406 | version () { | |
407 | echo "$quis, rsync-backup version $VERSION" | |
408 | } | |
409 | ||
410 | verbose=nil | |
411 | ||
412 | while getopts "hVc:f:v" opt; do | |
413 | case "$opt" in | |
414 | h) usage; exit 0 ;; | |
415 | V) version; config; exit 0 ;; | |
416 | c) conf=$OPTARG ;; | |
417 | f) | |
418 | case $formats in | |
419 | *":$OPTARG:"*) ;; | |
420 | *) echo >&2 "$0: unknown format \`$OPTARG'"; exit 1 ;; | |
421 | esac | |
422 | fmt=$OPTARG | |
423 | ;; | |
424 | v) verbose=t ;; | |
425 | *) exit 1 ;; | |
426 | esac | |
427 | done | |
428 | shift $((OPTIND - 1)) | |
429 | case $# in | |
430 | 0) declare -a patterns=("*") ;; | |
431 | *) declare -a patterns=("$@") ;; | |
432 | esac | |
433 | ||
434 | now=$(date +"%Y-%m-%d") | |
435 | now_time=$(date +"%H:%M:%S %z") | |
436 | now_jdn=$(julian $now) | |
437 | . "$conf" | |
438 | failure_logs | |
439 | fmt end | |
440 | ||
441 | ###----- That's all, folks -------------------------------------------------- |