comparison sbin/fzfs @ 276:3c24b07240f2

Move the implementation of "mount" and "umount" into the new tool fzfs. It is not jail-specific but in reality a helper for some ZFS management issues.
author Franz Glasner <fzglas.hg@dom66.de>
date Sat, 17 Sep 2022 16:47:32 +0200
parents
children 1fc3b04b39fa
comparison
equal deleted inserted replaced
275:5bb4c4044e48 276:3c24b07240f2
1 #!/bin/sh
2 # -*- indent-tabs-mode: nil; -*-
3 #:
4 #: A ZFS management helper tool.
5 #:
6 #: :Author: Franz Glasner
7 #: :Copyright: (c) 2022 Franz Glasner.
8 #: All rights reserved.
9 #: :License: BSD 3-Clause "New" or "Revised" License.
10 #: See LICENSE for details.
11 #: If you cannot find LICENSE see
12 #: <https://opensource.org/licenses/BSD-3-Clause>
13 #: :ID: @(#)@@PKGORIGIN@@ $HGid$
14 #:
15
16 set -eu
17
18 VERSION="@@VERSION@@"
19
20 USAGE='
21 USAGE: fzfs [ OPTIONS ] COMMAND [ COMMAND OPTIONS ] [ ARG ... ]
22
23 OPTIONS:
24
25 -V Print the program name and version number to stdout and exit
26
27 -h Print this help message to stdout and exit
28
29 COMMANDS:
30
31 mount [-O] [-u] [-n] DATASET [MOUNTPOINT]
32
33 Mount the ZFS dataset DATASET and all its children to mountpoint
34 MOUNTPOINT
35
36 -O Also mount datasets at mountpoints outside of their "natural"
37 and inherited mountpoints
38 -N Mount at their "natural" configured ZFS mountpoints
39 (MOUNTPOINT is not required)
40 -P Do not mount the given parent DATASET but only its children
41 -n Do not really mount but show what would be mounted where
42 -u Alias of -n
43
44 umount DATASET
45
46 Unmount the mounted DATASET and all its children
47
48 unmount
49
50 Alias for `umount'"'"'
51
52 '
53
54 #
55 #: Implementation of the "mount" command.
56 #:
57 #: Mount a dataset and recursively all its children datasets.
58 #:
59 command_mount() {
60 local _dsname _mountpoint
61 local _opt_dry_run _opt_mount_outside _opt_mount_natural
62 local _opt_mount_children_only
63
64 local _name _mp _canmount _mounted _rootds_mountpoint _relative_mp _real_mp
65
66 _opt_dry_run=""
67 _opt_mount_outside=""
68 _opt_mount_natural=""
69 _opt_mount_children_only=""
70 while getopts "ONPnu" _opt ; do
71 case ${_opt} in
72 O)
73 _opt_mount_outside="yes"
74 ;;
75 N)
76 _opt_mount_natural="yes"
77 ;;
78 P)
79 _opt_mount_children_only="yes"
80 ;;
81 n|u)
82 _opt_dry_run="yes"
83 ;;
84 \?|:)
85 return 2;
86 ;;
87 esac
88 done
89 shift $((OPTIND-1))
90 OPTIND=1
91
92 _dsname="${1-}"
93 _mountpoint="${2-}"
94
95 if [ -z "${_dsname}" ]; then
96 echo "ERROR: no dataset given" >&2
97 return 2
98 fi
99
100 _rootds_mountpoint="$(zfs list -H -o mountpoint -t filesystem "${_dsname}")" || \
101 { echo "ERROR: root dataset does not exist" >&2; return 1; }
102
103 if [ -z "${_mountpoint}" ]; then
104 if [ "${_opt_mount_natural}" = "yes" ]; then
105 _mountpoint="${_rootds_mountpoint}"
106 else
107 echo "ERROR: no mountpoint given" >&2
108 return 2
109 fi
110 else
111 if [ "${_opt_mount_natural}" = "yes" ]; then
112 echo "ERROR: Cannot have a custom mountpoint when \"-O\" is given" >&2
113 return 2
114 fi
115 fi
116
117 # Eventually remove a trailing slash
118 _mountpoint="${_mountpoint%/}"
119 if [ -z "${_mountpoint}" ]; then
120 echo "ERROR: would mount over the root filesystem" >&2
121 return 1
122 fi
123
124 zfs list -H -o name,mountpoint,canmount,mounted -s mountpoint -t filesystem -r "${_dsname}" \
125 | {
126 while IFS=$'\t' read -r _name _mp _canmount _mounted ; do
127 # Skip filesystems that are already mounted
128 [ "${_mounted}" = "yes" ] && continue
129 # Skip filesystems that must not be mounted
130 [ "${_canmount}" = "off" ] && continue
131 #
132 # Mount only the children and skip the given parent dataset
133 # if required
134 #
135 [ \( "${_opt_mount_children_only}" = "yes" \) -a \( "${_name}" = "${_dsname}" \) ] && continue
136 case "${_mp}" in
137 "none"|"legacy")
138 # Do nothing for filesystem with unset or legacy mountpoints
139 ;;
140 "${_rootds_mountpoint}"|"${_rootds_mountpoint}/"*)
141 #
142 # Handle only mountpoints that have a mountpoint below
143 # the parent datasets mountpoint
144 #
145
146 # Determine the mountpoint relative to the parent mountpoint
147 _relative_mp="${_mp#${_rootds_mountpoint}}"
148 # Eventually remove a trailing slash
149 _relative_mp="${_relative_mp%/}"
150 # The real effective full mountpoint
151 _real_mp="${_mountpoint}${_relative_mp}"
152
153 #
154 # Consistency and sanity check: computed real mountpoint must
155 # be equal to the configured mountpoint when no custom mountpoint
156 # is given.
157 #
158 if [ "${_opt_mount_natural}" = "yes" ]; then
159 if [ "${_real_mp}" != "${_mp}" ]; then
160 echo "ERROR: mountpoint mismatch" >&2
161 return 1
162 fi
163 fi
164
165 if [ "${_opt_dry_run}" = "yes" ]; then
166 echo "Would mount ${_name} on ${_real_mp}"
167 else
168 mkdir -p "${_real_mp}" 1> /dev/null 2> /dev/null || \
169 { echo "ERROR: cannot create mountpoint ${_real_mp}" >&2; return 1; }
170 echo "Mounting ${_name} on ${_real_mp}"
171 mount -t zfs "${_name}" "${_real_mp}" || return 1
172 fi
173 ;;
174 *)
175 if [ "${_opt_mount_outside}" = "yes" ]; then
176 if [ "${_opt_dry_run}" = "yes" ]; then
177 echo "Would mount ${_name} on configured ZFS dataset mountpoint ${_mp}"
178 else
179 echo "Mounting ${_name} on configured ZFS dataset mountpoint ${_mp}"
180 zfs mount "${_name}" || return 1
181 fi
182 else
183 echo "Skipping ${_name} because its configured ZFS mountpoint is not relative to given root dataset" 2>&1
184 fi
185 ;;
186 esac
187 done
188
189 return 0
190 }
191 }
192
193
194 #:
195 #: Implement the "umount" command.
196 #:
197 #: Umount a datasets and recursively all its children datasets.
198 #:
199 command_umount() {
200 local _dsname
201
202 local _name _mp _rest _rootds_mountpoint
203
204 _dsname="${1-}"
205 [ -z "${_dsname}" ] && { echo "ERROR: no dataset given" 1>&2; return 2; }
206
207 # Just determine whether the given dataset name exists
208 _rootds_mountpoint="$(zfs list -H -o mountpoint -t filesystem "${_dsname}")" || { echo "ERROR: dataset not found" 1>&2; return 1; }
209
210 mount -t zfs -p \
211 | grep -E "^${_dsname}(/|\s)" \
212 | sort -n -r \
213 | {
214 while IFS=' '$'\t' read -r _name _mp _rest ; do
215 echo "Umounting ${_name} on ${_mp}"
216 umount "${_mp}" || return 1
217 done
218 }
219 return 0
220 }
221
222 #
223 # Global option handling
224 #
225 while getopts "Vh" _opt ; do
226 case ${_opt} in
227 V)
228 printf 'ftjail v%s (rv:%s)\n' "${VERSION}" '@@HGREVISION@@'
229 exit 0
230 ;;
231 h)
232 echo "${USAGE}"
233 exit 0
234 ;;
235 \?)
236 exit 2;
237 ;;
238 *)
239 echo "ERROR: option handling failed" 1>&2
240 exit 2
241 ;;
242 esac
243 done
244 #
245 # Reset the Shell's option handling system to prepare for handling
246 # command-local options.
247 #
248 shift $((OPTIND-1))
249 OPTIND=1
250
251 test $# -gt 0 || { echo "ERROR: no command given" 1>&2; exit 2; }
252
253 command="$1"
254 shift
255
256 case "${command}" in
257 mount)
258 command_mount "$@"
259 ;;
260 umount|unmount)
261 command_umount "$@"
262 ;;
263 *)
264 echo "ERROR: unknown command \`${command}'" 1>&2
265 exit 2
266 ;;
267 esac