/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2015 by Delphix. All rights reserved. * Copyright (c) 2012, Joyent, Inc. All rights reserved. * Copyright (c) 2016 Martin Matuska. All rights reserved. */ /* * This file contains the functions used to support the ZFS integration * with zones. This includes validation (e.g. zonecfg dataset), cloning, * file system creation and destruction. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "zoneadm.h" libzfs_handle_t *g_zfs; typedef struct zfs_mount_data { char *match_name; zfs_handle_t *match_handle; } zfs_mount_data_t; typedef struct zfs_snapshot_data { char *match_name; /* zonename@SUNWzone */ int len; /* strlen of match_name */ int max; /* highest digit appended to snap name */ int num; /* number of snapshots to rename */ int cntr; /* counter for renaming snapshots */ } zfs_snapshot_data_t; typedef struct clone_data { zfs_handle_t *clone_zhp; /* clone dataset to promote */ time_t origin_creation; /* snapshot creation time of clone */ const char *snapshot; /* snapshot of dataset being demoted */ } clone_data_t; /* * A ZFS file system iterator call-back function which returns the * zfs_handle_t for a ZFS file system on the specified mount point. */ static int match_mountpoint(zfs_handle_t *zhp, void *data) { int res; zfs_mount_data_t *cbp; char mp[ZFS_MAXPROPLEN]; if (zfs_get_type(zhp) != ZFS_TYPE_FILESYSTEM) { zfs_close(zhp); return (0); } /* First check if the dataset is mounted. */ if (zfs_prop_get(zhp, ZFS_PROP_MOUNTED, mp, sizeof (mp), NULL, NULL, 0, B_FALSE) != 0 || strcmp(mp, "no") == 0) { zfs_close(zhp); return (0); } /* Now check mount point. */ if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mp, sizeof (mp), NULL, NULL, 0, B_FALSE) != 0) { zfs_close(zhp); return (0); } cbp = (zfs_mount_data_t *)data; if (strcmp(mp, "legacy") == 0) { /* If legacy, must look in mnttab for mountpoint. */ FILE *fp; struct mnttab entry; const char *nm; nm = zfs_get_name(zhp); if ((fp = fopen(MNTTAB, "r")) == NULL) { zfs_close(zhp); return (0); } while (getmntent(fp, &entry) == 0) { if (strcmp(nm, entry.mnt_special) == 0) { if (strcmp(entry.mnt_mountp, cbp->match_name) == 0) { (void) fclose(fp); cbp->match_handle = zhp; return (1); } break; } } (void) fclose(fp); } else if (strcmp(mp, cbp->match_name) == 0) { cbp->match_handle = zhp; return (1); } /* Iterate over any nested datasets. */ res = zfs_iter_filesystems(zhp, match_mountpoint, data); zfs_close(zhp); return (res); } /* * Get ZFS handle for the specified mount point. */ static zfs_handle_t * mount2zhandle(char *mountpoint) { zfs_mount_data_t cb; cb.match_name = mountpoint; cb.match_handle = NULL; (void) zfs_iter_root(g_zfs, match_mountpoint, &cb); return (cb.match_handle); } /* * Check if there is already a file system (zfs or any other type) mounted on * path. */ static boolean_t is_mountpnt(char *path) { FILE *fp; struct mnttab entry; if ((fp = fopen(MNTTAB, "r")) == NULL) return (B_FALSE); while (getmntent(fp, &entry) == 0) { if (strcmp(path, entry.mnt_mountp) == 0) { (void) fclose(fp); return (B_TRUE); } } (void) fclose(fp); return (B_FALSE); } /* * Run the brand's pre-snapshot hook before we take a ZFS snapshot of the zone. */ static int pre_snapshot(char *presnapbuf) { int status; /* No brand-specific handler */ if (presnapbuf[0] == '\0') return (Z_OK); /* Run the hook */ status = do_subproc(presnapbuf); if ((status = subproc_status(gettext("brand-specific presnapshot"), status, B_FALSE)) != ZONE_SUBPROC_OK) return (Z_ERR); return (Z_OK); } /* * Run the brand's post-snapshot hook after we take a ZFS snapshot of the zone. */ static int post_snapshot(char *postsnapbuf) { int status; /* No brand-specific handler */ if (postsnapbuf[0] == '\0') return (Z_OK); /* Run the hook */ status = do_subproc(postsnapbuf); if ((status = subproc_status(gettext("brand-specific postsnapshot"), status, B_FALSE)) != ZONE_SUBPROC_OK) return (Z_ERR); return (Z_OK); } /* * This is a ZFS snapshot iterator call-back function which returns the * highest number of SUNWzone snapshots that have been taken. */ static int get_snap_max(zfs_handle_t *zhp, void *data) { int res; zfs_snapshot_data_t *cbp; if (zfs_get_type(zhp) != ZFS_TYPE_SNAPSHOT) { zfs_close(zhp); return (0); } cbp = (zfs_snapshot_data_t *)data; if (strncmp(zfs_get_name(zhp), cbp->match_name, cbp->len) == 0) { char *nump; int num; cbp->num++; nump = (char *)(zfs_get_name(zhp) + cbp->len); num = atoi(nump); if (num > cbp->max) cbp->max = num; } res = zfs_iter_snapshots(zhp, B_FALSE, get_snap_max, data); zfs_close(zhp); return (res); } /* * Take a ZFS snapshot to be used for cloning the zone. */ static int take_snapshot(zfs_handle_t *zhp, char *snapshot_name, int snap_size, char *presnapbuf, char *postsnapbuf) { int res; char template[ZFS_MAX_DATASET_NAME_LEN]; zfs_snapshot_data_t cb; /* * First we need to figure out the next available name for the * zone snapshot. Look through the list of zones snapshots for * this file system to determine the maximum snapshot name. */ if (snprintf(template, sizeof (template), "%s@SUNWzone", zfs_get_name(zhp)) >= sizeof (template)) return (Z_ERR); cb.match_name = template; cb.len = strlen(template); cb.max = 0; if (zfs_iter_snapshots(zhp, B_FALSE, get_snap_max, &cb) != 0) return (Z_ERR); cb.max++; if (snprintf(snapshot_name, snap_size, "%s@SUNWzone%d", zfs_get_name(zhp), cb.max) >= snap_size) return (Z_ERR); if (pre_snapshot(presnapbuf) != Z_OK) return (Z_ERR); res = zfs_snapshot(g_zfs, snapshot_name, B_FALSE, NULL); if (post_snapshot(postsnapbuf) != Z_OK) return (Z_ERR); if (res != 0) return (Z_ERR); return (Z_OK); } /* * We are using an explicit snapshot from some earlier point in time so * we need to validate it. Run the brand specific hook. */ static int validate_snapshot(char *snapshot_name, char *snap_path, char *validsnapbuf) { int status; char cmdbuf[MAXPATHLEN]; /* No brand-specific handler */ if (validsnapbuf[0] == '\0') return (Z_OK); /* pass args - snapshot_name & snap_path */ if (snprintf(cmdbuf, sizeof (cmdbuf), "%s %s %s", validsnapbuf, snapshot_name, snap_path) >= sizeof (cmdbuf)) { zerror("Command line too long"); return (Z_ERR); } /* Run the hook */ status = do_subproc(cmdbuf); if ((status = subproc_status(gettext("brand-specific validatesnapshot"), status, B_FALSE)) != ZONE_SUBPROC_OK) return (Z_ERR); return (Z_OK); } /* * Remove the sw inventory file from inside this zonepath that we picked up out * of the snapshot. */ static int clean_out_clone() { int err; zone_dochandle_t handle; if ((handle = zonecfg_init_handle()) == NULL) { zperror(cmd_to_str(CMD_CLONE), B_TRUE); return (Z_ERR); } if ((err = zonecfg_get_handle(target_zone, handle)) != Z_OK) { errno = err; zperror(cmd_to_str(CMD_CLONE), B_TRUE); zonecfg_fini_handle(handle); return (Z_ERR); } zonecfg_rm_detached(handle, B_FALSE); zonecfg_fini_handle(handle); return (Z_OK); } /* * Make a ZFS clone on zonepath from snapshot_name. */ static int clone_snap(char *snapshot_name, char *zonepath) { int res = Z_OK; int err; zfs_handle_t *zhp; zfs_handle_t *clone; nvlist_t *props = NULL; if ((zhp = zfs_open(g_zfs, snapshot_name, ZFS_TYPE_SNAPSHOT)) == NULL) return (Z_NO_ENTRY); (void) printf(gettext("Cloning snapshot %s\n"), snapshot_name); /* * We turn off zfs SHARENFS and SHARESMB properties on the * zoneroot dataset in order to prevent the GZ from sharing * NGZ data by accident. */ if ((nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) || (nvlist_add_string(props, zfs_prop_to_name(ZFS_PROP_SHARENFS), "off") != 0) || (nvlist_add_string(props, zfs_prop_to_name(ZFS_PROP_SHARESMB), "off") != 0)) { nvlist_free(props); (void) fprintf(stderr, gettext("could not create ZFS clone " "%s: out of memory\n"), zonepath); return (Z_ERR); } err = zfs_clone(zhp, zonepath, props); zfs_close(zhp); nvlist_free(props); if (err != 0) return (Z_ERR); /* create the mountpoint if necessary */ if ((clone = zfs_open(g_zfs, zonepath, ZFS_TYPE_DATASET)) == NULL) return (Z_ERR); /* * The clone has been created so we need to print a diagnostic * message if one of the following steps fails for some reason. */ if (zfs_mount(clone, NULL, 0) != 0) { (void) fprintf(stderr, gettext("could not mount ZFS clone " "%s\n"), zfs_get_name(clone)); res = Z_ERR; } else if (clean_out_clone() != Z_OK) { (void) fprintf(stderr, gettext("could not remove the " "software inventory from ZFS clone %s\n"), zfs_get_name(clone)); res = Z_ERR; } zfs_close(clone); return (res); } /* * This function takes a zonepath and attempts to determine what the ZFS * file system name (not mountpoint) should be for that path. We do not * assume that zonepath is an existing directory or ZFS fs since we use * this function as part of the process of creating a new ZFS fs or clone. * * The way this works is that we look at the parent directory of the zonepath * to see if it is a ZFS fs. If it is, we get the name of that ZFS fs and * append the last component of the zonepath to generate the ZFS name for the * zonepath. This matches the algorithm that ZFS uses for automatically * mounting a new fs after it is created. * * Although a ZFS fs can be mounted anywhere, we don't worry about handling * all of the complexity that a user could possibly configure with arbitrary * mounts since there is no way to generate a ZFS name from a random path in * the file system. We only try to handle the automatic mounts that ZFS does * for each file system. ZFS restricts this so that a new fs must be created * in an existing parent ZFS fs. It then automatically mounts the new fs * directly under the mountpoint for the parent fs using the last component * of the name as the mountpoint directory. * * For example: * Name Mountpoint * space/eng/dev/test/zone1 /project1/eng/dev/test/zone1 * * Return Z_OK if the path mapped to a ZFS file system name, otherwise return * Z_ERR. */ static int path2name(char *zonepath, char *zfs_name, int len) { int res; char *bnm, *dnm, *dname, *bname; zfs_handle_t *zhp; struct stat stbuf; /* * We need two tmp strings to handle paths directly in / (e.g. /foo) * since dirname will overwrite the first char after "/" in this case. */ if ((bnm = strdup(zonepath)) == NULL) return (Z_ERR); if ((dnm = strdup(zonepath)) == NULL) { free(bnm); return (Z_ERR); } bname = basename(bnm); dname = dirname(dnm); /* * This is a quick test to save iterating over all of the zfs datasets * on the system (which can be a lot). If the parent dir is not in a * ZFS fs, then we're done. */ if (stat(dname, &stbuf) != 0 || !S_ISDIR(stbuf.st_mode) || strcmp(stbuf.st_fstype, MNTTYPE_ZFS) != 0) { free(bnm); free(dnm); return (Z_ERR); } /* See if the parent directory is its own ZFS dataset. */ if ((zhp = mount2zhandle(dname)) == NULL) { /* * The parent is not a ZFS dataset so we can't automatically * create a dataset on the given path. */ free(bnm); free(dnm); return (Z_ERR); } res = snprintf(zfs_name, len, "%s/%s", zfs_get_name(zhp), bname); free(bnm); free(dnm); zfs_close(zhp); if (res >= len) return (Z_ERR); return (Z_OK); } /* * A ZFS file system iterator call-back function used to determine if the * file system has dependents (snapshots & clones). */ /* ARGSUSED */ static int has_dependent(zfs_handle_t *zhp, void *data) { zfs_close(zhp); return (1); } /* * Given a snapshot name, get the file system path where the snapshot lives. * A snapshot name is of the form fs_name@snap_name. For example, snapshot * pl/zones/z1@SUNWzone1 would have a path of * /pl/zones/z1/.zfs/snapshot/SUNWzone1. */ static int snap2path(char *snap_name, char *path, int len) { char *p; zfs_handle_t *zhp; char mp[ZFS_MAXPROPLEN]; if ((p = strrchr(snap_name, '@')) == NULL) return (Z_ERR); /* Get the file system name from the snap_name. */ *p = '\0'; zhp = zfs_open(g_zfs, snap_name, ZFS_TYPE_DATASET); *p = '@'; if (zhp == NULL) return (Z_ERR); /* Get the file system mount point. */ if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mp, sizeof (mp), NULL, NULL, 0, B_FALSE) != 0) { zfs_close(zhp); return (Z_ERR); } zfs_close(zhp); p++; if (snprintf(path, len, "%s/.zfs/snapshot/%s", mp, p) >= len) return (Z_ERR); return (Z_OK); } /* * This callback function is used to iterate through a snapshot's dependencies * to find a filesystem that is a direct clone of the snapshot being iterated. */ static int get_direct_clone(zfs_handle_t *zhp, void *data) { clone_data_t *cd = data; char origin[ZFS_MAX_DATASET_NAME_LEN]; char ds_path[ZFS_MAX_DATASET_NAME_LEN]; if (zfs_get_type(zhp) != ZFS_TYPE_FILESYSTEM) { zfs_close(zhp); return (0); } (void) strlcpy(ds_path, zfs_get_name(zhp), sizeof (ds_path)); /* Make sure this is a direct clone of the snapshot we're iterating. */ if (zfs_prop_get(zhp, ZFS_PROP_ORIGIN, origin, sizeof (origin), NULL, NULL, 0, B_FALSE) != 0 || strcmp(origin, cd->snapshot) != 0) { zfs_close(zhp); return (0); } if (cd->clone_zhp != NULL) zfs_close(cd->clone_zhp); cd->clone_zhp = zhp; return (1); } /* * A ZFS file system iterator call-back function used to determine the clone * to promote. This function finds the youngest (i.e. last one taken) snapshot * that has a clone. If found, it returns a reference to that clone in the * callback data. */ static int find_clone(zfs_handle_t *zhp, void *data) { clone_data_t *cd = data; time_t snap_creation; int zret = 0; /* If snapshot has no clones, skip it */ if (zfs_prop_get_int(zhp, ZFS_PROP_NUMCLONES) == 0) { zfs_close(zhp); return (0); } cd->snapshot = zfs_get_name(zhp); /* Get the creation time of this snapshot */ snap_creation = (time_t)zfs_prop_get_int(zhp, ZFS_PROP_CREATION); /* * If this snapshot's creation time is greater than (i.e. younger than) * the current youngest snapshot found, iterate this snapshot to * get the right clone. */ if (snap_creation >= cd->origin_creation) { /* * Iterate the dependents of this snapshot to find a clone * that's a direct dependent. */ if ((zret = zfs_iter_dependents(zhp, B_FALSE, get_direct_clone, cd)) == -1) { zfs_close(zhp); return (1); } else if (zret == 1) { /* * Found a clone, update the origin_creation time * in the callback data. */ cd->origin_creation = snap_creation; } } zfs_close(zhp); return (0); } /* * A ZFS file system iterator call-back function used to remove standalone * snapshots. */ /* ARGSUSED */ static int rm_snap(zfs_handle_t *zhp, void *data) { /* If snapshot has clones, something is wrong */ if (zfs_prop_get_int(zhp, ZFS_PROP_NUMCLONES) != 0) { zfs_close(zhp); return (1); } if (zfs_unmount(zhp, NULL, 0) == 0) { (void) zfs_destroy(zhp, B_FALSE); } zfs_close(zhp); return (0); } /* * A ZFS snapshot iterator call-back function which renames snapshots. */ static int rename_snap(zfs_handle_t *zhp, void *data) { int res; zfs_snapshot_data_t *cbp; char template[ZFS_MAX_DATASET_NAME_LEN]; cbp = (zfs_snapshot_data_t *)data; /* * When renaming snapshots with the iterator, the iterator can see * the same snapshot after we've renamed up in the namespace. To * prevent this we check the count for the number of snapshots we have * to rename and stop at that point. */ if (cbp->cntr >= cbp->num) { zfs_close(zhp); return (0); } if (zfs_get_type(zhp) != ZFS_TYPE_SNAPSHOT) { zfs_close(zhp); return (0); } /* Only rename the snapshots we automatically generate when we clone. */ if (strncmp(zfs_get_name(zhp), cbp->match_name, cbp->len) != 0) { zfs_close(zhp); return (0); } (void) snprintf(template, sizeof (template), "%s%d", cbp->match_name, cbp->max++); res = (zfs_rename(zhp, template, B_FALSE, B_FALSE) != 0); if (res != 0) (void) fprintf(stderr, gettext("failed to rename snapshot %s " "to %s: %s\n"), zfs_get_name(zhp), template, libzfs_error_description(g_zfs)); cbp->cntr++; zfs_close(zhp); return (res); } /* * Rename the source dataset's snapshots that are automatically generated when * we clone a zone so that there won't be a name collision when we promote the * cloned dataset. Once the snapshots have been renamed, then promote the * clone. * * The snapshot rename process gets the highest number on the snapshot names * (the format is zonename@SUNWzoneXX where XX are digits) on both the source * and clone datasets, then renames the source dataset snapshots starting at * the next number. */ static int promote_clone(zfs_handle_t *src_zhp, zfs_handle_t *cln_zhp) { zfs_snapshot_data_t sd; char nm[ZFS_MAX_DATASET_NAME_LEN]; char template[ZFS_MAX_DATASET_NAME_LEN]; (void) strlcpy(nm, zfs_get_name(cln_zhp), sizeof (nm)); /* * Start by getting the clone's snapshot max which we use * during the rename of the original dataset's snapshots. */ (void) snprintf(template, sizeof (template), "%s@SUNWzone", nm); sd.match_name = template; sd.len = strlen(template); sd.max = 0; if (zfs_iter_snapshots(cln_zhp, B_FALSE, get_snap_max, &sd) != 0) return (Z_ERR); /* * Now make sure the source's snapshot max is at least as high as * the clone's snapshot max. */ (void) snprintf(template, sizeof (template), "%s@SUNWzone", zfs_get_name(src_zhp)); sd.match_name = template; sd.len = strlen(template); sd.num = 0; if (zfs_iter_snapshots(src_zhp, B_FALSE, get_snap_max, &sd) != 0) return (Z_ERR); /* * Now rename the source dataset's snapshots so there's no * conflict when we promote the clone. */ sd.max++; sd.cntr = 0; if (zfs_iter_snapshots(src_zhp, B_FALSE, rename_snap, &sd) != 0) return (Z_ERR); /* close and reopen the clone dataset to get the latest info */ zfs_close(cln_zhp); if ((cln_zhp = zfs_open(g_zfs, nm, ZFS_TYPE_FILESYSTEM)) == NULL) return (Z_ERR); if (zfs_promote(cln_zhp) != 0) { (void) fprintf(stderr, gettext("failed to promote %s: %s\n"), nm, libzfs_error_description(g_zfs)); return (Z_ERR); } zfs_close(cln_zhp); return (Z_OK); } /* * Promote the youngest clone. That clone will then become the origin of all * of the other clones that were hanging off of the source dataset. */ int promote_all_clones(zfs_handle_t *zhp) { clone_data_t cd; char nm[ZFS_MAX_DATASET_NAME_LEN]; cd.clone_zhp = NULL; cd.origin_creation = 0; cd.snapshot = NULL; if (zfs_iter_snapshots(zhp, B_FALSE, find_clone, &cd) != 0) { zfs_close(zhp); return (Z_ERR); } /* Nothing to promote. */ if (cd.clone_zhp == NULL) return (Z_OK); /* Found the youngest clone to promote. Promote it. */ if (promote_clone(zhp, cd.clone_zhp) != 0) { zfs_close(cd.clone_zhp); zfs_close(zhp); return (Z_ERR); } /* close and reopen the main dataset to get the latest info */ (void) strlcpy(nm, zfs_get_name(zhp), sizeof (nm)); zfs_close(zhp); if ((zhp = zfs_open(g_zfs, nm, ZFS_TYPE_FILESYSTEM)) == NULL) return (Z_ERR); return (Z_OK); } /* * Clone a pre-existing ZFS snapshot, either by making a direct ZFS clone, if * possible, or by copying the data from the snapshot to the zonepath. */ int clone_snapshot_zfs(char *snap_name, char *zonepath, char *validatesnap) { int err = Z_OK; char clone_name[MAXPATHLEN]; char snap_path[MAXPATHLEN]; if (snap2path(snap_name, snap_path, sizeof (snap_path)) != Z_OK) { (void) fprintf(stderr, gettext("unable to find path for %s.\n"), snap_name); return (Z_ERR); } if (validate_snapshot(snap_name, snap_path, validatesnap) != Z_OK) return (Z_NO_ENTRY); /* * The zonepath cannot be ZFS cloned, try to copy the data from * within the snapshot to the zonepath. */ if (path2name(zonepath, clone_name, sizeof (clone_name)) != Z_OK) { if ((err = clone_copy(snap_path, zonepath)) == Z_OK) if (clean_out_clone() != Z_OK) (void) fprintf(stderr, gettext("could not remove the " "software inventory from %s\n"), zonepath); return (err); } if ((err = clone_snap(snap_name, clone_name)) != Z_OK) { if (err != Z_NO_ENTRY) { /* * Cloning the snapshot failed. Fall back to trying * to install the zone by copying from the snapshot. */ if ((err = clone_copy(snap_path, zonepath)) == Z_OK) if (clean_out_clone() != Z_OK) (void) fprintf(stderr, gettext("could not remove the " "software inventory from %s\n"), zonepath); } else { /* * The snapshot is unusable for some reason so restore * the zone state to configured since we were unable to * actually do anything about getting the zone * installed. */ int tmp; if ((tmp = zone_set_state(target_zone, ZONE_STATE_CONFIGURED)) != Z_OK) { errno = tmp; zperror2(target_zone, gettext("could not set state")); } } } return (err); } /* * Attempt to clone a source_zone to a target zonepath by using a ZFS clone. */ int clone_zfs(char *source_zonepath, char *zonepath, char *presnapbuf, char *postsnapbuf) { zfs_handle_t *zhp; char clone_name[MAXPATHLEN]; char snap_name[MAXPATHLEN]; /* * Try to get a zfs handle for the source_zonepath. If this fails * the source_zonepath is not ZFS so return an error. */ if ((zhp = mount2zhandle(source_zonepath)) == NULL) return (Z_ERR); /* * Check if there is a file system already mounted on zonepath. If so, * we can't clone to the path so we should fall back to copying. */ if (is_mountpnt(zonepath)) { zfs_close(zhp); (void) fprintf(stderr, gettext("A file system is already mounted on %s,\n" "preventing use of a ZFS clone.\n"), zonepath); return (Z_ERR); } /* * Instead of using path2name to get the clone name from the zonepath, * we could generate a name from the source zone ZFS name. However, * this would mean we would create the clone under the ZFS fs of the * source instead of what the zonepath says. For example, * * source_zonepath zonepath * /pl/zones/dev/z1 /pl/zones/deploy/z2 * * We don't want the clone to be under "dev", we want it under * "deploy", so that we can leverage the normal attribute inheritance * that ZFS provides in the fs hierarchy. */ if (path2name(zonepath, clone_name, sizeof (clone_name)) != Z_OK) { zfs_close(zhp); return (Z_ERR); } if (take_snapshot(zhp, snap_name, sizeof (snap_name), presnapbuf, postsnapbuf) != Z_OK) { zfs_close(zhp); return (Z_ERR); } zfs_close(zhp); if (clone_snap(snap_name, clone_name) != Z_OK) { /* Clean up the snapshot we just took. */ if ((zhp = zfs_open(g_zfs, snap_name, ZFS_TYPE_SNAPSHOT)) != NULL) { if (zfs_unmount(zhp, NULL, 0) == 0) (void) zfs_destroy(zhp, B_FALSE); zfs_close(zhp); } return (Z_ERR); } (void) printf(gettext("Instead of copying, a ZFS clone has been " "created for this zone.\n")); return (Z_OK); } /* * Attempt to create a ZFS file system for the specified zonepath. * We either will successfully create a ZFS file system and get it mounted * on the zonepath or we don't. The caller doesn't care since a regular * directory is used for the zonepath if no ZFS file system is mounted there. */ void create_zfs_zonepath(char *zonepath) { zfs_handle_t *zhp; char zfs_name[MAXPATHLEN]; nvlist_t *props = NULL; if (path2name(zonepath, zfs_name, sizeof (zfs_name)) != Z_OK) return; /* Check if the dataset already exists. */ if ((zhp = zfs_open(g_zfs, zfs_name, ZFS_TYPE_DATASET)) != NULL) { zfs_close(zhp); return; } /* * We turn off zfs SHARENFS and SHARESMB properties on the * zoneroot dataset in order to prevent the GZ from sharing * NGZ data by accident. */ if ((nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) || (nvlist_add_string(props, zfs_prop_to_name(ZFS_PROP_SHARENFS), "off") != 0) || (nvlist_add_string(props, zfs_prop_to_name(ZFS_PROP_SHARESMB), "off") != 0)) { nvlist_free(props); (void) fprintf(stderr, gettext("cannot create ZFS dataset %s: " "out of memory\n"), zfs_name); } if (zfs_create(g_zfs, zfs_name, ZFS_TYPE_FILESYSTEM, props) != 0 || (zhp = zfs_open(g_zfs, zfs_name, ZFS_TYPE_DATASET)) == NULL) { (void) fprintf(stderr, gettext("cannot create ZFS dataset %s: " "%s\n"), zfs_name, libzfs_error_description(g_zfs)); nvlist_free(props); return; } nvlist_free(props); if (zfs_mount(zhp, NULL, 0) != 0) { (void) fprintf(stderr, gettext("cannot mount ZFS dataset %s: " "%s\n"), zfs_name, libzfs_error_description(g_zfs)); (void) zfs_destroy(zhp, B_FALSE); } else { if (chmod(zonepath, S_IRWXU) != 0) { (void) fprintf(stderr, gettext("file system %s " "successfully created, but chmod %o failed: %s\n"), zfs_name, S_IRWXU, strerror(errno)); (void) destroy_zfs(zonepath); } else { (void) printf(gettext("A ZFS file system has been " "created for this zone.\n")); } } zfs_close(zhp); } /* * If the zonepath is a ZFS file system, attempt to destroy it. We return Z_OK * if we were able to zfs_destroy the zonepath, otherwise we return Z_ERR * which means the caller should clean up the zonepath in the traditional * way. */ int destroy_zfs(char *zonepath) { zfs_handle_t *zhp; boolean_t is_clone = B_FALSE; char origin[ZFS_MAXPROPLEN]; if ((zhp = mount2zhandle(zonepath)) == NULL) return (Z_ERR); if (promote_all_clones(zhp) != 0) return (Z_ERR); /* Now cleanup any snapshots remaining. */ if (zfs_iter_snapshots(zhp, B_FALSE, rm_snap, NULL) != 0) { zfs_close(zhp); return (Z_ERR); } /* * We can't destroy the file system if it has still has dependents. * There shouldn't be any at this point, but we'll double check. */ if (zfs_iter_dependents(zhp, B_TRUE, has_dependent, NULL) != 0) { (void) fprintf(stderr, gettext("zfs destroy %s failed: the " "dataset still has dependents\n"), zfs_get_name(zhp)); zfs_close(zhp); return (Z_ERR); } /* * This might be a clone. Try to get the snapshot so we can attempt * to destroy that as well. */ if (zfs_prop_get(zhp, ZFS_PROP_ORIGIN, origin, sizeof (origin), NULL, NULL, 0, B_FALSE) == 0) is_clone = B_TRUE; if (zfs_unmount(zhp, NULL, 0) != 0) { (void) fprintf(stderr, gettext("zfs unmount %s failed: %s\n"), zfs_get_name(zhp), libzfs_error_description(g_zfs)); zfs_close(zhp); return (Z_ERR); } if (zfs_destroy(zhp, B_FALSE) != 0) { /* * If the destroy fails for some reason, try to remount * the file system so that we can use "rm -rf" to clean up * instead. */ (void) fprintf(stderr, gettext("zfs destroy %s failed: %s\n"), zfs_get_name(zhp), libzfs_error_description(g_zfs)); (void) zfs_mount(zhp, NULL, 0); zfs_close(zhp); return (Z_ERR); } /* * If the zone has ever been moved then the mountpoint dir will not be * cleaned up by the zfs_destroy(). To handle this case try to clean * it up now but don't worry if it fails, that will be normal. */ (void) rmdir(zonepath); (void) printf(gettext("The ZFS file system for this zone has been " "destroyed.\n")); if (is_clone) { zfs_handle_t *ohp; /* * Try to clean up the snapshot that the clone was taken from. */ if ((ohp = zfs_open(g_zfs, origin, ZFS_TYPE_SNAPSHOT)) != NULL) { if (zfs_iter_dependents(ohp, B_TRUE, has_dependent, NULL) == 0 && zfs_unmount(ohp, NULL, 0) == 0) (void) zfs_destroy(ohp, B_FALSE); zfs_close(ohp); } } zfs_close(zhp); return (Z_OK); } /* * Return true if the path is its own zfs file system. We determine this * by stat-ing the path to see if it is zfs and stat-ing the parent to see * if it is a different fs. */ boolean_t is_zonepath_zfs(char *zonepath) { int res; char *path; char *parent; struct statvfs64 buf1, buf2; if (statvfs64(zonepath, &buf1) != 0) return (B_FALSE); if (strcmp(buf1.f_basetype, "zfs") != 0) return (B_FALSE); if ((path = strdup(zonepath)) == NULL) return (B_FALSE); parent = dirname(path); res = statvfs64(parent, &buf2); free(path); if (res != 0) return (B_FALSE); if (buf1.f_fsid == buf2.f_fsid) return (B_FALSE); return (B_TRUE); } /* * Implement the fast move of a ZFS file system by simply updating the * mountpoint. Since it is file system already, we don't have the * issue of cross-file system copying. */ int move_zfs(char *zonepath, char *new_zonepath) { int ret = Z_ERR; zfs_handle_t *zhp; if ((zhp = mount2zhandle(zonepath)) == NULL) return (Z_ERR); if (zfs_prop_set(zhp, zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), new_zonepath) == 0) { /* * Clean up the old mount point. We ignore any failure since * the zone is already successfully mounted on the new path. */ (void) rmdir(zonepath); ret = Z_OK; } zfs_close(zhp); return (ret); } /* * Validate that the given dataset exists on the system, and that neither it nor * its children are zvols. * * Note that we don't do anything with the 'zoned' property here. All * management is done in zoneadmd when the zone is actually rebooted. This * allows us to automatically set the zoned property even when a zone is * rebooted by the administrator. */ int verify_datasets(zone_dochandle_t handle) { int return_code = Z_OK; struct zone_dstab dstab; zfs_handle_t *zhp; char propbuf[ZFS_MAXPROPLEN]; char source[ZFS_MAX_DATASET_NAME_LEN]; zprop_source_t srctype; if (zonecfg_setdsent(handle) != Z_OK) { /* * TRANSLATION_NOTE * zfs and dataset are literals that should not be translated. */ (void) fprintf(stderr, gettext("could not verify zfs datasets: " "unable to enumerate datasets\n")); return (Z_ERR); } while (zonecfg_getdsent(handle, &dstab) == Z_OK) { if ((zhp = zfs_open(g_zfs, dstab.zone_dataset_name, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)) == NULL) { (void) fprintf(stderr, gettext("could not verify zfs " "dataset %s: %s\n"), dstab.zone_dataset_name, libzfs_error_description(g_zfs)); return_code = Z_ERR; continue; } if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, propbuf, sizeof (propbuf), &srctype, source, sizeof (source), 0) == 0 && (srctype == ZPROP_SRC_INHERITED)) { (void) fprintf(stderr, gettext("could not verify zfs " "dataset %s: mountpoint cannot be inherited\n"), dstab.zone_dataset_name); return_code = Z_ERR; zfs_close(zhp); continue; } zfs_close(zhp); } (void) zonecfg_enddsent(handle); return (return_code); } /* * Verify that the ZFS dataset exists, and its mountpoint * property is set to "legacy". */ int verify_fs_zfs(struct zone_fstab *fstab) { zfs_handle_t *zhp; char propbuf[ZFS_MAXPROPLEN]; if ((zhp = zfs_open(g_zfs, fstab->zone_fs_special, ZFS_TYPE_DATASET)) == NULL) { (void) fprintf(stderr, gettext("could not verify fs %s: " "could not access zfs dataset '%s'\n"), fstab->zone_fs_dir, fstab->zone_fs_special); return (Z_ERR); } if (zfs_get_type(zhp) != ZFS_TYPE_FILESYSTEM) { (void) fprintf(stderr, gettext("cannot verify fs %s: " "'%s' is not a file system\n"), fstab->zone_fs_dir, fstab->zone_fs_special); zfs_close(zhp); return (Z_ERR); } if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, propbuf, sizeof (propbuf), NULL, NULL, 0, 0) != 0 || strcmp(propbuf, "legacy") != 0) { (void) fprintf(stderr, gettext("could not verify fs %s: " "zfs '%s' mountpoint is not \"legacy\"\n"), fstab->zone_fs_dir, fstab->zone_fs_special); zfs_close(zhp); return (Z_ERR); } zfs_close(zhp); return (Z_OK); } /* * Destroy the specified mnttab structure that was created by mnttab_dup(). * NOTE: The structure's mnt_time field isn't freed. */ static void mnttab_destroy(struct mnttab *tabp) { assert(tabp != NULL); free(tabp->mnt_mountp); free(tabp->mnt_special); free(tabp->mnt_fstype); free(tabp->mnt_mntopts); free(tabp); } /* * Duplicate the specified mnttab structure. The mnt_mountp and mnt_time * fields aren't duplicated. This function returns a pointer to the new mnttab * structure or NULL if an error occurred. If an error occurs, then this * function sets errno to reflect the error. mnttab structures created by * this function should be destroyed via mnttab_destroy(). */ static struct mnttab * mnttab_dup(const struct mnttab *srcp) { struct mnttab *retval; assert(srcp != NULL); retval = (struct mnttab *)calloc(1, sizeof (*retval)); if (retval == NULL) { errno = ENOMEM; return (NULL); } if (srcp->mnt_special != NULL) { retval->mnt_special = strdup(srcp->mnt_special); if (retval->mnt_special == NULL) goto err; } if (srcp->mnt_fstype != NULL) { retval->mnt_fstype = strdup(srcp->mnt_fstype); if (retval->mnt_fstype == NULL) goto err; } retval->mnt_mntopts = (char *)malloc(MAX_MNTOPT_STR * sizeof (char)); if (retval->mnt_mntopts == NULL) goto err; if (srcp->mnt_mntopts != NULL) { if (strlcpy(retval->mnt_mntopts, srcp->mnt_mntopts, MAX_MNTOPT_STR * sizeof (char)) >= MAX_MNTOPT_STR * sizeof (char)) { mnttab_destroy(retval); errno = EOVERFLOW; /* similar to mount(2) behavior */ return (NULL); } } else { retval->mnt_mntopts[0] = '\0'; } return (retval); err: mnttab_destroy(retval); errno = ENOMEM; return (NULL); } /* * Determine whether the specified ZFS dataset's mountpoint property is set * to "legacy". If the specified dataset does not have a legacy mountpoint, * then the string pointer to which the mountpoint argument points is assigned * a dynamically-allocated string containing the dataset's mountpoint * property. If the dataset's mountpoint property is "legacy" or a libzfs * error occurs, then the string pointer to which the mountpoint argument * points isn't modified. * * This function returns B_TRUE if it doesn't encounter any fatal errors. * It returns B_FALSE if it encounters a fatal error and sets errno to the * appropriate error code. */ static boolean_t get_zfs_non_legacy_mountpoint(const char *dataset_name, char **mountpoint) { zfs_handle_t *zhp; char propbuf[ZFS_MAXPROPLEN]; assert(dataset_name != NULL); assert(mountpoint != NULL); if ((zhp = zfs_open(g_zfs, dataset_name, ZFS_TYPE_DATASET)) == NULL) { errno = EINVAL; return (B_FALSE); } if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, propbuf, sizeof (propbuf), NULL, NULL, 0, 0) != 0) { zfs_close(zhp); errno = EINVAL; return (B_FALSE); } zfs_close(zhp); if (strcmp(propbuf, "legacy") != 0) { if ((*mountpoint = strdup(propbuf)) == NULL) { errno = ENOMEM; return (B_FALSE); } } return (B_TRUE); } /* * This zonecfg_find_mounts() callback records information about mounts of * interest in a zonepath. It also tallies the number of zone * root overlay mounts and the number of unexpected mounts found. * This function outputs errors using zerror() if it finds unexpected * mounts. cookiep should point to an initialized zone_mounts_t structure. * * This function returns zero on success and a nonzero value on failure. */ static int zone_mounts_cb(const struct mnttab *mountp, void *cookiep) { zone_mounts_t *mounts; const char *zone_mount_dir; assert(mountp != NULL); assert(cookiep != NULL); mounts = (zone_mounts_t *)cookiep; zone_mount_dir = mountp->mnt_mountp + mounts->zonepath_len; if (strcmp(zone_mount_dir, "/root") == 0) { /* * Check for an overlay mount. If we already detected a /root * mount, then the current mount must be an overlay mount. */ if (mounts->root_mnttab != NULL) { mounts->num_root_overlay_mounts++; return (0); } /* * Store the root mount's mnttab information in the * zone_mounts_t structure for future use. */ if ((mounts->root_mnttab = mnttab_dup(mountp)) == NULL) { zperror(cmd_to_str(CMD_MOVE), B_FALSE); return (-1); } /* * Determine if the filesystem is a ZFS filesystem with a * non-legacy mountpoint. If it is, then set the root * filesystem's mnttab's mnt_mountp field to a non-NULL * value, which will serve as a flag to indicate this special * condition. */ if (strcmp(mountp->mnt_fstype, MNTTYPE_ZFS) == 0 && get_zfs_non_legacy_mountpoint(mountp->mnt_special, &mounts->root_mnttab->mnt_mountp) != B_TRUE) { zperror(cmd_to_str(CMD_MOVE), B_FALSE); return (-1); } } else { /* * An unexpected mount was found. Notify the user. */ if (mounts->num_unexpected_mounts == 0) zerror(gettext("These file systems are mounted on " "subdirectories of %s.\n"), mounts->zonepath); mounts->num_unexpected_mounts++; (void) zfm_print(mountp, NULL); } return (0); } /* * Initialize the specified zone_mounts_t structure for the given zonepath. * If this function succeeds, it returns zero and the specified zone_mounts_t * structure contains information about mounts in the specified zonepath. * The function returns a nonzero value if it fails. The zone_mounts_t * structure doesn't need be destroyed via zone_mounts_destroy() if this * function fails. */ int zone_mounts_init(zone_mounts_t *mounts, const char *zonepath) { assert(mounts != NULL); assert(zonepath != NULL); bzero(mounts, sizeof (*mounts)); if ((mounts->zonepath = strdup(zonepath)) == NULL) { zerror(gettext("the process ran out of memory while checking " "for mounts in zonepath %s."), zonepath); return (-1); } mounts->zonepath_len = strlen(zonepath); if (zonecfg_find_mounts((char *)zonepath, zone_mounts_cb, mounts) == -1) { zerror(gettext("an error occurred while checking for mounts " "in zonepath %s."), zonepath); zone_mounts_destroy(mounts); return (-1); } return (0); } /* * Destroy the memory used by the specified zone_mounts_t structure's fields. * This function doesn't free the memory occupied by the structure itself * (i.e., it doesn't free the parameter). */ void zone_mounts_destroy(zone_mounts_t *mounts) { assert(mounts != NULL); free(mounts->zonepath); if (mounts->root_mnttab != NULL) mnttab_destroy(mounts->root_mnttab); } /* * Mount a moving zone's root filesystem (if it had a root filesystem mount * prior to the move) using the specified zonepath. mounts should refer to * the zone_mounts_t structure describing the zone's mount information. * * This function returns zero if the mount succeeds and a nonzero value * if it doesn't. */ int zone_mount_rootfs(zone_mounts_t *mounts, const char *zonepath) { char zoneroot[MAXPATHLEN]; struct mnttab *mtab; int flags; assert(mounts != NULL); assert(zonepath != NULL); /* * If there isn't a root filesystem, then don't do anything. */ mtab = mounts->root_mnttab; if (mtab == NULL) return (0); /* * Determine the root filesystem's new mountpoint. */ if (snprintf(zoneroot, sizeof (zoneroot), "%s/root", zonepath) >= sizeof (zoneroot)) { zerror(gettext("Zonepath %s is too long.\n"), zonepath); return (-1); } /* * If the root filesystem is a non-legacy ZFS filesystem (i.e., if it's * mnt_mountp field is non-NULL), then make the filesystem's new * mount point its mountpoint property and mount the filesystem. */ if (mtab->mnt_mountp != NULL) { zfs_handle_t *zhp; if ((zhp = zfs_open(g_zfs, mtab->mnt_special, ZFS_TYPE_DATASET)) == NULL) { zerror(gettext("could not get ZFS handle for the zone's" " root filesystem")); return (-1); } if (zfs_prop_set(zhp, zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), zoneroot) != 0) { zerror(gettext("could not modify zone's root " "filesystem's mountpoint property")); zfs_close(zhp); return (-1); } if (zfs_mount(zhp, mtab->mnt_mntopts, 0) != 0) { zerror(gettext("unable to mount zone root %s: %s"), zoneroot, libzfs_error_description(g_zfs)); if (zfs_prop_set(zhp, zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), mtab->mnt_mountp) != 0) zerror(gettext("unable to restore zone's root " "filesystem's mountpoint property")); zfs_close(zhp); return (-1); } zfs_close(zhp); return (0); } /* * The root filesystem is either a legacy-mounted ZFS filesystem or * a non-ZFS filesystem. Use mount(2) to mount the root filesystem. */ if (mtab->mnt_mntopts != NULL) flags = MS_OPTIONSTR; else flags = 0; if (mount(mtab->mnt_special, zoneroot, flags, mtab->mnt_fstype, NULL, 0, mtab->mnt_mntopts, MAX_MNTOPT_STR * sizeof (char)) != 0) { flags = errno; zerror(gettext("unable to mount zone root %s: %s"), zoneroot, strerror(flags)); return (-1); } return (0); } /* * Unmount a moving zone's root filesystem (if such a mount exists) using the * specified zonepath. mounts should refer to the zone_mounts_t structure * describing the zone's mount information. If force is B_TRUE, then if the * unmount fails, then the function will try to forcibly unmount the zone's root * filesystem. * * This function returns zero if the unmount (forced or otherwise) succeeds; * otherwise, it returns a nonzero value. */ int zone_unmount_rootfs(zone_mounts_t *mounts, const char *zonepath, boolean_t force) { char zoneroot[MAXPATHLEN]; struct mnttab *mtab; int err; assert(mounts != NULL); assert(zonepath != NULL); /* * If there isn't a root filesystem, then don't do anything. */ mtab = mounts->root_mnttab; if (mtab == NULL) return (0); /* * Determine the root filesystem's mountpoint. */ if (snprintf(zoneroot, sizeof (zoneroot), "%s/root", zonepath) >= sizeof (zoneroot)) { zerror(gettext("Zonepath %s is too long.\n"), zonepath); return (-1); } /* * If the root filesystem is a non-legacy ZFS fileystem, then unmount * the filesystem via libzfs. */ if (mtab->mnt_mountp != NULL) { zfs_handle_t *zhp; if ((zhp = zfs_open(g_zfs, mtab->mnt_special, ZFS_TYPE_DATASET)) == NULL) { zerror(gettext("could not get ZFS handle for the zone's" " root filesystem")); return (-1); } if (zfs_unmount(zhp, zoneroot, 0) != 0) { if (force && zfs_unmount(zhp, zoneroot, MS_FORCE) == 0) { zfs_close(zhp); return (0); } zerror(gettext("unable to unmount zone root %s: %s"), zoneroot, libzfs_error_description(g_zfs)); zfs_close(zhp); return (-1); } zfs_close(zhp); return (0); } /* * Use umount(2) to unmount the root filesystem. If this fails, then * forcibly unmount it if the force flag is set. */ if (umount(zoneroot) != 0) { if (force && umount2(zoneroot, MS_FORCE) == 0) return (0); err = errno; zerror(gettext("unable to unmount zone root %s: %s"), zoneroot, strerror(err)); return (-1); } return (0); } int init_zfs(void) { if ((g_zfs = libzfs_init()) == NULL) { (void) fprintf(stderr, gettext("failed to initialize ZFS " "library\n")); return (Z_ERR); } return (Z_OK); }