# HG changeset patch # User Franz Glasner # Date 1676850210 -3600 # Node ID 6be930eb74905b5d060d5d6d67b236eac84c7eee # Parent b4173e88c57c439c3062c38eaceb8f6ff124bc1c Implement the "fzfs create-tree" command diff -r b4173e88c57c -r 6be930eb7490 docs/conf.py --- a/docs/conf.py Sun Feb 19 14:47:17 2023 +0100 +++ b/docs/conf.py Mon Feb 20 00:43:30 2023 +0100 @@ -98,6 +98,7 @@ ("man/man8/ftjail-snapshot-tmpl", "ftjail-snapshot-tmpl", "Recursively create ZFS snapshots of the RO base datasets and the RW skeleton datasets", [author], 8), ("man/man8/ftjail-umount-tmpl", "ftjail-umount-tmpl", "Unmount mounted Thin Jail template datasets", [author], 8), ("man/man8/fzfs", "fzfs", "A ZFS management helper tool", [author], 8), + ("man/man8/fzfs-create-tree", "fzfs-create-tree", "Create a ZFS dataset tree based on an existing tree", [author], 8), ("man/man8/fzfs-mount", "fzfs-mount", "Recursively mount a ZFS dataset and its children", [author], 8), ("man/man8/fzfs-umount", "fzfs-umount", "Recursively unmount a ZFS datasets and its children", [author], 8), ] diff -r b4173e88c57c -r 6be930eb7490 docs/man/index8.rst --- a/docs/man/index8.rst Sun Feb 19 14:47:17 2023 +0100 +++ b/docs/man/index8.rst Mon Feb 20 00:43:30 2023 +0100 @@ -27,5 +27,6 @@ man8/ftjail-snapshot-tmpl man8/ftjail-umount-tmpl man8/fzfs + man8/fzfs-create-tree man8/fzfs-mount man8/fzfs-umount diff -r b4173e88c57c -r 6be930eb7490 docs/man/man8/ftjail-copy-skel.rst --- a/docs/man/man8/ftjail-copy-skel.rst Sun Feb 19 14:47:17 2023 +0100 +++ b/docs/man/man8/ftjail-copy-skel.rst Mon Feb 20 00:43:30 2023 +0100 @@ -29,6 +29,11 @@ Set the ZFS property `canmount=noauto` for all datasets in the target dataset. +.. option:: -D + + Just copy the dataset tree including properties but no filesystem + data. This option is incompatible to option :option:`-L`. + .. option:: -L Copy dataset properties optimized for employing a :file:`skeleton` diff -r b4173e88c57c -r 6be930eb7490 docs/man/man8/fzfs-create-tree.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/man/man8/fzfs-create-tree.rst Mon Feb 20 00:43:30 2023 +0100 @@ -0,0 +1,72 @@ +.. -*- coding: utf-8; indent-tabs-mode: nil; -*- + +fzfs-create-tree +================ + +.. program:: fzfs create-tree + + +Synopsis +-------- + +**fzfs create-tree** [**-A**] [**-M** `mountpoint`] [**-n**] [**-p**] [**-u**] `source-dataset` `dest-dataset` + +Description +----------- + +Copy the ZFS filesystem structure that is rooted at `source-dataset` +to the destination rooted at `dest-dataset`. + +The content of the filesystems is **not** copied. This is also true for +permissions and/or ACLs. + +`dest-dataset` must not exist already. + +By default some important dataset properties that are set locally in +the source tree are copied to the destination. This includes `atime`, +`exec`, `setuid`, `compression`, `primarycache`, `sync` and +`readonly`. + +By default `canmount` is also copied. But this can be modified with +options :option:`-A` and :option:`-p`. + + +Options +------- + +.. option:: -A + + Unconditionally set the ZFS property `canmount=noauto` for all + created datasets in the destination tree. This option overwrites + option :option:`-p`. + +.. option:: -M mountpoint + + Set the `mountpoint` property for the root `dest-dataset` to `mountpoint`. + All children will be set to inherit it. + +.. option:: -n + + Dry-run. Do not really create datasets but show what would be done. + +.. option:: -p + + Copy the `canmount` property only from the source to the root + destination dataset This will be overwritten by the option + :option:`-A`. + +.. option:: -u + + Do not mount the newly created datasets. + + +Environment +----------- + +All environment variables that affect :command:`zfs` are effective also. + + +See Also +-------- + +:manpage:`fzfs(8)` diff -r b4173e88c57c -r 6be930eb7490 docs/man/man8/fzfs.rst --- a/docs/man/man8/fzfs.rst Sun Feb 19 14:47:17 2023 +0100 +++ b/docs/man/man8/fzfs.rst Mon Feb 20 00:43:30 2023 +0100 @@ -17,6 +17,7 @@ Helper for ZFS management: - recursively mount and unmount a ZFS dataset tree +- create a ZFS dataset tree stucture from a source tree The following global options are implemented: @@ -34,6 +35,11 @@ Subcommands ----------- +:manpage:`fzfs-create-tree(8)` + + Recursively create a ZFS dataset tree based on an existing tree while + copying some important properties also + :manpage:`fzfs-mount(8)` Recursively mount a ZFS dataset and its children diff -r b4173e88c57c -r 6be930eb7490 docs/man/man8/local-bsdtools.rst --- a/docs/man/man8/local-bsdtools.rst Sun Feb 19 14:47:17 2023 +0100 +++ b/docs/man/man8/local-bsdtools.rst Mon Feb 20 00:43:30 2023 +0100 @@ -60,6 +60,7 @@ - :manpage:`fpkg(8)` - :manpage:`fzfs(8)` + * :manpage:`fzfs-create-tree(8)` * :manpage:`fzfs-mount(8)` * :manpage:`fzfs-umount(8)` diff -r b4173e88c57c -r 6be930eb7490 sbin/ftjail --- a/sbin/ftjail Sun Feb 19 14:47:17 2023 +0100 +++ b/sbin/ftjail Mon Feb 20 00:43:30 2023 +0100 @@ -40,7 +40,7 @@ snapshot-tmpl BASE-RO SKELETON-RW SNAPSHOT-NAME - copy-skel [-A] [-L] [-M MOUNTPOINT] [-P] [-u] SOURCE-DS SNAPSHOT-NAME TARGET-DS + copy-skel [-A] [-D] [-L] [-M MOUNTPOINT] [-P] [-u] SOURCE-DS SNAPSHOT-NAME TARGET-DS build-etcupdate-current-tmpl DIRECTORY TARBALL @@ -769,7 +769,7 @@ #: command_copy_skel() { local _ds_source _snapshot_name _ds_target - local _opt_symlink _opt_nomount _opt_canmount _opt_mountpoint + local _opt_symlink _opt_nomount _opt_canmount _opt_mountpoint _opt_nodata local _opt _name _relative_name _root_canmount @@ -777,12 +777,16 @@ _opt_nomount="" _opt_canmount="-o canmount=on" _opt_mountpoint="" + _opt_nodata="" - while getopts "ALM:Pu" _opt ; do + while getopts "ADLM:Pu" _opt ; do case ${_opt} in A) _opt_canmount="-o canmount=noauto" ;; + D) + _opt_nodata="yes" + ;; L) _opt_symlink="yes" ;; @@ -804,6 +808,7 @@ OPTIND=1 [ -z "${_opt_symlink}" ] && { echo "ERROR: -L or -P must be given" 1>&2; return 2; } + [ \( "${_opt_nodata}" = "yes" \) -a \( "${_opt_symlink}" = "yes" \) ] && { echo "ERROR: -L and -D are incompatible" 1>&2; return 2; } _ds_source="${1-}" _snapshot_name="${2-}" diff -r b4173e88c57c -r 6be930eb7490 sbin/fzfs --- 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 <&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