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