Fixed typo in error message email
[zfs-ubuntu/.git] / zfs-autosnap
1 #!/bin/bash
2
3 # Author: Carl Baldwin & Alan Pippin
4 # Description: This script takes a snapshot of the given zfs filesystem.
5 #              It also employs an intelligent algorithm to roll off,
6 #              or destroy, old snapshots.
7
8 # source our configuration
9 config="${0%/*}/zfs-scripts.conf"
10 [ -e "${config}.dist" ] && . ${config}.dist
11 [ -e "${config}" ] && . ${config}
12
13 if [[ -z "$SNAP_UNDER_TEST" ]]; then
14     exec >> $logdir/zfs-autosnap.log 2>&1
15 fi
16
17 # This script makes the following assumptions/requirements:
18 #  * this script only handles one zfs filesystem, a wrapper should be created
19 #    to handle more
20 #  * this script handles all snapshots that are named in this format:
21 #    YYYY-MM-DD.hh.mm
22 #  * It ignores other snapshots that don't follow this naming convention
23
24 # This converts the YYYY-MM-DD.hh.mm format to an integer.
25 datetime_to_minutes() {
26   perl -n -e '/(\d+)-(\d+)-(\d+)\.(\d+)\.(\d+)/; print $1 * 527040 + $2 * 44640 + $3 * 1440 + $4 * 60 + $5,"\n"'
27 }
28
29 # This converts date/time from YYYY-MM-DD.hh.mm to an integer that aligns
30 # things to prefer certain times such as the first of the month or midnight,
31 # etc
32 datetime_to_minutes2() {
33   perl -n -e '/(\d+)-(\d+)-(\d+)\.(\d+)\.(\d+)/;
34               $monthadj=int(($2-1)/3)-1; # Prefer months numbered 1,4,7 and 10
35               $dayadj=$3 == 1 ? -1 : 0;  # Make sure day 1 is prefered
36               $minadj=int($5/15);        # Prefer multiples of 15 minutes
37               $houradj=int($4/3);        # Prefer midnight,noon,etc
38               $intvalue=(
39                 1048576 *   $1
40               + 65536   * ( $2 + $monthadj )
41               + 2048    * ( $3 + $dayadj )
42               + 64      * ( $4 + $houradj )
43               +             $5 + $minadj
44               );
45               print $intvalue,"\n"'
46 }
47
48 # filesystem: This is the zfs filesystem to snapshot
49 # mountpoint: This is the mountpoint of the zfs filesystem to snapshot
50 # numsnapshots: This number is the number of equally spaced snapshots that should exist over any given period in the past
51 # maxagedays: This is the maximum number of days to keep any snapshot around for (0=infinite) (default=0).
52 filesystem=$1
53 mountpoint=${2-/$1}
54 numsnapshots=${3-12}
55 maxagedays=${4-0}
56 pool=`echo "$filesystem" | awk -F '/' '{ print $1 }'`
57
58 if [ -z "$filesystem" ] || [ -z "$mountpoint" ] || [ -z "$numsnapshots" ] || [ -z "$maxagedays" ]; then
59    echo "-E- Usage: $0 <filesystem> <mountpoint> <numsnapshots> <maxagedays>"
60    exit 1
61 fi
62
63 if [ -z "$SNAP_UNDER_TEST" -a ! -d "$mountpoint" ]; then
64    echo "-E- Unable to find the mountpoint: $mountpoint"
65    exit 1
66 fi
67
68 if [ -n "$SNAP_UNDER_TEST" ]; then
69     snapshotdir="./snapshot"
70 else
71     snapshotdir="${mountpoint}/.zfs/snapshot"
72 fi
73
74 if [ ! -d "$snapshotdir" ]; then
75    echo "-E- Unable to find the snapshotdir: $snapshotdir"
76    exit 1
77 fi
78
79 # Check to see if this zfs filesystem has a scrub being performed on it now.
80 # If it does, we cannot perform any snapshot create or destroy operations.
81 if [ -z "$SNAP_UNDER_TEST" ]; then
82     zpool status $pool | grep scan: | grep "in progress" > /dev/null 2>&1
83     if [ $? == 0 ]; then
84        echo "-W- The zfs pool '$pool' is currently being scrubbed. Skipping all snapshot operations."
85        exit 0
86     fi
87 fi
88
89 snapshot() {
90     echo "-I- Creating $1"
91     if [ -z "$SNAP_UNDER_TEST" ]; then
92         zfs snapshot "$1"
93     else
94         mkdir -p snapshot/$(dirname "$(echo "$1" | sed 's,.*@,,')")
95         touch snapshot/"$(echo "$1" | sed 's,.*@,,')"
96     fi
97 }
98
99 destroy() {
100     echo "-I- Destroying old $1"
101     if [ -z "$SNAP_UNDER_TEST" ]; then
102         zfs destroy "$1"
103     else
104         rm -f "$1"
105     fi
106 }
107
108 # Get the various components of the date
109 datetime=${ZFSDATETIME:-$(date +%Y-%m-%d.%H.%M)}
110
111 # Create the snapshot for this minute
112 snapshot "${filesystem}@${datetime}"
113
114 minutes=$(echo $datetime | datetime_to_minutes)
115
116 if ! mkdir "$lockdir" >/dev/null 2>&1; then
117   echo "-W- The zfs filesystem has been locked down. Skipping snapshot cleanup."
118   exit 0
119 fi
120 cleanup() { rm -rf "$lockdir"; }
121 trap cleanup EXIT
122
123 # Trim them down
124 snapshots=$(ls -d ${snapshotdir}/????-??-??.??.?? 2>/dev/null)
125 for snapshot in $snapshots; do
126   snapminutes=$(echo "$snapshot" | sed 's,.*/,,' | datetime_to_minutes)
127   snapminutes2=$(echo "$snapshot" | sed 's,.*/,,' | datetime_to_minutes2)
128   age=$((minutes - snapminutes))
129   window=1
130   while true; do
131     if [ $age -lt $((window * numsnapshots)) ]; then
132       case $((snapminutes2 % window)) in
133         0) ;;
134         *)
135           snapname=${filesystem}$(echo "$snapshot" |
136                                   sed 's,/\(.*\)/.zfs/snapshot/\(.*\),@\2,')
137           destroy "$snapname"
138         ;;
139       esac
140       break
141     fi
142     window=$((window*2))
143   done
144   if [ $maxagedays -gt 0 ] && [ $age -gt $((maxagedays * 24 * 60)) ]; then
145     snapname=${filesystem}$(echo "$snapshot" |
146                             sed 's,/\(.*\)/.zfs/snapshot/\(.*\),@\2,')
147     destroy "$snapname"
148   fi
149 done