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