diff sbin/fzfs @ 380:6be930eb7490

Implement the "fzfs create-tree" command
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 20 Feb 2023 00:43:30 +0100
parents 1fc3b04b39fa
children 84d2735fe7f6
line wrap: on
line diff
--- a/sbin/fzfs	Sun Feb 19 14:47:17 2023 +0100
+++ b/sbin/fzfs	Mon Feb 20 00:43:30 2023 +0100
@@ -28,6 +28,8 @@
 
 COMMANDS:
 
+  create-tree [-A] [-M MOUNTPOINT] [-n] [-p] [-u] SOURCE-DATASET DEST-DATASET
+
   mount [-O] [-N] [-P] [-u] [-n] DATASET [MOUNTPOINT]
 
   umount DATASET
@@ -36,6 +38,48 @@
 
 '
 
+
+#:
+#: Determine some important dataset properties that are set locally
+#:
+#: Args:
+#:   $1: the dataset
+#:   $2 ...: the properties to check for
+#:
+#: Output (stdout):
+#:   An option string suited for use in "zfs create"
+#:
+_get_local_properties() {
+    local ds
+
+    local _res _prop _value _source
+
+    ds="${1}"
+    shift
+
+    _res=""
+
+    for _prop in "$@" ; do
+        IFS=$'\t' read -r _value _source <<EOF73GHASGJKKJ354
+$(zfs get -H -p -o value,source "${_prop}" "${ds}")
+EOF73GHASGJKKJ354
+        case "${_source}" in
+            local)
+                if [ -z "${_res}" ]; then
+                    _res="-o ${_prop}=${_value}"
+                else
+                    _res="${_res} -o ${_prop}=${_value}"
+                fi
+                ;;
+            *)
+                # VOID
+                ;;
+        esac
+    done
+    echo "${_res}"
+}
+
+
 #
 #: Implementation of the "mount" command.
 #:
@@ -204,6 +248,128 @@
     return 0
 }
 
+
+#:
+#: Implement the "create-tree" command
+#:
+#: Create a ZFS dataset tree from a source tree tree including important properties
+#:
+command_create_tree() {
+    local _source_ds _target_ds
+    local _opt_dry_run _opt_mountpoint _opt_noauto _opt_nomount _opt_canmount_parent_only
+
+    local _opt _source_name _snapshot_name _current_name _current_type _relative_name _zfs_opts _zfs_canmount _zfs_mountpoint
+
+    _opt_noauto=""
+    _opt_dry_run=""
+    _opt_mountpoint=""
+    _opt_nomount=""
+    _opt_canmount_parent_only=""
+
+    while getopts "AM:npu" _opt ; do
+        case ${_opt} in
+            A)
+                _opt_noauto="-o canmount=noauto"
+                ;;
+            M)
+                _opt_mountpoint="${OPTARG}"
+                ;;
+            n)
+                _opt_dry_run="yes"
+                ;;
+            p)
+                _opt_canmount_parent_only="yes"
+                ;;
+            u)
+                _opt_nomount="-u"
+                ;;
+            \?|:)
+                return 2;
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    _source_ds="${1-}"
+    _target_ds="${2-}"
+
+    [ -z "${_source_ds}" ] && { echo "ERROR: no source dataset given" 1>&2; return 2; }
+    [ -z "${_target_ds}" ] && { echo "ERROR: no destination dataset given" 1>&2; return 2; }
+
+    _source_name="${_source_ds%@*}"
+    if [ "${_source_name}" != "${_source_ds}" ]; then
+        _snapshot_name="${_source_ds##*@}"
+    else
+        _snapshot_name=""
+    fi
+
+    [ -n "${_snapshot_name}" ] && { echo "ERROR: No snapshot sources are supported yet" 1>&2; return 2; }
+
+    zfs list -H -r -t filesystem -o name,type "${_source_ds}" \
+    | {
+        while IFS=$'\t' read -r _current_name _current_type ; do
+            # Determine the relative name of the dataset
+            _relative_name="${_current_name#${_source_name}}"
+            _relative_name="${_relative_name%@*}"
+            if [ "${_current_type}" != "filesystem" ]; then
+                echo "ERROR: Got a snapshot but expected a filesystem" 1>&2
+                return 1
+            fi
+            _zfs_opts="$(_get_local_properties "${_current_name}" atime exec setuid compression primarycache sync readonly)"
+            if [ -z "${_relative_name}" ]; then
+                #
+                # Root
+                #
+                if [ -z "${_opt_noauto}" ]; then
+                    _zfs_canmount="$(_get_local_properties "${_current_name}" canmount)"
+                else
+                    _zfs_canmount="${_opt_noauto}"
+                fi
+                if [ -n "${_opt_mountpoint}" ]; then
+                    _zfs_mountpoint="${_opt_mountpoint}"
+                else
+                    _zfs_mountpoint=""
+                fi
+
+                if [ "${_opt_dry_run}" = "yes" ]; then
+                    if [ -z "${_zfs_mountpoint}" ]; then
+                        echo "Would call: zfs create ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} ${_target_ds}${_relative_name}"
+                    else
+                        echo "Would call: zfs create ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} -o mountpoint=${_zfs_mountpoint} ${_target_ds}${_relative_name}"
+                    fi
+                else
+                    if [ -z "${_zfs_mountpoint}" ]; then
+                        zfs create -v ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} "${_target_ds}${_relative_name}"
+                    else
+                        zfs create -v ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} -o "mountpoint=${_zfs_mountpoint}" "${_target_ds}${_relative_name}"
+                    fi
+                fi
+            else
+                #
+                # Children
+                #
+                if [ -z "${_opt_noauto}" ]; then
+                    if [ "${_opt_canmount_parent_only}" = "yes" ]; then
+                        _zfs_canmount=""
+                    else
+                        _zfs_canmount="$(_get_local_properties "${_current_name}" canmount)"
+                    fi
+                else
+                    _zfs_canmount="${_opt_noauto}"
+                fi
+                if [ "${_opt_dry_run}" = "yes" ]; then
+                    echo "Would call: zfs create ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} ${_target_ds}${_relative_name}"
+                else
+                    zfs create -v ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} "${_target_ds}${_relative_name}"
+                fi
+            fi
+        done
+    }
+    return 0
+}
+
+
 #
 # Global option handling
 #
@@ -245,6 +411,9 @@
     umount|unmount)
         command_umount "$@"
         ;;
+    create-tree)
+        command_create_tree "$@"
+        ;;
     *)
         echo "ERROR: unknown command \`${command}'" 1>&2
         exit 2