1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 *
26 * Portions Copyright 2007 Ramprakash Jelari
27 * Copyright (c) 2014, 2020 by Delphix. All rights reserved.
28 * Copyright 2016 Igor Kozhukhov <[email protected]>
29 * Copyright (c) 2018 Datto Inc.
30 */
31
32 #include <libintl.h>
33 #include <libuutil.h>
34 #include <stddef.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <zone.h>
39
40 #include <libzfs.h>
41
42 #include "libzfs_impl.h"
43
44 /*
45 * Structure to keep track of dataset state. Before changing the 'sharenfs' or
46 * 'mountpoint' property, we record whether the filesystem was previously
47 * mounted/shared. This prior state dictates whether we remount/reshare the
48 * dataset after the property has been changed.
49 *
50 * The interface consists of the following sequence of functions:
51 *
52 * changelist_gather()
53 * changelist_prefix()
54 * < change property >
55 * changelist_postfix()
56 * changelist_free()
57 *
58 * Other interfaces:
59 *
60 * changelist_remove() - remove a node from a gathered list
61 * changelist_rename() - renames all datasets appropriately when doing a rename
62 * changelist_unshare() - unshares all the nodes in a given changelist
63 * changelist_haszonedchild() - check if there is any child exported to
64 * a local zone
65 */
66 typedef struct prop_changenode {
67 zfs_handle_t *cn_handle;
68 int cn_shared;
69 int cn_mounted;
70 int cn_zoned;
71 boolean_t cn_needpost; /* is postfix() needed? */
72 uu_avl_node_t cn_treenode;
73 } prop_changenode_t;
74
75 struct prop_changelist {
76 zfs_prop_t cl_prop;
77 zfs_prop_t cl_realprop;
78 zfs_prop_t cl_shareprop; /* used with sharenfs/sharesmb */
79 uu_avl_pool_t *cl_pool;
80 uu_avl_t *cl_tree;
81 boolean_t cl_waslegacy;
82 boolean_t cl_allchildren;
83 boolean_t cl_alldependents;
84 int cl_mflags; /* Mount flags */
85 int cl_gflags; /* Gather request flags */
86 boolean_t cl_haszonedchild;
87 };
88
89 /*
90 * If the property is 'mountpoint', go through and unmount filesystems as
91 * necessary. We don't do the same for 'sharenfs', because we can just re-share
92 * with different options without interrupting service. We do handle 'sharesmb'
93 * since there may be old resource names that need to be removed.
94 */
95 int
changelist_prefix(prop_changelist_t * clp)96 changelist_prefix(prop_changelist_t *clp)
97 {
98 prop_changenode_t *cn;
99 uu_avl_walk_t *walk;
100 int ret = 0;
101 boolean_t commit_smb_shares = B_FALSE;
102
103 if (clp->cl_prop != ZFS_PROP_MOUNTPOINT &&
104 clp->cl_prop != ZFS_PROP_SHARESMB)
105 return (0);
106
107 if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
108 return (-1);
109
110 while ((cn = uu_avl_walk_next(walk)) != NULL) {
111
112 /* if a previous loop failed, set the remaining to false */
113 if (ret == -1) {
114 cn->cn_needpost = B_FALSE;
115 continue;
116 }
117
118 /*
119 * If we are in the global zone, but this dataset is exported
120 * to a local zone, do nothing.
121 */
122 if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)
123 continue;
124
125 if (!ZFS_IS_VOLUME(cn->cn_handle)) {
126 /*
127 * Do the property specific processing.
128 */
129 switch (clp->cl_prop) {
130 case ZFS_PROP_MOUNTPOINT:
131 if (clp->cl_gflags & CL_GATHER_DONT_UNMOUNT)
132 break;
133 if (zfs_unmount(cn->cn_handle, NULL,
134 clp->cl_mflags) != 0) {
135 ret = -1;
136 cn->cn_needpost = B_FALSE;
137 }
138 break;
139 case ZFS_PROP_SHARESMB:
140 (void) zfs_unshare_smb(cn->cn_handle, NULL);
141 commit_smb_shares = B_TRUE;
142 break;
143
144 default:
145 break;
146 }
147 }
148 }
149
150 if (commit_smb_shares)
151 zfs_commit_smb_shares();
152 uu_avl_walk_end(walk);
153
154 if (ret == -1)
155 (void) changelist_postfix(clp);
156
157 return (ret);
158 }
159
160 /*
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
167 * filesystem.
168 */
169 int
changelist_postfix(prop_changelist_t * clp)170 changelist_postfix(prop_changelist_t *clp)
171 {
172 prop_changenode_t *cn;
173 uu_avl_walk_t *walk;
174 char shareopts[ZFS_MAXPROPLEN];
175 int errors = 0;
176 boolean_t commit_smb_shares = B_FALSE;
177 boolean_t commit_nfs_shares = B_FALSE;
178
179 /*
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).
185 */
186 if ((cn = uu_avl_last(clp->cl_tree)) == NULL)
187 return (0);
188
189 if (clp->cl_prop == ZFS_PROP_MOUNTPOINT &&
190 !(clp->cl_gflags & CL_GATHER_DONT_UNMOUNT))
191 remove_mountpoint(cn->cn_handle);
192
193 /*
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
196 * there are errors.
197 */
198 if ((walk = uu_avl_walk_start(clp->cl_tree,
199 UU_WALK_REVERSE | UU_WALK_ROBUST)) == NULL)
200 return (-1);
201
202 while ((cn = uu_avl_walk_next(walk)) != NULL) {
203
204 boolean_t sharenfs;
205 boolean_t sharesmb;
206 boolean_t mounted;
207 boolean_t needs_key;
208
209 /*
210 * If we are in the global zone, but this dataset is exported
211 * to a local zone, do nothing.
212 */
213 if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)
214 continue;
215
216 /* Only do post-processing if it's required */
217 if (!cn->cn_needpost)
218 continue;
219 cn->cn_needpost = B_FALSE;
220
221 zfs_refresh_properties(cn->cn_handle);
222
223 if (ZFS_IS_VOLUME(cn->cn_handle))
224 continue;
225
226 /*
227 * Remount if previously mounted or mountpoint was legacy,
228 * or sharenfs or sharesmb property is set.
229 */
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));
233
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));
237
238 needs_key = (zfs_prop_get_int(cn->cn_handle,
239 ZFS_PROP_KEYSTATUS) == ZFS_KEYSTATUS_UNAVAILABLE);
240
241 mounted = (clp->cl_gflags & CL_GATHER_DONT_UNMOUNT) ||
242 zfs_is_mounted(cn->cn_handle, NULL);
243
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)))) {
248
249 if (zfs_mount(cn->cn_handle, NULL, 0) != 0)
250 errors++;
251 else
252 mounted = TRUE;
253 }
254
255 /*
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.
259 */
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;
266 }
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;
273 }
274 }
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);
280
281 return (errors ? -1 : 0);
282 }
283
284 /*
285 * Is this "dataset" a child of "parent"?
286 */
287 boolean_t
isa_child_of(const char * dataset,const char * parent)288 isa_child_of(const char *dataset, const char *parent)
289 {
290 int len;
291
292 len = strlen(parent);
293
294 if (strncmp(dataset, parent, len) == 0 &&
295 (dataset[len] == '@' || dataset[len] == '/' ||
296 dataset[len] == '\0'))
297 return (B_TRUE);
298 else
299 return (B_FALSE);
300
301 }
302
303 /*
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.
309 */
310 void
changelist_rename(prop_changelist_t * clp,const char * src,const char * dst)311 changelist_rename(prop_changelist_t *clp, const char *src, const char *dst)
312 {
313 prop_changenode_t *cn;
314 uu_avl_walk_t *walk;
315 char newname[ZFS_MAX_DATASET_NAME_LEN];
316
317 if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
318 return;
319
320 while ((cn = uu_avl_walk_next(walk)) != NULL) {
321 /*
322 * Do not rename a clone that's not in the source hierarchy.
323 */
324 if (!isa_child_of(cn->cn_handle->zfs_name, src))
325 continue;
326
327 /*
328 * Destroy the previous mountpoint if needed.
329 */
330 remove_mountpoint(cn->cn_handle);
331
332 (void) strlcpy(newname, dst, sizeof (newname));
333 (void) strlcat(newname, cn->cn_handle->zfs_name + strlen(src),
334 sizeof (newname));
335
336 (void) strlcpy(cn->cn_handle->zfs_name, newname,
337 sizeof (cn->cn_handle->zfs_name));
338 }
339
340 uu_avl_walk_end(walk);
341 }
342
343 /*
344 * Given a gathered changelist for the 'sharenfs' or 'sharesmb' property,
345 * unshare all the datasets in the list.
346 */
347 int
changelist_unshare(prop_changelist_t * clp,zfs_share_proto_t * proto)348 changelist_unshare(prop_changelist_t *clp, zfs_share_proto_t *proto)
349 {
350 prop_changenode_t *cn;
351 uu_avl_walk_t *walk;
352 int ret = 0;
353
354 if (clp->cl_prop != ZFS_PROP_SHARENFS &&
355 clp->cl_prop != ZFS_PROP_SHARESMB)
356 return (0);
357
358 if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
359 return (-1);
360
361 while ((cn = uu_avl_walk_next(walk)) != NULL) {
362 if (zfs_unshare_proto(cn->cn_handle, NULL, proto) != 0)
363 ret = -1;
364 }
365
366 zfs_commit_proto(proto);
367 uu_avl_walk_end(walk);
368
369 return (ret);
370 }
371
372 /*
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().
376 */
377 int
changelist_haszonedchild(prop_changelist_t * clp)378 changelist_haszonedchild(prop_changelist_t *clp)
379 {
380 return (clp->cl_haszonedchild);
381 }
382
383 /*
384 * Remove a node from a gathered list.
385 */
386 void
changelist_remove(prop_changelist_t * clp,const char * name)387 changelist_remove(prop_changelist_t *clp, const char *name)
388 {
389 prop_changenode_t *cn;
390 uu_avl_walk_t *walk;
391
392 if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
393 return;
394
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);
399 free(cn);
400 uu_avl_walk_end(walk);
401 return;
402 }
403 }
404
405 uu_avl_walk_end(walk);
406 }
407
408 /*
409 * Release any memory associated with a changelist.
410 */
411 void
changelist_free(prop_changelist_t * clp)412 changelist_free(prop_changelist_t *clp)
413 {
414 prop_changenode_t *cn;
415
416 if (clp->cl_tree) {
417 uu_avl_walk_t *walk;
418
419 if ((walk = uu_avl_walk_start(clp->cl_tree,
420 UU_WALK_ROBUST)) == NULL)
421 return;
422
423 while ((cn = uu_avl_walk_next(walk)) != NULL) {
424 uu_avl_remove(clp->cl_tree, cn);
425 zfs_close(cn->cn_handle);
426 free(cn);
427 }
428
429 uu_avl_walk_end(walk);
430 uu_avl_destroy(clp->cl_tree);
431 }
432 if (clp->cl_pool)
433 uu_avl_pool_destroy(clp->cl_pool);
434
435 free(clp);
436 }
437
438 /*
439 * Add one dataset to changelist
440 */
441 static int
changelist_add_mounted(zfs_handle_t * zhp,void * data)442 changelist_add_mounted(zfs_handle_t *zhp, void *data)
443 {
444 prop_changelist_t *clp = data;
445 prop_changenode_t *cn;
446 uu_avl_index_t idx;
447
448 ASSERT3U(clp->cl_prop, ==, ZFS_PROP_MOUNTPOINT);
449
450 if ((cn = zfs_alloc(zfs_get_handle(zhp),
451 sizeof (prop_changenode_t))) == NULL) {
452 zfs_close(zhp);
453 return (ENOMEM);
454 }
455
456 cn->cn_handle = zhp;
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;
462
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;
466
467 uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);
468
469 if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {
470 uu_avl_insert(clp->cl_tree, cn, idx);
471 } else {
472 free(cn);
473 zfs_close(zhp);
474 }
475
476 return (0);
477 }
478
479 static int
change_one(zfs_handle_t * zhp,void * data)480 change_one(zfs_handle_t *zhp, void *data)
481 {
482 prop_changelist_t *clp = data;
483 char property[ZFS_MAXPROPLEN];
484 char where[64];
485 prop_changenode_t *cn = NULL;
486 zprop_source_t sourcetype = ZPROP_SRC_NONE;
487 zprop_source_t share_sourcetype = ZPROP_SRC_NONE;
488 int ret = 0;
489
490 /*
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.
498 */
499
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),
503 B_FALSE) != 0) {
504 goto out;
505 }
506
507 /*
508 * If we are "watching" sharenfs or sharesmb
509 * then check out the companion property which is tracked
510 * in cl_shareprop
511 */
512 if (clp->cl_shareprop != ZPROP_INVAL &&
513 zfs_prop_get(zhp, clp->cl_shareprop, property,
514 sizeof (property), &share_sourcetype, where, sizeof (where),
515 B_FALSE) != 0) {
516 goto out;
517 }
518
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) {
527 ret = -1;
528 goto out;
529 }
530
531 cn->cn_handle = zhp;
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;
537
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;
541
542 uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);
543
544 uu_avl_index_t idx;
545
546 if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {
547 uu_avl_insert(clp->cl_tree, cn, idx);
548 } else {
549 free(cn);
550 cn = NULL;
551 }
552
553 if (!clp->cl_alldependents)
554 ret = zfs_iter_children(zhp, change_one, data);
555
556 /*
557 * If we added the handle to the changelist, we will re-use it
558 * later so return without closing it.
559 */
560 if (cn != NULL)
561 return (ret);
562 }
563
564 out:
565 zfs_close(zhp);
566 return (ret);
567 }
568
569 static int
compare_props(const void * a,const void * b,zfs_prop_t prop)570 compare_props(const void *a, const void *b, zfs_prop_t prop)
571 {
572 const prop_changenode_t *ca = a;
573 const prop_changenode_t *cb = b;
574
575 char propa[MAXPATHLEN];
576 char propb[MAXPATHLEN];
577
578 boolean_t haspropa, haspropb;
579
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);
584
585 if (!haspropa && haspropb)
586 return (-1);
587 else if (haspropa && !haspropb)
588 return (1);
589 else if (!haspropa && !haspropb)
590 return (0);
591 else
592 return (strcmp(propb, propa));
593 }
594
595 /*ARGSUSED*/
596 static int
compare_mountpoints(const void * a,const void * b,void * unused)597 compare_mountpoints(const void *a, const void *b, void *unused)
598 {
599 /*
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.
604 */
605 return (compare_props(a, b, ZFS_PROP_MOUNTPOINT));
606 }
607
608 /*ARGSUSED*/
609 static int
compare_dataset_names(const void * a,const void * b,void * unused)610 compare_dataset_names(const void *a, const void *b, void *unused)
611 {
612 return (compare_props(a, b, ZFS_PROP_NAME));
613 }
614
615 /*
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.
622 */
623 prop_changelist_t *
changelist_gather(zfs_handle_t * zhp,zfs_prop_t prop,int gather_flags,int mnt_flags)624 changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int gather_flags,
625 int mnt_flags)
626 {
627 prop_changelist_t *clp;
628 prop_changenode_t *cn;
629 zfs_handle_t *temp;
630 char property[ZFS_MAXPROPLEN];
631 boolean_t legacy = B_FALSE;
632
633 if ((clp = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changelist_t))) == NULL)
634 return (NULL);
635
636 /*
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.
640 */
641 if (prop == ZFS_PROP_NAME || prop == ZFS_PROP_ZONED ||
642 prop == ZFS_PROP_MOUNTPOINT || prop == ZFS_PROP_SHARENFS ||
643 prop == ZFS_PROP_SHARESMB) {
644
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)) {
650 legacy = B_TRUE;
651 }
652 }
653
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);
662 return (NULL);
663 }
664
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;
668
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);
673 return (NULL);
674 }
675
676 /*
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
679 * change_one().
680 *
681 * Flag cl_alldependents to catch all children plus the dependents
682 * (clones) that are not in the hierarchy.
683 */
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;
694 } else {
695 clp->cl_prop = prop;
696 }
697 clp->cl_realprop = prop;
698
699 if (clp->cl_prop != ZFS_PROP_MOUNTPOINT &&
700 clp->cl_prop != ZFS_PROP_SHARENFS &&
701 clp->cl_prop != ZFS_PROP_SHARESMB)
702 return (clp);
703
704 /*
705 * If watching SHARENFS or SHARESMB then
706 * also watch its companion property.
707 */
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;
712
713 if (clp->cl_prop == ZFS_PROP_MOUNTPOINT &&
714 (clp->cl_gflags & CL_GATHER_ITER_MOUNTED)) {
715 /*
716 * Instead of iterating through all of the dataset children we
717 * gather mounted dataset children from MNTTAB
718 */
719 if (zfs_iter_mounted(zhp, changelist_add_mounted, clp) != 0) {
720 changelist_free(clp);
721 return (NULL);
722 }
723 } else if (clp->cl_alldependents) {
724 if (zfs_iter_dependents(zhp, B_TRUE, change_one, clp) != 0) {
725 changelist_free(clp);
726 return (NULL);
727 }
728 } else if (zfs_iter_children(zhp, change_one, clp) != 0) {
729 changelist_free(clp);
730 return (NULL);
731 }
732
733 /*
734 * We have to re-open ourselves because we auto-close all the handles
735 * and can't tell the difference.
736 */
737 if ((temp = zfs_open(zhp->zfs_hdl, zfs_get_name(zhp),
738 ZFS_TYPE_DATASET)) == NULL) {
739 changelist_free(clp);
740 return (NULL);
741 }
742
743 /*
744 * Always add ourself to the list. We add ourselves to the end so that
745 * we're the last to be unmounted.
746 */
747 if ((cn = zfs_alloc(zhp->zfs_hdl,
748 sizeof (prop_changenode_t))) == NULL) {
749 zfs_close(temp);
750 changelist_free(clp);
751 return (NULL);
752 }
753
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;
760
761 uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);
762 uu_avl_index_t idx;
763 if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {
764 uu_avl_insert(clp->cl_tree, cn, idx);
765 } else {
766 free(cn);
767 zfs_close(temp);
768 }
769
770 /*
771 * If the mountpoint property was previously 'legacy', or 'none',
772 * record it as the behavior of changelist_postfix() will be different.
773 */
774 if ((clp->cl_prop == ZFS_PROP_MOUNTPOINT) && legacy) {
775 /*
776 * do not automatically mount ex-legacy datasets if
777 * we specifically set canmount to noauto
778 */
779 if (zfs_prop_get_int(zhp, ZFS_PROP_CANMOUNT) !=
780 ZFS_CANMOUNT_NOAUTO)
781 clp->cl_waslegacy = B_TRUE;
782 }
783
784 return (clp);
785 }
786