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