1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in> 5 * Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org> 6 * Copyright (c) 2019 Wes Maag <wes@jwmaag.org> 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #include <sys/cdefs.h> 31 #include <sys/mntent.h> 32 33 #include "be.h" 34 #include "be_impl.h" 35 36 #define LIBBE_MOUNT_PREFIX "be_mount." 37 38 struct be_mountcheck_info { 39 const char *path; 40 char *name; 41 }; 42 43 struct be_mount_info { 44 libbe_handle_t *lbh; 45 const char *be; 46 const char *mountpoint; 47 int mntflags; 48 int deepmount; 49 int depth; 50 }; 51 52 static int 53 be_mountcheck_cb(zfs_handle_t *zfs_hdl, void *data) 54 { 55 struct be_mountcheck_info *info; 56 char *mountpoint; 57 58 if (data == NULL) 59 return (1); 60 info = (struct be_mountcheck_info *)data; 61 if (!zfs_is_mounted(zfs_hdl, &mountpoint)) 62 return (0); 63 if (strcmp(mountpoint, info->path) == 0) { 64 info->name = strdup(zfs_get_name(zfs_hdl)); 65 free(mountpoint); 66 return (1); 67 } 68 free(mountpoint); 69 return (0); 70 } 71 72 /* 73 * Called from be_mount, uses the given zfs_handle and attempts to 74 * mount it at the passed mountpoint. If the deepmount flag is set, continue 75 * calling the function for each child dataset. 76 */ 77 static int 78 be_mount_iter(zfs_handle_t *zfs_hdl, void *data) 79 { 80 int err; 81 char *mountpoint; 82 char tmp[BE_MAXPATHLEN], zfs_mnt[BE_MAXPATHLEN]; 83 struct be_mount_info *info; 84 85 info = (struct be_mount_info *)data; 86 87 if (zfs_is_mounted(zfs_hdl, &mountpoint)) { 88 free(mountpoint); 89 return (0); 90 } 91 92 /* 93 * canmount and mountpoint are both ignored for the BE dataset, because 94 * the rest of the system (kernel and loader) will effectively do the 95 * same. 96 */ 97 if (info->depth == 0) { 98 snprintf(tmp, BE_MAXPATHLEN, "%s", info->mountpoint); 99 } else { 100 if (zfs_prop_get_int(zfs_hdl, ZFS_PROP_CANMOUNT) == 101 ZFS_CANMOUNT_OFF) 102 return (0); 103 104 if (zfs_prop_get(zfs_hdl, ZFS_PROP_MOUNTPOINT, zfs_mnt, 105 BE_MAXPATHLEN, NULL, NULL, 0, 1)) 106 return (1); 107 108 /* 109 * We've encountered mountpoint=none at some intermediate 110 * dataset (e.g. zroot/var) that will have children that may 111 * need to be mounted. Skip mounting it, but iterate through 112 * the children. 113 */ 114 if (strcmp("none", zfs_mnt) == 0) 115 goto skipmount; 116 117 mountpoint = be_mountpoint_augmented(info->lbh, zfs_mnt); 118 snprintf(tmp, BE_MAXPATHLEN, "%s%s", info->mountpoint, 119 mountpoint); 120 } 121 122 if ((err = zfs_mount_at(zfs_hdl, NULL, info->mntflags, tmp)) != 0) { 123 switch (errno) { 124 case ENAMETOOLONG: 125 return (set_error(info->lbh, BE_ERR_PATHLEN)); 126 case ELOOP: 127 case ENOENT: 128 case ENOTDIR: 129 return (set_error(info->lbh, BE_ERR_BADPATH)); 130 case EPERM: 131 return (set_error(info->lbh, BE_ERR_PERMS)); 132 case EBUSY: 133 return (set_error(info->lbh, BE_ERR_PATHBUSY)); 134 default: 135 return (set_error(info->lbh, BE_ERR_UNKNOWN)); 136 } 137 } 138 139 if (!info->deepmount) 140 return (0); 141 142 skipmount: 143 ++info->depth; 144 err = zfs_iter_filesystems(zfs_hdl, be_mount_iter, info); 145 --info->depth; 146 return (err); 147 } 148 149 150 static int 151 be_umount_iter(zfs_handle_t *zfs_hdl, void *data) 152 { 153 154 int err; 155 char *mountpoint; 156 struct be_mount_info *info; 157 158 info = (struct be_mount_info *)data; 159 160 ++info->depth; 161 if((err = zfs_iter_filesystems(zfs_hdl, be_umount_iter, info)) != 0) { 162 return (err); 163 } 164 --info->depth; 165 166 if (!zfs_is_mounted(zfs_hdl, &mountpoint)) { 167 return (0); 168 } 169 170 if (info->depth == 0 && info->mountpoint == NULL) 171 info->mountpoint = mountpoint; 172 else 173 free(mountpoint); 174 175 if (zfs_unmount(zfs_hdl, NULL, info->mntflags) != 0) { 176 switch (errno) { 177 case ENAMETOOLONG: 178 return (set_error(info->lbh, BE_ERR_PATHLEN)); 179 case ELOOP: 180 case ENOENT: 181 case ENOTDIR: 182 return (set_error(info->lbh, BE_ERR_BADPATH)); 183 case EPERM: 184 return (set_error(info->lbh, BE_ERR_PERMS)); 185 case EBUSY: 186 return (set_error(info->lbh, BE_ERR_PATHBUSY)); 187 default: 188 return (set_error(info->lbh, BE_ERR_UNKNOWN)); 189 } 190 } 191 return (0); 192 } 193 194 /* 195 * usage 196 */ 197 int 198 be_mounted_at(libbe_handle_t *lbh, const char *path, nvlist_t *details) 199 { 200 char be[BE_MAXPATHLEN]; 201 zfs_handle_t *root_hdl; 202 struct be_mountcheck_info info; 203 prop_data_t propinfo; 204 205 bzero(&be, BE_MAXPATHLEN); 206 if ((root_hdl = zfs_open(lbh->lzh, lbh->root, 207 ZFS_TYPE_FILESYSTEM)) == NULL) 208 return (BE_ERR_ZFSOPEN); 209 210 info.path = path; 211 info.name = NULL; 212 zfs_iter_filesystems(root_hdl, be_mountcheck_cb, &info); 213 zfs_close(root_hdl); 214 215 if (info.name != NULL) { 216 if (details != NULL) { 217 if ((root_hdl = zfs_open(lbh->lzh, info.name, 218 ZFS_TYPE_FILESYSTEM)) == NULL) { 219 free(info.name); 220 return (BE_ERR_ZFSOPEN); 221 } 222 223 propinfo.lbh = lbh; 224 propinfo.list = details; 225 propinfo.single_object = false; 226 propinfo.bootonce = NULL; 227 prop_list_builder_cb(root_hdl, &propinfo); 228 zfs_close(root_hdl); 229 } 230 free(info.name); 231 return (0); 232 } 233 return (1); 234 } 235 236 /* 237 * usage 238 */ 239 int 240 be_mount(libbe_handle_t *lbh, const char *bootenv, const char *mountpoint, 241 int flags, char *result_loc) 242 { 243 char be[BE_MAXPATHLEN]; 244 char mnt_temp[BE_MAXPATHLEN]; 245 int mntflags, mntdeep; 246 int err; 247 struct be_mount_info info; 248 zfs_handle_t *zhdl; 249 250 if ((err = be_root_concat(lbh, bootenv, be)) != 0) 251 return (set_error(lbh, err)); 252 253 if ((err = be_exists(lbh, bootenv)) != 0) 254 return (set_error(lbh, err)); 255 256 if (is_mounted(lbh->lzh, be, NULL)) 257 return (set_error(lbh, BE_ERR_MOUNTED)); 258 259 mntdeep = (flags & BE_MNT_DEEP) ? 1 : 0; 260 mntflags = (flags & BE_MNT_FORCE) ? MNT_FORCE : 0; 261 262 /* Create mountpoint if it is not specified */ 263 if (mountpoint == NULL) { 264 const char *tmpdir; 265 266 tmpdir = getenv("TMPDIR"); 267 if (tmpdir == NULL) 268 tmpdir = _PATH_TMP; 269 270 if (snprintf(mnt_temp, sizeof(mnt_temp), "%s%s%sXXXX", tmpdir, 271 tmpdir[strlen(tmpdir) - 1] == '/' ? "" : "/", 272 LIBBE_MOUNT_PREFIX) >= (int)sizeof(mnt_temp)) 273 return (set_error(lbh, BE_ERR_PATHLEN)); 274 275 if (mkdtemp(mnt_temp) == NULL) 276 return (set_error(lbh, BE_ERR_IO)); 277 } 278 279 if ((zhdl = zfs_open(lbh->lzh, be, ZFS_TYPE_FILESYSTEM)) == NULL) 280 return (set_error(lbh, BE_ERR_ZFSOPEN)); 281 282 info.lbh = lbh; 283 info.be = be; 284 info.mountpoint = (mountpoint == NULL) ? mnt_temp : mountpoint; 285 info.mntflags = mntflags; 286 info.deepmount = mntdeep; 287 info.depth = 0; 288 289 if((err = be_mount_iter(zhdl, &info) != 0)) { 290 zfs_close(zhdl); 291 return (err); 292 } 293 zfs_close(zhdl); 294 295 if (result_loc != NULL) 296 strlcpy(result_loc, mountpoint == NULL ? mnt_temp : mountpoint, 297 BE_MAXPATHLEN); 298 299 return (BE_ERR_SUCCESS); 300 } 301 302 /* 303 * usage 304 */ 305 int 306 be_unmount(libbe_handle_t *lbh, const char *bootenv, int flags) 307 { 308 int err; 309 char be[BE_MAXPATHLEN]; 310 zfs_handle_t *root_hdl; 311 struct be_mount_info info; 312 313 if ((err = be_root_concat(lbh, bootenv, be)) != 0) 314 return (set_error(lbh, err)); 315 316 if ((root_hdl = zfs_open(lbh->lzh, be, ZFS_TYPE_FILESYSTEM)) == NULL) 317 return (set_error(lbh, BE_ERR_ZFSOPEN)); 318 319 info.lbh = lbh; 320 info.be = be; 321 info.mountpoint = NULL; 322 info.mntflags = (flags & BE_MNT_FORCE) ? MS_FORCE : 0; 323 info.depth = 0; 324 325 if ((err = be_umount_iter(root_hdl, &info)) != 0) { 326 free(__DECONST(char *, info.mountpoint)); 327 zfs_close(root_hdl); 328 return (err); 329 } 330 331 /* 332 * We'll attempt to remove the directory if we created it on a 333 * best-effort basis. rmdir(2) failure will not be reported. 334 */ 335 if (info.mountpoint != NULL) { 336 const char *mdir; 337 338 mdir = strrchr(info.mountpoint, '/'); 339 if (mdir == NULL) 340 mdir = info.mountpoint; 341 else 342 mdir++; 343 344 if (strncmp(mdir, LIBBE_MOUNT_PREFIX, 345 sizeof(LIBBE_MOUNT_PREFIX) - 1) == 0) { 346 (void)rmdir(info.mountpoint); 347 } 348 } 349 350 free(__DECONST(char *, info.mountpoint)); 351 352 zfs_close(root_hdl); 353 return (BE_ERR_SUCCESS); 354 } 355 356 /* 357 * This function will blow away the input buffer as needed if we're discovered 358 * to be looking at a root-mount. If the mountpoint is naturally beyond the 359 * root, however, the buffer may be left intact and a pointer to the section 360 * past altroot will be returned instead for the caller's perusal. 361 */ 362 char * 363 be_mountpoint_augmented(libbe_handle_t *lbh, char *mountpoint) 364 { 365 366 if (lbh->altroot_len == 0) 367 return (mountpoint); 368 if (mountpoint == NULL || *mountpoint == '\0') 369 return (mountpoint); 370 371 if (mountpoint[lbh->altroot_len] == '\0') { 372 *(mountpoint + 1) = '\0'; 373 return (mountpoint); 374 } else 375 return (mountpoint + lbh->altroot_len); 376 } 377