]> arthur.barton.de Git - backup-script.git/blob - bin/backup-script
Implement new "backup_type": "disabled"
[backup-script.git] / bin / backup-script
1 #!/bin/bash
2 #
3 # backup-script system for cloning systems using rsync
4 # Copyright (c)2008-2016 Alexander Barton <alex@barton.de>
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 # Please read the file COPYING, README and AUTHORS for more information.
11 #
12
13 NAME=$(basename "$0")
14 PIDFILE="/var/run/$NAME.pid"
15
16 DRYRUN=0
17 VERBOSE=0
18 TAG=""
19
20 export LC_ALL=C
21
22 declare -i count_all=0
23 declare -i count_started=0
24 declare -i count_ok=0
25 declare -i count_ok_vanished=0
26 declare -i count_enabled=0
27
28 destinations=""
29
30 # Default settings, can be overwritten in backup-script.conf:
31 [ -d "/usr/local/etc/backup-script.d" ] \
32         && conf_d="/usr/local/etc/backup-script.d" \
33         || conf_d="/etc/backup-script.d"
34 pre_exec=""
35 post_exec=""
36 default_backup_type="rsync"
37 default_source_root="/"
38 default_files="running-config"
39 default_target="/var/backups"
40 default_user="root"
41 default_ssh_args_add=""
42 default_rsync_args_add=""
43 default_exclude_args_add=""
44 default_exclude_dirs_add=""
45 default_compress=1
46 default_ping=1
47 default_local=0
48 default_generations=0
49 default_io_timeout="1800"
50 default_job_pre_exec=""
51 default_job_post_exec=""
52 default_tags=""
53
54 Usage() {
55         echo "Usage: $NAME [<options>] [<system> [<system> [...]]]"
56         echo
57         echo "  -n, --dry-run       Test run only, don't copy any data."
58         echo "  -p, --progress      Show progress, see rsync(1)."
59         echo "  -t TAG, --tag TAG   Only run jobs with tag TAG."
60         echo
61         echo "When no <system> is given, all defined systems are used."
62         echo
63         echo -e $config_info
64         echo
65         exit 2
66 }
67
68 CleanUp() {
69         if [ -n "$post_exec" ]; then
70                 echo "Executing \"$post_exec\" ..."
71                 sh -c $post_exec
72                 if [ $? -ne 0 ]; then
73                         echo "Warning: post-exec command failed!"
74                 fi
75                 echo
76         fi
77         rm -f "$PIDFILE"
78 }
79
80 GotSignal() {
81         echo
82         echo "--> Got break signal, cleaning up & aborting ..."
83         echo
84         CleanUp
85         echo -n "Aborted: "; date
86         echo
87         sleep 3
88         exit 9
89 }
90
91 ExecJob() {
92         local what="$1"
93         local cmd="$2"
94
95         echo "Running job ${what}-exec command ..."
96         [ "$local" -eq 0 ] \
97                 && cmd="$ssh_cmd ${user}@${system} $cmd"
98         echo -n "Start date (${what}-exec): "; date
99         echo "$cmd"
100         if [ "$DRYRUN" -eq 0 ]; then
101                 $SHELL -c "$cmd"; local ret=$?
102         else
103                 echo " *** Trial run, not executing ${what}-exec command!"
104                 ret=0
105         fi
106         [ $ret -eq 0 ] \
107                 && echo "The ${what}-exec command completed with status 0, OK." \
108                 || echo "The ${what}-exec command completed with ERRORS, code $ret!"
109         return $ret
110 }
111
112 GetFS() {
113         local dir="$1"
114
115         while [ -n "$dir" ]; do
116                 findmnt -fn -o FSTYPE --raw "$dir" 2>/dev/null; local r=$?
117                 if [ $r -eq 0 ]; then
118                         return 0
119                 elif [ $r -eq 127 ]; then
120                         echo "UNKNOWN"
121                         return 1
122                 fi
123                 dir=$(dirname "$dir") || return 1
124         done
125 }
126
127 CreateSubvolume() {
128         local volume="$1"
129         local fs
130         local dir
131
132         dir=$(dirname "$volume")
133         fs=$(GetFS "$dir")
134         case "$fs" in
135           "btrfs")
136                 btrfs subvolume create "$volume"  >/dev/null || return 1
137                 ;;
138           "zfs")
139                 zfs create "$(echo "$volume" | cut -c2-)" || return 1
140                 ;;
141           *)
142                 echo "CreateSubvolume: Incompatible FS type \"$fs\" on \"$dir\"!"
143                 return 9
144         esac
145         return 0
146 }
147
148 CloneSubvolume() {
149         local source="$1"
150         local volume="$2"
151         local snapshot="$3"
152         local dir
153         local fs
154         local link_name
155
156         dir=$(dirname "source")
157         fs=$(GetFS "$source")
158         case "$fs" in
159           "btrfs")
160                 btrfs subvolume snapshot "$source" "$snapshot"  >/dev/null || return 1
161                 ;;
162           "zfs")
163                 zfs snapshot "$snapshot" || return 1
164                 link_name="$(echo "$snapshot" | cut -d@ -f2-)"
165                 ln -s \
166                         "current/.zfs/snapshot/$link_name" \
167                         "$(dirname "$volume")/$link_name"
168                 ;;
169           *)
170                 echo "CloneSubvolume: Incompatible FS type \"$fs\" on \"$source\"!"
171                 return 9
172         esac
173         return 0
174 }
175
176 RenameSubvolume() {
177         local source="$1"
178         local target="$2"
179         local fs
180
181         fs=$(GetFS "$source")
182         case "$fs" in
183           "btrfs")
184                 mv "$source" "$target" || return 1
185                 ;;
186           "zfs")
187                 zfs rename \
188                   "$(echo "$source" | cut -c2-)" \
189                   "$(echo "$target" | cut -c2-)" \
190                         || return 1
191                 ;;
192           *)
193                 echo "RenameSubvolume: Incompatible FS type \"$fs\" on \"$source\"!"
194                 return 9
195         esac
196         return 0
197 }
198
199 DeleteSubvolume() {
200         local volume="$1"
201         local fs
202         local id
203         local snapshot
204
205         fs=$(GetFS "$volume")
206         case "$fs" in
207           "btrfs")
208                 btrfs subvolume delete "$volume" >/dev/null || return 1
209                 ;;
210           "zfs")
211                 id="$(basename "$volume")"
212                 if [ -h "$volume" ]; then
213                         snapshot="$(dirname "$volume")/current@$id"
214                 else
215                         snapshot="$volume"
216                 fi
217                 zfs destroy -r "$(echo "$snapshot" | cut -c2-)" >/dev/null || return 1
218                 [ -h "$volume" ] && rm "$volume"
219                 ;;
220           *)
221                 echo "DeleteSubvolume: Incompatible FS type \"$fs\" on \"$volume\"!"
222                 return 9
223         esac
224         return 0
225 }
226
227 Initialize_Last_SysTarget_Snapshot() {
228         sys_target="$1"
229         unset last
230         unset snapshot
231
232         fs=$(GetFS "$sys_target")
233         case "$fs" in
234           "btrfs")
235                 # Search directory of last generation, if any
236                 last=$(ls -1d "$sys_target"/[0-9]* 2>/dev/null | sort -r | head -n1)
237                 if [ -n "$last" ]; then
238                         if [ ! -d "$last" ]; then
239                                 echo "Last snapshot \"$last\" seems not to be a directory!? \"$system\" skipped!"
240                                 echo
241                                 return 1
242                         fi
243                 fi
244                 sys_target="$sys_target/$(date +%Y%m%d-%H%M%S)"
245                 snapshot="$sys_target"
246                 ;;
247           "zfs")
248                 # On ZFS, the last generation is always named "current"
249                 if [ -e "$sys_target/current" ]; then
250                         last="$sys_target/current"
251                         if [ "$(uname)" = "Linux" ]; then
252                                 date=$(LC_ALL=C stat "$1" | grep "^Modify: " \
253                                  | cut -d':' -f2- | cut -d. -f1)
254                         else
255                                 date=$(LC_ALL=C stat -f "%Sc" "$1")
256                         fi
257                         date=$(echo "$date" | sed -e's/^ //g' -e 's/[-:]//g' -e 's/ /-/g')
258
259                 else
260                         last=""
261                         date="$(date +%Y%m%d-%H%M%S)"
262                 fi
263                 snapshot="$(echo "$sys_target/current" | cut -c2-)@$date"
264                 sys_target="$sys_target/current"
265                 ;;
266           *)
267                 echo "Initialize_Last_SysTarget_Snapshot: Incompatible FS type \"$fs\" on \"$sys_target\"!"
268                 return 1
269         esac
270         return 0
271 }
272
273 # Search configuration file (last one is used as default!)
274 for conf in \
275         "/usr/local/etc/backup-script.conf" \
276         "/etc/backup-script.conf" \
277         "${conf_d}/backup-script.conf" \
278         "/usr/local/etc/backup-script.conf" \
279 ; do
280         [ -r "$conf" ] && break
281 done
282
283 # Read in configuration file
284 config_info="Configuration file is \"$conf\""
285 if [ -r "$conf" ]; then
286         # shellcheck source=/dev/null
287         source "$conf"
288 else
289         config_info="${config_info} (not readable, using defaults)"
290 fi
291 config_info="${config_info},\nusing \"$conf_d\" as configuration directory."
292
293 while [ $# -gt 0 ]; do
294         case "$1" in
295           "-n"|"--dry-run")
296                 DRYRUN=1; shift
297                 ;;
298           "-p"|"--progress")
299                 VERBOSE=1; shift
300                 ;;
301           "-t"|"--tag")
302                 shift; TAG="$1"; shift
303                 [ -n "$TAG" ] || Usage
304                 ;;
305           "-"*)
306                 Usage
307                 ;;
308           *)
309                 break
310         esac
311 done
312
313 echo -n "Started: "; date
314 echo -e "$config_info"
315
316 # Check rsync and its protocol version
317 rsync=$(which "rsync" 2>/dev/null)
318 if [ $? -ne 0 ]; then
319         echo "Failed to detect rsync(1)! Is it installed in your \$PATH?"
320         exit 1
321 fi
322 rsync_proto=$($rsync --version 2>/dev/null | head -n 1 | sed 's/.*  protocol version \([0-9]*\)$/\1/')
323 if [ $? -ne 0 ]; then
324         echo "Failed to detect protocol version of $rsync!"
325         exit 1
326 fi
327 echo "Rsync command is $rsync, protocol version $rsync_proto."
328
329 [[ -n "$TAG" ]] && echo "Running jobs tagged with \"$TAG\"."
330 echo
331
332 trap GotSignal SIGINT
333
334 if [ $# -ge 1 ]; then
335         for s in "$@"; do
336                 if [ ! -r "${conf_d}/$s" ]; then
337                         echo "$NAME: Can' read \"${conf_d}/$s\"!"
338                         exit 3
339                 fi
340                 sys+=("${conf_d}/$s")
341         done
342 else
343         sys=("${conf_d}/"*)
344 fi
345
346 # check and create PID file
347 if [ -e "$PIDFILE" ]; then
348         echo "Lockfile \"$PIDFILE\" already exists."
349         echo "Is an other instance still running?"
350         echo
351         echo -n "Aborted: "; date
352         echo
353         exit 4
354 fi
355 touch "$PIDFILE" 2>/dev/null
356 if [ $? -ne 0 ]; then
357         echo "Warning: can't create PID file \"$PIDFILE\"!"
358         echo
359 else
360         echo "$$" >>"$PIDFILE"
361 fi
362
363 if [ -n "$pre_exec" ]; then
364         echo "Executing \"$pre_exec\" ..."
365         sh -c $pre_exec
366         if [ $? -ne 0 ]; then
367                 echo "Error: pre-exec command failed!"; echo
368                 CleanUp
369                 echo "Aborting backup."; echo
370                 exit 5
371         fi
372         sleep 2
373         echo
374 fi
375
376 for f in "${sys[@]}"; do
377         [[ -r "$f" && -f "$f" ]] || continue
378
379         fname=$(basename "$f")
380         case "$fname" in
381                 "backup-script.conf"|*.sh)
382                         continue
383                         ;;
384         esac
385
386         # Set global defaults
387         system="$fname"
388         backup_type="$default_backup_type"
389         user="$default_user"
390         source_root="$default_source_root"
391         files="$default_files"
392         target="$default_target"
393         ssh_args_add="$default_ssh_args_add"
394         rsync_args_add="$default_rsync_args_add"
395         exclude_args_add="$default_exclude_args_add"
396         exclude_dirs_add="$default_exclude_dirs_add"
397         compress="$default_compress"
398         ping="$default_ping"
399         local="$default_local"
400         generations="$default_generations"
401         job_pre_exec="$default_job_pre_exec"
402         job_post_exec="$default_job_post_exec"
403         tags="$default_tags"
404         io_timeout="$default_io_timeout"
405
406         # Compatibility with backup-pull(1) script: Save global values ...
407         pre_exec_saved="$pre_exec"
408         post_exec_saved="$post_exec"
409
410         # Compatibility with backup-pull(1) script: Set defaults
411         host=""
412         unset source
413         unset pre_exec
414         unset post_exec
415
416         # Read in system configuration file
417         # shellcheck source=/dev/null
418         source "$f"
419
420         # Compatibility with backup-pull(1) script: Fix up configuration
421         [[ "$system" = "$fname" && -n "$host" ]] \
422                 && system="$host"
423         [[ "$source_root" = "$default_source_root" && -n "$source" ]] \
424                 && source_root="$source"
425         [[ -z "$job_pre_exec" && -n "$pre_exec" ]] \
426                 && job_pre_exec="$pre_exec"
427         [[ -z "$job_post_exec" && -n "$post_exec" ]] \
428                 && job_post_exec="$post_exec"
429
430         # Compatibility with backup-pull(1) script: Restore global values ...
431         pre_exec="$pre_exec_saved"
432         post_exec="$post_exec_saved"
433
434         # Validate configuration
435         if [[ "$system" = "localhost" || "$system" = "127.0.0.1" ]]; then
436                 # Local system
437                 local=1
438                 compress=0
439         fi
440
441         # Add "NONE" tag when no tags are given in the config file:
442         [[ -z "$tags" ]] && tags="NONE"
443         # Add "auto-tags":
444         [[ "$local" -eq 1 ]] && tags="$tags,LOCAL"
445         # Check tags
446         if [[ -n "$TAG" && "$TAG" != "ALL" ]]; then
447                 echo "$tags" | grep -E "(^|,)$TAG(,|$)" >/dev/null 2>&1
448                 if [ $? -ne 0 ]; then
449                         if [ "$DRYRUN" -ne 0 ]; then
450                                 echo "Tags of system \"$system\" don't match \"$TAG\": \"$tags\". Skipped."
451                                 echo
452                         fi
453                         continue
454                 fi
455         fi
456
457         # Make sure "source_root" ends with a slash ("/")
458         case "$source_root" in
459           *"/")
460                 ;;
461           *)
462                 source_root="$source_root/"
463         esac
464
465         # Make sure "target" DOESN'T end with a slash ("/")
466         case "$target" in
467           "*/")
468                 target=$( echo "$target" | sed -e 's/\/$//g' )
469                 ;;
470         esac
471
472         [ "$system" = "$fname" ] \
473                 && systxt="\"$system\"" \
474                 || systxt="\"$fname\" [\"$system\"]"
475         [ "$local" -eq 0 ] \
476                 && echo "Working on $systxt ..." \
477                 || echo "Working on $systxt (local system) ..."
478
479         count_all=$count_all+1
480
481         # Check if job is disabled
482         if [ "$backup_type" = "disabled" ]; then
483                 echo "Job is DISABLED and will be skipped."
484                 echo; continue
485         fi
486
487         count_enabled=$count_enabled+1
488
489         # Check target directory
490         if [ -z "$target" ]; then
491                 echo "No target directory specified for \"$system\"!? Skipped!"
492                 echo; continue
493         fi
494         if [ ! -d "$target" ]; then
495                 echo "Target \"$target\" is not a directory!? \"$system\" skipped!"
496                 echo; continue
497         fi
498
499         sys_target="$target/$fname"
500         sys_root="$sys_target"
501         if [[ "$DRYRUN" -eq 0 && ! -e "$sys_target" ]]; then
502                 if [ $generations -gt 0 ]; then
503                         CreateSubvolume "$sys_target"
504                 else
505                         mkdir -p "$sys_target"
506                 fi
507                 if [ $? -ne 0 ]; then
508                         echo "Can't create \"$sys_target\"!? \"$system\" skipped!"
509                         echo; continue
510                 fi
511         fi
512
513         if [[ "$local" -eq 0 && "$ping" -ne 0 ]]; then
514                 # Check if system is alive
515                 ping -c 1 "$system" >/dev/null 2>&1
516                 if [ $? -ne 0 ]; then
517                         echo "Host \"$system\" seems not to be alive!? Skipped."
518                         echo; continue
519                 fi
520                 echo "OK, host \"$system\" seems to be alive."
521         fi
522
523         if [ $generations -gt 0 ]; then
524                 # Make sure no old backup is stored in system directory
525                 if [ -e "$sys_target/.stamp" ]; then
526                         # There seems to be a genearation-less backup in the
527                         # target directory!
528                         echo "Target directory \"$sys_target\" seems to be unclean!? \"$system\" skipped!"
529                         echo; continue
530                 fi
531
532                 Initialize_Last_SysTarget_Snapshot "$sys_target" || continue
533
534                 if [[ -n "$last" && ! -e "$last/.stamp" ]]; then
535                         # Old backup directory without "stamp file", continue
536                         echo "Found incomplete snapshot in \"$last\", reusing and renaming it ..."
537                         RenameSubvolume "$last" "$sys_target"
538                         if [ $? -ne 0 ]; then
539                                 echo "Failed to rename last snapshot \"$last\" to \"$sys_target\"!? \"$system\" skipped!"
540                                 echo; continue
541                         fi
542                 elif [ -n "$last" ]; then
543                         # Old backup directory found, create new snapshot
544                         echo "Found last snapshot in \"$last\"."
545                         if [ "$DRYRUN" -eq 0 ]; then
546                                 CloneSubvolume "$last" "$sys_target" "$snapshot"; r=$?
547                                 if [ $r -ne 0 ]; then
548                                         echo "Can't create snapshot \"$snapshot\" of \"$last\", code $r!? \"$system\" skipped!"
549                                         echo; continue
550                                 fi
551                                 echo "Created new snapshot in \"$snapshot\"."
552                         else
553                                 echo " *** Trial run, not creating new snapshot in \"$snapshot\"!"
554                         fi
555                 else
556                         # No old backup found, create new subvolume
557                         if [ "$DRYRUN" -eq 0 ]; then
558                                 CreateSubvolume "$sys_target"; r=$?
559                                 if [ $r -ne 0 ]; then
560                                         echo "Can't create subvolume \"$sys_target\", code $r!? \"$system\" skipped!"
561                                         echo; continue
562                                 fi
563                                 echo "Created new subvolume in \"$sys_target\"."
564                         else
565                                 echo " *** Trial run, not creating new subvolume \"$sys_target\"!"
566                         fi
567                 fi
568         fi
569
570         ssh_cmd="ssh"
571         [ -n "$ssh_args_add" ] && ssh_cmd="$ssh_cmd $ssh_args_add"
572
573         # execute job "pre-exec" command, if any
574         if [ -n "$job_pre_exec" ]; then
575                 ExecJob pre "$job_pre_exec" ; ret=$?
576                 if [ $ret -ne 0 ]; then
577                         [ $ret -ne 99 ] && count_started=$count_started+1
578                         echo "Pre-exec command failed, \"$system\" skipped!"
579                         echo; continue
580                 fi
581         fi
582
583         # prepare (remote) command ...
584         if [[ "$backup_type" == "rsync" ]]; then
585                 cmd="$rsync --archive --timeout=$io_timeout"
586                 [ "$compress" -ne 0 ] && cmd="$cmd --compress"
587                 [ "$local" -eq 0 ] && cmd="$cmd --rsh=\"$ssh_cmd\""
588                 cmd="$cmd --delete-during --delete-excluded --sparse"
589                 if [ "$VERBOSE" -gt 0 ]; then
590                         [ "$rsync_proto" -ge 31 ] \
591                                 && cmd="$cmd --info=progress2" \
592                                 || cmd="$cmd --progress"
593                 fi
594                 set -f
595                 if [ "$source_root" = "$default_source_root" ]; then
596                         for dir in \
597                                 "/dev/**" \
598                                 "/media/**" \
599                                 "/mnt/**" \
600                                 "/net/**" \
601                                 "/proc/**" \
602                                 "/run/**" \
603                                 "/sys/**" \
604                                 "/tmp/**" \
605                                 "/var/cache/apt/**" \
606                                 "/var/log/**" \
607                                 "/var/tmp/**" \
608                         ; do
609                                 cmd="$cmd --exclude=$dir"
610                         done
611                 fi
612                 [ -n "$exclude_args_add" ] && cmd="$cmd $exclude_args_add"
613                 for dir in $exclude_dirs_add; do
614                         cmd="$cmd --exclude=$dir"
615                 done
616                 [ -n "$rsync_args_add" ] && cmd="$cmd $rsync_args_add"
617                 set +f
618
619                 [ "$local" -eq 0 ] \
620                         && cmd="$cmd ${user}@${system}:$source_root $sys_target/" \
621                         || cmd="$cmd $source_root $sys_target/"
622         elif [[ "$backup_type" == "scp" ]]; then
623                 cmd="scp"
624                 [ "$VERBOSE" -eq 0 ] && cmd="$cmd -q"
625                 for file in $files; do
626                         cmd="$cmd ${user}@${system}:$file $sys_target/"
627                 done
628         else
629                 echo "Backup type \"$backup_type\" undefined, \"$system\" skipped!"
630                 echo; continue
631         fi
632
633         echo "Backing up to \"$sys_target\" ..."
634         echo -n "Start date: "; date
635         echo "$cmd"
636         count_started=$count_started+1
637         ok=0
638
639         if [ "$DRYRUN" -eq 0 ]; then
640                 stamp_file="$sys_target/.stamp"
641                 rm -f "$stamp_file"
642
643                 # Execute backup command:
644                 start_t=$(date "+%s")
645                 $SHELL -c "$cmd"; ret=$?
646                 end_t=$(date "+%s")
647
648                 echo "code=$ret" >"$stamp_file"
649                 echo "start_t=$start_t" >>"$stamp_file"
650                 echo "end_t=$end_t" >>"$stamp_file"
651                 echo "cmd='$cmd'" >>"$stamp_file"
652                 echo "backup_host='`hostname -f`'" >>"$stamp_file"
653                 echo "backup_user='`id -un`'" >>"$stamp_file"
654         else
655                 echo " *** Trial run, not executing save command!"
656                 ret=0
657         fi
658
659         if [ $ret -eq 20 ]; then
660                 echo "Backup of \"$system\" interrupted. Aborting ..."
661                 GotSignal
662         fi
663
664         echo -n "End date: "; date
665         if [[ $ret -eq 0 || $ret -eq 24 ]]; then
666                 [ $ret -eq 24 ] && count_ok_vanished=$count_ok_vanished+1
667
668                 echo "System \"$system\" completed with status $ret, OK."
669                 [ "$DRYRUN" -gt 0 ] || count_ok=$count_ok+1
670                 ok=1
671         else
672                 echo "System \"$system\" completed with ERRORS, code $ret!"
673         fi
674
675         # execute job "post-exec" command, if any
676         if [ -n "$job_post_exec" ]; then
677                 ExecJob post "$job_post_exec"
678         fi
679
680         if [ $generations -gt 0 ]; then
681                 # Update "latest" symlink
682                 if [ "$DRYRUN" -eq 0 ]; then
683                         rm -f "$sys_root/latest"
684                         ln -s "$sys_target" "$sys_root/latest"
685                 fi
686                 # Clean up old generations
687                 declare -i gen_count=$generations+2
688                 to_delete=$(ls -1t "$sys_root" 2>/dev/null | tail -n+$gen_count | sort)
689                 if [[ -n "$to_delete" && $ok -eq 1 ]]; then
690                         [ "$DRYRUN" -eq 0 ] \
691                                 && echo "Deleting old backup generations (keep $generations) ..." \
692                                 || echo " *** Trial run, not deleting old generations:"
693                         for delete in $to_delete; do
694                                 dir="$sys_root/$delete"
695                                 if [ ! -e "$dir/.stamp" ]; then
696                                         echo "Not deleting \"$dir\", not a backup directory!?"
697                                         continue
698                                 fi
699                                 last=$(stat "$dir/.stamp" 2>/dev/null | grep "^Modify: " \
700                                  | cut -d':' -f2- | cut -d. -f1)
701                                 echo "Removing backup from" $last "..."
702                                 if [ "$DRYRUN" -eq 0 ]; then
703                                         DeleteSubvolume "$dir"
704                                         [ $? -eq 0 ] || \
705                                           echo "Failed to delete \"$dir\"!"
706                                 fi
707                         done
708                         echo -n "Clean up finished: "; date
709                 elif [ -n "$to_delete" ]; then
710                         echo "There have been errors, not cleaning up old generations!"
711                 else
712                         echo "Nothing to clean up (keep up to $generations generations)."
713                 fi
714         fi
715
716         destinations="$destinations $target"
717         echo
718 done
719
720 sync
721
722 if [ "$DRYRUN" -eq 0 ]; then
723         paths=""
724         paths_zfs=""
725         for dest in $(echo $destinations | sed -e 's/ /\n/g' | sort | uniq); do
726                 fs=$(GetFS "$dest")
727                 case $fs in
728                   "zfs" )
729                         paths_zfs="$paths_zfs $dest"
730                         ;;
731                   *)
732                         paths="$paths $dest"
733                 esac
734         done
735         if [ -n "$paths" ]; then
736                 df -h $paths
737                 echo
738         fi
739         if [ -n "$paths_zfs" ]; then
740                 zfs list $paths_zfs
741                 echo
742         fi
743 fi
744
745 CleanUp
746
747 echo -n "Done: "; date
748 echo
749 [ $count_all -eq 1 ] && s="" || s="s"
750 [ $count_enabled -eq $count_all ] \
751         && echo " - $count_all job$s defined (all enabled)," \
752         || echo " - $count_all job$s defined ($count_enabled enabled),"
753 [ $count_started -eq 1 ] && s="" || s="s"
754 echo " - $count_started job$s started,"
755 echo " - $count_ok done without errors."
756 echo
757
758 if [ $count_started -ne $count_ok ]; then
759         echo "----->  THERE HAVE BEEN ERRORS!  <-----"
760         echo
761         exit 6
762 elif [ $count_enabled -ne $count_started ]; then
763         exit 7
764 fi
765
766 # -eof-