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