#!/bin/bash # Author: Carl Baldwin & Alan Pippin # Description: This script takes a snapshot of the given zfs filesystem. # It also employs an intelligent algorithm to roll off, # or destroy, old snapshots. # source our configuration config="${0%/*}/zfs-scripts.conf" [ -e "${config}.dist" ] && . ${config}.dist [ -e "${config}" ] && . ${config} if [[ -z "$SNAP_UNDER_TEST" ]]; then exec >> $logdir/zfs-autosnap.log 2>&1 fi # This script makes the following assumptions/requirements: # * this script only handles one zfs filesystem, a wrapper should be created # to handle more # * this script handles all snapshots that are named in this format: # YYYY-MM-DD.hh.mm # * It ignores other snapshots that don't follow this naming convention # This converts the YYYY-MM-DD.hh.mm format to an integer. datetime_to_minutes() { perl -n -e '/(\d+)-(\d+)-(\d+)\.(\d+)\.(\d+)/; print $1 * 527040 + $2 * 44640 + $3 * 1440 + $4 * 60 + $5,"\n"' } # This converts date/time from YYYY-MM-DD.hh.mm to an integer that aligns # things to prefer certain times such as the first of the month or midnight, # etc datetime_to_minutes2() { perl -n -e '/(\d+)-(\d+)-(\d+)\.(\d+)\.(\d+)/; $monthadj=int(($2-1)/3)-1; # Prefer months numbered 1,4,7 and 10 $dayadj=$3 == 1 ? -1 : 0; # Make sure day 1 is prefered $minadj=int($5/15); # Prefer multiples of 15 minutes $houradj=int($4/3); # Prefer midnight,noon,etc $intvalue=( 1048576 * $1 + 65536 * ( $2 + $monthadj ) + 2048 * ( $3 + $dayadj ) + 64 * ( $4 + $houradj ) + $5 + $minadj ); print $intvalue,"\n"' } # filesystem: This is the zfs filesystem to snapshot # mountpoint: This is the mountpoint of the zfs filesystem to snapshot # numsnapshots: This number is the number of equally spaced snapshots that should exist over any given period in the past # maxagedays: This is the maximum number of days to keep any snapshot around for (0=infinite) (default=0). filesystem=$1 mountpoint=${2-/$1} numsnapshots=${3-12} maxagedays=${4-0} pool=`echo "$filesystem" | awk -F '/' '{ print $1 }'` if [ -z "$filesystem" ] || [ -z "$mountpoint" ] || [ -z "$numsnapshots" ] || [ -z "$maxagedays" ]; then echo "-E- Usage: $0 " exit 1 fi if [ -z "$SNAP_UNDER_TEST" -a ! -d "$mountpoint" ]; then echo "-E- Unable to find the mountpoint: $mountpoint" exit 1 fi if [ -n "$SNAP_UNDER_TEST" ]; then snapshotdir="./snapshot" else snapshotdir="${mountpoint}/.zfs/snapshot" fi if [ ! -d "$snapshotdir" ]; then echo "-E- Unable to find the snapshotdir: $snapshotdir" exit 1 fi # Check to see if this zfs filesystem has a scrub being performed on it now. # If it does, we cannot perform any snapshot create or destroy operations. if [ -z "$SNAP_UNDER_TEST" ]; then zpool status $pool | grep scrub: | grep "in progress" > /dev/null 2>&1 if [ $? == 0 ]; then echo "-W- The zfs pool '$pool' is currently being scrubbed. Skipping all snapshot operations." exit 0 fi fi snapshot() { echo "-I- Creating $1" if [ -z "$SNAP_UNDER_TEST" ]; then zfs snapshot "$1" else mkdir -p snapshot/$(dirname "$(echo "$1" | sed 's,.*@,,')") touch snapshot/"$(echo "$1" | sed 's,.*@,,')" fi } destroy() { echo "-I- Destroying old $1" if [ -z "$SNAP_UNDER_TEST" ]; then zfs destroy "$1" else rm -f "$1" fi } # Get the various components of the date datetime=${ZFSDATETIME:-$(date +%Y-%m-%d.%H.%M)} # Create the snapshot for this minute snapshot "${filesystem}@${datetime}" minutes=$(echo $datetime | datetime_to_minutes) if ! mkdir "$lockdir" >/dev/null 2>&1; then echo "-W- The zfs filesystem has been locked down. Skipping snapshot cleanup." exit 0 fi cleanup() { rm -rf "$lockdir"; } trap cleanup EXIT # Trim them down snapshots=$(ls -d ${snapshotdir}/????-??-??.??.?? 2>/dev/null) for snapshot in $snapshots; do snapminutes=$(echo "$snapshot" | sed 's,.*/,,' | datetime_to_minutes) snapminutes2=$(echo "$snapshot" | sed 's,.*/,,' | datetime_to_minutes2) age=$((minutes - snapminutes)) window=1 while true; do if [ $age -lt $((window * numsnapshots)) ]; then case $((snapminutes2 % window)) in 0) ;; *) snapname=${filesystem}$(echo "$snapshot" | sed 's,/\(.*\)/.zfs/snapshot/\(.*\),@\2,') destroy "$snapname" ;; esac break fi window=$((window*2)) done if [ $maxagedays -gt 0 ] && [ $age -gt $((maxagedays * 24 * 60)) ]; then snapname=${filesystem}$(echo "$snapshot" | sed 's,/\(.*\)/.zfs/snapshot/\(.*\),@\2,') destroy "$snapname" fi done