Index: stable/12/lib/libbe/be.c =================================================================== --- stable/12/lib/libbe/be.c (revision 343240) +++ stable/12/lib/libbe/be.c (revision 343241) @@ -1,985 +1,984 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2017 Kyle J. Kneitinger * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include "be.h" #include "be_impl.h" #if SOON static int be_create_child_noent(libbe_handle_t *lbh, const char *active, const char *child_path); static int be_create_child_cloned(libbe_handle_t *lbh, const char *active); #endif /* * Iterator function for locating the rootfs amongst the children of the * zfs_be_root set by loader(8). data is expected to be a libbe_handle_t *. */ static int be_locate_rootfs(libbe_handle_t *lbh) { struct statfs sfs; struct extmnttab entry; zfs_handle_t *zfs; /* * Check first if root is ZFS; if not, we'll bail on rootfs capture. * Unfortunately needed because zfs_path_to_zhandle will emit to * stderr if / isn't actually a ZFS filesystem, which we'd like * to avoid. */ if (statfs("/", &sfs) == 0) { statfs2mnttab(&sfs, &entry); if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0) return (1); } else return (1); zfs = zfs_path_to_zhandle(lbh->lzh, "/", ZFS_TYPE_FILESYSTEM); if (zfs == NULL) return (1); strlcpy(lbh->rootfs, zfs_get_name(zfs), sizeof(lbh->rootfs)); zfs_close(zfs); return (0); } /* * Initializes the libbe context to operate in the root boot environment * dataset, for example, zroot/ROOT. */ libbe_handle_t * libbe_init(const char *root) { + char altroot[MAXPATHLEN]; libbe_handle_t *lbh; char *poolname, *pos; int pnamelen; lbh = NULL; poolname = pos = NULL; if ((lbh = calloc(1, sizeof(libbe_handle_t))) == NULL) goto err; if ((lbh->lzh = libzfs_init()) == NULL) goto err; /* * Grab rootfs, we'll work backwards from there if an optional BE root * has not been passed in. */ if (be_locate_rootfs(lbh) != 0) { if (root == NULL) goto err; *lbh->rootfs = '\0'; } if (root == NULL) { /* Strip off the final slash from rootfs to get the be root */ strlcpy(lbh->root, lbh->rootfs, sizeof(lbh->root)); pos = strrchr(lbh->root, '/'); if (pos == NULL) goto err; *pos = '\0'; } else strlcpy(lbh->root, root, sizeof(lbh->root)); if ((pos = strchr(lbh->root, '/')) == NULL) goto err; pnamelen = pos - lbh->root; poolname = malloc(pnamelen + 1); if (poolname == NULL) goto err; strlcpy(poolname, lbh->root, pnamelen + 1); if ((lbh->active_phandle = zpool_open(lbh->lzh, poolname)) == NULL) goto err; free(poolname); poolname = NULL; if (zpool_get_prop(lbh->active_phandle, ZPOOL_PROP_BOOTFS, lbh->bootfs, sizeof(lbh->bootfs), NULL, true) != 0) goto err; + if (zpool_get_prop(lbh->active_phandle, ZPOOL_PROP_ALTROOT, + altroot, sizeof(altroot), NULL, true) == 0 && + strcmp(altroot, "-") != 0) + lbh->altroot_len = strlen(altroot); + return (lbh); err: if (lbh != NULL) { if (lbh->active_phandle != NULL) zpool_close(lbh->active_phandle); if (lbh->lzh != NULL) libzfs_fini(lbh->lzh); free(lbh); } free(poolname); return (NULL); } /* * Free memory allocated by libbe_init() */ void libbe_close(libbe_handle_t *lbh) { if (lbh->active_phandle != NULL) zpool_close(lbh->active_phandle); libzfs_fini(lbh->lzh); free(lbh); } /* * Proxy through to libzfs for the moment. */ void be_nicenum(uint64_t num, char *buf, size_t buflen) { zfs_nicenum(num, buf, buflen); } static int be_destroy_cb(zfs_handle_t *zfs_hdl, void *data) { int err; if ((err = zfs_iter_children(zfs_hdl, be_destroy_cb, data)) != 0) return (err); if ((err = zfs_destroy(zfs_hdl, false)) != 0) return (err); return (0); } /* * Destroy the boot environment or snapshot specified by the name * parameter. Options are or'd together with the possible values: * BE_DESTROY_FORCE : forces operation on mounted datasets */ int be_destroy(libbe_handle_t *lbh, const char *name, int options) { zfs_handle_t *fs; char path[BE_MAXPATHLEN]; char *p; int err, force, mounted; p = path; force = options & BE_DESTROY_FORCE; be_root_concat(lbh, name, path); if (strchr(name, '@') == NULL) { if (!zfs_dataset_exists(lbh->lzh, path, ZFS_TYPE_FILESYSTEM)) return (set_error(lbh, BE_ERR_NOENT)); if (strcmp(path, lbh->rootfs) == 0 || strcmp(path, lbh->bootfs) == 0) return (set_error(lbh, BE_ERR_DESTROYACT)); fs = zfs_open(lbh->lzh, p, ZFS_TYPE_FILESYSTEM); } else { if (!zfs_dataset_exists(lbh->lzh, path, ZFS_TYPE_SNAPSHOT)) return (set_error(lbh, BE_ERR_NOENT)); fs = zfs_open(lbh->lzh, p, ZFS_TYPE_SNAPSHOT); } if (fs == NULL) return (set_error(lbh, BE_ERR_ZFSOPEN)); /* Check if mounted, unmount if force is specified */ if ((mounted = zfs_is_mounted(fs, NULL)) != 0) { if (force) zfs_unmount(fs, NULL, 0); else return (set_error(lbh, BE_ERR_DESTROYMNT)); } if ((err = be_destroy_cb(fs, NULL)) != 0) { /* Children are still present or the mount is referenced */ if (err == EBUSY) return (set_error(lbh, BE_ERR_DESTROYMNT)); return (set_error(lbh, BE_ERR_UNKNOWN)); } return (0); } int be_snapshot(libbe_handle_t *lbh, const char *source, const char *snap_name, bool recursive, char *result) { char buf[BE_MAXPATHLEN]; time_t rawtime; int len, err; be_root_concat(lbh, source, buf); if ((err = be_exists(lbh, buf)) != 0) return (set_error(lbh, err)); if (snap_name != NULL) { if (strlcat(buf, "@", sizeof(buf)) >= sizeof(buf)) return (set_error(lbh, BE_ERR_INVALIDNAME)); if (strlcat(buf, snap_name, sizeof(buf)) >= sizeof(buf)) return (set_error(lbh, BE_ERR_INVALIDNAME)); if (result != NULL) snprintf(result, BE_MAXPATHLEN, "%s@%s", source, snap_name); } else { time(&rawtime); len = strlen(buf); strftime(buf + len, sizeof(buf) - len, "@%F-%T", localtime(&rawtime)); if (result != NULL && strlcpy(result, strrchr(buf, '/') + 1, sizeof(buf)) >= sizeof(buf)) return (set_error(lbh, BE_ERR_INVALIDNAME)); } if ((err = zfs_snapshot(lbh->lzh, buf, recursive, NULL)) != 0) { switch (err) { case EZFS_INVALIDNAME: return (set_error(lbh, BE_ERR_INVALIDNAME)); default: /* * The other errors that zfs_ioc_snapshot might return * shouldn't happen if we've set things up properly, so * we'll gloss over them and call it UNKNOWN as it will * require further triage. */ if (errno == ENOTSUP) return (set_error(lbh, BE_ERR_NOPOOL)); return (set_error(lbh, BE_ERR_UNKNOWN)); } } return (BE_ERR_SUCCESS); } /* * Create the boot environment specified by the name parameter */ int be_create(libbe_handle_t *lbh, const char *name) { int err; err = be_create_from_existing(lbh, name, be_active_path(lbh)); return (set_error(lbh, err)); } - static int be_deep_clone_prop(int prop, void *cb) { int err; struct libbe_dccb *dccb; zprop_source_t src; char pval[BE_MAXPATHLEN]; char source[BE_MAXPATHLEN]; char *val; dccb = cb; /* Skip some properties we don't want to touch */ if (prop == ZFS_PROP_CANMOUNT) return (ZPROP_CONT); /* Don't copy readonly properties */ if (zfs_prop_readonly(prop)) return (ZPROP_CONT); if ((err = zfs_prop_get(dccb->zhp, prop, (char *)&pval, sizeof(pval), &src, (char *)&source, sizeof(source), false))) /* Just continue if we fail to read a property */ return (ZPROP_CONT); /* Only copy locally defined properties */ if (src != ZPROP_SRC_LOCAL) return (ZPROP_CONT); /* Augment mountpoint with altroot, if needed */ val = pval; - if (prop == ZFS_PROP_MOUNTPOINT && *dccb->altroot != '\0') { - if (pval[strlen(dccb->altroot)] == '\0') - strlcpy(pval, "/", sizeof(pval)); - else - val = pval + strlen(dccb->altroot); - } + if (prop == ZFS_PROP_MOUNTPOINT) + val = be_mountpoint_augmented(dccb->lbh, val); + nvlist_add_string(dccb->props, zfs_prop_to_name(prop), val); return (ZPROP_CONT); } static int be_deep_clone(zfs_handle_t *ds, void *data) { int err; char be_path[BE_MAXPATHLEN]; char snap_path[BE_MAXPATHLEN]; const char *dspath; char *dsname; zfs_handle_t *snap_hdl; nvlist_t *props; struct libbe_deep_clone *isdc, sdc; struct libbe_dccb dccb; isdc = (struct libbe_deep_clone *)data; dspath = zfs_get_name(ds); if ((dsname = strrchr(dspath, '/')) == NULL) return (BE_ERR_UNKNOWN); dsname++; if (isdc->bename == NULL) snprintf(be_path, sizeof(be_path), "%s/%s", isdc->be_root, dsname); else snprintf(be_path, sizeof(be_path), "%s/%s", isdc->be_root, isdc->bename); snprintf(snap_path, sizeof(snap_path), "%s@%s", dspath, isdc->snapname); if (zfs_dataset_exists(isdc->lbh->lzh, be_path, ZFS_TYPE_DATASET)) return (set_error(isdc->lbh, BE_ERR_EXISTS)); if ((snap_hdl = zfs_open(isdc->lbh->lzh, snap_path, ZFS_TYPE_SNAPSHOT)) == NULL) return (set_error(isdc->lbh, BE_ERR_ZFSOPEN)); nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP); nvlist_add_string(props, "canmount", "noauto"); + dccb.lbh = isdc->lbh; dccb.zhp = ds; dccb.props = props; - if (zpool_get_prop(isdc->lbh->active_phandle, ZPOOL_PROP_ALTROOT, - dccb.altroot, sizeof(dccb.altroot), NULL, true) != 0 || - strcmp(dccb.altroot, "-") == 0) - *dccb.altroot = '\0'; if (zprop_iter(be_deep_clone_prop, &dccb, B_FALSE, B_FALSE, ZFS_TYPE_FILESYSTEM) == ZPROP_INVAL) return (-1); if ((err = zfs_clone(snap_hdl, be_path, props)) != 0) err = BE_ERR_ZFSCLONE; nvlist_free(props); zfs_close(snap_hdl); /* Failed to clone */ if (err != BE_ERR_SUCCESS) return (set_error(isdc->lbh, err)); sdc.lbh = isdc->lbh; sdc.bename = NULL; sdc.snapname = isdc->snapname; sdc.be_root = (char *)&be_path; err = zfs_iter_filesystems(ds, be_deep_clone, &sdc); return (err); } /* * Create the boot environment from pre-existing snapshot */ int be_create_from_existing_snap(libbe_handle_t *lbh, const char *name, const char *snap) { int err; char be_path[BE_MAXPATHLEN]; char snap_path[BE_MAXPATHLEN]; const char *bename; char *parentname, *snapname; zfs_handle_t *parent_hdl; struct libbe_deep_clone sdc; if ((err = be_validate_name(lbh, name)) != 0) return (set_error(lbh, err)); if ((err = be_root_concat(lbh, snap, snap_path)) != 0) return (set_error(lbh, err)); if ((err = be_validate_snap(lbh, snap_path)) != 0) return (set_error(lbh, err)); if ((err = be_root_concat(lbh, name, be_path)) != 0) return (set_error(lbh, err)); if ((bename = strrchr(name, '/')) == NULL) bename = name; else bename++; if ((parentname = strdup(snap_path)) == NULL) return (set_error(lbh, BE_ERR_UNKNOWN)); snapname = strchr(parentname, '@'); if (snapname == NULL) { free(parentname); return (set_error(lbh, BE_ERR_UNKNOWN)); } *snapname = '\0'; snapname++; sdc.lbh = lbh; sdc.bename = bename; sdc.snapname = snapname; sdc.be_root = lbh->root; parent_hdl = zfs_open(lbh->lzh, parentname, ZFS_TYPE_DATASET); err = be_deep_clone(parent_hdl, &sdc); free(parentname); return (set_error(lbh, err)); } /* * Create a boot environment from an existing boot environment */ int be_create_from_existing(libbe_handle_t *lbh, const char *name, const char *old) { int err; char buf[BE_MAXPATHLEN]; if ((err = be_snapshot(lbh, old, NULL, true, (char *)&buf)) != 0) return (set_error(lbh, err)); err = be_create_from_existing_snap(lbh, name, (char *)buf); return (set_error(lbh, err)); } /* * Verifies that a snapshot has a valid name, exists, and has a mountpoint of * '/'. Returns BE_ERR_SUCCESS (0), upon success, or the relevant BE_ERR_* upon * failure. Does not set the internal library error state. */ int be_validate_snap(libbe_handle_t *lbh, const char *snap_name) { if (strlen(snap_name) >= BE_MAXPATHLEN) return (BE_ERR_PATHLEN); if (!zfs_dataset_exists(lbh->lzh, snap_name, ZFS_TYPE_SNAPSHOT)) return (BE_ERR_NOENT); return (BE_ERR_SUCCESS); } /* * Idempotently appends the name argument to the root boot environment path * and copies the resulting string into the result buffer (which is assumed * to be at least BE_MAXPATHLEN characters long. Returns BE_ERR_SUCCESS upon * success, BE_ERR_PATHLEN if the resulting path is longer than BE_MAXPATHLEN, * or BE_ERR_INVALIDNAME if the name is a path that does not begin with * zfs_be_root. Does not set internal library error state. */ int be_root_concat(libbe_handle_t *lbh, const char *name, char *result) { size_t name_len, root_len; name_len = strlen(name); root_len = strlen(lbh->root); /* Act idempotently; return be name if it is already a full path */ if (strrchr(name, '/') != NULL) { if (strstr(name, lbh->root) != name) return (BE_ERR_INVALIDNAME); if (name_len >= BE_MAXPATHLEN) return (BE_ERR_PATHLEN); strlcpy(result, name, BE_MAXPATHLEN); return (BE_ERR_SUCCESS); } else if (name_len + root_len + 1 < BE_MAXPATHLEN) { snprintf(result, BE_MAXPATHLEN, "%s/%s", lbh->root, name); return (BE_ERR_SUCCESS); } return (BE_ERR_PATHLEN); } /* * Verifies the validity of a boot environment name (A-Za-z0-9-_.). Returns * BE_ERR_SUCCESS (0) if name is valid, otherwise returns BE_ERR_INVALIDNAME * or BE_ERR_PATHLEN. * Does not set internal library error state. */ int be_validate_name(libbe_handle_t *lbh, const char *name) { for (int i = 0; *name; i++) { char c = *(name++); if (isalnum(c) || (c == '-') || (c == '_') || (c == '.')) continue; return (BE_ERR_INVALIDNAME); } /* * Impose the additional restriction that the entire dataset name must * not exceed the maximum length of a dataset, i.e. MAXNAMELEN. */ if (strlen(lbh->root) + 1 + strlen(name) > MAXNAMELEN) return (BE_ERR_PATHLEN); return (BE_ERR_SUCCESS); } /* * usage */ int be_rename(libbe_handle_t *lbh, const char *old, const char *new) { char full_old[BE_MAXPATHLEN]; char full_new[BE_MAXPATHLEN]; zfs_handle_t *zfs_hdl; int err; /* * be_validate_name is documented not to set error state, so we should * do so here. */ if ((err = be_validate_name(lbh, new)) != 0) return (set_error(lbh, err)); if ((err = be_root_concat(lbh, old, full_old)) != 0) return (set_error(lbh, err)); if ((err = be_root_concat(lbh, new, full_new)) != 0) return (set_error(lbh, err)); if (!zfs_dataset_exists(lbh->lzh, full_old, ZFS_TYPE_DATASET)) return (set_error(lbh, BE_ERR_NOENT)); if (zfs_dataset_exists(lbh->lzh, full_new, ZFS_TYPE_DATASET)) return (set_error(lbh, BE_ERR_EXISTS)); if ((zfs_hdl = zfs_open(lbh->lzh, full_old, ZFS_TYPE_FILESYSTEM)) == NULL) return (set_error(lbh, BE_ERR_ZFSOPEN)); /* recurse, nounmount, forceunmount */ struct renameflags flags = { .nounmount = 1, }; err = zfs_rename(zfs_hdl, NULL, full_new, flags); zfs_close(zfs_hdl); if (err != 0) return (set_error(lbh, BE_ERR_UNKNOWN)); return (0); } int be_export(libbe_handle_t *lbh, const char *bootenv, int fd) { char snap_name[BE_MAXPATHLEN]; char buf[BE_MAXPATHLEN]; zfs_handle_t *zfs; int err; if ((err = be_snapshot(lbh, bootenv, NULL, true, snap_name)) != 0) /* Use the error set by be_snapshot */ return (err); be_root_concat(lbh, snap_name, buf); if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_DATASET)) == NULL) return (set_error(lbh, BE_ERR_ZFSOPEN)); err = zfs_send_one(zfs, NULL, fd, 0); zfs_close(zfs); return (err); } int be_import(libbe_handle_t *lbh, const char *bootenv, int fd) { char buf[BE_MAXPATHLEN]; time_t rawtime; nvlist_t *props; zfs_handle_t *zfs; int err, len; char nbuf[24]; /* * We don't need this to be incredibly random, just unique enough that * it won't conflict with an existing dataset name. Chopping time * down to 32 bits is probably good enough for this. */ snprintf(nbuf, 24, "tmp%u", (uint32_t)(time(NULL) & 0xFFFFFFFF)); if ((err = be_root_concat(lbh, nbuf, buf)) != 0) /* * Technically this is our problem, but we try to use short * enough names that we won't run into problems except in * worst-case BE root approaching MAXPATHLEN. */ return (set_error(lbh, BE_ERR_PATHLEN)); time(&rawtime); len = strlen(buf); strftime(buf + len, sizeof(buf) - len, "@%F-%T", localtime(&rawtime)); if ((err = lzc_receive(buf, NULL, NULL, false, fd)) != 0) { switch (err) { case EINVAL: return (set_error(lbh, BE_ERR_NOORIGIN)); case ENOENT: return (set_error(lbh, BE_ERR_NOENT)); case EIO: return (set_error(lbh, BE_ERR_IO)); default: return (set_error(lbh, BE_ERR_UNKNOWN)); } } if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_SNAPSHOT)) == NULL) return (set_error(lbh, BE_ERR_ZFSOPEN)); nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP); nvlist_add_string(props, "canmount", "noauto"); nvlist_add_string(props, "mountpoint", "/"); be_root_concat(lbh, bootenv, buf); err = zfs_clone(zfs, buf, props); zfs_close(zfs); nvlist_free(props); if (err != 0) return (set_error(lbh, BE_ERR_UNKNOWN)); /* * Finally, we open up the dataset we just cloned the snapshot so that * we may promote it. This is necessary in order to clean up the ghost * snapshot that doesn't need to be seen after the operation is * complete. */ if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_DATASET)) == NULL) return (set_error(lbh, BE_ERR_ZFSOPEN)); err = zfs_promote(zfs); zfs_close(zfs); if (err != 0) return (set_error(lbh, BE_ERR_UNKNOWN)); /* Clean up the temporary snapshot */ return (be_destroy(lbh, nbuf, 0)); } #if SOON static int be_create_child_noent(libbe_handle_t *lbh, const char *active, const char *child_path) { nvlist_t *props; zfs_handle_t *zfs; int err; nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP); nvlist_add_string(props, "canmount", "noauto"); nvlist_add_string(props, "mountpoint", child_path); /* Create */ if ((err = zfs_create(lbh->lzh, active, ZFS_TYPE_DATASET, props)) != 0) { switch (err) { case EZFS_EXISTS: return (set_error(lbh, BE_ERR_EXISTS)); case EZFS_NOENT: return (set_error(lbh, BE_ERR_NOENT)); case EZFS_BADTYPE: case EZFS_BADVERSION: return (set_error(lbh, BE_ERR_NOPOOL)); case EZFS_BADPROP: default: /* We set something up wrong, probably... */ return (set_error(lbh, BE_ERR_UNKNOWN)); } } nvlist_free(props); if ((zfs = zfs_open(lbh->lzh, active, ZFS_TYPE_DATASET)) == NULL) return (set_error(lbh, BE_ERR_ZFSOPEN)); /* Set props */ if ((err = zfs_prop_set(zfs, "canmount", "noauto")) != 0) { zfs_close(zfs); /* * Similar to other cases, this shouldn't fail unless we've * done something wrong. This is a new dataset that shouldn't * have been mounted anywhere between creation and now. */ if (err == EZFS_NOMEM) return (set_error(lbh, BE_ERR_NOMEM)); return (set_error(lbh, BE_ERR_UNKNOWN)); } zfs_close(zfs); return (BE_ERR_SUCCESS); } static int be_create_child_cloned(libbe_handle_t *lbh, const char *active) { char buf[BE_MAXPATHLEN], tmp[BE_MAXPATHLEN];; zfs_handle_t *zfs; int err; /* XXX TODO ? */ /* * Establish if the existing path is a zfs dataset or just * the subdirectory of one */ strlcpy(tmp, "tmp/be_snap.XXXXX", sizeof(tmp)); if (mktemp(tmp) == NULL) return (set_error(lbh, BE_ERR_UNKNOWN)); be_root_concat(lbh, tmp, buf); printf("Here %s?\n", buf); if ((err = zfs_snapshot(lbh->lzh, buf, false, NULL)) != 0) { switch (err) { case EZFS_INVALIDNAME: return (set_error(lbh, BE_ERR_INVALIDNAME)); default: /* * The other errors that zfs_ioc_snapshot might return * shouldn't happen if we've set things up properly, so * we'll gloss over them and call it UNKNOWN as it will * require further triage. */ if (errno == ENOTSUP) return (set_error(lbh, BE_ERR_NOPOOL)); return (set_error(lbh, BE_ERR_UNKNOWN)); } } /* Clone */ if ((zfs = zfs_open(lbh->lzh, buf, ZFS_TYPE_SNAPSHOT)) == NULL) return (BE_ERR_ZFSOPEN); if ((err = zfs_clone(zfs, active, NULL)) != 0) /* XXX TODO correct error */ return (set_error(lbh, BE_ERR_UNKNOWN)); /* set props */ zfs_close(zfs); return (BE_ERR_SUCCESS); } int be_add_child(libbe_handle_t *lbh, const char *child_path, bool cp_if_exists) { struct stat sb; char active[BE_MAXPATHLEN], buf[BE_MAXPATHLEN]; nvlist_t *props; const char *s; /* Require absolute paths */ if (*child_path != '/') return (set_error(lbh, BE_ERR_BADPATH)); strlcpy(active, be_active_path(lbh), BE_MAXPATHLEN); strcpy(buf, active); /* Create non-mountable parent dataset(s) */ s = child_path; for (char *p; (p = strchr(s+1, '/')) != NULL; s = p) { size_t len = p - s; strncat(buf, s, len); nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP); nvlist_add_string(props, "canmount", "off"); nvlist_add_string(props, "mountpoint", "none"); zfs_create(lbh->lzh, buf, ZFS_TYPE_DATASET, props); nvlist_free(props); } /* Path does not exist as a descendent of / yet */ if (strlcat(active, child_path, BE_MAXPATHLEN) >= BE_MAXPATHLEN) return (set_error(lbh, BE_ERR_PATHLEN)); if (stat(child_path, &sb) != 0) { /* Verify that error is ENOENT */ if (errno != ENOENT) return (set_error(lbh, BE_ERR_UNKNOWN)); return (be_create_child_noent(lbh, active, child_path)); } else if (cp_if_exists) /* Path is already a descendent of / and should be copied */ return (be_create_child_cloned(lbh, active)); return (set_error(lbh, BE_ERR_EXISTS)); } #endif /* SOON */ static int be_set_nextboot(libbe_handle_t *lbh, nvlist_t *config, uint64_t pool_guid, const char *zfsdev) { nvlist_t **child; uint64_t vdev_guid; int c, children; if (nvlist_lookup_nvlist_array(config, ZPOOL_CONFIG_CHILDREN, &child, &children) == 0) { for (c = 0; c < children; ++c) if (be_set_nextboot(lbh, child[c], pool_guid, zfsdev) != 0) return (1); return (0); } if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_GUID, &vdev_guid) != 0) { return (1); } if (zpool_nextboot(lbh->lzh, pool_guid, vdev_guid, zfsdev) != 0) { perror("ZFS_IOC_NEXTBOOT failed"); return (1); } return (0); } /* * Deactivate old BE dataset; currently just sets canmount=noauto */ static int be_deactivate(libbe_handle_t *lbh, const char *ds) { zfs_handle_t *zfs; if ((zfs = zfs_open(lbh->lzh, ds, ZFS_TYPE_DATASET)) == NULL) return (1); if (zfs_prop_set(zfs, "canmount", "noauto") != 0) return (1); zfs_close(zfs); return (0); } int be_activate(libbe_handle_t *lbh, const char *bootenv, bool temporary) { char be_path[BE_MAXPATHLEN]; char buf[BE_MAXPATHLEN]; nvlist_t *config, *dsprops, *vdevs; char *origin; uint64_t pool_guid; zfs_handle_t *zhp; int err; be_root_concat(lbh, bootenv, be_path); /* Note: be_exists fails if mountpoint is not / */ if ((err = be_exists(lbh, be_path)) != 0) return (set_error(lbh, err)); if (temporary) { config = zpool_get_config(lbh->active_phandle, NULL); if (config == NULL) /* config should be fetchable... */ return (set_error(lbh, BE_ERR_UNKNOWN)); if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID, &pool_guid) != 0) /* Similarly, it shouldn't be possible */ return (set_error(lbh, BE_ERR_UNKNOWN)); /* Expected format according to zfsbootcfg(8) man */ snprintf(buf, sizeof(buf), "zfs:%s:", be_path); /* We have no config tree */ if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &vdevs) != 0) return (set_error(lbh, BE_ERR_NOPOOL)); return (be_set_nextboot(lbh, vdevs, pool_guid, buf)); } else { if (be_deactivate(lbh, lbh->bootfs) != 0) return (-1); /* Obtain bootenv zpool */ err = zpool_set_prop(lbh->active_phandle, "bootfs", be_path); if (err) return (-1); zhp = zfs_open(lbh->lzh, be_path, ZFS_TYPE_FILESYSTEM); if (zhp == NULL) return (-1); if (be_prop_list_alloc(&dsprops) != 0) return (-1); if (be_get_dataset_props(lbh, be_path, dsprops) != 0) { nvlist_free(dsprops); return (-1); } if (nvlist_lookup_string(dsprops, "origin", &origin) == 0) err = zfs_promote(zhp); nvlist_free(dsprops); zfs_close(zhp); if (err) return (-1); } return (BE_ERR_SUCCESS); } Index: stable/12/lib/libbe/be_access.c =================================================================== --- stable/12/lib/libbe/be_access.c (revision 343240) +++ stable/12/lib/libbe/be_access.c (revision 343241) @@ -1,197 +1,316 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2017 Kyle J. Kneitinger * Copyright (c) 2018 Kyle Evans + * Copyright (c) 2019 Wes Maag * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "be.h" #include "be_impl.h" struct be_mountcheck_info { const char *path; char *name; }; +struct be_mount_info { + libbe_handle_t *lbh; + const char *be; + const char *mountpoint; + int mntflags; + int deepmount; +}; + static int be_mountcheck_cb(zfs_handle_t *zfs_hdl, void *data) { struct be_mountcheck_info *info; char *mountpoint; if (data == NULL) return (1); info = (struct be_mountcheck_info *)data; if (!zfs_is_mounted(zfs_hdl, &mountpoint)) return (0); if (strcmp(mountpoint, info->path) == 0) { info->name = strdup(zfs_get_name(zfs_hdl)); free(mountpoint); return (1); } free(mountpoint); return (0); } /* + * Called from be_mount, uses the given zfs_handle and attempts to + * mount it at the passed mountpoint. If the deepmount flag is set, continue + * calling the function for each child dataset. + */ +static int +be_mount_iter(zfs_handle_t *zfs_hdl, void *data) +{ + int err; + char *mountpoint; + char tmp[BE_MAXPATHLEN], zfs_mnt[BE_MAXPATHLEN]; + struct be_mount_info *info; + + info = (struct be_mount_info *)data; + + if (zfs_is_mounted(zfs_hdl, &mountpoint)) { + free(mountpoint); + return (0); + } + + if (zfs_prop_get_int(zfs_hdl, ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_OFF) + return (0); + + if (zfs_prop_get(zfs_hdl, ZFS_PROP_MOUNTPOINT, zfs_mnt, BE_MAXPATHLEN, + NULL, NULL, 0, 1)) + return (1); + + if (strcmp("none", zfs_mnt) != 0) { + char opt = '\0'; + + mountpoint = be_mountpoint_augmented(info->lbh, zfs_mnt); + + snprintf(tmp, BE_MAXPATHLEN, "%s%s", info->mountpoint, + mountpoint); + + if ((err = zmount(zfs_get_name(zfs_hdl), tmp, info->mntflags, + __DECONST(char *, MNTTYPE_ZFS), NULL, 0, &opt, 1)) != 0) { + switch (errno) { + case ENAMETOOLONG: + return (set_error(info->lbh, BE_ERR_PATHLEN)); + case ELOOP: + case ENOENT: + case ENOTDIR: + return (set_error(info->lbh, BE_ERR_BADPATH)); + case EPERM: + return (set_error(info->lbh, BE_ERR_PERMS)); + case EBUSY: + return (set_error(info->lbh, BE_ERR_PATHBUSY)); + default: + return (set_error(info->lbh, BE_ERR_UNKNOWN)); + } + } + } + + if (!info->deepmount) + return (0); + + return (zfs_iter_filesystems(zfs_hdl, be_mount_iter, info)); +} + + +static int +be_umount_iter(zfs_handle_t *zfs_hdl, void *data) +{ + + int err; + char *mountpoint; + struct be_mount_info *info; + + info = (struct be_mount_info *)data; + + if((err = zfs_iter_filesystems(zfs_hdl, be_umount_iter, info)) != 0) { + return (err); + } + + if (!zfs_is_mounted(zfs_hdl, &mountpoint)) { + return (0); + } + free(mountpoint); + + if (zfs_unmount(zfs_hdl, NULL, info->mntflags) != 0) { + switch (errno) { + case ENAMETOOLONG: + return (set_error(info->lbh, BE_ERR_PATHLEN)); + case ELOOP: + case ENOENT: + case ENOTDIR: + return (set_error(info->lbh, BE_ERR_BADPATH)); + case EPERM: + return (set_error(info->lbh, BE_ERR_PERMS)); + case EBUSY: + return (set_error(info->lbh, BE_ERR_PATHBUSY)); + default: + return (set_error(info->lbh, BE_ERR_UNKNOWN)); + } + } + return (0); +} + +/* * usage */ int be_mounted_at(libbe_handle_t *lbh, const char *path, nvlist_t *details) { char be[BE_MAXPATHLEN]; zfs_handle_t *root_hdl; struct be_mountcheck_info info; prop_data_t propinfo; bzero(&be, BE_MAXPATHLEN); if ((root_hdl = zfs_open(lbh->lzh, lbh->root, ZFS_TYPE_FILESYSTEM)) == NULL) return (BE_ERR_ZFSOPEN); info.path = path; info.name = NULL; zfs_iter_filesystems(root_hdl, be_mountcheck_cb, &info); zfs_close(root_hdl); if (info.name != NULL) { if (details != NULL) { if ((root_hdl = zfs_open(lbh->lzh, lbh->root, ZFS_TYPE_FILESYSTEM)) == NULL) { free(info.name); return (BE_ERR_ZFSOPEN); } propinfo.lbh = lbh; propinfo.list = details; propinfo.single_object = false; prop_list_builder_cb(root_hdl, &propinfo); zfs_close(root_hdl); } free(info.name); return (0); } return (1); } /* * usage */ int be_mount(libbe_handle_t *lbh, char *bootenv, char *mountpoint, int flags, char *result_loc) { char be[BE_MAXPATHLEN]; char mnt_temp[BE_MAXPATHLEN]; - int mntflags; + int mntflags, mntdeep; int err; + struct be_mount_info info; + zfs_handle_t *zhdl; if ((err = be_root_concat(lbh, bootenv, be)) != 0) return (set_error(lbh, err)); if ((err = be_exists(lbh, bootenv)) != 0) return (set_error(lbh, err)); if (is_mounted(lbh->lzh, be, NULL)) return (set_error(lbh, BE_ERR_MOUNTED)); + mntdeep = (flags & BE_MNT_DEEP) ? 1 : 0; mntflags = (flags & BE_MNT_FORCE) ? MNT_FORCE : 0; /* Create mountpoint if it is not specified */ if (mountpoint == NULL) { strlcpy(mnt_temp, "/tmp/be_mount.XXXX", sizeof(mnt_temp)); if (mkdtemp(mnt_temp) == NULL) return (set_error(lbh, BE_ERR_IO)); } - char opt = '\0'; - if ((err = zmount(be, (mountpoint == NULL) ? mnt_temp : mountpoint, - mntflags, __DECONST(char *, MNTTYPE_ZFS), NULL, 0, &opt, 1)) != 0) { - switch (errno) { - case ENAMETOOLONG: - return (set_error(lbh, BE_ERR_PATHLEN)); - case ELOOP: - case ENOENT: - case ENOTDIR: - return (set_error(lbh, BE_ERR_BADPATH)); - case EPERM: - return (set_error(lbh, BE_ERR_PERMS)); - case EBUSY: - return (set_error(lbh, BE_ERR_PATHBUSY)); - default: - return (set_error(lbh, BE_ERR_UNKNOWN)); - } + if ((zhdl = zfs_open(lbh->lzh, be, ZFS_TYPE_FILESYSTEM)) == NULL) + return (set_error(lbh, BE_ERR_ZFSOPEN)); + + info.lbh = lbh; + info.be = be; + info.mountpoint = (mountpoint == NULL) ? mnt_temp : mountpoint; + info.mntflags = mntflags; + info.deepmount = mntdeep; + + if((err = be_mount_iter(zhdl, &info) != 0)) { + zfs_close(zhdl); + return (err); } + zfs_close(zhdl); if (result_loc != NULL) strlcpy(result_loc, mountpoint == NULL ? mnt_temp : mountpoint, BE_MAXPATHLEN); return (BE_ERR_SUCCESS); } - /* * usage */ int be_unmount(libbe_handle_t *lbh, char *bootenv, int flags) { - int err, mntflags; + int err; char be[BE_MAXPATHLEN]; zfs_handle_t *root_hdl; + struct be_mount_info info; if ((err = be_root_concat(lbh, bootenv, be)) != 0) return (set_error(lbh, err)); if ((root_hdl = zfs_open(lbh->lzh, be, ZFS_TYPE_FILESYSTEM)) == NULL) return (set_error(lbh, BE_ERR_ZFSOPEN)); - mntflags = (flags & BE_MNT_FORCE) ? MS_FORCE : 0; + info.lbh = lbh; + info.be = be; + info.mountpoint = NULL; + info.mntflags = (flags & BE_MNT_FORCE) ? MS_FORCE : 0; - if (zfs_unmount(root_hdl, NULL, mntflags) != 0) { + if ((err = be_umount_iter(root_hdl, &info)) != 0) { zfs_close(root_hdl); - switch (errno) { - case ENAMETOOLONG: - return (set_error(lbh, BE_ERR_PATHLEN)); - case ELOOP: - case ENOENT: - case ENOTDIR: - return (set_error(lbh, BE_ERR_BADPATH)); - case EPERM: - return (set_error(lbh, BE_ERR_PERMS)); - case EBUSY: - return (set_error(lbh, BE_ERR_PATHBUSY)); - default: - return (set_error(lbh, BE_ERR_UNKNOWN)); - } + return (err); } - zfs_close(root_hdl); + zfs_close(root_hdl); return (BE_ERR_SUCCESS); +} + +/* + * This function will blow away the input buffer as needed if we're discovered + * to be looking at a root-mount. If the mountpoint is naturally beyond the + * root, however, the buffer may be left intact and a pointer to the section + * past altroot will be returned instead for the caller's perusal. + */ +char * +be_mountpoint_augmented(libbe_handle_t *lbh, char *mountpoint) +{ + + if (lbh->altroot_len == 0) + return (mountpoint); + if (mountpoint == NULL || *mountpoint == '\0') + return (mountpoint); + + if (mountpoint[lbh->altroot_len] == '\0') { + *(mountpoint + 1) = '\0'; + return (mountpoint); + } else + return (mountpoint + lbh->altroot_len); } Index: stable/12/lib/libbe/be_impl.h =================================================================== --- stable/12/lib/libbe/be_impl.h (revision 343240) +++ stable/12/lib/libbe/be_impl.h (revision 343241) @@ -1,73 +1,76 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2017 Kyle J. Kneitinger * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _LIBBE_IMPL_H #define _LIBBE_IMPL_H #include #include "be.h" struct libbe_handle { - libzfs_handle_t *lzh; - zpool_handle_t *active_phandle; char root[BE_MAXPATHLEN]; char rootfs[BE_MAXPATHLEN]; char bootfs[BE_MAXPATHLEN]; + size_t altroot_len; + zpool_handle_t *active_phandle; + libzfs_handle_t *lzh; be_error_t error; bool print_on_err; }; struct libbe_deep_clone { libbe_handle_t *lbh; const char *bename; const char *snapname; const char *be_root; }; struct libbe_dccb { + libbe_handle_t *lbh; zfs_handle_t *zhp; nvlist_t *props; - char altroot[MAXPATHLEN]; }; typedef struct prop_data { nvlist_t *list; libbe_handle_t *lbh; bool single_object; /* list will contain props directly */ } prop_data_t; int prop_list_builder_cb(zfs_handle_t *, void *); int be_proplist_update(prop_data_t *); + +char *be_mountpoint_augmented(libbe_handle_t *lbh, char *mountpoint); /* Clobbers any previous errors */ int set_error(libbe_handle_t *, be_error_t); #endif /* _LIBBE_IMPL_H */ Index: stable/12/sbin/bectl/bectl.c =================================================================== --- stable/12/sbin/bectl/bectl.c (revision 343240) +++ stable/12/sbin/bectl/bectl.c (revision 343241) @@ -1,539 +1,541 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2017 Kyle J. Kneitinger * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bectl.h" static int bectl_cmd_activate(int argc, char *argv[]); static int bectl_cmd_create(int argc, char *argv[]); static int bectl_cmd_destroy(int argc, char *argv[]); static int bectl_cmd_export(int argc, char *argv[]); static int bectl_cmd_import(int argc, char *argv[]); #if SOON static int bectl_cmd_add(int argc, char *argv[]); #endif static int bectl_cmd_mount(int argc, char *argv[]); static int bectl_cmd_rename(int argc, char *argv[]); static int bectl_cmd_unmount(int argc, char *argv[]); libbe_handle_t *be; int usage(bool explicit) { FILE *fp; fp = explicit ? stdout : stderr; fprintf(fp, "%s", "usage:\tbectl {-h | -? | subcommand [args...]}\n" #if SOON "\tbectl add (path)*\n" #endif "\tbectl activate [-t] beName\n" "\tbectl create [-r] [-e {nonActiveBe | beName@snapshot}] beName\n" "\tbectl create [-r] beName@snapshot\n" "\tbectl destroy [-F] {beName | beName@snapshot}\n" "\tbectl export sourceBe\n" "\tbectl import targetBe\n" "\tbectl jail {-b | -U} [{-o key=value | -u key}]... " "{jailID | jailName}\n" "\t bootenv [utility [argument ...]]\n" "\tbectl list [-DHas]\n" "\tbectl mount beName [mountpoint]\n" "\tbectl rename origBeName newBeName\n" "\tbectl {ujail | unjail} {jailID | jailName} bootenv\n" "\tbectl {umount | unmount} [-f] beName\n"); return (explicit ? 0 : EX_USAGE); } /* * Represents a relationship between the command name and the parser action * that handles it. */ struct command_map_entry { const char *command; int (*fn)(int argc, char *argv[]); }; static struct command_map_entry command_map[] = { { "activate", bectl_cmd_activate }, { "create", bectl_cmd_create }, { "destroy", bectl_cmd_destroy }, { "export", bectl_cmd_export }, { "import", bectl_cmd_import }, #if SOON { "add", bectl_cmd_add }, #endif { "jail", bectl_cmd_jail }, { "list", bectl_cmd_list }, { "mount", bectl_cmd_mount }, { "rename", bectl_cmd_rename }, { "unjail", bectl_cmd_unjail }, { "unmount", bectl_cmd_unmount }, }; static int get_cmd_index(const char *cmd, int *idx) { int map_size; map_size = nitems(command_map); for (int i = 0; i < map_size; ++i) { if (strcmp(cmd, command_map[i].command) == 0) { *idx = i; return (0); } } return (1); } static int bectl_cmd_activate(int argc, char *argv[]) { int err, opt; bool temp; temp = false; while ((opt = getopt(argc, argv, "t")) != -1) { switch (opt) { case 't': temp = true; break; default: fprintf(stderr, "bectl activate: unknown option '-%c'\n", optopt); return (usage(false)); } } argc -= optind; argv += optind; if (argc != 1) { fprintf(stderr, "bectl activate: wrong number of arguments\n"); return (usage(false)); } /* activate logic goes here */ if ((err = be_activate(be, argv[0], temp)) != 0) /* XXX TODO: more specific error msg based on err */ printf("did not successfully activate boot environment %s\n", argv[0]); else printf("successfully activated boot environment %s\n", argv[0]); if (temp) printf("for next boot\n"); return (err); } /* * TODO: when only one arg is given, and it contains an "@" the this should * create that snapshot */ static int bectl_cmd_create(int argc, char *argv[]) { char *atpos, *bootenv, *snapname, *source; int err, opt; bool recursive; snapname = NULL; recursive = false; while ((opt = getopt(argc, argv, "e:r")) != -1) { switch (opt) { case 'e': snapname = optarg; break; case 'r': recursive = true; break; default: fprintf(stderr, "bectl create: unknown option '-%c'\n", optopt); return (usage(false)); } } argc -= optind; argv += optind; if (argc != 1) { fprintf(stderr, "bectl create: wrong number of arguments\n"); return (usage(false)); } bootenv = *argv; if ((atpos = strchr(bootenv, '@')) != NULL) { /* * This is the "create a snapshot variant". No new boot * environment is to be created here. */ *atpos++ = '\0'; err = be_snapshot(be, bootenv, atpos, recursive, NULL); } else if (snapname != NULL) { if (strchr(snapname, '@') != NULL) err = be_create_from_existing_snap(be, bootenv, snapname); else err = be_create_from_existing(be, bootenv, snapname); } else { if ((snapname = strchr(bootenv, '@')) != NULL) { *(snapname++) = '\0'; if ((err = be_snapshot(be, be_active_path(be), snapname, true, NULL)) != BE_ERR_SUCCESS) fprintf(stderr, "failed to create snapshot\n"); asprintf(&source, "%s@%s", be_active_path(be), snapname); err = be_create_from_existing_snap(be, bootenv, source); return (err); } else err = be_create(be, bootenv); } switch (err) { case BE_ERR_SUCCESS: break; default: if (atpos != NULL) fprintf(stderr, "failed to create a snapshot '%s' of '%s'\n", atpos, bootenv); else if (snapname == NULL) fprintf(stderr, "failed to create bootenv %s\n", bootenv); else fprintf(stderr, "failed to create bootenv %s from snapshot %s\n", bootenv, snapname); } return (err); } static int bectl_cmd_export(int argc, char *argv[]) { char *bootenv; if (argc == 1) { fprintf(stderr, "bectl export: missing boot environment name\n"); return (usage(false)); } if (argc > 2) { fprintf(stderr, "bectl export: extra arguments provided\n"); return (usage(false)); } bootenv = argv[1]; if (isatty(STDOUT_FILENO)) { fprintf(stderr, "bectl export: must redirect output\n"); return (EX_USAGE); } be_export(be, bootenv, STDOUT_FILENO); return (0); } static int bectl_cmd_import(int argc, char *argv[]) { char *bootenv; int err; if (argc == 1) { fprintf(stderr, "bectl import: missing boot environment name\n"); return (usage(false)); } if (argc > 2) { fprintf(stderr, "bectl import: extra arguments provided\n"); return (usage(false)); } bootenv = argv[1]; if (isatty(STDIN_FILENO)) { fprintf(stderr, "bectl import: input can not be from terminal\n"); return (EX_USAGE); } err = be_import(be, bootenv, STDIN_FILENO); return (err); } #if SOON static int bectl_cmd_add(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "bectl add: must provide at least one path\n"); return (usage(false)); } for (int i = 1; i < argc; ++i) { printf("arg %d: %s\n", i, argv[i]); /* XXX TODO catch err */ be_add_child(be, argv[i], true); } return (0); } #endif static int bectl_cmd_destroy(int argc, char *argv[]) { char *target; int opt, err; bool force; force = false; while ((opt = getopt(argc, argv, "F")) != -1) { switch (opt) { case 'F': force = true; break; default: fprintf(stderr, "bectl destroy: unknown option '-%c'\n", optopt); return (usage(false)); } } argc -= optind; argv += optind; if (argc != 1) { fprintf(stderr, "bectl destroy: wrong number of arguments\n"); return (usage(false)); } target = argv[0]; err = be_destroy(be, target, force); return (err); } static int bectl_cmd_mount(int argc, char *argv[]) { char result_loc[BE_MAXPATHLEN]; char *bootenv, *mountpoint; - int err; + int err, mntflags; + /* XXX TODO: Allow shallow */ + mntflags = BE_MNT_DEEP; if (argc < 2) { fprintf(stderr, "bectl mount: missing argument(s)\n"); return (usage(false)); } if (argc > 3) { fprintf(stderr, "bectl mount: too many arguments\n"); return (usage(false)); } bootenv = argv[1]; mountpoint = ((argc == 3) ? argv[2] : NULL); - err = be_mount(be, bootenv, mountpoint, 0, result_loc); + err = be_mount(be, bootenv, mountpoint, mntflags, result_loc); switch (err) { case BE_ERR_SUCCESS: printf("successfully mounted %s at %s\n", bootenv, result_loc); break; default: fprintf(stderr, (argc == 3) ? "failed to mount bootenv %s at %s\n" : "failed to mount bootenv %s at temporary path %s\n", bootenv, mountpoint); } return (err); } static int bectl_cmd_rename(int argc, char *argv[]) { char *dest, *src; int err; if (argc < 3) { fprintf(stderr, "bectl rename: missing argument\n"); return (usage(false)); } if (argc > 3) { fprintf(stderr, "bectl rename: too many arguments\n"); return (usage(false)); } src = argv[1]; dest = argv[2]; err = be_rename(be, src, dest); switch (err) { case BE_ERR_SUCCESS: break; default: fprintf(stderr, "failed to rename bootenv %s to %s\n", src, dest); } return (0); } static int bectl_cmd_unmount(int argc, char *argv[]) { char *bootenv, *cmd; int err, flags, opt; /* Store alias used */ cmd = argv[0]; flags = 0; while ((opt = getopt(argc, argv, "f")) != -1) { switch (opt) { case 'f': flags |= BE_MNT_FORCE; break; default: fprintf(stderr, "bectl %s: unknown option '-%c'\n", cmd, optopt); return (usage(false)); } } argc -= optind; argv += optind; if (argc != 1) { fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd); return (usage(false)); } bootenv = argv[0]; err = be_unmount(be, bootenv, flags); switch (err) { case BE_ERR_SUCCESS: break; default: fprintf(stderr, "failed to unmount bootenv %s\n", bootenv); } return (err); } int main(int argc, char *argv[]) { const char *command; char *root; int command_index, rc; root = NULL; if (argc < 2) return (usage(false)); if (strcmp(argv[1], "-r") == 0) { if (argc < 4) return (usage(false)); root = strdup(argv[2]); command = argv[3]; argc -= 3; argv += 3; } else { command = argv[1]; argc -= 1; argv += 1; } /* Handle command aliases */ if (strcmp(command, "umount") == 0) command = "unmount"; if (strcmp(command, "ujail") == 0) command = "unjail"; if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0)) return (usage(true)); if (get_cmd_index(command, &command_index)) { fprintf(stderr, "unknown command: %s\n", command); return (usage(false)); } if ((be = libbe_init(root)) == NULL) return (-1); libbe_print_on_error(be, true); rc = command_map[command_index].fn(argc, argv); libbe_close(be); return (rc); } Index: stable/12/sbin/bectl/bectl_jail.c =================================================================== --- stable/12/sbin/bectl/bectl_jail.c (revision 343240) +++ stable/12/sbin/bectl/bectl_jail.c (revision 343241) @@ -1,421 +1,423 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2018 Kyle Evans * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include "bectl.h" static void jailparam_grow(void); static void jailparam_add(const char *name, const char *val); static int jailparam_del(const char *name); static bool jailparam_addarg(char *arg); static int jailparam_delarg(char *arg); static int bectl_search_jail_paths(const char *mnt); static int bectl_locate_jail(const char *ident); /* We'll start with 8 parameters initially and grow as needed. */ #define INIT_PARAMCOUNT 8 static struct jailparam *jp; static int jpcnt; static int jpused; static char mnt_loc[BE_MAXPATHLEN]; static void jailparam_grow(void) { jpcnt *= 2; jp = realloc(jp, jpcnt * sizeof(*jp)); if (jp == NULL) err(2, "realloc"); } static void jailparam_add(const char *name, const char *val) { int i; for (i = 0; i < jpused; ++i) { if (strcmp(name, jp[i].jp_name) == 0) break; } if (i < jpused) jailparam_free(&jp[i], 1); else if (jpused == jpcnt) /* The next slot isn't allocated yet */ jailparam_grow(); if (jailparam_init(&jp[i], name) != 0) return; if (jailparam_import(&jp[i], val) != 0) return; ++jpused; } static int jailparam_del(const char *name) { int i; char *val; for (i = 0; i < jpused; ++i) { if (strcmp(name, jp[i].jp_name) == 0) break; } if (i == jpused) return (ENOENT); for (; i < jpused - 1; ++i) { val = jailparam_export(&jp[i + 1]); jailparam_free(&jp[i], 1); /* * Given the context, the following will really only fail if * they can't allocate the copy of the name or value. */ if (jailparam_init(&jp[i], jp[i + 1].jp_name) != 0) { free(val); return (ENOMEM); } if (jailparam_import(&jp[i], val) != 0) { jailparam_free(&jp[i], 1); free(val); return (ENOMEM); } free(val); } jailparam_free(&jp[i], 1); --jpused; return (0); } static bool jailparam_addarg(char *arg) { char *name, *val; if (arg == NULL) return (false); name = arg; if ((val = strchr(arg, '=')) == NULL) { fprintf(stderr, "bectl jail: malformed jail option '%s'\n", arg); return (false); } *val++ = '\0'; if (strcmp(name, "path") == 0) { if (strlen(val) >= BE_MAXPATHLEN) { fprintf(stderr, "bectl jail: skipping too long path assignment '%s' (max length = %d)\n", val, BE_MAXPATHLEN); return (false); } strlcpy(mnt_loc, val, sizeof(mnt_loc)); } jailparam_add(name, val); return (true); } static int jailparam_delarg(char *arg) { char *name, *val; if (arg == NULL) return (EINVAL); name = arg; if ((val = strchr(name, '=')) != NULL) *val++ = '\0'; if (strcmp(name, "path") == 0) *mnt_loc = '\0'; return (jailparam_del(name)); } int bectl_cmd_jail(int argc, char *argv[]) { char *bootenv, *mountpoint; - int jid, opt, ret; + int jid, mntflags, opt, ret; bool default_hostname, interactive, unjail; pid_t pid; + /* XXX TODO: Allow shallow */ + mntflags = BE_MNT_DEEP; default_hostname = interactive = unjail = true; jpcnt = INIT_PARAMCOUNT; jp = malloc(jpcnt * sizeof(*jp)); if (jp == NULL) err(2, "malloc"); jailparam_add("persist", "true"); jailparam_add("allow.mount", "true"); jailparam_add("allow.mount.devfs", "true"); jailparam_add("enforce_statfs", "1"); while ((opt = getopt(argc, argv, "bo:Uu:")) != -1) { switch (opt) { case 'b': interactive = false; break; case 'o': if (jailparam_addarg(optarg)) { /* * optarg has been modified to null terminate * at the assignment operator. */ if (strcmp(optarg, "host.hostname") == 0) default_hostname = false; } break; case 'U': unjail = false; break; case 'u': if ((ret = jailparam_delarg(optarg)) == 0) { if (strcmp(optarg, "host.hostname") == 0) default_hostname = true; } else if (ret != ENOENT) { fprintf(stderr, "bectl jail: error unsetting \"%s\"\n", optarg); return (ret); } break; default: fprintf(stderr, "bectl jail: unknown option '-%c'\n", optopt); return (usage(false)); } } argc -= optind; argv += optind; /* struct jail be_jail = { 0 }; */ if (argc < 1) { fprintf(stderr, "bectl jail: missing boot environment name\n"); return (usage(false)); } bootenv = argv[0]; /* * XXX TODO: if its already mounted, perhaps there should be a flag to * indicate its okay to proceed?? */ if (*mnt_loc == '\0') mountpoint = NULL; else mountpoint = mnt_loc; - if (be_mount(be, bootenv, mountpoint, 0, mnt_loc) != BE_ERR_SUCCESS) { + if (be_mount(be, bootenv, mountpoint, mntflags, mnt_loc) != BE_ERR_SUCCESS) { fprintf(stderr, "could not mount bootenv\n"); return (1); } if (default_hostname) jailparam_add("host.hostname", bootenv); /* * This is our indicator that path was not set by the user, so we'll use * the path that libbe generated for us. */ if (mountpoint == NULL) jailparam_add("path", mnt_loc); /* Create the jail for now, attach later as-needed */ jid = jailparam_set(jp, jpused, JAIL_CREATE); if (jid == -1) { fprintf(stderr, "unable to create jail. error: %d\n", errno); return (1); } jailparam_free(jp, jpused); free(jp); /* We're not interactive, nothing more to do here. */ if (!interactive) return (0); pid = fork(); switch(pid) { case -1: perror("fork"); return (1); case 0: jail_attach(jid); /* We're attached within the jail... good bye! */ chdir("/"); if (argc > 1) execve(argv[1], &argv[1], NULL); else execl("/bin/sh", "/bin/sh", NULL); fprintf(stderr, "bectl jail: failed to execute %s\n", (argc > 1 ? argv[1] : "/bin/sh")); _exit(1); default: /* Wait for the child to get back, see if we need to unjail */ waitpid(pid, NULL, 0); } if (unjail) { jail_remove(jid); - unmount(mnt_loc, 0); + be_unmount(be, bootenv, 0); } return (0); } static int bectl_search_jail_paths(const char *mnt) { int jid; char lastjid[16]; char jailpath[MAXPATHLEN]; /* jail_getv expects name/value strings */ snprintf(lastjid, sizeof(lastjid), "%d", 0); jid = 0; while ((jid = jail_getv(0, "lastjid", lastjid, "path", &jailpath, NULL)) != -1) { /* the jail we've been looking for */ if (strcmp(jailpath, mnt) == 0) return (jid); /* update lastjid and keep on looking */ snprintf(lastjid, sizeof(lastjid), "%d", jid); } return (-1); } /* * Locate a jail based on an arbitrary identifier. This may be either a name, * a jid, or a BE name. Returns the jid or -1 on failure. */ static int bectl_locate_jail(const char *ident) { nvlist_t *belist, *props; char *mnt; int jid; /* Try the easy-match first */ jid = jail_getid(ident); if (jid != -1) return (jid); /* Attempt to try it as a BE name, first */ if (be_prop_list_alloc(&belist) != 0) return (-1); if (be_get_bootenv_props(be, belist) != 0) return (-1); if (nvlist_lookup_nvlist(belist, ident, &props) == 0) { /* path where a boot environment is mounted */ if (nvlist_lookup_string(props, "mounted", &mnt) == 0) { /* looking for a jail that matches our bootenv path */ jid = bectl_search_jail_paths(mnt); be_prop_list_free(belist); return (jid); } be_prop_list_free(belist); } return (-1); } int bectl_cmd_unjail(int argc, char *argv[]) { char path[MAXPATHLEN]; char *cmd, *name, *target; int jid; /* Store alias used */ cmd = argv[0]; if (argc != 2) { fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd); return (usage(false)); } target = argv[1]; /* Locate the jail */ if ((jid = bectl_locate_jail(target)) == -1) { fprintf(stderr, "bectl %s: failed to locate BE by '%s'\n", cmd, target); return (1); } bzero(&path, MAXPATHLEN); name = jail_getname(jid); if (jail_getv(0, "name", name, "path", path, NULL) != jid) { free(name); fprintf(stderr, "bectl %s: failed to get path for jail requested by '%s'\n", cmd, target); return (1); } free(name); if (be_mounted_at(be, path, NULL) != 0) { fprintf(stderr, "bectl %s: jail requested by '%s' not a BE\n", cmd, target); return (1); } jail_remove(jid); - unmount(path, 0); + be_unmount(be, target, 0); return (0); } Index: stable/12/sbin/bectl/tests/bectl_test.sh =================================================================== --- stable/12/sbin/bectl/tests/bectl_test.sh (revision 343240) +++ stable/12/sbin/bectl/tests/bectl_test.sh (revision 343241) @@ -1,328 +1,342 @@ # # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # # Copyright (c) 2018 Kyle Evans # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # $FreeBSD$ # Establishes a bectl_create zpool that can be used for some light testing; contains # a 'default' BE and not much else. bectl_create_setup() { zpool=$1 disk=$2 mnt=$3 kldload -n -q zfs || atf_skip "ZFS module not loaded on the current system" atf_check mkdir -p ${mnt} atf_check truncate -s 1G ${disk} atf_check zpool create -o altroot=${mnt} ${zpool} ${disk} atf_check zfs create -o mountpoint=none ${zpool}/ROOT atf_check zfs create -o mountpoint=/ -o canmount=noauto \ ${zpool}/ROOT/default } +bectl_create_deep_setup() +{ + zpool=$1 + disk=$2 + mnt=$3 + bectl_create_setup ${zpool} ${disk} ${mnt} + atf_check mkdir -p ${root} + atf_check -o ignore bectl -r ${zpool}/ROOT mount default ${root} + atf_check mkdir -p ${root}/usr + atf_check zfs create -o mountpoint=/usr -o canmount=noauto \ + ${zpool}/ROOT/default/usr + atf_check -o ignore bectl -r ${zpool}/ROOT umount default +} + bectl_cleanup() { zpool=$1 if zpool get health ${zpool} >/dev/null 2>&1; then zpool destroy ${zpool} fi } atf_test_case bectl_create cleanup bectl_create_head() { atf_set "descr" "Check the various forms of bectl create" atf_set "require.user" root } bectl_create_body() { cwd=$(realpath .) zpool=bectl_test disk=${cwd}/disk.img mount=${cwd}/mnt bectl_create_setup ${zpool} ${disk} ${mount} # Test standard creation, creation of a snapshot, and creation from a # snapshot. atf_check bectl -r ${zpool}/ROOT create -e default default2 atf_check bectl -r ${zpool}/ROOT create default2@test_snap atf_check bectl -r ${zpool}/ROOT create -e default2@test_snap default3 } bectl_create_cleanup() { bectl_cleanup bectl_test } atf_test_case bectl_destroy cleanup bectl_destroy_head() { atf_set "descr" "Check bectl destroy" atf_set "require.user" root } bectl_destroy_body() { cwd=$(realpath .) zpool=bectl_test disk=${cwd}/disk.img mount=${cwd}/mnt bectl_create_setup ${zpool} ${disk} ${mount} atf_check bectl -r ${zpool}/ROOT create -e default default2 atf_check -o not-empty zfs get mountpoint ${zpool}/ROOT/default2 atf_check bectl -r ${zpool}/ROOT destroy default2 atf_check -e not-empty -s not-exit:0 zfs get mountpoint ${zpool}/ROOT/default2 } bectl_destroy_cleanup() { bectl_cleanup bectl_test } atf_test_case bectl_export_import cleanup bectl_export_import_head() { atf_set "descr" "Check bectl export and import" atf_set "require.user" root } bectl_export_import_body() { cwd=$(realpath .) zpool=bectl_test disk=${cwd}/disk.img mount=${cwd}/mnt bectl_create_setup ${zpool} ${disk} ${mount} atf_check -o save:exported bectl -r ${zpool}/ROOT export default atf_check -x "bectl -r ${zpool}/ROOT import default2 < exported" atf_check -o not-empty zfs get mountpoint ${zpool}/ROOT/default2 atf_check bectl -r ${zpool}/ROOT destroy default2 atf_check -e not-empty -s not-exit:0 zfs get mountpoint \ ${zpool}/ROOT/default2 } bectl_export_import_cleanup() { bectl_cleanup bectl_test } atf_test_case bectl_list cleanup bectl_list_head() { atf_set "descr" "Check bectl list" atf_set "require.user" root } bectl_list_body() { cwd=$(realpath .) zpool=bectl_test disk=${cwd}/disk.img mount=${cwd}/mnt bectl_create_setup ${zpool} ${disk} ${mount} # Test the list functionality, including that BEs come and go away # as they're created and destroyed. Creation and destruction tests # use the 'zfs' utility to verify that they're actually created, so # these are just light tests that 'list' is picking them up. atf_check -o save:list.out bectl -r ${zpool}/ROOT list atf_check -o not-empty grep 'default' list.out atf_check bectl -r ${zpool}/ROOT create -e default default2 atf_check -o save:list.out bectl -r ${zpool}/ROOT list atf_check -o not-empty grep 'default2' list.out atf_check bectl -r ${zpool}/ROOT destroy default2 atf_check -o save:list.out bectl -r ${zpool}/ROOT list atf_check -s not-exit:0 grep 'default2' list.out # XXX TODO: Formatting checks } bectl_list_cleanup() { bectl_cleanup bectl_test } atf_test_case bectl_mount cleanup bectl_mount_head() { atf_set "descr" "Check bectl mount/unmount" atf_set "require.user" root } bectl_mount_body() { cwd=$(realpath .) zpool=bectl_test disk=${cwd}/disk.img mount=${cwd}/mnt root=${mount}/root - bectl_create_setup ${zpool} ${disk} ${mount} + bectl_create_deep_setup ${zpool} ${disk} ${mount} atf_check mkdir -p ${root} # Test unmount first... atf_check -o not-empty bectl -r ${zpool}/ROOT mount default ${root} atf_check -o not-empty -x "mount | grep '^${zpool}/ROOT/default'" atf_check bectl -r ${zpool}/ROOT unmount default atf_check -s not-exit:0 -x "mount | grep '^${zpool}/ROOT/default'" # Then umount! atf_check -o not-empty bectl -r ${zpool}/ROOT mount default ${root} atf_check -o not-empty -x "mount | grep '^${zpool}/ROOT/default'" atf_check bectl -r ${zpool}/ROOT umount default atf_check -s not-exit:0 -x "mount | grep '^${zpool}/ROOT/default'" } bectl_mount_cleanup() { bectl_cleanup bectl_test } atf_test_case bectl_rename cleanup bectl_rename_head() { atf_set "descr" "Check bectl rename" atf_set "require.user" root } bectl_rename_body() { cwd=$(realpath .) zpool=bectl_test disk=${cwd}/disk.img mount=${cwd}/mnt bectl_create_setup ${zpool} ${disk} ${mount} atf_check bectl -r ${zpool}/ROOT rename default default2 atf_check -o not-empty zfs get mountpoint ${zpool}/ROOT/default2 atf_check -e not-empty -s not-exit:0 zfs get mountpoint \ ${zpool}/ROOT/default } bectl_rename_cleanup() { bectl_cleanup bectl_test } atf_test_case bectl_jail cleanup bectl_jail_head() { atf_set "descr" "Check bectl rename" atf_set "require.user" root } bectl_jail_body() { cwd=$(realpath .) zpool=bectl_test disk=${cwd}/disk.img mount=${cwd}/mnt root=${mount}/root if [ ! -f /rescue/rescue ]; then atf_skip "This test requires a rescue binary" fi - bectl_create_setup ${zpool} ${disk} ${mount} + bectl_create_deep_setup ${zpool} ${disk} ${mount} # Prepare our minimal BE... plop a rescue binary into it atf_check mkdir -p ${root} atf_check -o ignore bectl -r ${zpool}/ROOT mount default ${root} atf_check mkdir -p ${root}/rescue atf_check cp /rescue/rescue ${root}/rescue/rescue atf_check bectl -r ${zpool}/ROOT umount default # Prepare a second boot environment atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT create -e default target # When a jail name is not explicit, it should match the jail id. atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT jail -b -o jid=233637 default atf_check -o inline:"233637\n" -s exit:0 -x "jls -j 233637 name" atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT unjail default # Basic command-mode tests, with and without jail cleanup - atf_check -o inline:"rescue\n" bectl -r ${zpool}/ROOT \ + atf_check -o inline:"rescue\nusr\n" bectl -r ${zpool}/ROOT \ jail default /rescue/rescue ls -1 - atf_check -o inline:"rescue\n" bectl -r ${zpool}/ROOT \ + atf_check -o inline:"rescue\nusr\n" bectl -r ${zpool}/ROOT \ jail -Uo path=${root} default /rescue/rescue ls -1 atf_check [ -f ${root}/rescue/rescue ] atf_check bectl -r ${zpool}/ROOT ujail default # Batch mode tests atf_check bectl -r ${zpool}/ROOT jail -bo path=${root} default atf_check -o not-empty -x "jls | grep -F \"${root}\"" atf_check bectl -r ${zpool}/ROOT ujail default atf_check -s not-exit:0 -x "jls | grep -F \"${root}\"" # 'unjail' naming atf_check bectl -r ${zpool}/ROOT jail -b default atf_check bectl -r ${zpool}/ROOT unjail default atf_check -s not-exit:0 -x "jls | grep -F \"${root}\"" # 'unjail' by BE name. Force bectl to lookup jail id by the BE name. atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT jail -b default atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT jail -b -o name=bectl_test target atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT unjail target atf_check -o empty -s exit:0 bectl -r ${zpool}/ROOT unjail default # cannot unjail an unjailed BE (by either command name) atf_check -e ignore -s not-exit:0 bectl -r ${zpool}/ROOT ujail default atf_check -e ignore -s not-exit:0 bectl -r ${zpool}/ROOT unjail default # set+unset atf_check bectl -r ${zpool}/ROOT jail -b -o path=${root} -u path default # Ensure that it didn't mount at ${root} atf_check -s not-exit:0 -x "mount | grep -F '${root}'" atf_check bectl -r ${zpool}/ROOT ujail default } # If a test has failed, it's possible that the boot environment hasn't # been 'unjail'ed. We want to remove the jail before 'bectl_cleanup' # attempts to destroy the zpool. bectl_jail_cleanup() { for bootenv in "default" "target"; do # mountpoint of the boot environment mountpoint="$(bectl -r bectl_test/ROOT list -H | grep ${bootenv} | awk '{print $3}')" # see if any jail paths match the boot environment mountpoint jailid="$(jls | grep ${mountpoint} | awk '{print $1}')" if [ -z "$jailid" ]; then continue; fi jail -r ${jailid} done; bectl_cleanup bectl_test } atf_init_test_cases() { atf_add_test_case bectl_create atf_add_test_case bectl_destroy atf_add_test_case bectl_export_import atf_add_test_case bectl_list atf_add_test_case bectl_mount atf_add_test_case bectl_rename atf_add_test_case bectl_jail } Index: stable/12 =================================================================== --- stable/12 (revision 343240) +++ stable/12 (revision 343241) Property changes on: stable/12 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r342903,342911