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 (zfs_unmount(cn->cn_handle, NULL, 132 clp->cl_mflags) != 0) { 133 ret = -1; 134 cn->cn_needpost = B_FALSE; 135 } 136 break; 137 case ZFS_PROP_SHARESMB: 138 (void) zfs_unshare_smb(cn->cn_handle, NULL); 139 commit_smb_shares = B_TRUE; 140 break; 141 142 default: 143 break; 144 } 145 } 146 } 147 148 if (commit_smb_shares) 149 zfs_commit_smb_shares(); 150 uu_avl_walk_end(walk); 151 152 if (ret == -1) 153 (void) changelist_postfix(clp); 154 155 return (ret); 156 } 157 158 /* 159 * If the property is 'mountpoint' or 'sharenfs', go through and remount and/or 160 * reshare the filesystems as necessary. In changelist_gather() we recorded 161 * whether the filesystem was previously shared or mounted. The action we take 162 * depends on the previous state, and whether the value was previously 'legacy'. 163 * For non-legacy properties, we only remount/reshare the filesystem if it was 164 * previously mounted/shared. Otherwise, we always remount/reshare the 165 * filesystem. 166 */ 167 int 168 changelist_postfix(prop_changelist_t *clp) 169 { 170 prop_changenode_t *cn; 171 uu_avl_walk_t *walk; 172 char shareopts[ZFS_MAXPROPLEN]; 173 int errors = 0; 174 boolean_t commit_smb_shares = B_FALSE; 175 boolean_t commit_nfs_shares = B_FALSE; 176 177 /* 178 * If we're changing the mountpoint, attempt to destroy the underlying 179 * mountpoint. All other datasets will have inherited from this dataset 180 * (in which case their mountpoints exist in the filesystem in the new 181 * location), or have explicit mountpoints set (in which case they won't 182 * be in the changelist). 183 */ 184 if ((cn = uu_avl_last(clp->cl_tree)) == NULL) 185 return (0); 186 187 if (clp->cl_prop == ZFS_PROP_MOUNTPOINT) 188 remove_mountpoint(cn->cn_handle); 189 190 /* 191 * We walk the datasets in reverse, because we want to mount any parent 192 * datasets before mounting the children. We walk all datasets even if 193 * there are errors. 194 */ 195 if ((walk = uu_avl_walk_start(clp->cl_tree, 196 UU_WALK_REVERSE | UU_WALK_ROBUST)) == NULL) 197 return (-1); 198 199 while ((cn = uu_avl_walk_next(walk)) != NULL) { 200 201 boolean_t sharenfs; 202 boolean_t sharesmb; 203 boolean_t mounted; 204 boolean_t needs_key; 205 206 /* 207 * If we are in the global zone, but this dataset is exported 208 * to a local zone, do nothing. 209 */ 210 if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned) 211 continue; 212 213 /* Only do post-processing if it's required */ 214 if (!cn->cn_needpost) 215 continue; 216 cn->cn_needpost = B_FALSE; 217 218 zfs_refresh_properties(cn->cn_handle); 219 220 if (ZFS_IS_VOLUME(cn->cn_handle)) 221 continue; 222 223 /* 224 * Remount if previously mounted or mountpoint was legacy, 225 * or sharenfs or sharesmb property is set. 226 */ 227 sharenfs = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARENFS, 228 shareopts, sizeof (shareopts), NULL, NULL, 0, 229 B_FALSE) == 0) && (strcmp(shareopts, "off") != 0)); 230 231 sharesmb = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARESMB, 232 shareopts, sizeof (shareopts), NULL, NULL, 0, 233 B_FALSE) == 0) && (strcmp(shareopts, "off") != 0)); 234 235 needs_key = (zfs_prop_get_int(cn->cn_handle, 236 ZFS_PROP_KEYSTATUS) == ZFS_KEYSTATUS_UNAVAILABLE); 237 238 mounted = zfs_is_mounted(cn->cn_handle, NULL); 239 240 if (!mounted && !needs_key && (cn->cn_mounted || 241 ((sharenfs || sharesmb || clp->cl_waslegacy) && 242 (zfs_prop_get_int(cn->cn_handle, 243 ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_ON)))) { 244 245 if (zfs_mount(cn->cn_handle, NULL, 0) != 0) 246 errors++; 247 else 248 mounted = TRUE; 249 } 250 251 /* 252 * If the file system is mounted we always re-share even 253 * if the filesystem is currently shared, so that we can 254 * adopt any new options. 255 */ 256 if (sharenfs && mounted) { 257 errors += zfs_share_nfs(cn->cn_handle); 258 commit_nfs_shares = B_TRUE; 259 } else if (cn->cn_shared || clp->cl_waslegacy) { 260 errors += zfs_unshare_nfs(cn->cn_handle, NULL); 261 commit_nfs_shares = B_TRUE; 262 } 263 if (sharesmb && mounted) { 264 errors += zfs_share_smb(cn->cn_handle); 265 commit_smb_shares = B_TRUE; 266 } else if (cn->cn_shared || clp->cl_waslegacy) { 267 errors += zfs_unshare_smb(cn->cn_handle, NULL); 268 commit_smb_shares = B_TRUE; 269 } 270 } 271 if (commit_nfs_shares) 272 zfs_commit_nfs_shares(); 273 if (commit_smb_shares) 274 zfs_commit_smb_shares(); 275 uu_avl_walk_end(walk); 276 277 return (errors ? -1 : 0); 278 } 279 280 /* 281 * Is this "dataset" a child of "parent"? 282 */ 283 boolean_t 284 isa_child_of(const char *dataset, const char *parent) 285 { 286 int len; 287 288 len = strlen(parent); 289 290 if (strncmp(dataset, parent, len) == 0 && 291 (dataset[len] == '@' || dataset[len] == '/' || 292 dataset[len] == '\0')) 293 return (B_TRUE); 294 else 295 return (B_FALSE); 296 297 } 298 299 /* 300 * If we rename a filesystem, child filesystem handles are no longer valid 301 * since we identify each dataset by its name in the ZFS namespace. As a 302 * result, we have to go through and fix up all the names appropriately. We 303 * could do this automatically if libzfs kept track of all open handles, but 304 * this is a lot less work. 305 */ 306 void 307 changelist_rename(prop_changelist_t *clp, const char *src, const char *dst) 308 { 309 prop_changenode_t *cn; 310 uu_avl_walk_t *walk; 311 char newname[ZFS_MAX_DATASET_NAME_LEN]; 312 313 if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL) 314 return; 315 316 while ((cn = uu_avl_walk_next(walk)) != NULL) { 317 /* 318 * Do not rename a clone that's not in the source hierarchy. 319 */ 320 if (!isa_child_of(cn->cn_handle->zfs_name, src)) 321 continue; 322 323 /* 324 * Destroy the previous mountpoint if needed. 325 */ 326 remove_mountpoint(cn->cn_handle); 327 328 (void) strlcpy(newname, dst, sizeof (newname)); 329 (void) strlcat(newname, cn->cn_handle->zfs_name + strlen(src), 330 sizeof (newname)); 331 332 (void) strlcpy(cn->cn_handle->zfs_name, newname, 333 sizeof (cn->cn_handle->zfs_name)); 334 } 335 336 uu_avl_walk_end(walk); 337 } 338 339 /* 340 * Given a gathered changelist for the 'sharenfs' or 'sharesmb' property, 341 * unshare all the datasets in the list. 342 */ 343 int 344 changelist_unshare(prop_changelist_t *clp, zfs_share_proto_t *proto) 345 { 346 prop_changenode_t *cn; 347 uu_avl_walk_t *walk; 348 int ret = 0; 349 350 if (clp->cl_prop != ZFS_PROP_SHARENFS && 351 clp->cl_prop != ZFS_PROP_SHARESMB) 352 return (0); 353 354 if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL) 355 return (-1); 356 357 while ((cn = uu_avl_walk_next(walk)) != NULL) { 358 if (zfs_unshare_proto(cn->cn_handle, NULL, proto) != 0) 359 ret = -1; 360 } 361 362 zfs_commit_proto(proto); 363 uu_avl_walk_end(walk); 364 365 return (ret); 366 } 367 368 /* 369 * Check if there is any child exported to a local zone in a given changelist. 370 * This information has already been recorded while gathering the changelist 371 * via changelist_gather(). 372 */ 373 int 374 changelist_haszonedchild(prop_changelist_t *clp) 375 { 376 return (clp->cl_haszonedchild); 377 } 378 379 /* 380 * Remove a node from a gathered list. 381 */ 382 void 383 changelist_remove(prop_changelist_t *clp, const char *name) 384 { 385 prop_changenode_t *cn; 386 uu_avl_walk_t *walk; 387 388 if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL) 389 return; 390 391 while ((cn = uu_avl_walk_next(walk)) != NULL) { 392 if (strcmp(cn->cn_handle->zfs_name, name) == 0) { 393 uu_avl_remove(clp->cl_tree, cn); 394 zfs_close(cn->cn_handle); 395 free(cn); 396 uu_avl_walk_end(walk); 397 return; 398 } 399 } 400 401 uu_avl_walk_end(walk); 402 } 403 404 /* 405 * Release any memory associated with a changelist. 406 */ 407 void 408 changelist_free(prop_changelist_t *clp) 409 { 410 prop_changenode_t *cn; 411 412 if (clp->cl_tree) { 413 uu_avl_walk_t *walk; 414 415 if ((walk = uu_avl_walk_start(clp->cl_tree, 416 UU_WALK_ROBUST)) == NULL) 417 return; 418 419 while ((cn = uu_avl_walk_next(walk)) != NULL) { 420 uu_avl_remove(clp->cl_tree, cn); 421 zfs_close(cn->cn_handle); 422 free(cn); 423 } 424 425 uu_avl_walk_end(walk); 426 uu_avl_destroy(clp->cl_tree); 427 } 428 if (clp->cl_pool) 429 uu_avl_pool_destroy(clp->cl_pool); 430 431 free(clp); 432 } 433 434 /* 435 * Add one dataset to changelist 436 */ 437 static int 438 changelist_add_mounted(zfs_handle_t *zhp, void *data) 439 { 440 prop_changelist_t *clp = data; 441 prop_changenode_t *cn; 442 uu_avl_index_t idx; 443 444 ASSERT3U(clp->cl_prop, ==, ZFS_PROP_MOUNTPOINT); 445 446 if ((cn = zfs_alloc(zfs_get_handle(zhp), 447 sizeof (prop_changenode_t))) == NULL) { 448 zfs_close(zhp); 449 return (ENOMEM); 450 } 451 452 cn->cn_handle = zhp; 453 cn->cn_mounted = zfs_is_mounted(zhp, NULL); 454 ASSERT3U(cn->cn_mounted, ==, B_TRUE); 455 cn->cn_shared = zfs_is_shared(zhp); 456 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); 457 cn->cn_needpost = B_TRUE; 458 459 /* Indicate if any child is exported to a local zone. */ 460 if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned) 461 clp->cl_haszonedchild = B_TRUE; 462 463 uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool); 464 465 if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) { 466 uu_avl_insert(clp->cl_tree, cn, idx); 467 } else { 468 free(cn); 469 zfs_close(zhp); 470 } 471 472 return (0); 473 } 474 475 static int 476 change_one(zfs_handle_t *zhp, void *data) 477 { 478 prop_changelist_t *clp = data; 479 char property[ZFS_MAXPROPLEN]; 480 char where[64]; 481 prop_changenode_t *cn = NULL; 482 zprop_source_t sourcetype = ZPROP_SRC_NONE; 483 zprop_source_t share_sourcetype = ZPROP_SRC_NONE; 484 int ret = 0; 485 486 /* 487 * We only want to unmount/unshare those filesystems that may inherit 488 * from the target filesystem. If we find any filesystem with a 489 * locally set mountpoint, we ignore any children since changing the 490 * property will not affect them. If this is a rename, we iterate 491 * over all children regardless, since we need them unmounted in 492 * order to do the rename. Also, if this is a volume and we're doing 493 * a rename, then always add it to the changelist. 494 */ 495 496 if (!(ZFS_IS_VOLUME(zhp) && clp->cl_realprop == ZFS_PROP_NAME) && 497 zfs_prop_get(zhp, clp->cl_prop, property, 498 sizeof (property), &sourcetype, where, sizeof (where), 499 B_FALSE) != 0) { 500 goto out; 501 } 502 503 /* 504 * If we are "watching" sharenfs or sharesmb 505 * then check out the companion property which is tracked 506 * in cl_shareprop 507 */ 508 if (clp->cl_shareprop != ZPROP_INVAL && 509 zfs_prop_get(zhp, clp->cl_shareprop, property, 510 sizeof (property), &share_sourcetype, where, sizeof (where), 511 B_FALSE) != 0) { 512 goto out; 513 } 514 515 if (clp->cl_alldependents || clp->cl_allchildren || 516 sourcetype == ZPROP_SRC_DEFAULT || 517 sourcetype == ZPROP_SRC_INHERITED || 518 (clp->cl_shareprop != ZPROP_INVAL && 519 (share_sourcetype == ZPROP_SRC_DEFAULT || 520 share_sourcetype == ZPROP_SRC_INHERITED))) { 521 if ((cn = zfs_alloc(zfs_get_handle(zhp), 522 sizeof (prop_changenode_t))) == NULL) { 523 ret = -1; 524 goto out; 525 } 526 527 cn->cn_handle = zhp; 528 cn->cn_mounted = (clp->cl_gflags & CL_GATHER_MOUNT_ALWAYS) || 529 zfs_is_mounted(zhp, NULL); 530 cn->cn_shared = zfs_is_shared(zhp); 531 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); 532 cn->cn_needpost = B_TRUE; 533 534 /* Indicate if any child is exported to a local zone. */ 535 if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned) 536 clp->cl_haszonedchild = B_TRUE; 537 538 uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool); 539 540 uu_avl_index_t idx; 541 542 if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) { 543 uu_avl_insert(clp->cl_tree, cn, idx); 544 } else { 545 free(cn); 546 cn = NULL; 547 } 548 549 if (!clp->cl_alldependents) 550 ret = zfs_iter_children(zhp, change_one, data); 551 552 /* 553 * If we added the handle to the changelist, we will re-use it 554 * later so return without closing it. 555 */ 556 if (cn != NULL) 557 return (ret); 558 } 559 560 out: 561 zfs_close(zhp); 562 return (ret); 563 } 564 565 static int 566 compare_props(const void *a, const void *b, zfs_prop_t prop) 567 { 568 const prop_changenode_t *ca = a; 569 const prop_changenode_t *cb = b; 570 571 char propa[MAXPATHLEN]; 572 char propb[MAXPATHLEN]; 573 574 boolean_t haspropa, haspropb; 575 576 haspropa = (zfs_prop_get(ca->cn_handle, prop, propa, sizeof (propa), 577 NULL, NULL, 0, B_FALSE) == 0); 578 haspropb = (zfs_prop_get(cb->cn_handle, prop, propb, sizeof (propb), 579 NULL, NULL, 0, B_FALSE) == 0); 580 581 if (!haspropa && haspropb) 582 return (-1); 583 else if (haspropa && !haspropb) 584 return (1); 585 else if (!haspropa && !haspropb) 586 return (0); 587 else 588 return (strcmp(propb, propa)); 589 } 590 591 /*ARGSUSED*/ 592 static int 593 compare_mountpoints(const void *a, const void *b, void *unused) 594 { 595 /* 596 * When unsharing or unmounting filesystems, we need to do it in 597 * mountpoint order. This allows the user to have a mountpoint 598 * hierarchy that is different from the dataset hierarchy, and still 599 * allow it to be changed. 600 */ 601 return (compare_props(a, b, ZFS_PROP_MOUNTPOINT)); 602 } 603 604 /*ARGSUSED*/ 605 static int 606 compare_dataset_names(const void *a, const void *b, void *unused) 607 { 608 return (compare_props(a, b, ZFS_PROP_NAME)); 609 } 610 611 /* 612 * Given a ZFS handle and a property, construct a complete list of datasets 613 * that need to be modified as part of this process. For anything but the 614 * 'mountpoint' and 'sharenfs' properties, this just returns an empty list. 615 * Otherwise, we iterate over all children and look for any datasets that 616 * inherit the property. For each such dataset, we add it to the list and 617 * mark whether it was shared beforehand. 618 */ 619 prop_changelist_t * 620 changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int gather_flags, 621 int mnt_flags) 622 { 623 prop_changelist_t *clp; 624 prop_changenode_t *cn; 625 zfs_handle_t *temp; 626 char property[ZFS_MAXPROPLEN]; 627 boolean_t legacy = B_FALSE; 628 629 if ((clp = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changelist_t))) == NULL) 630 return (NULL); 631 632 /* 633 * For mountpoint-related tasks, we want to sort everything by 634 * mountpoint, so that we mount and unmount them in the appropriate 635 * order, regardless of their position in the hierarchy. 636 */ 637 if (prop == ZFS_PROP_NAME || prop == ZFS_PROP_ZONED || 638 prop == ZFS_PROP_MOUNTPOINT || prop == ZFS_PROP_SHARENFS || 639 prop == ZFS_PROP_SHARESMB) { 640 641 if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, 642 property, sizeof (property), 643 NULL, NULL, 0, B_FALSE) == 0 && 644 (strcmp(property, "legacy") == 0 || 645 strcmp(property, "none") == 0)) { 646 legacy = B_TRUE; 647 } 648 } 649 650 clp->cl_pool = uu_avl_pool_create("changelist_pool", 651 sizeof (prop_changenode_t), 652 offsetof(prop_changenode_t, cn_treenode), 653 legacy ? compare_dataset_names : compare_mountpoints, 0); 654 if (clp->cl_pool == NULL) { 655 assert(uu_error() == UU_ERROR_NO_MEMORY); 656 (void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error"); 657 changelist_free(clp); 658 return (NULL); 659 } 660 661 clp->cl_tree = uu_avl_create(clp->cl_pool, NULL, UU_DEFAULT); 662 clp->cl_gflags = gather_flags; 663 clp->cl_mflags = mnt_flags; 664 665 if (clp->cl_tree == NULL) { 666 assert(uu_error() == UU_ERROR_NO_MEMORY); 667 (void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error"); 668 changelist_free(clp); 669 return (NULL); 670 } 671 672 /* 673 * If this is a rename or the 'zoned' property, we pretend we're 674 * changing the mountpoint and flag it so we can catch all children in 675 * change_one(). 676 * 677 * Flag cl_alldependents to catch all children plus the dependents 678 * (clones) that are not in the hierarchy. 679 */ 680 if (prop == ZFS_PROP_NAME) { 681 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 682 clp->cl_alldependents = B_TRUE; 683 } else if (prop == ZFS_PROP_ZONED) { 684 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 685 clp->cl_allchildren = B_TRUE; 686 } else if (prop == ZFS_PROP_CANMOUNT) { 687 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 688 } else if (prop == ZFS_PROP_VOLSIZE) { 689 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 690 } else { 691 clp->cl_prop = prop; 692 } 693 clp->cl_realprop = prop; 694 695 if (clp->cl_prop != ZFS_PROP_MOUNTPOINT && 696 clp->cl_prop != ZFS_PROP_SHARENFS && 697 clp->cl_prop != ZFS_PROP_SHARESMB) 698 return (clp); 699 700 /* 701 * If watching SHARENFS or SHARESMB then 702 * also watch its companion property. 703 */ 704 if (clp->cl_prop == ZFS_PROP_SHARENFS) 705 clp->cl_shareprop = ZFS_PROP_SHARESMB; 706 else if (clp->cl_prop == ZFS_PROP_SHARESMB) 707 clp->cl_shareprop = ZFS_PROP_SHARENFS; 708 709 if (clp->cl_prop == ZFS_PROP_MOUNTPOINT && 710 (clp->cl_gflags & CL_GATHER_ITER_MOUNTED)) { 711 /* 712 * Instead of iterating through all of the dataset children we 713 * gather mounted dataset children from MNTTAB 714 */ 715 if (zfs_iter_mounted(zhp, changelist_add_mounted, clp) != 0) { 716 changelist_free(clp); 717 return (NULL); 718 } 719 } else if (clp->cl_alldependents) { 720 if (zfs_iter_dependents(zhp, B_TRUE, change_one, clp) != 0) { 721 changelist_free(clp); 722 return (NULL); 723 } 724 } else if (zfs_iter_children(zhp, change_one, clp) != 0) { 725 changelist_free(clp); 726 return (NULL); 727 } 728 729 /* 730 * We have to re-open ourselves because we auto-close all the handles 731 * and can't tell the difference. 732 */ 733 if ((temp = zfs_open(zhp->zfs_hdl, zfs_get_name(zhp), 734 ZFS_TYPE_DATASET)) == NULL) { 735 changelist_free(clp); 736 return (NULL); 737 } 738 739 /* 740 * Always add ourself to the list. We add ourselves to the end so that 741 * we're the last to be unmounted. 742 */ 743 if ((cn = zfs_alloc(zhp->zfs_hdl, 744 sizeof (prop_changenode_t))) == NULL) { 745 zfs_close(temp); 746 changelist_free(clp); 747 return (NULL); 748 } 749 750 cn->cn_handle = temp; 751 cn->cn_mounted = (clp->cl_gflags & CL_GATHER_MOUNT_ALWAYS) || 752 zfs_is_mounted(temp, NULL); 753 cn->cn_shared = zfs_is_shared(temp); 754 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); 755 cn->cn_needpost = B_TRUE; 756 757 uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool); 758 uu_avl_index_t idx; 759 if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) { 760 uu_avl_insert(clp->cl_tree, cn, idx); 761 } else { 762 free(cn); 763 zfs_close(temp); 764 } 765 766 /* 767 * If the mountpoint property was previously 'legacy', or 'none', 768 * record it as the behavior of changelist_postfix() will be different. 769 */ 770 if ((clp->cl_prop == ZFS_PROP_MOUNTPOINT) && legacy) { 771 /* 772 * do not automatically mount ex-legacy datasets if 773 * we specifically set canmount to noauto 774 */ 775 if (zfs_prop_get_int(zhp, ZFS_PROP_CANMOUNT) != 776 ZFS_CANMOUNT_NOAUTO) 777 clp->cl_waslegacy = B_TRUE; 778 } 779 780 return (clp); 781 } 782