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.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
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]
23 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
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.
42 #include "libzfs_impl.h"
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.
50 * The interface consists of the following sequence of functions:
55 * changelist_postfix()
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
66 typedef struct prop_changenode {
67 zfs_handle_t *cn_handle;
71 boolean_t cn_needpost; /* is postfix() needed? */
72 uu_avl_node_t cn_treenode;
75 struct prop_changelist {
77 zfs_prop_t cl_realprop;
78 zfs_prop_t cl_shareprop; /* used with sharenfs/sharesmb */
79 uu_avl_pool_t *cl_pool;
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;
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.
96 changelist_prefix(prop_changelist_t *clp)
98 prop_changenode_t *cn;
101 boolean_t commit_smb_shares = B_FALSE;
103 if (clp->cl_prop != ZFS_PROP_MOUNTPOINT &&
104 clp->cl_prop != ZFS_PROP_SHARESMB)
107 if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
110 while ((cn = uu_avl_walk_next(walk)) != NULL) {
112 /* if a previous loop failed, set the remaining to false */
114 cn->cn_needpost = B_FALSE;
119 * If we are in the global zone, but this dataset is exported
120 * to a local zone, do nothing.
122 if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)
125 if (!ZFS_IS_VOLUME(cn->cn_handle)) {
127 * Do the property specific processing.
129 switch (clp->cl_prop) {
130 case ZFS_PROP_MOUNTPOINT:
131 if (clp->cl_gflags & CL_GATHER_DONT_UNMOUNT)
133 if (zfs_unmount(cn->cn_handle, NULL,
134 clp->cl_mflags) != 0) {
136 cn->cn_needpost = B_FALSE;
139 case ZFS_PROP_SHARESMB:
140 (void) zfs_unshare_smb(cn->cn_handle, NULL);
141 commit_smb_shares = B_TRUE;
150 if (commit_smb_shares)
151 zfs_commit_smb_shares();
152 uu_avl_walk_end(walk);
155 (void) changelist_postfix(clp);
161 * If the property is 'mountpoint' or 'sharenfs', go through and remount and/or
162 * reshare the filesystems as necessary. In changelist_gather() we recorded
163 * whether the filesystem was previously shared or mounted. The action we take
164 * depends on the previous state, and whether the value was previously 'legacy'.
165 * For non-legacy properties, we only remount/reshare the filesystem if it was
166 * previously mounted/shared. Otherwise, we always remount/reshare the
170 changelist_postfix(prop_changelist_t *clp)
172 prop_changenode_t *cn;
174 char shareopts[ZFS_MAXPROPLEN];
176 boolean_t commit_smb_shares = B_FALSE;
177 boolean_t commit_nfs_shares = B_FALSE;
180 * If we're changing the mountpoint, attempt to destroy the underlying
181 * mountpoint. All other datasets will have inherited from this dataset
182 * (in which case their mountpoints exist in the filesystem in the new
183 * location), or have explicit mountpoints set (in which case they won't
184 * be in the changelist).
186 if ((cn = uu_avl_last(clp->cl_tree)) == NULL)
189 if (clp->cl_prop == ZFS_PROP_MOUNTPOINT &&
190 !(clp->cl_gflags & CL_GATHER_DONT_UNMOUNT))
191 remove_mountpoint(cn->cn_handle);
194 * We walk the datasets in reverse, because we want to mount any parent
195 * datasets before mounting the children. We walk all datasets even if
198 if ((walk = uu_avl_walk_start(clp->cl_tree,
199 UU_WALK_REVERSE | UU_WALK_ROBUST)) == NULL)
202 while ((cn = uu_avl_walk_next(walk)) != NULL) {
210 * If we are in the global zone, but this dataset is exported
211 * to a local zone, do nothing.
213 if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)
216 /* Only do post-processing if it's required */
217 if (!cn->cn_needpost)
219 cn->cn_needpost = B_FALSE;
221 zfs_refresh_properties(cn->cn_handle);
223 if (ZFS_IS_VOLUME(cn->cn_handle))
227 * Remount if previously mounted or mountpoint was legacy,
228 * or sharenfs or sharesmb property is set.
230 sharenfs = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARENFS,
231 shareopts, sizeof (shareopts), NULL, NULL, 0,
232 B_FALSE) == 0) && (strcmp(shareopts, "off") != 0));
234 sharesmb = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARESMB,
235 shareopts, sizeof (shareopts), NULL, NULL, 0,
236 B_FALSE) == 0) && (strcmp(shareopts, "off") != 0));
238 needs_key = (zfs_prop_get_int(cn->cn_handle,
239 ZFS_PROP_KEYSTATUS) == ZFS_KEYSTATUS_UNAVAILABLE);
241 mounted = (clp->cl_gflags & CL_GATHER_DONT_UNMOUNT) ||
242 zfs_is_mounted(cn->cn_handle, NULL);
244 if (!mounted && !needs_key && (cn->cn_mounted ||
245 ((sharenfs || sharesmb || clp->cl_waslegacy) &&
246 (zfs_prop_get_int(cn->cn_handle,
247 ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_ON)))) {
249 if (zfs_mount(cn->cn_handle, NULL, 0) != 0)
256 * If the file system is mounted we always re-share even
257 * if the filesystem is currently shared, so that we can
258 * adopt any new options.
260 if (sharenfs && mounted) {
261 errors += zfs_share_nfs(cn->cn_handle);
262 commit_nfs_shares = B_TRUE;
263 } else if (cn->cn_shared || clp->cl_waslegacy) {
264 errors += zfs_unshare_nfs(cn->cn_handle, NULL);
265 commit_nfs_shares = B_TRUE;
267 if (sharesmb && mounted) {
268 errors += zfs_share_smb(cn->cn_handle);
269 commit_smb_shares = B_TRUE;
270 } else if (cn->cn_shared || clp->cl_waslegacy) {
271 errors += zfs_unshare_smb(cn->cn_handle, NULL);
272 commit_smb_shares = B_TRUE;
275 if (commit_nfs_shares)
276 zfs_commit_nfs_shares();
277 if (commit_smb_shares)
278 zfs_commit_smb_shares();
279 uu_avl_walk_end(walk);
281 return (errors ? -1 : 0);
285 * Is this "dataset" a child of "parent"?
288 isa_child_of(const char *dataset, const char *parent)
292 len = strlen(parent);
294 if (strncmp(dataset, parent, len) == 0 &&
295 (dataset[len] == '@' || dataset[len] == '/' ||
296 dataset[len] == '\0'))
304 * If we rename a filesystem, child filesystem handles are no longer valid
305 * since we identify each dataset by its name in the ZFS namespace. As a
306 * result, we have to go through and fix up all the names appropriately. We
307 * could do this automatically if libzfs kept track of all open handles, but
308 * this is a lot less work.
311 changelist_rename(prop_changelist_t *clp, const char *src, const char *dst)
313 prop_changenode_t *cn;
315 char newname[ZFS_MAX_DATASET_NAME_LEN];
317 if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
320 while ((cn = uu_avl_walk_next(walk)) != NULL) {
322 * Do not rename a clone that's not in the source hierarchy.
324 if (!isa_child_of(cn->cn_handle->zfs_name, src))
328 * Destroy the previous mountpoint if needed.
330 remove_mountpoint(cn->cn_handle);
332 (void) strlcpy(newname, dst, sizeof (newname));
333 (void) strlcat(newname, cn->cn_handle->zfs_name + strlen(src),
336 (void) strlcpy(cn->cn_handle->zfs_name, newname,
337 sizeof (cn->cn_handle->zfs_name));
340 uu_avl_walk_end(walk);
344 * Given a gathered changelist for the 'sharenfs' or 'sharesmb' property,
345 * unshare all the datasets in the list.
348 changelist_unshare(prop_changelist_t *clp, zfs_share_proto_t *proto)
350 prop_changenode_t *cn;
354 if (clp->cl_prop != ZFS_PROP_SHARENFS &&
355 clp->cl_prop != ZFS_PROP_SHARESMB)
358 if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
361 while ((cn = uu_avl_walk_next(walk)) != NULL) {
362 if (zfs_unshare_proto(cn->cn_handle, NULL, proto) != 0)
366 zfs_commit_proto(proto);
367 uu_avl_walk_end(walk);
373 * Check if there is any child exported to a local zone in a given changelist.
374 * This information has already been recorded while gathering the changelist
375 * via changelist_gather().
378 changelist_haszonedchild(prop_changelist_t *clp)
380 return (clp->cl_haszonedchild);
384 * Remove a node from a gathered list.
387 changelist_remove(prop_changelist_t *clp, const char *name)
389 prop_changenode_t *cn;
392 if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
395 while ((cn = uu_avl_walk_next(walk)) != NULL) {
396 if (strcmp(cn->cn_handle->zfs_name, name) == 0) {
397 uu_avl_remove(clp->cl_tree, cn);
398 zfs_close(cn->cn_handle);
400 uu_avl_walk_end(walk);
405 uu_avl_walk_end(walk);
409 * Release any memory associated with a changelist.
412 changelist_free(prop_changelist_t *clp)
414 prop_changenode_t *cn;
419 if ((walk = uu_avl_walk_start(clp->cl_tree,
420 UU_WALK_ROBUST)) == NULL)
423 while ((cn = uu_avl_walk_next(walk)) != NULL) {
424 uu_avl_remove(clp->cl_tree, cn);
425 zfs_close(cn->cn_handle);
429 uu_avl_walk_end(walk);
430 uu_avl_destroy(clp->cl_tree);
433 uu_avl_pool_destroy(clp->cl_pool);
439 * Add one dataset to changelist
442 changelist_add_mounted(zfs_handle_t *zhp, void *data)
444 prop_changelist_t *clp = data;
445 prop_changenode_t *cn;
448 ASSERT3U(clp->cl_prop, ==, ZFS_PROP_MOUNTPOINT);
450 if ((cn = zfs_alloc(zfs_get_handle(zhp),
451 sizeof (prop_changenode_t))) == NULL) {
457 cn->cn_mounted = zfs_is_mounted(zhp, NULL);
458 ASSERT3U(cn->cn_mounted, ==, B_TRUE);
459 cn->cn_shared = zfs_is_shared(zhp);
460 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);
461 cn->cn_needpost = B_TRUE;
463 /* Indicate if any child is exported to a local zone. */
464 if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)
465 clp->cl_haszonedchild = B_TRUE;
467 uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);
469 if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {
470 uu_avl_insert(clp->cl_tree, cn, idx);
480 change_one(zfs_handle_t *zhp, void *data)
482 prop_changelist_t *clp = data;
483 char property[ZFS_MAXPROPLEN];
485 prop_changenode_t *cn = NULL;
486 zprop_source_t sourcetype = ZPROP_SRC_NONE;
487 zprop_source_t share_sourcetype = ZPROP_SRC_NONE;
491 * We only want to unmount/unshare those filesystems that may inherit
492 * from the target filesystem. If we find any filesystem with a
493 * locally set mountpoint, we ignore any children since changing the
494 * property will not affect them. If this is a rename, we iterate
495 * over all children regardless, since we need them unmounted in
496 * order to do the rename. Also, if this is a volume and we're doing
497 * a rename, then always add it to the changelist.
500 if (!(ZFS_IS_VOLUME(zhp) && clp->cl_realprop == ZFS_PROP_NAME) &&
501 zfs_prop_get(zhp, clp->cl_prop, property,
502 sizeof (property), &sourcetype, where, sizeof (where),
508 * If we are "watching" sharenfs or sharesmb
509 * then check out the companion property which is tracked
512 if (clp->cl_shareprop != ZPROP_INVAL &&
513 zfs_prop_get(zhp, clp->cl_shareprop, property,
514 sizeof (property), &share_sourcetype, where, sizeof (where),
519 if (clp->cl_alldependents || clp->cl_allchildren ||
520 sourcetype == ZPROP_SRC_DEFAULT ||
521 sourcetype == ZPROP_SRC_INHERITED ||
522 (clp->cl_shareprop != ZPROP_INVAL &&
523 (share_sourcetype == ZPROP_SRC_DEFAULT ||
524 share_sourcetype == ZPROP_SRC_INHERITED))) {
525 if ((cn = zfs_alloc(zfs_get_handle(zhp),
526 sizeof (prop_changenode_t))) == NULL) {
532 cn->cn_mounted = (clp->cl_gflags & CL_GATHER_MOUNT_ALWAYS) ||
533 zfs_is_mounted(zhp, NULL);
534 cn->cn_shared = zfs_is_shared(zhp);
535 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);
536 cn->cn_needpost = B_TRUE;
538 /* Indicate if any child is exported to a local zone. */
539 if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)
540 clp->cl_haszonedchild = B_TRUE;
542 uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);
546 if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {
547 uu_avl_insert(clp->cl_tree, cn, idx);
553 if (!clp->cl_alldependents)
554 ret = zfs_iter_children(zhp, change_one, data);
557 * If we added the handle to the changelist, we will re-use it
558 * later so return without closing it.
570 compare_props(const void *a, const void *b, zfs_prop_t prop)
572 const prop_changenode_t *ca = a;
573 const prop_changenode_t *cb = b;
575 char propa[MAXPATHLEN];
576 char propb[MAXPATHLEN];
578 boolean_t haspropa, haspropb;
580 haspropa = (zfs_prop_get(ca->cn_handle, prop, propa, sizeof (propa),
581 NULL, NULL, 0, B_FALSE) == 0);
582 haspropb = (zfs_prop_get(cb->cn_handle, prop, propb, sizeof (propb),
583 NULL, NULL, 0, B_FALSE) == 0);
585 if (!haspropa && haspropb)
587 else if (haspropa && !haspropb)
589 else if (!haspropa && !haspropb)
592 return (strcmp(propb, propa));
597 compare_mountpoints(const void *a, const void *b, void *unused)
600 * When unsharing or unmounting filesystems, we need to do it in
601 * mountpoint order. This allows the user to have a mountpoint
602 * hierarchy that is different from the dataset hierarchy, and still
603 * allow it to be changed.
605 return (compare_props(a, b, ZFS_PROP_MOUNTPOINT));
610 compare_dataset_names(const void *a, const void *b, void *unused)
612 return (compare_props(a, b, ZFS_PROP_NAME));
616 * Given a ZFS handle and a property, construct a complete list of datasets
617 * that need to be modified as part of this process. For anything but the
618 * 'mountpoint' and 'sharenfs' properties, this just returns an empty list.
619 * Otherwise, we iterate over all children and look for any datasets that
620 * inherit the property. For each such dataset, we add it to the list and
621 * mark whether it was shared beforehand.
624 changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int gather_flags,
627 prop_changelist_t *clp;
628 prop_changenode_t *cn;
630 char property[ZFS_MAXPROPLEN];
631 boolean_t legacy = B_FALSE;
633 if ((clp = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changelist_t))) == NULL)
637 * For mountpoint-related tasks, we want to sort everything by
638 * mountpoint, so that we mount and unmount them in the appropriate
639 * order, regardless of their position in the hierarchy.
641 if (prop == ZFS_PROP_NAME || prop == ZFS_PROP_ZONED ||
642 prop == ZFS_PROP_MOUNTPOINT || prop == ZFS_PROP_SHARENFS ||
643 prop == ZFS_PROP_SHARESMB) {
645 if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT,
646 property, sizeof (property),
647 NULL, NULL, 0, B_FALSE) == 0 &&
648 (strcmp(property, "legacy") == 0 ||
649 strcmp(property, "none") == 0)) {
654 clp->cl_pool = uu_avl_pool_create("changelist_pool",
655 sizeof (prop_changenode_t),
656 offsetof(prop_changenode_t, cn_treenode),
657 legacy ? compare_dataset_names : compare_mountpoints, 0);
658 if (clp->cl_pool == NULL) {
659 assert(uu_error() == UU_ERROR_NO_MEMORY);
660 (void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error");
661 changelist_free(clp);
665 clp->cl_tree = uu_avl_create(clp->cl_pool, NULL, UU_DEFAULT);
666 clp->cl_gflags = gather_flags;
667 clp->cl_mflags = mnt_flags;
669 if (clp->cl_tree == NULL) {
670 assert(uu_error() == UU_ERROR_NO_MEMORY);
671 (void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error");
672 changelist_free(clp);
677 * If this is a rename or the 'zoned' property, we pretend we're
678 * changing the mountpoint and flag it so we can catch all children in
681 * Flag cl_alldependents to catch all children plus the dependents
682 * (clones) that are not in the hierarchy.
684 if (prop == ZFS_PROP_NAME) {
685 clp->cl_prop = ZFS_PROP_MOUNTPOINT;
686 clp->cl_alldependents = B_TRUE;
687 } else if (prop == ZFS_PROP_ZONED) {
688 clp->cl_prop = ZFS_PROP_MOUNTPOINT;
689 clp->cl_allchildren = B_TRUE;
690 } else if (prop == ZFS_PROP_CANMOUNT) {
691 clp->cl_prop = ZFS_PROP_MOUNTPOINT;
692 } else if (prop == ZFS_PROP_VOLSIZE) {
693 clp->cl_prop = ZFS_PROP_MOUNTPOINT;
697 clp->cl_realprop = prop;
699 if (clp->cl_prop != ZFS_PROP_MOUNTPOINT &&
700 clp->cl_prop != ZFS_PROP_SHARENFS &&
701 clp->cl_prop != ZFS_PROP_SHARESMB)
705 * If watching SHARENFS or SHARESMB then
706 * also watch its companion property.
708 if (clp->cl_prop == ZFS_PROP_SHARENFS)
709 clp->cl_shareprop = ZFS_PROP_SHARESMB;
710 else if (clp->cl_prop == ZFS_PROP_SHARESMB)
711 clp->cl_shareprop = ZFS_PROP_SHARENFS;
713 if (clp->cl_prop == ZFS_PROP_MOUNTPOINT &&
714 (clp->cl_gflags & CL_GATHER_ITER_MOUNTED)) {
716 * Instead of iterating through all of the dataset children we
717 * gather mounted dataset children from MNTTAB
719 if (zfs_iter_mounted(zhp, changelist_add_mounted, clp) != 0) {
720 changelist_free(clp);
723 } else if (clp->cl_alldependents) {
724 if (zfs_iter_dependents(zhp, B_TRUE, change_one, clp) != 0) {
725 changelist_free(clp);
728 } else if (zfs_iter_children(zhp, change_one, clp) != 0) {
729 changelist_free(clp);
734 * We have to re-open ourselves because we auto-close all the handles
735 * and can't tell the difference.
737 if ((temp = zfs_open(zhp->zfs_hdl, zfs_get_name(zhp),
738 ZFS_TYPE_DATASET)) == NULL) {
739 changelist_free(clp);
744 * Always add ourself to the list. We add ourselves to the end so that
745 * we're the last to be unmounted.
747 if ((cn = zfs_alloc(zhp->zfs_hdl,
748 sizeof (prop_changenode_t))) == NULL) {
750 changelist_free(clp);
754 cn->cn_handle = temp;
755 cn->cn_mounted = (clp->cl_gflags & CL_GATHER_MOUNT_ALWAYS) ||
756 zfs_is_mounted(temp, NULL);
757 cn->cn_shared = zfs_is_shared(temp);
758 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);
759 cn->cn_needpost = B_TRUE;
761 uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);
763 if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {
764 uu_avl_insert(clp->cl_tree, cn, idx);
771 * If the mountpoint property was previously 'legacy', or 'none',
772 * record it as the behavior of changelist_postfix() will be different.
774 if ((clp->cl_prop == ZFS_PROP_MOUNTPOINT) && legacy) {
776 * do not automatically mount ex-legacy datasets if
777 * we specifically set canmount to noauto
779 if (zfs_prop_get_int(zhp, ZFS_PROP_CANMOUNT) !=
781 clp->cl_waslegacy = B_TRUE;