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 */ 28 29 #include <libintl.h> 30 #include <libuutil.h> 31 #include <stddef.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <unistd.h> 35 #include <zone.h> 36 37 #include <libzfs.h> 38 39 #include "libzfs_impl.h" 40 41 /* 42 * Structure to keep track of dataset state. Before changing the 'sharenfs' or 43 * 'mountpoint' property, we record whether the filesystem was previously 44 * mounted/shared. This prior state dictates whether we remount/reshare the 45 * dataset after the property has been changed. 46 * 47 * The interface consists of the following sequence of functions: 48 * 49 * changelist_gather() 50 * changelist_prefix() 51 * < change property > 52 * changelist_postfix() 53 * changelist_free() 54 * 55 * Other interfaces: 56 * 57 * changelist_remove() - remove a node from a gathered list 58 * changelist_rename() - renames all datasets appropriately when doing a rename 59 * changelist_unshare() - unshares all the nodes in a given changelist 60 * changelist_haszonedchild() - check if there is any child exported to 61 * a local zone 62 */ 63 typedef struct prop_changenode { 64 zfs_handle_t *cn_handle; 65 int cn_shared; 66 int cn_mounted; 67 int cn_zoned; 68 boolean_t cn_needpost; /* is postfix() needed? */ 69 uu_list_node_t cn_listnode; 70 } prop_changenode_t; 71 72 struct prop_changelist { 73 zfs_prop_t cl_prop; 74 zfs_prop_t cl_realprop; 75 zfs_prop_t cl_shareprop; /* used with sharenfs/sharesmb */ 76 uu_list_pool_t *cl_pool; 77 uu_list_t *cl_list; 78 boolean_t cl_waslegacy; 79 boolean_t cl_allchildren; 80 boolean_t cl_alldependents; 81 int cl_mflags; /* Mount flags */ 82 int cl_gflags; /* Gather request flags */ 83 boolean_t cl_haszonedchild; 84 boolean_t cl_sorted; 85 }; 86 87 /* 88 * If the property is 'mountpoint', go through and unmount filesystems as 89 * necessary. We don't do the same for 'sharenfs', because we can just re-share 90 * with different options without interrupting service. We do handle 'sharesmb' 91 * since there may be old resource names that need to be removed. 92 */ 93 int 94 changelist_prefix(prop_changelist_t *clp) 95 { 96 prop_changenode_t *cn; 97 int ret = 0; 98 99 if (clp->cl_prop != ZFS_PROP_MOUNTPOINT && 100 clp->cl_prop != ZFS_PROP_SHARESMB) 101 return (0); 102 103 for (cn = uu_list_first(clp->cl_list); cn != NULL; 104 cn = uu_list_next(clp->cl_list, cn)) { 105 106 /* if a previous loop failed, set the remaining to false */ 107 if (ret == -1) { 108 cn->cn_needpost = B_FALSE; 109 continue; 110 } 111 112 /* 113 * If we are in the global zone, but this dataset is exported 114 * to a local zone, do nothing. 115 */ 116 if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned) 117 continue; 118 119 if (!ZFS_IS_VOLUME(cn->cn_handle)) { 120 /* 121 * Do the property specific processing. 122 */ 123 switch (clp->cl_prop) { 124 case ZFS_PROP_MOUNTPOINT: 125 if (zfs_unmount(cn->cn_handle, NULL, 126 clp->cl_mflags) != 0) { 127 ret = -1; 128 cn->cn_needpost = B_FALSE; 129 } 130 break; 131 case ZFS_PROP_SHARESMB: 132 (void) zfs_unshare_smb(cn->cn_handle, NULL); 133 break; 134 } 135 } 136 } 137 138 if (ret == -1) 139 (void) changelist_postfix(clp); 140 141 return (ret); 142 } 143 144 /* 145 * If the property is 'mountpoint' or 'sharenfs', go through and remount and/or 146 * reshare the filesystems as necessary. In changelist_gather() we recorded 147 * whether the filesystem was previously shared or mounted. The action we take 148 * depends on the previous state, and whether the value was previously 'legacy'. 149 * For non-legacy properties, we only remount/reshare the filesystem if it was 150 * previously mounted/shared. Otherwise, we always remount/reshare the 151 * filesystem. 152 */ 153 int 154 changelist_postfix(prop_changelist_t *clp) 155 { 156 prop_changenode_t *cn; 157 char shareopts[ZFS_MAXPROPLEN]; 158 int errors = 0; 159 libzfs_handle_t *hdl; 160 161 /* 162 * If we're changing the mountpoint, attempt to destroy the underlying 163 * mountpoint. All other datasets will have inherited from this dataset 164 * (in which case their mountpoints exist in the filesystem in the new 165 * location), or have explicit mountpoints set (in which case they won't 166 * be in the changelist). 167 */ 168 if ((cn = uu_list_last(clp->cl_list)) == NULL) 169 return (0); 170 171 if (clp->cl_prop == ZFS_PROP_MOUNTPOINT) 172 remove_mountpoint(cn->cn_handle); 173 174 /* 175 * It is possible that the changelist_prefix() used libshare 176 * to unshare some entries. Since libshare caches data, an 177 * attempt to reshare during postfix can fail unless libshare 178 * is uninitialized here so that it will reinitialize later. 179 */ 180 if (cn->cn_handle != NULL) { 181 hdl = cn->cn_handle->zfs_hdl; 182 assert(hdl != NULL); 183 zfs_uninit_libshare(hdl); 184 } 185 186 /* 187 * We walk the datasets in reverse, because we want to mount any parent 188 * datasets before mounting the children. We walk all datasets even if 189 * there are errors. 190 */ 191 for (cn = uu_list_last(clp->cl_list); cn != NULL; 192 cn = uu_list_prev(clp->cl_list, cn)) { 193 194 boolean_t sharenfs; 195 boolean_t sharesmb; 196 boolean_t mounted; 197 198 /* 199 * If we are in the global zone, but this dataset is exported 200 * to a local zone, do nothing. 201 */ 202 if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned) 203 continue; 204 205 /* Only do post-processing if it's required */ 206 if (!cn->cn_needpost) 207 continue; 208 cn->cn_needpost = B_FALSE; 209 210 zfs_refresh_properties(cn->cn_handle); 211 212 if (ZFS_IS_VOLUME(cn->cn_handle)) 213 continue; 214 215 /* 216 * Remount if previously mounted or mountpoint was legacy, 217 * or sharenfs or sharesmb property is set. 218 */ 219 sharenfs = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARENFS, 220 shareopts, sizeof (shareopts), NULL, NULL, 0, 221 B_FALSE) == 0) && (strcmp(shareopts, "off") != 0)); 222 223 sharesmb = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARESMB, 224 shareopts, sizeof (shareopts), NULL, NULL, 0, 225 B_FALSE) == 0) && (strcmp(shareopts, "off") != 0)); 226 227 mounted = zfs_is_mounted(cn->cn_handle, NULL); 228 229 if (!mounted && (cn->cn_mounted || 230 ((sharenfs || sharesmb || clp->cl_waslegacy) && 231 (zfs_prop_get_int(cn->cn_handle, 232 ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_ON)))) { 233 234 if (zfs_mount(cn->cn_handle, NULL, 0) != 0) 235 errors++; 236 else 237 mounted = TRUE; 238 } 239 240 /* 241 * If the file system is mounted we always re-share even 242 * if the filesystem is currently shared, so that we can 243 * adopt any new options. 244 */ 245 if (sharenfs && mounted) 246 errors += zfs_share_nfs(cn->cn_handle); 247 else if (cn->cn_shared || clp->cl_waslegacy) 248 errors += zfs_unshare_nfs(cn->cn_handle, NULL); 249 if (sharesmb && mounted) 250 errors += zfs_share_smb(cn->cn_handle); 251 else if (cn->cn_shared || clp->cl_waslegacy) 252 errors += zfs_unshare_smb(cn->cn_handle, NULL); 253 } 254 255 return (errors ? -1 : 0); 256 } 257 258 /* 259 * Is this "dataset" a child of "parent"? 260 */ 261 boolean_t 262 isa_child_of(const char *dataset, const char *parent) 263 { 264 int len; 265 266 len = strlen(parent); 267 268 if (strncmp(dataset, parent, len) == 0 && 269 (dataset[len] == '@' || dataset[len] == '/' || 270 dataset[len] == '\0')) 271 return (B_TRUE); 272 else 273 return (B_FALSE); 274 275 } 276 277 /* 278 * If we rename a filesystem, child filesystem handles are no longer valid 279 * since we identify each dataset by its name in the ZFS namespace. As a 280 * result, we have to go through and fix up all the names appropriately. We 281 * could do this automatically if libzfs kept track of all open handles, but 282 * this is a lot less work. 283 */ 284 void 285 changelist_rename(prop_changelist_t *clp, const char *src, const char *dst) 286 { 287 prop_changenode_t *cn; 288 char newname[ZFS_MAXNAMELEN]; 289 290 for (cn = uu_list_first(clp->cl_list); cn != NULL; 291 cn = uu_list_next(clp->cl_list, cn)) { 292 /* 293 * Do not rename a clone that's not in the source hierarchy. 294 */ 295 if (!isa_child_of(cn->cn_handle->zfs_name, src)) 296 continue; 297 298 /* 299 * Destroy the previous mountpoint if needed. 300 */ 301 remove_mountpoint(cn->cn_handle); 302 303 (void) strlcpy(newname, dst, sizeof (newname)); 304 (void) strcat(newname, cn->cn_handle->zfs_name + strlen(src)); 305 306 (void) strlcpy(cn->cn_handle->zfs_name, newname, 307 sizeof (cn->cn_handle->zfs_name)); 308 } 309 } 310 311 /* 312 * Given a gathered changelist for the 'sharenfs' or 'sharesmb' property, 313 * unshare all the datasets in the list. 314 */ 315 int 316 changelist_unshare(prop_changelist_t *clp, zfs_share_proto_t *proto) 317 { 318 prop_changenode_t *cn; 319 int ret = 0; 320 321 if (clp->cl_prop != ZFS_PROP_SHARENFS && 322 clp->cl_prop != ZFS_PROP_SHARESMB) 323 return (0); 324 325 for (cn = uu_list_first(clp->cl_list); cn != NULL; 326 cn = uu_list_next(clp->cl_list, cn)) { 327 if (zfs_unshare_proto(cn->cn_handle, NULL, proto) != 0) 328 ret = -1; 329 } 330 331 return (ret); 332 } 333 334 /* 335 * Check if there is any child exported to a local zone in a given changelist. 336 * This information has already been recorded while gathering the changelist 337 * via changelist_gather(). 338 */ 339 int 340 changelist_haszonedchild(prop_changelist_t *clp) 341 { 342 return (clp->cl_haszonedchild); 343 } 344 345 /* 346 * Remove a node from a gathered list. 347 */ 348 void 349 changelist_remove(prop_changelist_t *clp, const char *name) 350 { 351 prop_changenode_t *cn; 352 353 for (cn = uu_list_first(clp->cl_list); cn != NULL; 354 cn = uu_list_next(clp->cl_list, cn)) { 355 356 if (strcmp(cn->cn_handle->zfs_name, name) == 0) { 357 uu_list_remove(clp->cl_list, cn); 358 zfs_close(cn->cn_handle); 359 free(cn); 360 return; 361 } 362 } 363 } 364 365 /* 366 * Release any memory associated with a changelist. 367 */ 368 void 369 changelist_free(prop_changelist_t *clp) 370 { 371 prop_changenode_t *cn; 372 void *cookie; 373 374 if (clp->cl_list) { 375 cookie = NULL; 376 while ((cn = uu_list_teardown(clp->cl_list, &cookie)) != NULL) { 377 zfs_close(cn->cn_handle); 378 free(cn); 379 } 380 381 uu_list_destroy(clp->cl_list); 382 } 383 if (clp->cl_pool) 384 uu_list_pool_destroy(clp->cl_pool); 385 386 free(clp); 387 } 388 389 static int 390 change_one(zfs_handle_t *zhp, void *data) 391 { 392 prop_changelist_t *clp = data; 393 char property[ZFS_MAXPROPLEN]; 394 char where[64]; 395 prop_changenode_t *cn; 396 zprop_source_t sourcetype; 397 zprop_source_t share_sourcetype; 398 399 /* 400 * We only want to unmount/unshare those filesystems that may inherit 401 * from the target filesystem. If we find any filesystem with a 402 * locally set mountpoint, we ignore any children since changing the 403 * property will not affect them. If this is a rename, we iterate 404 * over all children regardless, since we need them unmounted in 405 * order to do the rename. Also, if this is a volume and we're doing 406 * a rename, then always add it to the changelist. 407 */ 408 409 if (!(ZFS_IS_VOLUME(zhp) && clp->cl_realprop == ZFS_PROP_NAME) && 410 zfs_prop_get(zhp, clp->cl_prop, property, 411 sizeof (property), &sourcetype, where, sizeof (where), 412 B_FALSE) != 0) { 413 zfs_close(zhp); 414 return (0); 415 } 416 417 /* 418 * If we are "watching" sharenfs or sharesmb 419 * then check out the companion property which is tracked 420 * in cl_shareprop 421 */ 422 if (clp->cl_shareprop != ZPROP_INVAL && 423 zfs_prop_get(zhp, clp->cl_shareprop, property, 424 sizeof (property), &share_sourcetype, where, sizeof (where), 425 B_FALSE) != 0) { 426 zfs_close(zhp); 427 return (0); 428 } 429 430 if (clp->cl_alldependents || clp->cl_allchildren || 431 sourcetype == ZPROP_SRC_DEFAULT || 432 sourcetype == ZPROP_SRC_INHERITED || 433 (clp->cl_shareprop != ZPROP_INVAL && 434 (share_sourcetype == ZPROP_SRC_DEFAULT || 435 share_sourcetype == ZPROP_SRC_INHERITED))) { 436 if ((cn = zfs_alloc(zfs_get_handle(zhp), 437 sizeof (prop_changenode_t))) == NULL) { 438 zfs_close(zhp); 439 return (-1); 440 } 441 442 cn->cn_handle = zhp; 443 cn->cn_mounted = (clp->cl_gflags & CL_GATHER_MOUNT_ALWAYS) || 444 zfs_is_mounted(zhp, NULL); 445 cn->cn_shared = zfs_is_shared(zhp); 446 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); 447 cn->cn_needpost = B_TRUE; 448 449 /* Indicate if any child is exported to a local zone. */ 450 if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned) 451 clp->cl_haszonedchild = B_TRUE; 452 453 uu_list_node_init(cn, &cn->cn_listnode, clp->cl_pool); 454 455 if (clp->cl_sorted) { 456 uu_list_index_t idx; 457 458 (void) uu_list_find(clp->cl_list, cn, NULL, 459 &idx); 460 uu_list_insert(clp->cl_list, cn, idx); 461 } else { 462 /* 463 * Add this child to beginning of the list. Children 464 * below this one in the hierarchy will get added above 465 * this one in the list. This produces a list in 466 * reverse dataset name order. 467 * This is necessary when the original mountpoint 468 * is legacy or none. 469 */ 470 ASSERT(!clp->cl_alldependents); 471 verify(uu_list_insert_before(clp->cl_list, 472 uu_list_first(clp->cl_list), cn) == 0); 473 } 474 475 if (!clp->cl_alldependents) 476 return (zfs_iter_children(zhp, change_one, data)); 477 } else { 478 zfs_close(zhp); 479 } 480 481 return (0); 482 } 483 484 /*ARGSUSED*/ 485 static int 486 compare_mountpoints(const void *a, const void *b, void *unused) 487 { 488 const prop_changenode_t *ca = a; 489 const prop_changenode_t *cb = b; 490 491 char mounta[MAXPATHLEN]; 492 char mountb[MAXPATHLEN]; 493 494 boolean_t hasmounta, hasmountb; 495 496 /* 497 * When unsharing or unmounting filesystems, we need to do it in 498 * mountpoint order. This allows the user to have a mountpoint 499 * hierarchy that is different from the dataset hierarchy, and still 500 * allow it to be changed. However, if either dataset doesn't have a 501 * mountpoint (because it is a volume or a snapshot), we place it at the 502 * end of the list, because it doesn't affect our change at all. 503 */ 504 hasmounta = (zfs_prop_get(ca->cn_handle, ZFS_PROP_MOUNTPOINT, mounta, 505 sizeof (mounta), NULL, NULL, 0, B_FALSE) == 0); 506 hasmountb = (zfs_prop_get(cb->cn_handle, ZFS_PROP_MOUNTPOINT, mountb, 507 sizeof (mountb), NULL, NULL, 0, B_FALSE) == 0); 508 509 if (!hasmounta && hasmountb) 510 return (-1); 511 else if (hasmounta && !hasmountb) 512 return (1); 513 else if (!hasmounta && !hasmountb) 514 return (0); 515 else 516 return (strcmp(mountb, mounta)); 517 } 518 519 /* 520 * Given a ZFS handle and a property, construct a complete list of datasets 521 * that need to be modified as part of this process. For anything but the 522 * 'mountpoint' and 'sharenfs' properties, this just returns an empty list. 523 * Otherwise, we iterate over all children and look for any datasets that 524 * inherit the property. For each such dataset, we add it to the list and 525 * mark whether it was shared beforehand. 526 */ 527 prop_changelist_t * 528 changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int gather_flags, 529 int mnt_flags) 530 { 531 prop_changelist_t *clp; 532 prop_changenode_t *cn; 533 zfs_handle_t *temp; 534 char property[ZFS_MAXPROPLEN]; 535 uu_compare_fn_t *compare = NULL; 536 boolean_t legacy = B_FALSE; 537 538 if ((clp = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changelist_t))) == NULL) 539 return (NULL); 540 541 /* 542 * For mountpoint-related tasks, we want to sort everything by 543 * mountpoint, so that we mount and unmount them in the appropriate 544 * order, regardless of their position in the hierarchy. 545 */ 546 if (prop == ZFS_PROP_NAME || prop == ZFS_PROP_ZONED || 547 prop == ZFS_PROP_MOUNTPOINT || prop == ZFS_PROP_SHARENFS || 548 prop == ZFS_PROP_SHARESMB) { 549 550 if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, 551 property, sizeof (property), 552 NULL, NULL, 0, B_FALSE) == 0 && 553 (strcmp(property, "legacy") == 0 || 554 strcmp(property, "none") == 0)) { 555 556 legacy = B_TRUE; 557 } 558 if (!legacy) { 559 compare = compare_mountpoints; 560 clp->cl_sorted = B_TRUE; 561 } 562 } 563 564 clp->cl_pool = uu_list_pool_create("changelist_pool", 565 sizeof (prop_changenode_t), 566 offsetof(prop_changenode_t, cn_listnode), 567 compare, 0); 568 if (clp->cl_pool == NULL) { 569 assert(uu_error() == UU_ERROR_NO_MEMORY); 570 (void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error"); 571 changelist_free(clp); 572 return (NULL); 573 } 574 575 clp->cl_list = uu_list_create(clp->cl_pool, NULL, 576 clp->cl_sorted ? UU_LIST_SORTED : 0); 577 clp->cl_gflags = gather_flags; 578 clp->cl_mflags = mnt_flags; 579 580 if (clp->cl_list == NULL) { 581 assert(uu_error() == UU_ERROR_NO_MEMORY); 582 (void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error"); 583 changelist_free(clp); 584 return (NULL); 585 } 586 587 /* 588 * If this is a rename or the 'zoned' property, we pretend we're 589 * changing the mountpoint and flag it so we can catch all children in 590 * change_one(). 591 * 592 * Flag cl_alldependents to catch all children plus the dependents 593 * (clones) that are not in the hierarchy. 594 */ 595 if (prop == ZFS_PROP_NAME) { 596 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 597 clp->cl_alldependents = B_TRUE; 598 } else if (prop == ZFS_PROP_ZONED) { 599 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 600 clp->cl_allchildren = B_TRUE; 601 } else if (prop == ZFS_PROP_CANMOUNT) { 602 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 603 } else if (prop == ZFS_PROP_VOLSIZE) { 604 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 605 } else if (prop == ZFS_PROP_FSID_GUID) { 606 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 607 } else { 608 clp->cl_prop = prop; 609 } 610 clp->cl_realprop = prop; 611 612 if (clp->cl_prop != ZFS_PROP_MOUNTPOINT && 613 clp->cl_prop != ZFS_PROP_SHARENFS && 614 clp->cl_prop != ZFS_PROP_SHARESMB) 615 return (clp); 616 617 /* 618 * If watching SHARENFS or SHARESMB then 619 * also watch its companion property. 620 */ 621 if (clp->cl_prop == ZFS_PROP_SHARENFS) 622 clp->cl_shareprop = ZFS_PROP_SHARESMB; 623 else if (clp->cl_prop == ZFS_PROP_SHARESMB) 624 clp->cl_shareprop = ZFS_PROP_SHARENFS; 625 626 if (clp->cl_alldependents) { 627 if (zfs_iter_dependents(zhp, B_TRUE, change_one, clp) != 0) { 628 changelist_free(clp); 629 return (NULL); 630 } 631 } else if (zfs_iter_children(zhp, change_one, clp) != 0) { 632 changelist_free(clp); 633 return (NULL); 634 } 635 636 /* 637 * We have to re-open ourselves because we auto-close all the handles 638 * and can't tell the difference. 639 */ 640 if ((temp = zfs_open(zhp->zfs_hdl, zfs_get_name(zhp), 641 ZFS_TYPE_DATASET)) == NULL) { 642 changelist_free(clp); 643 return (NULL); 644 } 645 646 /* 647 * Always add ourself to the list. We add ourselves to the end so that 648 * we're the last to be unmounted. 649 */ 650 if ((cn = zfs_alloc(zhp->zfs_hdl, 651 sizeof (prop_changenode_t))) == NULL) { 652 zfs_close(temp); 653 changelist_free(clp); 654 return (NULL); 655 } 656 657 cn->cn_handle = temp; 658 cn->cn_mounted = (clp->cl_gflags & CL_GATHER_MOUNT_ALWAYS) || 659 zfs_is_mounted(temp, NULL); 660 cn->cn_shared = zfs_is_shared(temp); 661 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); 662 cn->cn_needpost = B_TRUE; 663 664 uu_list_node_init(cn, &cn->cn_listnode, clp->cl_pool); 665 if (clp->cl_sorted) { 666 uu_list_index_t idx; 667 (void) uu_list_find(clp->cl_list, cn, NULL, &idx); 668 uu_list_insert(clp->cl_list, cn, idx); 669 } else { 670 /* 671 * Add the target dataset to the end of the list. 672 * The list is not really unsorted. The list will be 673 * in reverse dataset name order. This is necessary 674 * when the original mountpoint is legacy or none. 675 */ 676 verify(uu_list_insert_after(clp->cl_list, 677 uu_list_last(clp->cl_list), cn) == 0); 678 } 679 680 /* 681 * If the mountpoint property was previously 'legacy', or 'none', 682 * record it as the behavior of changelist_postfix() will be different. 683 */ 684 if ((clp->cl_prop == ZFS_PROP_MOUNTPOINT) && legacy) { 685 /* 686 * do not automatically mount ex-legacy datasets if 687 * we specifically set canmount to noauto 688 */ 689 if (zfs_prop_get_int(zhp, ZFS_PROP_CANMOUNT) != 690 ZFS_CANMOUNT_NOAUTO) 691 clp->cl_waslegacy = B_TRUE; 692 } 693 694 return (clp); 695 }