Path: blob/main/sys/contrib/openzfs/lib/libzfs/libzfs_changelist.c
48378 views
// SPDX-License-Identifier: CDDL-1.01/*2* CDDL HEADER START3*4* The contents of this file are subject to the terms of the5* Common Development and Distribution License (the "License").6* You may not use this file except in compliance with the License.7*8* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE9* or https://opensource.org/licenses/CDDL-1.0.10* See the License for the specific language governing permissions11* and limitations under the License.12*13* When distributing Covered Code, include this CDDL HEADER in each14* file and include the License file at usr/src/OPENSOLARIS.LICENSE.15* If applicable, add the following below this CDDL HEADER, with the16* fields enclosed by brackets "[]" replaced with your own identifying17* information: Portions Copyright [yyyy] [name of copyright owner]18*19* CDDL HEADER END20*/2122/*23* Copyright 2010 Sun Microsystems, Inc. All rights reserved.24* Use is subject to license terms.25*26* Portions Copyright 2007 Ramprakash Jelari27* Copyright (c) 2014, 2020 by Delphix. All rights reserved.28* Copyright 2016 Igor Kozhukhov <[email protected]>29* Copyright (c) 2018 Datto Inc.30*/3132#include <libintl.h>33#include <libuutil.h>34#include <stddef.h>35#include <stdlib.h>36#include <string.h>37#include <unistd.h>38#include <zone.h>3940#include <libzfs.h>4142#include "libzfs_impl.h"4344/*45* Structure to keep track of dataset state. Before changing the 'sharenfs' or46* 'mountpoint' property, we record whether the filesystem was previously47* mounted/shared. This prior state dictates whether we remount/reshare the48* dataset after the property has been changed.49*50* The interface consists of the following sequence of functions:51*52* changelist_gather()53* changelist_prefix()54* < change property >55* changelist_postfix()56* changelist_free()57*58* Other interfaces:59*60* changelist_remove() - remove a node from a gathered list61* changelist_rename() - renames all datasets appropriately when doing a rename62* changelist_unshare() - unshares all the nodes in a given changelist63* changelist_haszonedchild() - check if there is any child exported to64* a local zone65*/66typedef struct prop_changenode {67zfs_handle_t *cn_handle;68int cn_shared;69int cn_mounted;70int cn_zoned;71boolean_t cn_needpost; /* is postfix() needed? */72uu_avl_node_t cn_treenode;73} prop_changenode_t;7475struct prop_changelist {76zfs_prop_t cl_prop;77zfs_prop_t cl_realprop;78zfs_prop_t cl_shareprop; /* used with sharenfs/sharesmb */79uu_avl_pool_t *cl_pool;80uu_avl_t *cl_tree;81boolean_t cl_waslegacy;82boolean_t cl_allchildren;83boolean_t cl_alldependents;84int cl_mflags; /* Mount flags */85int cl_gflags; /* Gather request flags */86boolean_t cl_haszonedchild;87};8889/*90* If the property is 'mountpoint', go through and unmount filesystems as91* necessary. We don't do the same for 'sharenfs', because we can just re-share92* with different options without interrupting service. We do handle 'sharesmb'93* since there may be old resource names that need to be removed.94*/95int96changelist_prefix(prop_changelist_t *clp)97{98prop_changenode_t *cn;99uu_avl_walk_t *walk;100int ret = 0;101const enum sa_protocol smb[] = {SA_PROTOCOL_SMB, SA_NO_PROTOCOL};102boolean_t commit_smb_shares = B_FALSE;103104if (clp->cl_prop != ZFS_PROP_MOUNTPOINT &&105clp->cl_prop != ZFS_PROP_SHARESMB)106return (0);107108/*109* If CL_GATHER_DONT_UNMOUNT is set, don't want to unmount/unshare and110* later (re)mount/(re)share the filesystem in postfix phase, so we111* return from here. If filesystem is mounted or unmounted, leave it112* as it is.113*/114if (clp->cl_gflags & CL_GATHER_DONT_UNMOUNT)115return (0);116117if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)118return (-1);119120while ((cn = uu_avl_walk_next(walk)) != NULL) {121122/* if a previous loop failed, set the remaining to false */123if (ret == -1) {124cn->cn_needpost = B_FALSE;125continue;126}127128/*129* If we are in the global zone, but this dataset is exported130* to a local zone, do nothing.131*/132if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)133continue;134135if (!ZFS_IS_VOLUME(cn->cn_handle)) {136/*137* Do the property specific processing.138*/139switch (clp->cl_prop) {140case ZFS_PROP_MOUNTPOINT:141if (zfs_unmount(cn->cn_handle, NULL,142clp->cl_mflags) != 0) {143ret = -1;144cn->cn_needpost = B_FALSE;145}146break;147case ZFS_PROP_SHARESMB:148(void) zfs_unshare(cn->cn_handle, NULL,149smb);150commit_smb_shares = B_TRUE;151break;152153default:154break;155}156}157}158159if (commit_smb_shares)160zfs_commit_shares(smb);161uu_avl_walk_end(walk);162163if (ret == -1)164(void) changelist_postfix(clp);165166return (ret);167}168169/*170* If the property is 'mountpoint' or 'sharenfs', go through and remount and/or171* reshare the filesystems as necessary. In changelist_gather() we recorded172* whether the filesystem was previously shared or mounted. The action we take173* depends on the previous state, and whether the value was previously 'legacy'.174* For non-legacy properties, we always remount/reshare the filesystem,175* if CL_GATHER_DONT_UNMOUNT is not set.176*/177int178changelist_postfix(prop_changelist_t *clp)179{180prop_changenode_t *cn;181uu_avl_walk_t *walk;182char shareopts[ZFS_MAXPROPLEN];183boolean_t commit_smb_shares = B_FALSE;184boolean_t commit_nfs_shares = B_FALSE;185186/*187* If CL_GATHER_DONT_UNMOUNT is set, it means we don't want to (un)mount188* or (re/un)share the filesystem, so we return from here. If filesystem189* is mounted or unmounted, leave it as it is.190*/191if (clp->cl_gflags & CL_GATHER_DONT_UNMOUNT)192return (0);193194/*195* If we're changing the mountpoint, attempt to destroy the underlying196* mountpoint. All other datasets will have inherited from this dataset197* (in which case their mountpoints exist in the filesystem in the new198* location), or have explicit mountpoints set (in which case they won't199* be in the changelist).200*/201if ((cn = uu_avl_last(clp->cl_tree)) == NULL)202return (0);203204if (clp->cl_prop == ZFS_PROP_MOUNTPOINT &&205!(clp->cl_gflags & CL_GATHER_DONT_UNMOUNT))206remove_mountpoint(cn->cn_handle);207208/*209* We walk the datasets in reverse, because we want to mount any parent210* datasets before mounting the children. We walk all datasets even if211* there are errors.212*/213if ((walk = uu_avl_walk_start(clp->cl_tree,214UU_WALK_REVERSE | UU_WALK_ROBUST)) == NULL)215return (-1);216217while ((cn = uu_avl_walk_next(walk)) != NULL) {218219boolean_t sharenfs;220boolean_t sharesmb;221boolean_t mounted;222boolean_t needs_key;223224/*225* If we are in the global zone, but this dataset is exported226* to a local zone, do nothing.227*/228if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)229continue;230231/* Only do post-processing if it's required */232if (!cn->cn_needpost)233continue;234cn->cn_needpost = B_FALSE;235236zfs_refresh_properties(cn->cn_handle);237238if (ZFS_IS_VOLUME(cn->cn_handle))239continue;240241/*242* Remount if previously mounted or mountpoint was legacy,243* or sharenfs or sharesmb property is set.244*/245sharenfs = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARENFS,246shareopts, sizeof (shareopts), NULL, NULL, 0,247B_FALSE) == 0) && (strcmp(shareopts, "off") != 0));248249sharesmb = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARESMB,250shareopts, sizeof (shareopts), NULL, NULL, 0,251B_FALSE) == 0) && (strcmp(shareopts, "off") != 0));252253needs_key = (zfs_prop_get_int(cn->cn_handle,254ZFS_PROP_KEYSTATUS) == ZFS_KEYSTATUS_UNAVAILABLE);255256mounted = zfs_is_mounted(cn->cn_handle, NULL);257258if (!mounted && !needs_key && (cn->cn_mounted ||259(((clp->cl_prop == ZFS_PROP_MOUNTPOINT &&260clp->cl_prop == clp->cl_realprop) ||261sharenfs || sharesmb || clp->cl_waslegacy) &&262(zfs_prop_get_int(cn->cn_handle,263ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_ON)))) {264265if (zfs_mount(cn->cn_handle, NULL, 0) == 0)266mounted = TRUE;267}268269/*270* If the file system is mounted we always re-share even271* if the filesystem is currently shared, so that we can272* adopt any new options.273*/274const enum sa_protocol nfs[] =275{SA_PROTOCOL_NFS, SA_NO_PROTOCOL};276if (sharenfs && mounted) {277zfs_share(cn->cn_handle, nfs);278commit_nfs_shares = B_TRUE;279} else if (cn->cn_shared || clp->cl_waslegacy) {280zfs_unshare(cn->cn_handle, NULL, nfs);281commit_nfs_shares = B_TRUE;282}283const enum sa_protocol smb[] =284{SA_PROTOCOL_SMB, SA_NO_PROTOCOL};285if (sharesmb && mounted) {286zfs_share(cn->cn_handle, smb);287commit_smb_shares = B_TRUE;288} else if (cn->cn_shared || clp->cl_waslegacy) {289zfs_unshare(cn->cn_handle, NULL, smb);290commit_smb_shares = B_TRUE;291}292}293294enum sa_protocol proto[SA_PROTOCOL_COUNT + 1], *p = proto;295if (commit_nfs_shares)296*p++ = SA_PROTOCOL_NFS;297if (commit_smb_shares)298*p++ = SA_PROTOCOL_SMB;299*p++ = SA_NO_PROTOCOL;300zfs_commit_shares(proto);301uu_avl_walk_end(walk);302303return (0);304}305306/*307* Is this "dataset" a child of "parent"?308*/309static boolean_t310isa_child_of(const char *dataset, const char *parent)311{312int len;313314len = strlen(parent);315316if (strncmp(dataset, parent, len) == 0 &&317(dataset[len] == '@' || dataset[len] == '/' ||318dataset[len] == '\0'))319return (B_TRUE);320else321return (B_FALSE);322323}324325/*326* If we rename a filesystem, child filesystem handles are no longer valid327* since we identify each dataset by its name in the ZFS namespace. As a328* result, we have to go through and fix up all the names appropriately. We329* could do this automatically if libzfs kept track of all open handles, but330* this is a lot less work.331*/332void333changelist_rename(prop_changelist_t *clp, const char *src, const char *dst)334{335prop_changenode_t *cn;336uu_avl_walk_t *walk;337char newname[ZFS_MAX_DATASET_NAME_LEN];338339if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)340return;341342while ((cn = uu_avl_walk_next(walk)) != NULL) {343/*344* Do not rename a clone that's not in the source hierarchy.345*/346if (!isa_child_of(cn->cn_handle->zfs_name, src))347continue;348349/*350* Destroy the previous mountpoint if needed.351*/352remove_mountpoint(cn->cn_handle);353354(void) strlcpy(newname, dst, sizeof (newname));355(void) strlcat(newname, cn->cn_handle->zfs_name + strlen(src),356sizeof (newname));357358(void) strlcpy(cn->cn_handle->zfs_name, newname,359sizeof (cn->cn_handle->zfs_name));360}361362uu_avl_walk_end(walk);363}364365/*366* Given a gathered changelist for the 'sharenfs' or 'sharesmb' property,367* unshare all the datasets in the list.368*/369int370changelist_unshare(prop_changelist_t *clp, const enum sa_protocol *proto)371{372prop_changenode_t *cn;373uu_avl_walk_t *walk;374int ret = 0;375376if (clp->cl_prop != ZFS_PROP_SHARENFS &&377clp->cl_prop != ZFS_PROP_SHARESMB)378return (0);379380if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)381return (-1);382383while ((cn = uu_avl_walk_next(walk)) != NULL) {384if (zfs_unshare(cn->cn_handle, NULL, proto) != 0)385ret = -1;386}387388for (const enum sa_protocol *p = proto; *p != SA_NO_PROTOCOL; ++p)389sa_commit_shares(*p);390uu_avl_walk_end(walk);391392return (ret);393}394395/*396* Check if there is any child exported to a local zone in a given changelist.397* This information has already been recorded while gathering the changelist398* via changelist_gather().399*/400int401changelist_haszonedchild(prop_changelist_t *clp)402{403return (clp->cl_haszonedchild);404}405406/*407* Remove a node from a gathered list.408*/409void410changelist_remove(prop_changelist_t *clp, const char *name)411{412prop_changenode_t *cn;413uu_avl_walk_t *walk;414415if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)416return;417418while ((cn = uu_avl_walk_next(walk)) != NULL) {419if (strcmp(cn->cn_handle->zfs_name, name) == 0) {420uu_avl_remove(clp->cl_tree, cn);421zfs_close(cn->cn_handle);422free(cn);423uu_avl_walk_end(walk);424return;425}426}427428uu_avl_walk_end(walk);429}430431/*432* Release any memory associated with a changelist.433*/434void435changelist_free(prop_changelist_t *clp)436{437prop_changenode_t *cn;438439if (clp->cl_tree) {440uu_avl_walk_t *walk;441442if ((walk = uu_avl_walk_start(clp->cl_tree,443UU_WALK_ROBUST)) == NULL)444return;445446while ((cn = uu_avl_walk_next(walk)) != NULL) {447uu_avl_remove(clp->cl_tree, cn);448zfs_close(cn->cn_handle);449free(cn);450}451452uu_avl_walk_end(walk);453uu_avl_destroy(clp->cl_tree);454}455if (clp->cl_pool)456uu_avl_pool_destroy(clp->cl_pool);457458free(clp);459}460461/*462* Add one dataset to changelist463*/464static int465changelist_add_mounted(zfs_handle_t *zhp, void *data)466{467prop_changelist_t *clp = data;468prop_changenode_t *cn;469uu_avl_index_t idx;470471ASSERT3U(clp->cl_prop, ==, ZFS_PROP_MOUNTPOINT);472473cn = zfs_alloc(zfs_get_handle(zhp), sizeof (prop_changenode_t));474cn->cn_handle = zhp;475cn->cn_mounted = zfs_is_mounted(zhp, NULL);476ASSERT3U(cn->cn_mounted, ==, B_TRUE);477cn->cn_shared = zfs_is_shared(zhp, NULL, NULL);478cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);479cn->cn_needpost = B_TRUE;480481/* Indicate if any child is exported to a local zone. */482if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)483clp->cl_haszonedchild = B_TRUE;484485uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);486487if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {488uu_avl_insert(clp->cl_tree, cn, idx);489} else {490free(cn);491zfs_close(zhp);492}493494return (0);495}496497static int498change_one(zfs_handle_t *zhp, void *data)499{500prop_changelist_t *clp = data;501char property[ZFS_MAXPROPLEN];502char where[64];503prop_changenode_t *cn = NULL;504zprop_source_t sourcetype = ZPROP_SRC_NONE;505zprop_source_t share_sourcetype = ZPROP_SRC_NONE;506int ret = 0;507508/*509* We only want to unmount/unshare those filesystems that may inherit510* from the target filesystem. If we find any filesystem with a511* locally set mountpoint, we ignore any children since changing the512* property will not affect them. If this is a rename, we iterate513* over all children regardless, since we need them unmounted in514* order to do the rename. Also, if this is a volume and we're doing515* a rename, then always add it to the changelist.516*/517518if (!(ZFS_IS_VOLUME(zhp) && clp->cl_realprop == ZFS_PROP_NAME) &&519zfs_prop_get(zhp, clp->cl_prop, property,520sizeof (property), &sourcetype, where, sizeof (where),521B_FALSE) != 0) {522goto out;523}524525/*526* If we are "watching" sharenfs or sharesmb527* then check out the companion property which is tracked528* in cl_shareprop529*/530if (clp->cl_shareprop != ZPROP_INVAL &&531zfs_prop_get(zhp, clp->cl_shareprop, property,532sizeof (property), &share_sourcetype, where, sizeof (where),533B_FALSE) != 0) {534goto out;535}536537if (clp->cl_alldependents || clp->cl_allchildren ||538sourcetype == ZPROP_SRC_DEFAULT ||539sourcetype == ZPROP_SRC_INHERITED ||540(clp->cl_shareprop != ZPROP_INVAL &&541(share_sourcetype == ZPROP_SRC_DEFAULT ||542share_sourcetype == ZPROP_SRC_INHERITED))) {543cn = zfs_alloc(zfs_get_handle(zhp), sizeof (prop_changenode_t));544cn->cn_handle = zhp;545cn->cn_mounted = (clp->cl_gflags & CL_GATHER_MOUNT_ALWAYS) ||546zfs_is_mounted(zhp, NULL);547cn->cn_shared = zfs_is_shared(zhp, NULL, NULL);548cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);549cn->cn_needpost = B_TRUE;550551/* Indicate if any child is exported to a local zone. */552if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)553clp->cl_haszonedchild = B_TRUE;554555uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);556557uu_avl_index_t idx;558559if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {560uu_avl_insert(clp->cl_tree, cn, idx);561} else {562free(cn);563cn = NULL;564}565566if (!clp->cl_alldependents) {567if (clp->cl_prop != ZFS_PROP_MOUNTPOINT) {568ret = zfs_iter_filesystems_v2(zhp, 0,569change_one, data);570} else {571ret = zfs_iter_children_v2(zhp, 0, change_one,572data);573}574}575576/*577* If we added the handle to the changelist, we will re-use it578* later so return without closing it.579*/580if (cn != NULL)581return (ret);582}583584out:585zfs_close(zhp);586return (ret);587}588589static int590compare_props(const void *a, const void *b, zfs_prop_t prop)591{592const prop_changenode_t *ca = a;593const prop_changenode_t *cb = b;594595char propa[MAXPATHLEN];596char propb[MAXPATHLEN];597598boolean_t haspropa, haspropb;599600haspropa = (zfs_prop_get(ca->cn_handle, prop, propa, sizeof (propa),601NULL, NULL, 0, B_FALSE) == 0);602haspropb = (zfs_prop_get(cb->cn_handle, prop, propb, sizeof (propb),603NULL, NULL, 0, B_FALSE) == 0);604605if (!haspropa && haspropb)606return (-1);607else if (haspropa && !haspropb)608return (1);609else if (!haspropa && !haspropb)610return (0);611else612return (strcmp(propb, propa));613}614615static int616compare_mountpoints(const void *a, const void *b, void *unused)617{618/*619* When unsharing or unmounting filesystems, we need to do it in620* mountpoint order. This allows the user to have a mountpoint621* hierarchy that is different from the dataset hierarchy, and still622* allow it to be changed.623*/624(void) unused;625return (compare_props(a, b, ZFS_PROP_MOUNTPOINT));626}627628static int629compare_dataset_names(const void *a, const void *b, void *unused)630{631(void) unused;632return (compare_props(a, b, ZFS_PROP_NAME));633}634635/*636* Given a ZFS handle and a property, construct a complete list of datasets637* that need to be modified as part of this process. For anything but the638* 'mountpoint' and 'sharenfs' properties, this just returns an empty list.639* Otherwise, we iterate over all children and look for any datasets that640* inherit the property. For each such dataset, we add it to the list and641* mark whether it was shared beforehand.642*/643prop_changelist_t *644changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int gather_flags,645int mnt_flags)646{647prop_changelist_t *clp;648prop_changenode_t *cn;649zfs_handle_t *temp;650char property[ZFS_MAXPROPLEN];651boolean_t legacy = B_FALSE;652653clp = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changelist_t));654655/*656* For mountpoint-related tasks, we want to sort everything by657* mountpoint, so that we mount and unmount them in the appropriate658* order, regardless of their position in the hierarchy.659*/660if (prop == ZFS_PROP_NAME || prop == ZFS_PROP_ZONED ||661prop == ZFS_PROP_MOUNTPOINT || prop == ZFS_PROP_SHARENFS ||662prop == ZFS_PROP_SHARESMB) {663664if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT,665property, sizeof (property),666NULL, NULL, 0, B_FALSE) == 0 &&667(strcmp(property, "legacy") == 0 ||668strcmp(property, "none") == 0)) {669legacy = B_TRUE;670}671}672673clp->cl_pool = uu_avl_pool_create("changelist_pool",674sizeof (prop_changenode_t),675offsetof(prop_changenode_t, cn_treenode),676legacy ? compare_dataset_names : compare_mountpoints, 0);677if (clp->cl_pool == NULL) {678assert(uu_error() == UU_ERROR_NO_MEMORY);679(void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error");680changelist_free(clp);681return (NULL);682}683684clp->cl_tree = uu_avl_create(clp->cl_pool, NULL, UU_DEFAULT);685clp->cl_gflags = gather_flags;686clp->cl_mflags = mnt_flags;687688if (clp->cl_tree == NULL) {689assert(uu_error() == UU_ERROR_NO_MEMORY);690(void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error");691changelist_free(clp);692return (NULL);693}694695/*696* If this is a rename or the 'zoned' property, we pretend we're697* changing the mountpoint and flag it so we can catch all children in698* change_one().699*700* Flag cl_alldependents to catch all children plus the dependents701* (clones) that are not in the hierarchy.702*/703if (prop == ZFS_PROP_NAME) {704clp->cl_prop = ZFS_PROP_MOUNTPOINT;705clp->cl_alldependents = B_TRUE;706} else if (prop == ZFS_PROP_ZONED) {707clp->cl_prop = ZFS_PROP_MOUNTPOINT;708clp->cl_allchildren = B_TRUE;709} else if (prop == ZFS_PROP_CANMOUNT) {710clp->cl_prop = ZFS_PROP_MOUNTPOINT;711} else if (prop == ZFS_PROP_VOLSIZE) {712clp->cl_prop = ZFS_PROP_MOUNTPOINT;713} else {714clp->cl_prop = prop;715}716clp->cl_realprop = prop;717718if (clp->cl_prop != ZFS_PROP_MOUNTPOINT &&719clp->cl_prop != ZFS_PROP_SHARENFS &&720clp->cl_prop != ZFS_PROP_SHARESMB)721return (clp);722723/*724* If watching SHARENFS or SHARESMB then725* also watch its companion property.726*/727if (clp->cl_prop == ZFS_PROP_SHARENFS)728clp->cl_shareprop = ZFS_PROP_SHARESMB;729else if (clp->cl_prop == ZFS_PROP_SHARESMB)730clp->cl_shareprop = ZFS_PROP_SHARENFS;731732if (clp->cl_prop == ZFS_PROP_MOUNTPOINT &&733(clp->cl_gflags & CL_GATHER_ITER_MOUNTED)) {734/*735* Instead of iterating through all of the dataset children we736* gather mounted dataset children from MNTTAB737*/738if (zfs_iter_mounted(zhp, changelist_add_mounted, clp) != 0) {739changelist_free(clp);740return (NULL);741}742} else if (clp->cl_alldependents) {743if (zfs_iter_dependents_v2(zhp, 0, B_TRUE, change_one,744clp) != 0) {745changelist_free(clp);746return (NULL);747}748} else if (clp->cl_prop != ZFS_PROP_MOUNTPOINT) {749if (zfs_iter_filesystems_v2(zhp, 0, change_one, clp) != 0) {750changelist_free(clp);751return (NULL);752}753} else if (zfs_iter_children_v2(zhp, 0, change_one, clp) != 0) {754changelist_free(clp);755return (NULL);756}757758/*759* We have to re-open ourselves because we auto-close all the handles760* and can't tell the difference.761*/762if ((temp = zfs_open(zhp->zfs_hdl, zfs_get_name(zhp),763ZFS_TYPE_DATASET)) == NULL) {764changelist_free(clp);765return (NULL);766}767768/*769* Always add ourself to the list. We add ourselves to the end so that770* we're the last to be unmounted.771*/772cn = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changenode_t));773cn->cn_handle = temp;774cn->cn_mounted = (clp->cl_gflags & CL_GATHER_MOUNT_ALWAYS) ||775zfs_is_mounted(temp, NULL);776cn->cn_shared = zfs_is_shared(temp, NULL, NULL);777cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);778cn->cn_needpost = B_TRUE;779780uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);781uu_avl_index_t idx;782if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {783uu_avl_insert(clp->cl_tree, cn, idx);784} else {785free(cn);786zfs_close(temp);787}788789/*790* If the mountpoint property was previously 'legacy', or 'none',791* record it as the behavior of changelist_postfix() will be different.792*/793if ((clp->cl_prop == ZFS_PROP_MOUNTPOINT) && legacy) {794/*795* do not automatically mount ex-legacy datasets if796* we specifically set canmount to noauto797*/798if (zfs_prop_get_int(zhp, ZFS_PROP_CANMOUNT) !=799ZFS_CANMOUNT_NOAUTO)800clp->cl_waslegacy = B_TRUE;801}802803return (clp);804}805806807