1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * 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.LICENSE
9 * or https://opensource.org/licenses/CDDL-1.0.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 *
26 * Portions Copyright 2007 Ramprakash Jelari
27 * Copyright (c) 2014, 2020 by Delphix. All rights reserved.
28 * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
29 * Copyright (c) 2018 Datto Inc.
30 */
31
32#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>
39
40#include <libzfs.h>
41
42#include "libzfs_impl.h"
43
44/*
45 * Structure to keep track of dataset state.  Before changing the 'sharenfs' or
46 * 'mountpoint' property, we record whether the filesystem was previously
47 * mounted/shared.  This prior state dictates whether we remount/reshare the
48 * 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 list
61 * changelist_rename() - renames all datasets appropriately when doing a rename
62 * changelist_unshare() - unshares all the nodes in a given changelist
63 * changelist_haszonedchild() - check if there is any child exported to
64 *				a local zone
65 */
66typedef struct prop_changenode {
67	zfs_handle_t		*cn_handle;
68	int			cn_shared;
69	int			cn_mounted;
70	int			cn_zoned;
71	boolean_t		cn_needpost;	/* is postfix() needed? */
72	uu_avl_node_t		cn_treenode;
73} prop_changenode_t;
74
75struct prop_changelist {
76	zfs_prop_t		cl_prop;
77	zfs_prop_t		cl_realprop;
78	zfs_prop_t		cl_shareprop;  /* used with sharenfs/sharesmb */
79	uu_avl_pool_t		*cl_pool;
80	uu_avl_t		*cl_tree;
81	boolean_t		cl_waslegacy;
82	boolean_t		cl_allchildren;
83	boolean_t		cl_alldependents;
84	int			cl_mflags;	/* Mount flags */
85	int			cl_gflags;	/* Gather request flags */
86	boolean_t		cl_haszonedchild;
87};
88
89/*
90 * If the property is 'mountpoint', go through and unmount filesystems as
91 * necessary.  We don't do the same for 'sharenfs', because we can just re-share
92 * with different options without interrupting service. We do handle 'sharesmb'
93 * since there may be old resource names that need to be removed.
94 */
95int
96changelist_prefix(prop_changelist_t *clp)
97{
98	prop_changenode_t *cn;
99	uu_avl_walk_t *walk;
100	int ret = 0;
101	const enum sa_protocol smb[] = {SA_PROTOCOL_SMB, SA_NO_PROTOCOL};
102	boolean_t commit_smb_shares = B_FALSE;
103
104	if (clp->cl_prop != ZFS_PROP_MOUNTPOINT &&
105	    clp->cl_prop != ZFS_PROP_SHARESMB)
106		return (0);
107
108	/*
109	 * If CL_GATHER_DONT_UNMOUNT is set, don't want to unmount/unshare and
110	 * later (re)mount/(re)share the filesystem in postfix phase, so we
111	 * return from here. If filesystem is mounted or unmounted, leave it
112	 * as it is.
113	 */
114	if (clp->cl_gflags & CL_GATHER_DONT_UNMOUNT)
115		return (0);
116
117	if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
118		return (-1);
119
120	while ((cn = uu_avl_walk_next(walk)) != NULL) {
121
122		/* if a previous loop failed, set the remaining to false */
123		if (ret == -1) {
124			cn->cn_needpost = B_FALSE;
125			continue;
126		}
127
128		/*
129		 * If we are in the global zone, but this dataset is exported
130		 * to a local zone, do nothing.
131		 */
132		if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)
133			continue;
134
135		if (!ZFS_IS_VOLUME(cn->cn_handle)) {
136			/*
137			 * Do the property specific processing.
138			 */
139			switch (clp->cl_prop) {
140			case ZFS_PROP_MOUNTPOINT:
141				if (zfs_unmount(cn->cn_handle, NULL,
142				    clp->cl_mflags) != 0) {
143					ret = -1;
144					cn->cn_needpost = B_FALSE;
145				}
146				break;
147			case ZFS_PROP_SHARESMB:
148				(void) zfs_unshare(cn->cn_handle, NULL,
149				    smb);
150				commit_smb_shares = B_TRUE;
151				break;
152
153			default:
154				break;
155			}
156		}
157	}
158
159	if (commit_smb_shares)
160		zfs_commit_shares(smb);
161	uu_avl_walk_end(walk);
162
163	if (ret == -1)
164		(void) changelist_postfix(clp);
165
166	return (ret);
167}
168
169/*
170 * If the property is 'mountpoint' or 'sharenfs', go through and remount and/or
171 * reshare the filesystems as necessary.  In changelist_gather() we recorded
172 * whether the filesystem was previously shared or mounted.  The action we take
173 * 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 */
177int
178changelist_postfix(prop_changelist_t *clp)
179{
180	prop_changenode_t *cn;
181	uu_avl_walk_t *walk;
182	char shareopts[ZFS_MAXPROPLEN];
183	boolean_t commit_smb_shares = B_FALSE;
184	boolean_t commit_nfs_shares = B_FALSE;
185
186	/*
187	 * If CL_GATHER_DONT_UNMOUNT is set, it means we don't want to (un)mount
188	 * or (re/un)share the filesystem, so we return from here. If filesystem
189	 * is mounted or unmounted, leave it as it is.
190	 */
191	if (clp->cl_gflags & CL_GATHER_DONT_UNMOUNT)
192		return (0);
193
194	/*
195	 * If we're changing the mountpoint, attempt to destroy the underlying
196	 * mountpoint.  All other datasets will have inherited from this dataset
197	 * (in which case their mountpoints exist in the filesystem in the new
198	 * location), or have explicit mountpoints set (in which case they won't
199	 * be in the changelist).
200	 */
201	if ((cn = uu_avl_last(clp->cl_tree)) == NULL)
202		return (0);
203
204	if (clp->cl_prop == ZFS_PROP_MOUNTPOINT &&
205	    !(clp->cl_gflags & CL_GATHER_DONT_UNMOUNT))
206		remove_mountpoint(cn->cn_handle);
207
208	/*
209	 * We walk the datasets in reverse, because we want to mount any parent
210	 * datasets before mounting the children.  We walk all datasets even if
211	 * there are errors.
212	 */
213	if ((walk = uu_avl_walk_start(clp->cl_tree,
214	    UU_WALK_REVERSE | UU_WALK_ROBUST)) == NULL)
215		return (-1);
216
217	while ((cn = uu_avl_walk_next(walk)) != NULL) {
218
219		boolean_t sharenfs;
220		boolean_t sharesmb;
221		boolean_t mounted;
222		boolean_t needs_key;
223
224		/*
225		 * If we are in the global zone, but this dataset is exported
226		 * to a local zone, do nothing.
227		 */
228		if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)
229			continue;
230
231		/* Only do post-processing if it's required */
232		if (!cn->cn_needpost)
233			continue;
234		cn->cn_needpost = B_FALSE;
235
236		zfs_refresh_properties(cn->cn_handle);
237
238		if (ZFS_IS_VOLUME(cn->cn_handle))
239			continue;
240
241		/*
242		 * Remount if previously mounted or mountpoint was legacy,
243		 * or sharenfs or sharesmb  property is set.
244		 */
245		sharenfs = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARENFS,
246		    shareopts, sizeof (shareopts), NULL, NULL, 0,
247		    B_FALSE) == 0) && (strcmp(shareopts, "off") != 0));
248
249		sharesmb = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARESMB,
250		    shareopts, sizeof (shareopts), NULL, NULL, 0,
251		    B_FALSE) == 0) && (strcmp(shareopts, "off") != 0));
252
253		needs_key = (zfs_prop_get_int(cn->cn_handle,
254		    ZFS_PROP_KEYSTATUS) == ZFS_KEYSTATUS_UNAVAILABLE);
255
256		mounted = zfs_is_mounted(cn->cn_handle, NULL);
257
258		if (!mounted && !needs_key && (cn->cn_mounted ||
259		    (((clp->cl_prop == ZFS_PROP_MOUNTPOINT &&
260		    clp->cl_prop == clp->cl_realprop) ||
261		    sharenfs || sharesmb || clp->cl_waslegacy) &&
262		    (zfs_prop_get_int(cn->cn_handle,
263		    ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_ON)))) {
264
265			if (zfs_mount(cn->cn_handle, NULL, 0) == 0)
266				mounted = TRUE;
267		}
268
269		/*
270		 * If the file system is mounted we always re-share even
271		 * if the filesystem is currently shared, so that we can
272		 * adopt any new options.
273		 */
274		const enum sa_protocol nfs[] =
275		    {SA_PROTOCOL_NFS, SA_NO_PROTOCOL};
276		if (sharenfs && mounted) {
277			zfs_share(cn->cn_handle, nfs);
278			commit_nfs_shares = B_TRUE;
279		} else if (cn->cn_shared || clp->cl_waslegacy) {
280			zfs_unshare(cn->cn_handle, NULL, nfs);
281			commit_nfs_shares = B_TRUE;
282		}
283		const enum sa_protocol smb[] =
284		    {SA_PROTOCOL_SMB, SA_NO_PROTOCOL};
285		if (sharesmb && mounted) {
286			zfs_share(cn->cn_handle, smb);
287			commit_smb_shares = B_TRUE;
288		} else if (cn->cn_shared || clp->cl_waslegacy) {
289			zfs_unshare(cn->cn_handle, NULL, smb);
290			commit_smb_shares = B_TRUE;
291		}
292	}
293
294	enum sa_protocol proto[SA_PROTOCOL_COUNT + 1], *p = proto;
295	if (commit_nfs_shares)
296		*p++ = SA_PROTOCOL_NFS;
297	if (commit_smb_shares)
298		*p++ = SA_PROTOCOL_SMB;
299	*p++ = SA_NO_PROTOCOL;
300	zfs_commit_shares(proto);
301	uu_avl_walk_end(walk);
302
303	return (0);
304}
305
306/*
307 * Is this "dataset" a child of "parent"?
308 */
309static boolean_t
310isa_child_of(const char *dataset, const char *parent)
311{
312	int len;
313
314	len = strlen(parent);
315
316	if (strncmp(dataset, parent, len) == 0 &&
317	    (dataset[len] == '@' || dataset[len] == '/' ||
318	    dataset[len] == '\0'))
319		return (B_TRUE);
320	else
321		return (B_FALSE);
322
323}
324
325/*
326 * If we rename a filesystem, child filesystem handles are no longer valid
327 * since we identify each dataset by its name in the ZFS namespace.  As a
328 * result, we have to go through and fix up all the names appropriately.  We
329 * could do this automatically if libzfs kept track of all open handles, but
330 * this is a lot less work.
331 */
332void
333changelist_rename(prop_changelist_t *clp, const char *src, const char *dst)
334{
335	prop_changenode_t *cn;
336	uu_avl_walk_t *walk;
337	char newname[ZFS_MAX_DATASET_NAME_LEN];
338
339	if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
340		return;
341
342	while ((cn = uu_avl_walk_next(walk)) != NULL) {
343		/*
344		 * Do not rename a clone that's not in the source hierarchy.
345		 */
346		if (!isa_child_of(cn->cn_handle->zfs_name, src))
347			continue;
348
349		/*
350		 * Destroy the previous mountpoint if needed.
351		 */
352		remove_mountpoint(cn->cn_handle);
353
354		(void) strlcpy(newname, dst, sizeof (newname));
355		(void) strlcat(newname, cn->cn_handle->zfs_name + strlen(src),
356		    sizeof (newname));
357
358		(void) strlcpy(cn->cn_handle->zfs_name, newname,
359		    sizeof (cn->cn_handle->zfs_name));
360	}
361
362	uu_avl_walk_end(walk);
363}
364
365/*
366 * Given a gathered changelist for the 'sharenfs' or 'sharesmb' property,
367 * unshare all the datasets in the list.
368 */
369int
370changelist_unshare(prop_changelist_t *clp, const enum sa_protocol *proto)
371{
372	prop_changenode_t *cn;
373	uu_avl_walk_t *walk;
374	int ret = 0;
375
376	if (clp->cl_prop != ZFS_PROP_SHARENFS &&
377	    clp->cl_prop != ZFS_PROP_SHARESMB)
378		return (0);
379
380	if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
381		return (-1);
382
383	while ((cn = uu_avl_walk_next(walk)) != NULL) {
384		if (zfs_unshare(cn->cn_handle, NULL, proto) != 0)
385			ret = -1;
386	}
387
388	for (const enum sa_protocol *p = proto; *p != SA_NO_PROTOCOL; ++p)
389		sa_commit_shares(*p);
390	uu_avl_walk_end(walk);
391
392	return (ret);
393}
394
395/*
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 changelist
398 * via changelist_gather().
399 */
400int
401changelist_haszonedchild(prop_changelist_t *clp)
402{
403	return (clp->cl_haszonedchild);
404}
405
406/*
407 * Remove a node from a gathered list.
408 */
409void
410changelist_remove(prop_changelist_t *clp, const char *name)
411{
412	prop_changenode_t *cn;
413	uu_avl_walk_t *walk;
414
415	if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
416		return;
417
418	while ((cn = uu_avl_walk_next(walk)) != NULL) {
419		if (strcmp(cn->cn_handle->zfs_name, name) == 0) {
420			uu_avl_remove(clp->cl_tree, cn);
421			zfs_close(cn->cn_handle);
422			free(cn);
423			uu_avl_walk_end(walk);
424			return;
425		}
426	}
427
428	uu_avl_walk_end(walk);
429}
430
431/*
432 * Release any memory associated with a changelist.
433 */
434void
435changelist_free(prop_changelist_t *clp)
436{
437	prop_changenode_t *cn;
438
439	if (clp->cl_tree) {
440		uu_avl_walk_t *walk;
441
442		if ((walk = uu_avl_walk_start(clp->cl_tree,
443		    UU_WALK_ROBUST)) == NULL)
444			return;
445
446		while ((cn = uu_avl_walk_next(walk)) != NULL) {
447			uu_avl_remove(clp->cl_tree, cn);
448			zfs_close(cn->cn_handle);
449			free(cn);
450		}
451
452		uu_avl_walk_end(walk);
453		uu_avl_destroy(clp->cl_tree);
454	}
455	if (clp->cl_pool)
456		uu_avl_pool_destroy(clp->cl_pool);
457
458	free(clp);
459}
460
461/*
462 * Add one dataset to changelist
463 */
464static int
465changelist_add_mounted(zfs_handle_t *zhp, void *data)
466{
467	prop_changelist_t *clp = data;
468	prop_changenode_t *cn;
469	uu_avl_index_t idx;
470
471	ASSERT3U(clp->cl_prop, ==, ZFS_PROP_MOUNTPOINT);
472
473	cn = zfs_alloc(zfs_get_handle(zhp), sizeof (prop_changenode_t));
474	cn->cn_handle = zhp;
475	cn->cn_mounted = zfs_is_mounted(zhp, NULL);
476	ASSERT3U(cn->cn_mounted, ==, B_TRUE);
477	cn->cn_shared = zfs_is_shared(zhp, NULL, NULL);
478	cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);
479	cn->cn_needpost = B_TRUE;
480
481	/* Indicate if any child is exported to a local zone. */
482	if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)
483		clp->cl_haszonedchild = B_TRUE;
484
485	uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);
486
487	if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {
488		uu_avl_insert(clp->cl_tree, cn, idx);
489	} else {
490		free(cn);
491		zfs_close(zhp);
492	}
493
494	return (0);
495}
496
497static int
498change_one(zfs_handle_t *zhp, void *data)
499{
500	prop_changelist_t *clp = data;
501	char property[ZFS_MAXPROPLEN];
502	char where[64];
503	prop_changenode_t *cn = NULL;
504	zprop_source_t sourcetype = ZPROP_SRC_NONE;
505	zprop_source_t share_sourcetype = ZPROP_SRC_NONE;
506	int ret = 0;
507
508	/*
509	 * We only want to unmount/unshare those filesystems that may inherit
510	 * from the target filesystem.  If we find any filesystem with a
511	 * locally set mountpoint, we ignore any children since changing the
512	 * property will not affect them.  If this is a rename, we iterate
513	 * over all children regardless, since we need them unmounted in
514	 * order to do the rename.  Also, if this is a volume and we're doing
515	 * a rename, then always add it to the changelist.
516	 */
517
518	if (!(ZFS_IS_VOLUME(zhp) && clp->cl_realprop == ZFS_PROP_NAME) &&
519	    zfs_prop_get(zhp, clp->cl_prop, property,
520	    sizeof (property), &sourcetype, where, sizeof (where),
521	    B_FALSE) != 0) {
522		goto out;
523	}
524
525	/*
526	 * If we are "watching" sharenfs or sharesmb
527	 * then check out the companion property which is tracked
528	 * in cl_shareprop
529	 */
530	if (clp->cl_shareprop != ZPROP_INVAL &&
531	    zfs_prop_get(zhp, clp->cl_shareprop, property,
532	    sizeof (property), &share_sourcetype, where, sizeof (where),
533	    B_FALSE) != 0) {
534		goto out;
535	}
536
537	if (clp->cl_alldependents || clp->cl_allchildren ||
538	    sourcetype == ZPROP_SRC_DEFAULT ||
539	    sourcetype == ZPROP_SRC_INHERITED ||
540	    (clp->cl_shareprop != ZPROP_INVAL &&
541	    (share_sourcetype == ZPROP_SRC_DEFAULT ||
542	    share_sourcetype == ZPROP_SRC_INHERITED))) {
543		cn = zfs_alloc(zfs_get_handle(zhp), sizeof (prop_changenode_t));
544		cn->cn_handle = zhp;
545		cn->cn_mounted = (clp->cl_gflags & CL_GATHER_MOUNT_ALWAYS) ||
546		    zfs_is_mounted(zhp, NULL);
547		cn->cn_shared = zfs_is_shared(zhp, NULL, NULL);
548		cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);
549		cn->cn_needpost = B_TRUE;
550
551		/* Indicate if any child is exported to a local zone. */
552		if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)
553			clp->cl_haszonedchild = B_TRUE;
554
555		uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);
556
557		uu_avl_index_t idx;
558
559		if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {
560			uu_avl_insert(clp->cl_tree, cn, idx);
561		} else {
562			free(cn);
563			cn = NULL;
564		}
565
566		if (!clp->cl_alldependents)
567			ret = zfs_iter_children_v2(zhp, 0, change_one, data);
568
569		/*
570		 * If we added the handle to the changelist, we will re-use it
571		 * later so return without closing it.
572		 */
573		if (cn != NULL)
574			return (ret);
575	}
576
577out:
578	zfs_close(zhp);
579	return (ret);
580}
581
582static int
583compare_props(const void *a, const void *b, zfs_prop_t prop)
584{
585	const prop_changenode_t *ca = a;
586	const prop_changenode_t *cb = b;
587
588	char propa[MAXPATHLEN];
589	char propb[MAXPATHLEN];
590
591	boolean_t haspropa, haspropb;
592
593	haspropa = (zfs_prop_get(ca->cn_handle, prop, propa, sizeof (propa),
594	    NULL, NULL, 0, B_FALSE) == 0);
595	haspropb = (zfs_prop_get(cb->cn_handle, prop, propb, sizeof (propb),
596	    NULL, NULL, 0, B_FALSE) == 0);
597
598	if (!haspropa && haspropb)
599		return (-1);
600	else if (haspropa && !haspropb)
601		return (1);
602	else if (!haspropa && !haspropb)
603		return (0);
604	else
605		return (strcmp(propb, propa));
606}
607
608static int
609compare_mountpoints(const void *a, const void *b, void *unused)
610{
611	/*
612	 * When unsharing or unmounting filesystems, we need to do it in
613	 * mountpoint order.  This allows the user to have a mountpoint
614	 * hierarchy that is different from the dataset hierarchy, and still
615	 * allow it to be changed.
616	 */
617	(void) unused;
618	return (compare_props(a, b, ZFS_PROP_MOUNTPOINT));
619}
620
621static int
622compare_dataset_names(const void *a, const void *b, void *unused)
623{
624	(void) unused;
625	return (compare_props(a, b, ZFS_PROP_NAME));
626}
627
628/*
629 * Given a ZFS handle and a property, construct a complete list of datasets
630 * that need to be modified as part of this process.  For anything but the
631 * 'mountpoint' and 'sharenfs' properties, this just returns an empty list.
632 * Otherwise, we iterate over all children and look for any datasets that
633 * inherit the property.  For each such dataset, we add it to the list and
634 * mark whether it was shared beforehand.
635 */
636prop_changelist_t *
637changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int gather_flags,
638    int mnt_flags)
639{
640	prop_changelist_t *clp;
641	prop_changenode_t *cn;
642	zfs_handle_t *temp;
643	char property[ZFS_MAXPROPLEN];
644	boolean_t legacy = B_FALSE;
645
646	clp = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changelist_t));
647
648	/*
649	 * For mountpoint-related tasks, we want to sort everything by
650	 * mountpoint, so that we mount and unmount them in the appropriate
651	 * order, regardless of their position in the hierarchy.
652	 */
653	if (prop == ZFS_PROP_NAME || prop == ZFS_PROP_ZONED ||
654	    prop == ZFS_PROP_MOUNTPOINT || prop == ZFS_PROP_SHARENFS ||
655	    prop == ZFS_PROP_SHARESMB) {
656
657		if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT,
658		    property, sizeof (property),
659		    NULL, NULL, 0, B_FALSE) == 0 &&
660		    (strcmp(property, "legacy") == 0 ||
661		    strcmp(property, "none") == 0)) {
662			legacy = B_TRUE;
663		}
664	}
665
666	clp->cl_pool = uu_avl_pool_create("changelist_pool",
667	    sizeof (prop_changenode_t),
668	    offsetof(prop_changenode_t, cn_treenode),
669	    legacy ? compare_dataset_names : compare_mountpoints, 0);
670	if (clp->cl_pool == NULL) {
671		assert(uu_error() == UU_ERROR_NO_MEMORY);
672		(void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error");
673		changelist_free(clp);
674		return (NULL);
675	}
676
677	clp->cl_tree = uu_avl_create(clp->cl_pool, NULL, UU_DEFAULT);
678	clp->cl_gflags = gather_flags;
679	clp->cl_mflags = mnt_flags;
680
681	if (clp->cl_tree == NULL) {
682		assert(uu_error() == UU_ERROR_NO_MEMORY);
683		(void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error");
684		changelist_free(clp);
685		return (NULL);
686	}
687
688	/*
689	 * If this is a rename or the 'zoned' property, we pretend we're
690	 * changing the mountpoint and flag it so we can catch all children in
691	 * change_one().
692	 *
693	 * Flag cl_alldependents to catch all children plus the dependents
694	 * (clones) that are not in the hierarchy.
695	 */
696	if (prop == ZFS_PROP_NAME) {
697		clp->cl_prop = ZFS_PROP_MOUNTPOINT;
698		clp->cl_alldependents = B_TRUE;
699	} else if (prop == ZFS_PROP_ZONED) {
700		clp->cl_prop = ZFS_PROP_MOUNTPOINT;
701		clp->cl_allchildren = B_TRUE;
702	} else if (prop == ZFS_PROP_CANMOUNT) {
703		clp->cl_prop = ZFS_PROP_MOUNTPOINT;
704	} else if (prop == ZFS_PROP_VOLSIZE) {
705		clp->cl_prop = ZFS_PROP_MOUNTPOINT;
706	} else {
707		clp->cl_prop = prop;
708	}
709	clp->cl_realprop = prop;
710
711	if (clp->cl_prop != ZFS_PROP_MOUNTPOINT &&
712	    clp->cl_prop != ZFS_PROP_SHARENFS &&
713	    clp->cl_prop != ZFS_PROP_SHARESMB)
714		return (clp);
715
716	/*
717	 * If watching SHARENFS or SHARESMB then
718	 * also watch its companion property.
719	 */
720	if (clp->cl_prop == ZFS_PROP_SHARENFS)
721		clp->cl_shareprop = ZFS_PROP_SHARESMB;
722	else if (clp->cl_prop == ZFS_PROP_SHARESMB)
723		clp->cl_shareprop = ZFS_PROP_SHARENFS;
724
725	if (clp->cl_prop == ZFS_PROP_MOUNTPOINT &&
726	    (clp->cl_gflags & CL_GATHER_ITER_MOUNTED)) {
727		/*
728		 * Instead of iterating through all of the dataset children we
729		 * gather mounted dataset children from MNTTAB
730		 */
731		if (zfs_iter_mounted(zhp, changelist_add_mounted, clp) != 0) {
732			changelist_free(clp);
733			return (NULL);
734		}
735	} else if (clp->cl_alldependents) {
736		if (zfs_iter_dependents_v2(zhp, 0, B_TRUE, change_one,
737		    clp) != 0) {
738			changelist_free(clp);
739			return (NULL);
740		}
741	} else if (zfs_iter_children_v2(zhp, 0, change_one, clp) != 0) {
742		changelist_free(clp);
743		return (NULL);
744	}
745
746	/*
747	 * We have to re-open ourselves because we auto-close all the handles
748	 * and can't tell the difference.
749	 */
750	if ((temp = zfs_open(zhp->zfs_hdl, zfs_get_name(zhp),
751	    ZFS_TYPE_DATASET)) == NULL) {
752		changelist_free(clp);
753		return (NULL);
754	}
755
756	/*
757	 * Always add ourself to the list.  We add ourselves to the end so that
758	 * we're the last to be unmounted.
759	 */
760	cn = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changenode_t));
761	cn->cn_handle = temp;
762	cn->cn_mounted = (clp->cl_gflags & CL_GATHER_MOUNT_ALWAYS) ||
763	    zfs_is_mounted(temp, NULL);
764	cn->cn_shared = zfs_is_shared(temp, NULL, NULL);
765	cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);
766	cn->cn_needpost = B_TRUE;
767
768	uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);
769	uu_avl_index_t idx;
770	if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {
771		uu_avl_insert(clp->cl_tree, cn, idx);
772	} else {
773		free(cn);
774		zfs_close(temp);
775	}
776
777	/*
778	 * If the mountpoint property was previously 'legacy', or 'none',
779	 * record it as the behavior of changelist_postfix() will be different.
780	 */
781	if ((clp->cl_prop == ZFS_PROP_MOUNTPOINT) && legacy) {
782		/*
783		 * do not automatically mount ex-legacy datasets if
784		 * we specifically set canmount to noauto
785		 */
786		if (zfs_prop_get_int(zhp, ZFS_PROP_CANMOUNT) !=
787		    ZFS_CANMOUNT_NOAUTO)
788			clp->cl_waslegacy = B_TRUE;
789	}
790
791	return (clp);
792}
793