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