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