]> arthur.barton.de Git - backup-script.git/blob - bin/backup-script
4107e2425a614c2441fb9c2f1b96e2fb5368f0f1
[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         source "$conf"
285 else
286         config_info="${config_info} (not readable, using defaults)"
287 fi
288 config_info="${config_info},\nusing \"$conf_d\" as configuration directory."
289
290 while [ $# -gt 0 ]; do
291         case "$1" in
292           "-n"|"--dry-run")
293                 DRYRUN=1; shift
294                 ;;
295           "-p"|"--progress")
296                 VERBOSE=1; shift
297                 ;;
298           "-t"|"--tag")
299                 shift; TAG="$1"; shift
300                 [ -n "$TAG" ] || Usage
301                 ;;
302           "-"*)
303                 Usage
304                 ;;
305           *)
306                 break
307         esac
308 done
309
310 echo -n "Started: "; date
311 echo -e "$config_info"
312
313 # Check rsync and its protocol version
314 rsync=$(which "rsync" 2>/dev/null)
315 if [ $? -ne 0 ]; then
316         echo "Failed to detect rsync(1)! Is it installed in your \$PATH?"
317         exit 1
318 fi
319 rsync_proto=$($rsync --version 2>/dev/null | head -n 1 | sed 's/.*  protocol version \([0-9]*\)$/\1/')
320 if [ $? -ne 0 ]; then
321         echo "Failed to detect protocol version of $rsync!"
322         exit 1
323 fi
324 echo "Rsync command is $rsync, protocol version $rsync_proto."
325
326 [[ -n "$TAG" ]] && echo "Running jobs tagged with \"$TAG\"."
327 echo
328
329 trap GotSignal SIGINT
330
331 if [ $# -ge 1 ]; then
332         for s in "$@"; do
333                 if [ ! -r "${conf_d}/$s" ]; then
334                         echo "$NAME: Can' read \"${conf_d}/$s\"!"
335                         exit 3
336                 fi
337                 sys="$sys ${conf_d}/$s"
338         done
339 else
340         sys="${conf_d}/"*
341 fi
342
343 # check and create PID file
344 if [ -e "$PIDFILE" ]; then
345         echo "Lockfile \"$PIDFILE\" already exists."
346         echo "Is an other instance still running?"
347         echo
348         echo -n "Aborted: "; date
349         echo
350         exit 4
351 fi
352 touch "$PIDFILE" 2>/dev/null
353 if [ $? -ne 0 ]; then
354         echo "Warning: can't create PID file \"$PIDFILE\"!"
355         echo
356 else
357         echo "$$" >>"$PIDFILE"
358 fi
359
360 if [ -n "$pre_exec" ]; then
361         echo "Executing \"$pre_exec\" ..."
362         sh -c $pre_exec
363         if [ $? -ne 0 ]; then
364                 echo "Error: pre-exec command failed!"; echo
365                 CleanUp
366                 echo "Aborting backup."; echo
367                 exit 5
368         fi
369         sleep 2
370         echo
371 fi
372
373 for f in $sys; do
374         [[ -r "$f" && -f "$f" ]] || continue
375
376         fname=$(basename "$f")
377         case "$fname" in
378                 "backup-script.conf"|*.sh)
379                         continue
380                         ;;
381         esac
382
383         # Set global defaults
384         system="$fname"
385         backup_type="$default_backup_type"
386         user="$default_user"
387         source_root="$default_source_root"
388         files="$default_files"
389         target="$default_target"
390         ssh_args_add="$default_ssh_args_add"
391         rsync_args_add="$default_rsync_args_add"
392         exclude_args_add="$default_exclude_args_add"
393         exclude_dirs_add="$default_exclude_dirs_add"
394         compress="$default_compress"
395         ping="$default_ping"
396         local="$default_local"
397         generations="$default_generations"
398         job_pre_exec="$default_job_pre_exec"
399         job_post_exec="$default_job_post_exec"
400         tags="$default_tags"
401
402         # Compatibility with backup-pull(1) script: Save global values ...
403         pre_exec_saved="$pre_exec"
404         post_exec_saved="$post_exec"
405
406         # Compatibility with backup-pull(1) script: Set defaults
407         host=""
408         unset source
409         unset pre_exec
410         unset post_exec
411
412         # Read in system configuration file
413         source "$f"
414
415         # Compatibility with backup-pull(1) script: Fix up configuration
416         [[ "$system" = "$fname" && -n "$host" ]] \
417                 && system="$host"
418         [[ "$source_root" = "$default_source_root" && -n "$source" ]] \
419                 && source_root="$source"
420         [[ -z "$job_pre_exec" && -n "$pre_exec" ]] \
421                 && job_pre_exec="$pre_exec"
422         [[ -z "$job_post_exec" && -n "$post_exec" ]] \
423                 && job_post_exec="$post_exec"
424
425         # Compatibility with backup-pull(1) script: Restore global values ...
426         pre_exec="$pre_exec_saved"
427         post_exec="$post_exec_saved"
428
429         # Validate configuration
430         if [[ "$system" = "localhost" || "$system" = "127.0.0.1" ]]; then
431                 # Local system
432                 local=1
433                 compress=0
434         fi
435
436         # Add "NONE" tag when no tags are given in the config file:
437         [[ -z "$tags" ]] && tags="NONE"
438         # Add "auto-tags":
439         [[ "$local" -eq 1 ]] && tags="$tags,LOCAL"
440         # Check tags
441         if [[ -n "$TAG" && "$TAG" != "ALL" ]]; then
442                 echo "$tags" | grep -E "(^|,)$TAG(,|$)" >/dev/null 2>&1
443                 if [ $? -ne 0 ]; then
444                         if [ "$DRYRUN" -ne 0 ]; then
445                                 echo "Tags of system \"$system\" don't match \"$TAG\": \"$tags\". Skipped."
446                                 echo
447                         fi
448                         continue
449                 fi
450         fi
451
452         # Make sure "source" ends with a slash ("/")
453         case "$source" in
454           "*/")
455                 ;;
456           "*")
457                 source="$source/"
458         esac
459
460         # Make sure "target" DOESN'T end with a slash ("/")
461         case "$target" in
462           "*/")
463                 target=$( echo "$target" | sed -e 's/\/$//g' )
464                 ;;
465         esac
466
467         [ "$system" = "$fname" ] \
468                 && systxt="\"$system\"" \
469                 || systxt="\"$fname\" [\"$system\"]"
470         [ "$local" -eq 0 ] \
471                 && echo "Working on $systxt ..." \
472                 || echo "Working on $systxt (local system) ..."
473
474         count_all=$count_all+1
475
476         # Check target directory
477         if [ -z "$target" ]; then
478                 echo "No target directory specified for \"$system\"!? Skipped!"
479                 echo; continue
480         fi
481         if [ ! -d "$target" ]; then
482                 echo "Target \"$target\" is not a directory!? \"$system\" skipped!"
483                 echo; continue
484         fi
485
486         sys_target="$target/$fname"
487         sys_root="$sys_target"
488         if [[ "$DRYRUN" -eq 0 && ! -e "$sys_target" ]]; then
489                 if [ $generations -gt 0 ]; then
490                         CreateSubvolume "$sys_target"
491                 else
492                         mkdir -p "$sys_target"
493                 fi
494                 if [ $? -ne 0 ]; then
495                         echo "Can't create \"$sys_target\"!? \"$system\" skipped!"
496                         echo; continue
497                 fi
498         fi
499
500         if [[ "$local" -eq 0 && "$ping" -ne 0 ]]; then
501                 # Check if system is alive
502                 ping -c 1 "$system" >/dev/null 2>&1
503                 if [ $? -ne 0 ]; then
504                         echo "Host \"$system\" seems not to be alive!? Skipped."
505                         echo; continue
506                 fi
507                 echo "OK, host \"$system\" seems to be alive."
508         fi
509
510         if [ $generations -gt 0 ]; then
511                 # Make sure no old backup is stored in system directory
512                 if [ -e "$sys_target/.stamp" ]; then
513                         # There seems to be a genearation-less backup in the
514                         # target directory!
515                         echo "Target directory \"$sys_target\" seems to be unclean!? \"$system\" skipped!"
516                         echo; continue
517                 fi
518
519                 Initialize_Last_SysTarget_Snapshot "$sys_target" || continue
520
521                 if [[ -n "$last" && ! -e "$last/.stamp" ]]; then
522                         # Old backup directory without "stamp file", continue
523                         echo "Found incomplete snapshot in \"$last\", reusing and renaming it ..."
524                         RenameSubvolume "$last" "$sys_target"
525                         if [ $? -ne 0 ]; then
526                                 echo "Failed to rename last snapshot \"$last\" to \"$sys_target\"!? \"$system\" skipped!"
527                                 echo; continue
528                         fi
529                 elif [ -n "$last" ]; then
530                         # Old backup directory found, create new snapshot
531                         echo "Found last snapshot in \"$last\"."
532                         if [ "$DRYRUN" -eq 0 ]; then
533                                 CloneSubvolume "$last" "$sys_target" "$snapshot"; r=$?
534                                 if [ $r -ne 0 ]; then
535                                         echo "Can't create snapshot \"$snapshot\" of \"$last\", code $r!? \"$system\" skipped!"
536                                         echo; continue
537                                 fi
538                                 echo "Created new snapshot in \"$snapshot\"."
539                         else
540                                 echo " *** Trial run, not creating new snapshot in \"$snapshot\"!"
541                         fi
542                 else
543                         # No old backup found, create new subvolume
544                         if [ "$DRYRUN" -eq 0 ]; then
545                                 CreateSubvolume "$sys_target"; r=$?
546                                 if [ $r -ne 0 ]; then
547                                         echo "Can't create subvolume \"$sys_target\", code $r!? \"$system\" skipped!"
548                                         echo; continue
549                                 fi
550                                 echo "Created new subvolume in \"$sys_target\"."
551                         else
552                                 echo " *** Trial run, not creating new subvolume \"$sys_target\"!"
553                         fi
554                 fi
555         fi
556
557         ssh_cmd="ssh"
558         [ -n "$ssh_args_add" ] && ssh_cmd="$ssh_cmd $ssh_args_add"
559
560         # execute job "pre-exec" command, if any
561         if [ -n "$job_pre_exec" ]; then
562                 ExecJob pre "$job_pre_exec" ; ret=$?
563                 if [ $ret -ne 0 ]; then
564                         [ $ret -ne 99 ] && count_started=$count_started+1
565                         echo "Pre-exec command failed, \"$system\" skipped!"
566                         echo; continue
567                 fi
568         fi
569
570         # prepare (remote) command ...
571         if [[ "$backup_type" == "rsync" ]]; then
572                 cmd="$rsync --archive"
573                 [ "$compress" -ne 0 ] && cmd="$cmd --compress"
574                 [ "$local" -eq 0 ] && cmd="$cmd --rsh=\"$ssh_cmd\""
575                 cmd="$cmd --delete --delete-excluded --sparse"
576                 if [ "$VERBOSE" -gt 0 ]; then
577                         [ "$rsync_proto" -ge 31 ] \
578                                 && cmd="$cmd --info=progress2" \
579                                 || cmd="$cmd --progress"
580                 fi
581                 set -f
582                 if [ "$source_root" = "$default_source_root" ]; then
583                         for dir in \
584                                 "/dev/**" \
585                                 "/media/**" \
586                                 "/mnt/**" \
587                                 "/net/**" \
588                                 "/proc/**" \
589                                 "/run/**" \
590                                 "/sys/**" \
591                                 "/tmp/**" \
592                                 "/var/cache/apt/**" \
593                                 "/var/log/**" \
594                                 "/var/tmp/**" \
595                         ; do
596                                 cmd="$cmd --exclude=$dir"
597                         done
598                 fi
599                 [ -n "$exclude_args_add" ] && cmd="$cmd $exclude_args_add"
600                 for dir in $exclude_dirs_add; do
601                         cmd="$cmd --exclude=$dir"
602                 done
603                 [ -n "$rsync_args_add" ] && cmd="$cmd $rsync_args_add"
604                 set +f
605
606                 [ "$local" -eq 0 ] \
607                         && cmd="$cmd ${user}@${system}:$source_root $sys_target/" \
608                         || cmd="$cmd $source_root $sys_target/"
609         elif [[ "$backup_type" == "scp" ]]; then
610                 cmd="scp"
611                 [ "$VERBOSE" -eq 0 ] && cmd="$cmd -q"
612                 for file in $files; do
613                         cmd="$cmd ${user}@${system}:$file $sys_target/"
614                 done
615         else
616                 echo "Backup type \"$backup_type\" undefined, \"$system\" skipped!"
617                 echo; continue
618         fi
619
620         echo "Backing up to \"$sys_target\" ..."
621         echo -n "Start date: "; date
622         echo "$cmd"
623         count_started=$count_started+1
624         ok=0
625
626         if [ "$DRYRUN" -eq 0 ]; then
627                 stamp_file="$sys_target/.stamp"
628                 rm -f "$stamp_file"
629
630                 # Execute backup command:
631                 start_t=$(date "+%s")
632                 $SHELL -c "$cmd"; ret=$?
633                 end_t=$(date "+%s")
634
635                 echo "code=$ret" >"$stamp_file"
636                 echo "start_t=$start_t" >>"$stamp_file"
637                 echo "end_t=$end_t" >>"$stamp_file"
638                 echo "cmd='$cmd'" >>"$stamp_file"
639                 echo "backup_host='`hostname -f`'" >>"$stamp_file"
640                 echo "backup_user='`id -un`'" >>"$stamp_file"
641         else
642                 echo " *** Trial run, not executing save command!"
643                 ret=0
644         fi
645
646         if [ $ret -eq 20 ]; then
647                 echo "Backup of \"$system\" interrupted. Aborting ..."
648                 GotSignal
649         fi
650
651         echo -n "End date: "; date
652         if [[ $ret -eq 0 || $ret -eq 24 ]]; then
653                 [ $ret -eq 24 ] && count_ok_vanished=$count_ok_vanished+1
654
655                 echo "System \"$system\" completed with status $ret, OK."
656                 [ "$DRYRUN" -gt 0 ] || count_ok=$count_ok+1
657                 ok=1
658         else
659                 echo "System \"$system\" completed with ERRORS, code $ret!"
660         fi
661
662         # execute job "post-exec" command, if any
663         if [ -n "$job_post_exec" ]; then
664                 ExecJob post "$job_post_exec"
665         fi
666
667         if [ $generations -gt 0 ]; then
668                 # Update "latest" symlink
669                 if [ "$DRYRUN" -eq 0 ]; then
670                         rm -f "$sys_root/latest"
671                         ln -s "$sys_target" "$sys_root/latest"
672                 fi
673                 # Clean up old generations
674                 declare -i gen_count=$generations+2
675                 to_delete=$(ls -1t "$sys_root" 2>/dev/null | tail -n+$gen_count | sort)
676                 if [[ -n "$to_delete" && $ok -eq 1 ]]; then
677                         [ "$DRYRUN" -eq 0 ] \
678                                 && echo "Deleting old backup generations (keep $generations) ..." \
679                                 || echo " *** Trial run, not deleting old generations:"
680                         for delete in $to_delete; do
681                                 dir="$sys_root/$delete"
682                                 if [ ! -e "$dir/.stamp" ]; then
683                                         echo "Not deleting \"$dir\", not a backup directory!?"
684                                         continue
685                                 fi
686                                 last=$(stat "$dir/.stamp" 2>/dev/null | grep "^Modify: " \
687                                  | cut -d':' -f2- | cut -d. -f1)
688                                 echo "Removing backup from" $last "..."
689                                 if [ "$DRYRUN" -eq 0 ]; then
690                                         DeleteSubvolume "$dir"
691                                         [ $? -eq 0 ] || \
692                                           echo "Failed to delete \"$dir\"!"
693                                 fi
694                         done
695                         echo -n "Clean up finished: "; date
696                 elif [ -n "$to_delete" ]; then
697                         echo "There have been errors, not cleaning up old generations!"
698                 else
699                         echo "Nothing to clean up (keep up to $generations generations)."
700                 fi
701         fi
702
703         destinations="$destinations $target"
704         echo
705 done
706
707 sync
708
709 if [ "$DRYRUN" -eq 0 ]; then
710         paths=""
711         paths_zfs=""
712         for dest in $(echo $destinations | sed -e 's/ /\n/g' | sort | uniq); do
713                 fs=$(GetFS "$dest")
714                 case $fs in
715                   "zfs" )
716                         paths_zfs="$paths_zfs $dest"
717                         ;;
718                   *)
719                         paths="$paths $dest"
720                 esac
721         done
722         if [ -n "$paths" ]; then
723                 df -h $paths
724                 echo
725         fi
726         if [ -n "$paths_zfs" ]; then
727                 zfs list $paths_zfs
728                 echo
729         fi
730 fi
731
732 CleanUp
733
734 echo -n "Done: "; date
735 echo
736 [ $count_all -eq 1 ] && s="" || s="s"
737 echo " - $count_all job$s defined,"
738 [ $count_started -eq 1 ] && s="" || s="s"
739 echo " - $count_started job$s started,"
740 echo " - $count_ok done without errors."
741 echo
742
743 if [ $count_started -ne $count_ok ]; then
744         echo "----->  THERE HAVE BEEN ERRORS!  <-----"
745         echo
746         exit 6
747 elif [ $count_all -ne $count_started ]; then
748         exit 7
749 fi
750
751 # -eof-