]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - lib/libzfs/libzfs_changelist.c
Update vendor openzfs to fd20a8
[FreeBSD/FreeBSD.git] / lib / libzfs / libzfs_changelist.c
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 <ikozhukhov@gmail.com>
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
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
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
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
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
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
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
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
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
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
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
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
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
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 *
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 }