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 static 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 } 786