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