Mercurial > hgrepos > FreeBSD > ports > sysutils > local-bsdtools
changeset 341:a204a7415d4a
"ftjail freebsd-update" is implemented.
It supports custom mountpoints also.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Sat, 03 Dec 2022 21:12:23 +0100 |
| parents | d3b5fe2712ca |
| children | 89877869a665 |
| files | docs/man/man8/ftjail-freebsd-update.rst sbin/ftjail |
| diffstat | 2 files changed, 207 insertions(+), 18 deletions(-) [+] |
line wrap: on
line diff
--- a/docs/man/man8/ftjail-freebsd-update.rst Sat Dec 03 09:46:18 2022 +0100 +++ b/docs/man/man8/ftjail-freebsd-update.rst Sat Dec 03 21:12:23 2022 +0100 @@ -6,7 +6,7 @@ Synopsis -------- -**ftjail freebsd-update** [**-k**] [**-o** `old-origin`] `directory` +**ftjail freebsd-update** [**-k**] [**-o** `old-origin`] `directory` `new-origin` [`etcupdate-tarball`] Description @@ -14,6 +14,12 @@ A :manpage:`freebsd-update(8)` for a Thin Jail. +Make the ZFS dataset mounted at `directory` a read-only clone of `new-origin`. + +If `etcupdate-tarball` is given also call :manpage:`etcupdate(8)` on +the fully re-mounted directory tree that is rooted at `directory` +using `etcupdate-tarball` as new "current". + Options ------- @@ -22,7 +28,7 @@ .. option:: -k - Keep all temporary files. + Keep all temporary files. Temporary files are created in :file:`/var/tmp`. .. note:: On unexpected errors temp files are automatically kept.
--- a/sbin/ftjail Sat Dec 03 09:46:18 2022 +0100 +++ b/sbin/ftjail Sat Dec 03 21:12:23 2022 +0100 @@ -44,7 +44,7 @@ build-etcupdate-current-tmpl DIRECTORY TARBALL - freebsd-update [-k] [-o OLD-ORIGIN] DIRECTORY + freebsd-update [-k] [-o OLD-ORIGIN] DIRECTORY NEW-ORIGIN [ETCUPDATE-TARBALL] ENVIRONMENT: @@ -874,15 +874,104 @@ #: +#: Determine extra clone options with respect to the "mountpoint" property +#: +#: Args: +#: $1: the dataset +#: +#: Output (stdout) +#: The extra clone arguments +#: +#: Exit: +#: On unexpected source values +#: +_get_clone_extra_prop_for_mountpoint() { + local ds + + local _mp_name _mp_property _mp_value _mp_source + + ds="${1}" + + zfs get -H mountpoint "${ds}" \ + | { + IFS=$'\t' read -r _mp_name _mp_property _mp_value _mp_source + case "${_mp_source}" in + local) + echo -n "-o mountpoint=${_mp_value}" + ;; + default|inherited*) + ;; + temporary*|received*|'-'|none) + # XXX FIXME: Is this relevant on FreeBSD? + echo "ERROR: Unexpected SOURCE \"${_mp_source}\" for mountpoint at \`${_mp_value}'" 1>&2 + exit 1 + ;; + *) + echo "ERROR: Unexpected SOURCE for mountpoint property at \`${_mp_value}'" 1>&2 + exit 1; + ;; + esac + if [ "${_mp_value}" != "${_directory}" ]; then + echo "WARNING: dataset is not mounted at its configured mountpoint but elsewhere (probably via \"mount -t zfs\")" 1>&2 + fi + } +} + + +#: +#: Determine the "canmount" property for a dataset +#: +#: Args: +#: $1: the dataset +#: +#: Output (stdout): +#: The local value or "DEFAULT" for the (unset) default +#: +#: Exit: +#: On unexpected source values +#: +_get_canmount_setting_for_dataset() { + local ds + + local _cm_name _cm_property _cm_value _cm_source + + ds="${1}" + + zfs get -H canmount "${ds}" \ + | { + IFS=$'\t' read -r _cm_name _cm_property _cm_value _cm_source + case "${_cm_source}" in + local) + echo -n "canmount=${_cm_value}" + ;; + default) + echo -n "DEFAULT" + ;; + inherited|temporary*|received*|'-'|none) + # XXX FIXME: Is this relevant on FreeBSD? + echo "ERROR: Unexpected SOURCE \"${_cm_source}\" for canmount at \`${_cm_name}'" 1>&2 + exit 1 + ;; + *) + echo "ERROR: Unexpected SOURCE for canmount property at \`${_cm_name}'" 1>&2 + exit 1; + ;; + esac + } +} + + +#: #: Implement the "freebsd-update" command for a thin jail #: command_freebsd_update() { - local _directory + local _directory _new_origin _etcupdate_tarball local _opt_keep _opt_old_origin - local _res _jailname _dir_mounts _dir_fn_fstab _dir_basename + local _res _jailname _dir_mounts _dir_fn_fstab _dir_basename _dir_fn_tldir local _root_dataset _root_mountpoint _root_type _root_options - local _dummy _opt + local _clone_extra_props _canmount_prop + local _line _opt local _root_readonly _root_origin _opt_keep="no" @@ -904,9 +993,17 @@ OPTIND=1 _directory="${1-}" + _new_origin="${2-}" + _etcupdate_tarball="${3-}" [ -z "${_directory}" ] && { echo "ERROR: no directory given" 1>&2; return 2; } - [ -d "${_directory}" ] || { echo "ERROR: directory \`${_directory}' does not exist" 1>&2; exit 1; } + [ -d "${_directory}" ] || { echo "ERROR: directory \`${_directory}' does not exist" 1>&2; return 1; } + + [ -z "${_new_origin}" ] && { echo "ERROR: no new origin given" 1>&2; return 2; } + zfs list -H -o name -t snapshot "${_new_origin}" >/dev/null || { echo "ERROR: new origin does not exist" 1>&2; return 1; } + if [ -n "${_etcupdate_tarball}" ]; then + [ -f "${_etcupdate_tarball}" ] || { echo "ERROR: given etcupdate tarball does not exist " 1>&2; return 1; } + fi _dir_basename="$(basename ${_directory})" @@ -916,10 +1013,10 @@ set -e if [ ${_res} -ne 2 ] ; then if [ ${_res} -ne 0 ] ; then - exit ${_res} + return ${_res} else echo "ERROR: Please stop the \`${_jailname}' jail" >&2 - exit 1 + return 1 fi fi _dir_mounts="$(_get_mounts_at_directory "${_directory}")" @@ -928,32 +1025,43 @@ # Check preconditions thoroughly! # # Check that the first item/line is a read-only ZFS mount directly - # at the given directory. + # at the given directory. This must also be its configured + # mountpoint in ZFS. # Also check that it is a clone proper. # - IFS=' '$'\t' read -r _root_dataset _root_mountpoint _root_type _root_options _dummy <<EOF4tHGCSS + IFS=' '$'\t' read -r _root_dataset _root_mountpoint _root_type _root_options _line <<EOF4tHGCSS ${_dir_mounts} EOF4tHGCSS - [ "${_root_mountpoint}" != "${_directory}" ] && { echo "ERROR: found root mountpoint does not match given directory" 1>&2; exit 1; } - [ "${_root_type}" != "zfs" ] && { echo "ERROR: root mountpoint is not from a ZFS dataset" 1>&2; exit 1; } + [ "${_root_mountpoint}" != "${_directory}" ] && { echo "ERROR: found root mountpoint does not match given directory" 1>&2; return 1; } + [ "${_root_type}" != "zfs" ] && { echo "ERROR: root mountpoint is not from a ZFS dataset" 1>&2; return 1; } _root_readonly="$(zfs list -H -o readonly "${_root_dataset}")" - [ "${_root_readonly}" != "on" ] && { echo "ERROR: the root dataset is not mounted read-only" 1>&2; exit 1; } + [ "${_root_readonly}" != "on" ] && { echo "ERROR: the root dataset is not mounted read-only" 1>&2; return 1; } _root_origin="$(zfs list -H -o origin "${_root_dataset}")" if [ -n "${_opt_old_origin}" ]; then - [ "${_opt_old_origin}" != "${_root_origin}" ] && { echo "ERROR: origin mismatch" 1>&2; exit 1; } + [ "${_opt_old_origin}" != "${_root_origin}" ] && { echo "ERROR: origin mismatch" 1>&2; return 1; } else - [ "${_root_origin}" = '-' ] && { echo "ERROR: the root dataset is not a ZFS clone" 1>&2; exit 1; } + [ "${_root_origin}" = '-' ] && { echo "ERROR: the root dataset is not a ZFS clone" 1>&2; return 1; } fi + + # Determine we need to clone with a custom (non inherited) "mountpoint" + _clone_extra_props="$(_get_clone_extra_prop_for_mountpoint "${_root_dataset}") " + # Determine we need to clone with a custom (non inherited) "canmount" + _canmount_prop="$(_get_canmount_setting_for_dataset "${_root_dataset}")" + # # XXX FIXME: should we check that _root_options equals "ro" or # start with "ro," # _root_origin="$(zfs list -H -o origin "${_root_dataset}")" - _dir_fn_fstab="$(env TMPDIR=/var/tmp mktemp -t ftjail-fstab.${_dir_basename})" + _dir_fn_fstab="$(env TMPDIR=/var/tmp mktemp -t ftjail_${_dir_basename}.fstab)" echo -n "${_dir_mounts}" >>"${_dir_fn_fstab}" + _dir_fn_tldir="$(env TMPDIR=/var/tmp mktemp -t ftjail_${_dir_basename}.tldir)" + find "${_directory}" -depth 1 -type d 2>/dev/null | sort >>"${_dir_fn_tldir}" + # Unmount in reverse order: unmount can do it for us - umount -a -F "${_dir_fn_fstab}" -v || exit 1 + echo "Unmounting all datasets mounted at \`${_directory}'" + umount -a -F "${_dir_fn_fstab}" -v # # XXX TBD: Hooks to create some new top-level dirs (/srv /proc et @@ -962,9 +1070,84 @@ # completely mounting the stored fstab. # + # + # Destroy the current read-only root clone and make a new clone based + # on the given new origin. + # The new clone temporarily is RW and is not to be mounted automatically. + # These both properties are set again below after the new base is + # adjusted properly. + # + echo "Destroying the cloned root dataset \`${_root_dataset}'" + zfs destroy -v "${_root_dataset}" + echo "Cloning a new root dataset \`${_root_dataset}' from new origin \`${_new_origin}'" + zfs clone -o readonly=off -o canmount=noauto ${_clone_extra_props} "${_new_origin}" "${_root_dataset}" + # + # NOTE: Always mount with "mount -t zfs" because a custom + # mountpoint is not reflected in the "mountpoint" + # property. So in scripts to be sure to unmount and re-mount + # at the same location always use "mount -t zfs". + # + echo "Remounting only the root dataset at \`${_directory}'" + [ ! -d "${_directory}" ] && mkdir "${_directory}" + mount -t zfs "${_root_dataset}" "${_directory}" + # + # Re-create all currently missing top-level dirs (aka mountpoint) + # in the new clone. Most probably they serve as mountpoints for other + # datasets. + # + # XXX FIXME: Re-create the current mode bits and/or ACLs also. + # But most probably they are set properly in the mounted + # datasets. + # + echo "Recreating missing top-level directories" + cat "${_dir_fn_tldir}" \ + | { + while IFS='' read -r _line ; do + if [ ! -d "${_line}" ]; then + echo "Recreating top-level directory: ${_line}" + mkdir "${_line}" + fi + done + } + echo "Unmounting the new root dataset" + umount "${_directory}" + echo "Re-setting some ZFS properties on the new cloned dataset" + zfs set readonly=on "${_root_dataset}" + # + # Copy "canmount" properly last because it has been set to "noauto" + # temporarily. + # + if [ -n "${_canmount_prop}" ]; then + if [ "${_canmount_prop}" = "DEFAULT" ]; then + # + # "zfs inherit" is not possible for "canmount". + # Use "inherit -S" to simulate a reset to "default" somewhat + # + # See also: https://github.com/openzfs/zfs/issues/5733 + # + zfs inherit -S canmount "${_root_dataset}" + else + zfs set "${_canmount_prop}" "${_root_dataset}" + fi + fi + + # Mount again + echo "Mounting all datasets rooted at \`${_directory}'" + [ ! -d "${_directory}" ] && mkdir "${_directory}" + mount -a -F "${_dir_fn_fstab}" -v + + # Update configs + if [ -n "${_etcupdate_tarball}" ]; then + echo "Calling etcupdate for DESTDIR=${_directory}" + etcupdate -D "${_directory}" -t "${_etcupdate_tarball}" + fi + if [ "${_opt_keep}" != "yes" ]; then + echo "Cleaning up...""" + [ -n "${_dir_fn_tldir}" } && [ -f "${_dir_fn_tldir}" ] && rm -f "${_dir_fn_tldir}" [ -n "${_dir_fn_fstab}" ] && [ -f "${_dir_fn_fstab}" ] && rm -f "${_dir_fn_fstab}" fi + echo "Done." }
