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