ZFS snapshots and nesting

Hello, I maintain the FreeBSD ports for the client and the server. I’d like to integrate some ZFS snapshot scripts into the client port. However, I’ve got some questions on how snapshotting works in UrBackup, and particularly in the presence of nested filesystems. I’ve got some working scripts (based generally on the BTRFS scripts and on the thread ZFS snapshot on client), and they seem to work great on a single ZFS filesystem; however, they break down when there is nesting.

From my experimentation, it seems that UrBackup calls the script specified by create_filesystem_snapshot for each path listed in the “Default directories to backup” and then sources the files to be backed up for each listed path based on the SNAPSHOT variable that the create_filesystem_snapshot script sets when it is called for that path.

I’ve never used BTRFS and I don’t know how its snapshotting operates, but in ZFS a snapshot of a filesystem is independent of any child filesystems, which causes some issues when trying to use snapshots with UrBackup. For example, in testing I created two ZFS filesystems: “zroot/foo” and “zroot/foo/bar” and mounted them in a nested manner:

NAME                    USED  AVAIL  REFER  MOUNTPOINT
zroot/foo               176K  99.7G    88K  /foo
zroot/foo/bar            88K  99.7G    88K  /foo/bar

I then created a file (“a”) in /foo and another file (“b”) in /foo/bar:

$ tree /foo/
/foo/
|-- a
`-- bar
    `-- b

Now, if you set the “Default directories to backup” to “/foo” my create_filesystem_snapshot script creates a snapshot at /foo/.zfs/snaphsot/SNAPSHOT_NAME/ and UrBackup performs a backup of that path. Since /foo is on a different filesystem than /foo/bar, the backup includes a top-level folder “foo” that includes the file “a” and the folder “/foo/bar”, but it does NOT backup the file “/foo/bar/b” since it wasn’t in foo’s snapshot.

If you instead set the “Default directories to backup” to “/foo; /foo/bar” UrBackup first backs snapshots “/foo” and backs up “/foo/.zfs/snaphsot/SNAPSHOT_NAME1/”. This results in the backup including a top-level folder, “foo”, that includes the file “a” and the directory “bar”, as before. UrBackup then snapshots “/foo/bar” and backs up “/foo/bar/.zfs/snaphsot/SNAPSHOT_NAME2/”. This results in the backup including another top level folder, “bar”, that includes the the file “b”.

So, by specifying each directory path that corresponds to a ZFS filesystem you get all files backed up, with snapshotting. However, the backup lacks the original hierarchical structure. Instead, each ZFS filesystem becomes a top-level folder in the backup.

I’m curious if this is the same backup behavior you get with BTRFS and/or LVM (my guess is that it is). If so, then I guess it is what it is. Otherwise, is there anything I could do in my snapshotting scripts to remedy the behavior?

Here are my scripts:

zfs_create_filesystem_snapshot:

#!/bin/sh

set -e

SNAP_ID=$1
SNAP_MOUNTPOINT="$2"
SNAP_NAME="$3"
SNAP_ORIG_PATH="$4"

TYPE=$(df -T -P | egrep " ${SNAP_MOUNTPOINT}\$" | head -n 1 | tr -s " " | cut -d" " -f2)

if [ "$TYPE" = "zfs" ]; then
        ZVOL=`df -P | egrep " ${SNAP_MOUNTPOINT}\$" | cut -d" " -f1`
        /sbin/zfs snapshot ${ZVOL}@${SNAP_ID}
else
        echo "Cannot create snapshot of file system with type $TYPE"
        exit 1
fi

echo "SNAPSHOT=${SNAP_MOUNTPOINT}/.zfs/snapshot/${SNAP_ID}"

exit 0

zfs_remove_filesystem_snapshot:

#!/bin/sh

set -e

SNAP_ID=$1
SNAP_MOUNTPOINT="$2"
SNAP_NAME="$3"
SNAP_DEST="$4"
SNAP_ORIG_PATH="$5"

if ! [ -e $SNAP_MOUNTPOINT ]; then
        echo "Snapshot at $SNAP_MOUNTPOINT was already removed"
        exit 0
fi

TYPE=$(df -T -P | egrep " ${SNAP_ORIG_PATH}\$" | tr -s " " | cut -d" " -f2)

if [ "$TYPE" = "zfs" ]; then
        ZVOL=`/sbin/zfs list -t snapshot | egrep "${SNAP_ID}" | cut -d" " -f1`
        /sbin/zfs destroy $ZVOL
else
        echo "Cannot remove snapshot at $SNAP_MOUNTPOINT. File system type $TYPE not supported."
        exit 1
fi

exit 0

It currently doesn’t properly handle mountpoints below the the specified mountpoint in combination with snapshot scripts.
To fix it, I think it needs to explicitly snapshot and switch over if it detects a mountpoint in the original (non-snapshotted) path.
So if ZFS does not recursivly snapshot, it’s the same as with btrfs, LVM and dattodb snapshotting.