Mercurial > hgrepos > FreeBSD > ports > sysutils > local-bsdtools
view sbin/fjail @ 203:eb95c986cc59
Use -t filesystem in all "zfs list" calls
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Sun, 21 Aug 2022 12:18:15 +0200 |
| parents | 6b7a084ddf1d |
| children | ef397b84b047 |
line wrap: on
line source
#!/bin/sh # -*- indent-tabs-mode: nil; -*- : 'A very minimal BSD Jail management tool. :Author: Franz Glasner :Copyright: (c) 2019-2022 Franz Glasner. All rights reserved. :License: BSD 3-Clause "New" or "Revised" License. See LICENSE for details. If you cannot find LICENSE see <https://opensource.org/licenses/BSD-3-Clause> :ID: @(#)@@PKGORIGIN@@ $HGid$ ' set -eu VERSION="@@VERSION@@" USAGE=' USAGE: fjail [ OPTIONS ] COMMAND [ COMMAND OPTIONS ] [ ARG ... ] OPTIONS: -V Print the program name and version number to stdout and exit -h Print this help message to stdout and exit COMMANDS: datasets [-u] PARENT CHILD Create ZFS datasets to be used within a jail PARENT must exist already and CHILD must not exist. -s Also create a dataset for freebsd-update data files -t Create a more tiny set of datasets -T Create only an extra tiny set of datasets -u Do not automatically mount newly created datasets mount [-u] [-n] DATASET MOUNTPOINT Mount the ZFS dataset DATASET and all its children to mountpoint MOUNTPOINT -n Do not really mount but show what would be mounted where -u Alias of -n umount DATASET Unmount the mounted DATASET and all its children privs MOUNTPOINT Adjust some Unix privileges to mounted jail datasets populate MOUNTPOINT BASETXZ Populate the jail directory in MOUNTPOINT with the base system in BASETXZ copy SOURCE-DATASET DEST-DATASET Copy a tree of ZFS datasets with "zfs send -R" and "zfs receive". Note that the destination dataset must not exist already. -r Copy the datasets with the -Lec options (aka "raw") -u Do not automatically mount received datasets ENVIRONMENT: All environment variables that affect "zfs" are effective also. DESCRIPTION: All commands with the exception of "populate" require ZFS as filesystem. ' # Reset to standard umask umask 0022 _get_dataset_for_mountpoint() { : 'Use `mount -t zfs -p` to determine the ZFS dataset for a given mountpoint. ' local _mountpoint local _ds _mount _rest _mountpoint="$1" mount -t zfs -p \ | { while IFS=' '$'\t' read -r _ds _mount _rest ; do if [ "$_mount" = "$_mountpoint" ]; then echo "${_ds}" return 0 fi done return 1 } } _get_dataset_for_varempty() { : 'Allow special handling for <mountpoint>/var/empty which may be mounted read-only. ' local _mountpoint local _ve_mount _mountpoint="$1" if [ "$_mountpoint" = '/' ]; then _ve_mount='/var/empty' else _ve_mount="${_mountpoint}/var/empty" fi _get_dataset_for_mountpoint "${_ve_mount}" } # # "datasets" -- create the ZFS dataset tree # # command_datasets [ -u ] parent-dataset child-dataset # # -u do not automatically mount newly created datasets # command_datasets() { # parent ZFS dataset -- child ZFS dataset name local _pds _cds # and its mount point local _pmp _get # full name of the dataset local _ds # dynamic ZFS options -- create cache for freebsd-update -- use a more tiny layout local _zfsopts _fbsdupdate _tiny _zfsopts="" _fbsdupdate="" _tiny="no" while getopts "ustT" _opt ; do case ${_opt} in t) # use a more tiny layout _tiny="yes" ;; T) # extra tiny layout _tiny="extra" ;; u) # do not mount newly created datasets _zfsopts="${_zfsopts} -u" ;; s) # create also a dataset for freebsd-update data _fbsdupdate="yes" ;; \?|:) return 2; ;; esac done shift $((OPTIND-1)) OPTIND=1 _pds="$1" if [ -z "${_pds}" ]; then echo "ERROR: no parent dataset given" >&2 return 2 fi _pmp=$(zfs list -H -o mountpoint -t filesystem "${_pds}" 2>/dev/null) || { echo "ERROR: dataset \`${_pds}' does not exist" >&2; return 1; } case "${_pmp}" in none) echo "ERROR: dataset \`${_pds}' has no mountpoint" >&2 return 1 ;; legacy) echo "ERROR: dataset \`${_pds}' has a \`${_mp}' mountpoint" >&2 return 1 ;; *) # VOID ;; esac _cds="$2" if [ -z "${_cds}" ]; then echo "ERROR: no child dataset given" >&2 return 2 fi _ds="${_pds}/${_cds}" echo "Resulting new root dataset is \`${_ds}' at mountpoint \`${_pmp}/${_cds}'" if zfs list -H -o mountpoint -t filesystem "${_ds}" >/dev/null 2>/dev/null; then echo "ERROR: dataset \`${_ds}' does already exist" >&2 return 1 fi # # NOTE: For BEs these directory will be *excluded* from the BE # # /tmp # /usr/home # /usr/ports # /usr/src # /var/audit # /var/crash # /var/log # /var/mail # /var/tmp # zfs create ${_zfsopts} -o atime=off "${_ds}" zfs create ${_zfsopts} -o sync=disabled -o setuid=off "${_ds}/tmp" if [ "${_tiny}" != "extra" ]; then if [ "${_tiny}" = "yes" ]; then zfs create ${_zfsopts} -o canmount=off "${_ds}/usr" else zfs create ${_zfsopts} "${_ds}/usr" fi zfs create ${_zfsopts} -o setuid=off "${_ds}/usr/home" zfs create ${_zfsopts} "${_ds}/usr/local" fi if [ \( "${_tiny}" = "yes" \) -o \( "${_tiny}" = "extra" \) ]; then zfs create ${_zfsopts} -o canmount=off "${_ds}/var" else zfs create ${_zfsopts} "${_ds}/var" fi if [ "${_tiny}" != "extra" ]; then zfs create ${_zfsopts} -o exec=off -o setuid=off "${_ds}/var/audit" zfs create ${_zfsopts} -o exec=off -o setuid=off "${_ds}/var/cache" zfs create ${_zfsopts} -o exec=off -o setuid=off -o primarycache=metadata -o compression=off "${_ds}/var/cache/pkg" zfs create ${_zfsopts} -o exec=off -o setuid=off -o compression=off "${_ds}/var/crash" fi if [ "$_fbsdupdate" = "yes" ]; then if [ \( "${_tiny}" = "yes" \) -o \( "${_tiny}" = "extra" \) ]; then zfs create ${_zfsopts} -o canmount=off -o exec=off -o setuid=off "${_ds}/var/db" else zfs create ${_zfsopts} -o exec=off -o setuid=off "${_ds}/var/db" fi zfs create ${_zfsopts} -o exec=off -o setuid=off -o primarycache=metadata -o compression=off "${_ds}/var/db/freebsd-update" fi zfs create ${_zfsopts} -o readonly=on -o exec=off -o setuid=off "${_ds}/var/empty" zfs create ${_zfsopts} -o exec=off -o setuid=off -o primarycache=metadata "${_ds}/var/log" zfs create ${_zfsopts} -o exec=off -o setuid=off -o atime=on "${_ds}/var/mail" zfs create ${_zfsopts} -o sync=disabled -o exec=off -o setuid=off -o compression=off -o primarycache=all "${_ds}/var/run" zfs create ${_zfsopts} -o sync=disabled -o setuid=off "${_ds}/var/tmp" } # # "populate" -- populate the datasets with content from a FreeBSD base.txz # # command_populate mountpoint basetxz # command_populate() { # MOUNTPOINT -- base.txz local _mp _basetxz _mp="$1" _basetxz="$2" if [ -z "${_mp}" ]; then echo "ERROR: no mountpoint given" >&2 return 2 fi if [ -z "${_basetxz}" ]; then echo "ERROR: no base.txz given" >&2 return 2 fi if [ ! -d "${_mp}" ]; then echo "ERROR: mountpoint \`${_mp}' does not exist" >&2 return 1 fi if [ ! -r "${_basetxz}" ]; then echo "ERROR: file \`${_basetxz}' is not readable" >&2 return 1 fi # # Handle /var/empty separately later: could be already there and # mounted read-only. # tar -C "${_mp}" --exclude=./var/empty -xJp -f "${_basetxz}" || { echo "ERROR: tar encountered errors" >&2; return 1; } if [ -d "${_mp}/var/empty" ]; then # # If /var/empty exists already try to extract with changing the # flags (e.g. `schg'). But be ignore errors here. # tar -C "${_mp}" -xJp -f "${_basetxz}" ./var/empty || { echo "tar warnings for handling ./var/empty ignored because ./var/empty exists already" >&2; } else # Just extract /var/empty normally tar -C "${_mp}" -xJp -f "${_basetxz}" ./var/empty || { echo "ERROR: tar encountered errors" >&2; return 1; } fi find "${_mp}/boot" -type f -delete } # # "copy" -- ZFS copy of datasets # # command_copy source-dataset destination-dataset # command_copy() { # source dataset -- destination dataset local _source _dest # dynamic ZFS options -- ZFS copy options local _zfsopts _zfscopyopts _zfsopts="" _zfscopyopts="" while getopts "ru" _opt ; do case ${_opt} in r) # Use raw datasets _zfscopyopts="-Lec" ;; u) # do not mount newly created datasets _zfsopts="${_zfsopts} -u" ;; \?|:) return 2; ;; esac done shift $((OPTIND-1)) OPTIND=1 _source="$1" if [ -z "${_source}" ]; then echo "ERROR: no source dataset given" >&2 return 2 fi _dest="$2" if [ -z "${_dest}" ]; then echo "ERROR: no source dataset given" >&2 return 2 fi zfs send -R ${_zfscopyopts} -n -v "${_source}" || { echo "ERROR: ZFS operation failed in no-op mode" >&2; return 1; } zfs send -R ${_zfscopyopts} "${_source}" | zfs receive ${_zfsopts} "${_dest}" || { echo "ERROR: ZFS operation failed" >&2; return 1; } } # # "mount" -- recursively mount a dataset including subordinate datasets # # command_mount dataset mountpoint # command_mount() { local _dsname _mountpoint local _name _mp _canmount _mounted local _rootds_mountpoint _relative_mp _real_mp local _dry_run _dry_run="" while getopts "nu" _opt ; do case ${_opt} in n|u) _dry_run="yes" ;; \?|:) return 2; ;; esac done shift $((OPTIND-1)) OPTIND=1 _dsname="${1-}" _mountpoint="${2-}" if [ -z "${_dsname}" ]; then echo "ERROR: no dataset given" >&2 return 2 fi if [ -z "${_mountpoint}" ]; then echo "ERROR: no mountpoint given" >&2 return 2 fi # Remove a trailing slash _mountpoint="${_mountpoint%/}" if [ -z "${_mountpoint}" ]; then echo "ERROR: would mount over the root filesystem" >&2 return 1 fi _rootds_mountpoint="$(zfs list -H -o mountpoint -t filesystem "${_dsname}")" || \ { echo "ERROR: root dataset does not exist" >&2; return 1; } zfs list -H -o name,mountpoint,canmount,mounted -s mountpoint -t filesystem -r "${_dsname}" \ | { while IFS=$'\t' read -r _name _mp _canmount _mounted ; do # Skip filesystems that are already mounted [ "${_mounted}" = "yes" ] && continue # Skip filesystems that must not be mounted [ "${_canmount}" = "off" ] && continue case "${_mp}" in "none"|"legacy") # Do nothing for filesystem with unset or legacy mountpoints ;; "${_rootds_mountpoint}"|"${_rootds_mountpoint}/"*) # # Handle only mountpoints that have a mountpoint below # the parent datasets mountpoint # # Determine the mountpoint relative to the parent mountpoint _relative_mp="${_mp#${_rootds_mountpoint}}" # Remove a trailing slash _relative_mp="${_relative_mp%/}" _real_mp="${_mountpoint}${_relative_mp}" if [ "${_dry_run}" = "yes" ]; then echo "Would mount ${_name} on ${_real_mp}" else mkdir -p "${_real_mp}" 1> /dev/null 2> /dev/null || \ { echo "ERROR: cannot create mountpoint ${_real_mp}" >&2; return 1; } echo "Mounting ${_name} on ${_real_mp}" mount -t zfs "${_name}" "${_real_mp}" || return 1 fi ;; *) # XXX FIXME Option to do a zfs mount $_name ??? echo "Skipping ${_name} because its configured mountpoint is not relative to given root dataset" 2>&1 ;; esac done return 0 } } # # "umount" -- Recursively unmount ZFS datasets # # command_umount dataset # command_umount() { local _dsname local _name _mp _rest local _rootds_mountpoint _dsname="${1-}" [ -z "${_dsname}" ] && \ { echo "ERROR: no dataset given" >&2; return 2; } # Just determine whether the given dataset name exists _rootds_mountpoint="$(zfs list -H -o mountpoint -t filesystem "${_dsname}")" || { echo "ERROR: dataset not found" >&2; return 1; } mount -t zfs -p \ | grep -E "^${_dsname}(/|\s)" \ | sort -n -r \ | { while IFS=' '$'\t' read -r _name _mp _rest ; do echo "Umounting ${_name} on ${_mp}" umount "${_mp}" || return 1 done return 0 } } # # "privs" -- adjust privileges # # To be used when all ZFS datasets are mounted. # command_privs() { # mountpoint local _mp _d _veds _get _vestatus _mp="$1" if [ -z "${_mp}" ]; then echo "ERROR: no mountpoint given" >&2 return 2 fi if [ ! -d "${_mp}" ]; then echo "ERROR: directory \`${_mp}' does not exist" >&2 return 1 fi for _d in tmp var/tmp ; do chmod 01777 "${_mp}/${_d}" done chown root:mail "${_mp}/var/mail" chmod 0775 "${_mp}/var/mail" # # Handle <mountpoint>/var/empty specially: # make it writeable temporarily if it is mounted read-only: # _vestatus="" _veds="$(_get_dataset_for_varempty "${_mp}")" if [ $? -eq 0 ]; then _vestatus=$(zfs list -H -o readonly -t filesystem ${_veds} 2>/dev/null) || { echo "ERROR: cannot determine readonly status of ${_mp}/var/empty" >&2; return 1; } if [ "${_vestatus}" = "on" ]; then zfs set readonly=off ${_veds} 1> /dev/null || { echo "ERROR: cannot reset readonly-status of ${_mp}/var/empty" >&2; return 1; } fi fi # Set the access rights and the file flags as given in mtree chmod 0555 "${_mp}/var/empty" || { echo "WARNING: Cannot chmod on var/empty" >&2; } chflags schg "${_mp}/var/empty" || { echo "WARNING: Cannot chflags on var/empty" >&2; } # Reset the read-only status of the mountpoint as it was before if [ "${_vestatus}" = "on" ]; then zfs set readonly=on ${_veds} 1> /dev/null || { echo "ERROR: cannot reactivate readonly-status of ${_mp}/var/empty" >&2; return 1; } fi } # # Global option handling # while getopts "Vh" _opt ; do case ${_opt} in V) printf 'fjail v%s (rv:%s)\n' "${VERSION}" '@@HGREVISION@@' exit 0 ;; h) echo "${USAGE}" exit 0 ;; \?) exit 2; ;; *) echo "ERROR: option handling failed" >&2 exit 2 ;; esac done # # Reset the Shell's option handling system to prepare for handling # command-local options. # shift $((OPTIND-1)) OPTIND=1 test $# -gt 0 || { echo "ERROR: no command given" >&2; exit 2; } command="$1" shift case "${command}" in datasets) command_datasets "$@" ;; mount) command_mount "$@" ;; umount|unmount) command_umount "$@" ;; privs) command_privs "$@" ;; populate) command_populate "$@" ;; copy) command_copy "$@" ;; *) echo "ERROR: unknown command \`${command}'" >&2 exit 2 ;; esac
