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