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 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <libintl.h> 29 #include <libuutil.h> 30 #include <stddef.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <unistd.h> 34 #include <zone.h> 35 36 #include <libzfs.h> 37 38 #include "libzfs_impl.h" 39 40 /* 41 * Structure to keep track of dataset state. Before changing the 'sharenfs' or 42 * 'mountpoint' property, we record whether the filesystem was previously 43 * mounted/shared. This prior state dictates whether we remount/reshare the 44 * dataset after the property has been changed. 45 * 46 * The interface consists of the following sequence of functions: 47 * 48 * changelist_gather() 49 * changelist_prefix() 50 * < change property > 51 * changelist_postfix() 52 * changelist_free() 53 * 54 * Other interfaces: 55 * 56 * changelist_remove() - remove a node from a gathered list 57 * changelist_rename() - renames all datasets appropriately when doing a rename 58 * changelist_unshare() - unshares all the nodes in a given changelist 59 * changelist_haszonedchild() - check if there is any child exported to 60 * a local zone 61 */ 62 typedef struct prop_changenode { 63 zfs_handle_t *cn_handle; 64 int cn_shared; 65 int cn_mounted; 66 int cn_zoned; 67 uu_list_node_t cn_listnode; 68 } prop_changenode_t; 69 70 struct prop_changelist { 71 zfs_prop_t cl_prop; 72 zfs_prop_t cl_realprop; 73 uu_list_pool_t *cl_pool; 74 uu_list_t *cl_list; 75 boolean_t cl_waslegacy; 76 boolean_t cl_allchildren; 77 boolean_t cl_alldependents; 78 int cl_flags; 79 boolean_t cl_haszonedchild; 80 boolean_t cl_sorted; 81 }; 82 83 /* 84 * If the property is 'mountpoint', go through and unmount filesystems as 85 * necessary. We don't do the same for 'sharenfs', because we can just re-share 86 * with different options without interrupting service. 87 */ 88 int 89 changelist_prefix(prop_changelist_t *clp) 90 { 91 prop_changenode_t *cn; 92 int ret = 0; 93 94 if (clp->cl_prop != ZFS_PROP_MOUNTPOINT) 95 return (0); 96 97 for (cn = uu_list_first(clp->cl_list); cn != NULL; 98 cn = uu_list_next(clp->cl_list, cn)) { 99 /* 100 * if we are in a global zone, but this dataset is exported to 101 * a local zone, do nothing. 102 */ 103 if ((getzoneid() == GLOBAL_ZONEID) && cn->cn_zoned) 104 continue; 105 106 /* 107 * If we have a volume and this was a rename, remove the 108 * /dev/zvol links 109 */ 110 if (ZFS_IS_VOLUME(cn->cn_handle) && 111 clp->cl_realprop == ZFS_PROP_NAME) { 112 if (zvol_remove_link(cn->cn_handle->zfs_hdl, 113 cn->cn_handle->zfs_name) != 0) 114 ret = -1; 115 } else if (zfs_unmount(cn->cn_handle, NULL, clp->cl_flags) != 0) 116 ret = -1; 117 } 118 119 return (ret); 120 } 121 122 /* 123 * If the proeprty is 'mountpoint' or 'sharenfs', go through and remount and/or 124 * reshare the filesystems as necessary. In changelist_gather() we recorded 125 * whether the filesystem was previously shared or mounted. The action we take 126 * depends on the previous state, and whether the value was previously 'legacy'. 127 * For non-legacy properties, we only remount/reshare the filesystem if it was 128 * previously mounted/shared. Otherwise, we always remount/reshare the 129 * filesystem. 130 */ 131 int 132 changelist_postfix(prop_changelist_t *clp) 133 { 134 prop_changenode_t *cn; 135 int ret = 0; 136 137 /* 138 * If we're changing the mountpoint, attempt to destroy the underlying 139 * mountpoint. All other datasets will have inherited from this dataset 140 * (in which case their mountpoints exist in the filesystem in the new 141 * location), or have explicit mountpoints set (in which case they won't 142 * be in the changelist). 143 */ 144 if ((cn = uu_list_last(clp->cl_list)) == NULL) 145 return (0); 146 147 if (clp->cl_prop == ZFS_PROP_MOUNTPOINT) 148 remove_mountpoint(cn->cn_handle); 149 150 /* 151 * We walk the datasets in reverse, because we want to mount any parent 152 * datasets before mounting the children. 153 */ 154 for (cn = uu_list_last(clp->cl_list); cn != NULL; 155 cn = uu_list_prev(clp->cl_list, cn)) { 156 /* 157 * if we are in a global zone, but this dataset is exported to 158 * a local zone, do nothing. 159 */ 160 if ((getzoneid() == GLOBAL_ZONEID) && cn->cn_zoned) 161 continue; 162 163 zfs_refresh_properties(cn->cn_handle); 164 165 /* 166 * If this is a volume and we're doing a rename, recreate the 167 * /dev/zvol links. 168 */ 169 if (ZFS_IS_VOLUME(cn->cn_handle) && 170 clp->cl_realprop == ZFS_PROP_NAME) { 171 if (zvol_create_link(cn->cn_handle->zfs_hdl, 172 cn->cn_handle->zfs_name) != 0) 173 ret = -1; 174 continue; 175 } 176 177 if ((clp->cl_waslegacy || cn->cn_mounted) && 178 !zfs_is_mounted(cn->cn_handle, NULL) && 179 zfs_mount(cn->cn_handle, NULL, 0) != 0) 180 ret = -1; 181 182 /* 183 * We always re-share even if the filesystem is currently 184 * shared, so that we can adopt any new options. 185 */ 186 if ((cn->cn_shared || 187 (clp->cl_prop == ZFS_PROP_SHARENFS && clp->cl_waslegacy))) { 188 char shareopts[ZFS_MAXPROPLEN]; 189 if (zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARENFS, 190 shareopts, sizeof (shareopts), NULL, NULL, 0, 191 B_FALSE) == 0 && strcmp(shareopts, "off") == 0) 192 ret = zfs_unshare(cn->cn_handle, NULL); 193 else 194 ret = zfs_share(cn->cn_handle); 195 } 196 } 197 198 return (ret); 199 } 200 201 /* 202 * Is this "dataset" a child of "parent"? 203 */ 204 static boolean_t 205 isa_child_of(char *dataset, const char *parent) 206 { 207 int len; 208 209 /* snapshot does not have a child */ 210 if (strchr(parent, '@')) 211 return (B_FALSE); 212 213 len = strlen(parent); 214 215 if (strncmp(dataset, parent, len) == 0 && 216 (dataset[len] == '/' || dataset[len] == '\0')) 217 return (B_TRUE); 218 else 219 return (B_FALSE); 220 221 } 222 223 /* 224 * If we rename a filesystem, and child filesystem handles are no longer valid, 225 * since we identify datasets by their name in the ZFS namespace. So, we have 226 * to go through and fix up all the names appropriately. We could do this 227 * automatically if libzfs kept track of all open handles, but this is a lot 228 * less work. 229 */ 230 void 231 changelist_rename(prop_changelist_t *clp, const char *src, const char *dst) 232 { 233 prop_changenode_t *cn; 234 char newname[ZFS_MAXNAMELEN]; 235 236 for (cn = uu_list_first(clp->cl_list); cn != NULL; 237 cn = uu_list_next(clp->cl_list, cn)) { 238 /* 239 * Do not rename a clone that's not in the source hierarchy. 240 */ 241 if (!isa_child_of(cn->cn_handle->zfs_name, src)) 242 continue; 243 244 /* 245 * Destroy the previous mountpoint if needed. 246 */ 247 remove_mountpoint(cn->cn_handle); 248 249 (void) strlcpy(newname, dst, sizeof (newname)); 250 (void) strcat(newname, cn->cn_handle->zfs_name + strlen(src)); 251 252 (void) strlcpy(cn->cn_handle->zfs_name, newname, 253 sizeof (cn->cn_handle->zfs_name)); 254 } 255 } 256 257 /* 258 * Given a gathered changelist for the "sharenfs" property, 259 * unshare all the nodes in the list. 260 */ 261 int 262 changelist_unshare(prop_changelist_t *clp) 263 { 264 prop_changenode_t *cn; 265 int ret = 0; 266 267 if (clp->cl_prop != ZFS_PROP_SHARENFS) 268 return (0); 269 270 for (cn = uu_list_first(clp->cl_list); cn != NULL; 271 cn = uu_list_next(clp->cl_list, cn)) { 272 273 if (zfs_unshare(cn->cn_handle, NULL) != 0) 274 ret = -1; 275 } 276 277 return (ret); 278 } 279 280 /* 281 * Check if there is any child exported to a local zone in a 282 * given changelist. This information has already been recorded 283 * while gathering the changelist via changelist_gather(). 284 */ 285 int 286 changelist_haszonedchild(prop_changelist_t *clp) 287 { 288 return (clp->cl_haszonedchild); 289 } 290 291 /* 292 * Remove a node from a gathered list. 293 */ 294 void 295 changelist_remove(zfs_handle_t *zhp, prop_changelist_t *clp) 296 { 297 prop_changenode_t *cn; 298 299 for (cn = uu_list_first(clp->cl_list); cn != NULL; 300 cn = uu_list_next(clp->cl_list, cn)) { 301 302 if (strcmp(cn->cn_handle->zfs_name, zhp->zfs_name) == 0) { 303 uu_list_remove(clp->cl_list, cn); 304 zfs_close(cn->cn_handle); 305 free(cn); 306 return; 307 } 308 } 309 } 310 311 /* 312 * Release any memory associated with a changelist. 313 */ 314 void 315 changelist_free(prop_changelist_t *clp) 316 { 317 prop_changenode_t *cn; 318 uu_list_walk_t *walk; 319 320 if (clp->cl_list) { 321 verify((walk = uu_list_walk_start(clp->cl_list, 322 UU_WALK_ROBUST)) != NULL); 323 324 while ((cn = uu_list_walk_next(walk)) != NULL) { 325 326 uu_list_remove(clp->cl_list, cn); 327 328 zfs_close(cn->cn_handle); 329 free(cn); 330 } 331 332 uu_list_walk_end(walk); 333 334 uu_list_destroy(clp->cl_list); 335 } 336 if (clp->cl_pool) 337 uu_list_pool_destroy(clp->cl_pool); 338 339 free(clp); 340 } 341 342 static int 343 change_one(zfs_handle_t *zhp, void *data) 344 { 345 prop_changelist_t *clp = data; 346 char property[ZFS_MAXPROPLEN]; 347 char where[64]; 348 prop_changenode_t *cn; 349 zfs_source_t sourcetype; 350 351 /* 352 * We only want to unmount/unshare those filesystems which may 353 * inherit from the target filesystem. If we find any filesystem 354 * with a locally set mountpoint, we ignore any children since changing 355 * the property will not affect them. If this is a rename, we iterate 356 * over all children regardless, since we need them unmounted in order 357 * to do the rename. Also, if this is a volume and we're doing a 358 * rename, then always add it to the changelist. 359 */ 360 361 if (!(ZFS_IS_VOLUME(zhp) && clp->cl_realprop == ZFS_PROP_NAME) && 362 zfs_prop_get(zhp, clp->cl_prop, property, 363 sizeof (property), &sourcetype, where, sizeof (where), 364 B_FALSE) != 0) { 365 zfs_close(zhp); 366 return (0); 367 } 368 369 if (clp->cl_alldependents || clp->cl_allchildren || 370 sourcetype == ZFS_SRC_DEFAULT || sourcetype == ZFS_SRC_INHERITED) { 371 if ((cn = zfs_alloc(zfs_get_handle(zhp), 372 sizeof (prop_changenode_t))) == NULL) { 373 zfs_close(zhp); 374 return (-1); 375 } 376 377 cn->cn_handle = zhp; 378 cn->cn_mounted = zfs_is_mounted(zhp, NULL); 379 cn->cn_shared = zfs_is_shared(zhp, NULL); 380 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); 381 382 /* indicate if any child is exported to a local zone */ 383 if ((getzoneid() == GLOBAL_ZONEID) && cn->cn_zoned) 384 clp->cl_haszonedchild = B_TRUE; 385 386 uu_list_node_init(cn, &cn->cn_listnode, clp->cl_pool); 387 388 if (clp->cl_sorted) { 389 uu_list_index_t idx; 390 391 (void) uu_list_find(clp->cl_list, cn, NULL, 392 &idx); 393 uu_list_insert(clp->cl_list, cn, idx); 394 } else { 395 ASSERT(!clp->cl_alldependents); 396 verify(uu_list_insert_before(clp->cl_list, 397 uu_list_first(clp->cl_list), cn) == 0); 398 } 399 400 if (!clp->cl_alldependents) 401 return (zfs_iter_children(zhp, change_one, data)); 402 } else { 403 zfs_close(zhp); 404 } 405 406 return (0); 407 } 408 409 /*ARGSUSED*/ 410 static int 411 compare_mountpoints(const void *a, const void *b, void *unused) 412 { 413 const prop_changenode_t *ca = a; 414 const prop_changenode_t *cb = b; 415 416 char mounta[MAXPATHLEN]; 417 char mountb[MAXPATHLEN]; 418 419 boolean_t hasmounta, hasmountb; 420 421 /* 422 * When unsharing or unmounting filesystems, we need to do it in 423 * mountpoint order. This allows the user to have a mountpoint 424 * hierarchy that is different from the dataset hierarchy, and still 425 * allow it to be changed. However, if either dataset doesn't have a 426 * mountpoint (because it is a volume or a snapshot), we place it at the 427 * end of the list, because it doesn't affect our change at all. 428 */ 429 hasmounta = (zfs_prop_get(ca->cn_handle, ZFS_PROP_MOUNTPOINT, mounta, 430 sizeof (mounta), NULL, NULL, 0, B_FALSE) == 0); 431 hasmountb = (zfs_prop_get(cb->cn_handle, ZFS_PROP_MOUNTPOINT, mountb, 432 sizeof (mountb), NULL, NULL, 0, B_FALSE) == 0); 433 434 if (!hasmounta && hasmountb) 435 return (-1); 436 else if (hasmounta && !hasmountb) 437 return (1); 438 else if (!hasmounta && !hasmountb) 439 return (0); 440 else 441 return (strcmp(mountb, mounta)); 442 } 443 444 /* 445 * Given a ZFS handle and a property, construct a complete list of datasets that 446 * need to be modified as part of this process. For anything but the 447 * 'mountpoint' and 'sharenfs' properties, this just returns an empty list. 448 * Otherwise, we iterate over all children and look for any datasets which 449 * inherit this property. For each such dataset, we add it to the list and mark 450 * whether it was shared beforehand. 451 */ 452 prop_changelist_t * 453 changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int flags) 454 { 455 prop_changelist_t *clp; 456 prop_changenode_t *cn; 457 zfs_handle_t *temp; 458 char property[ZFS_MAXPROPLEN]; 459 uu_compare_fn_t *compare = NULL; 460 461 if ((clp = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changelist_t))) == NULL) 462 return (NULL); 463 464 /* 465 * For mountpoint-related tasks, we want to sort everything by 466 * mountpoint, so that we mount and unmount them in the appropriate 467 * order, regardless of their position in the hierarchy. 468 */ 469 if (prop == ZFS_PROP_NAME || prop == ZFS_PROP_ZONED || 470 prop == ZFS_PROP_MOUNTPOINT || prop == ZFS_PROP_SHARENFS) { 471 compare = compare_mountpoints; 472 clp->cl_sorted = B_TRUE; 473 } 474 475 clp->cl_pool = uu_list_pool_create("changelist_pool", 476 sizeof (prop_changenode_t), 477 offsetof(prop_changenode_t, cn_listnode), 478 compare, 0); 479 if (clp->cl_pool == NULL) { 480 assert(uu_error() == UU_ERROR_NO_MEMORY); 481 (void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error"); 482 changelist_free(clp); 483 return (NULL); 484 } 485 486 clp->cl_list = uu_list_create(clp->cl_pool, NULL, 487 clp->cl_sorted ? UU_LIST_SORTED : 0); 488 clp->cl_flags = flags; 489 490 if (clp->cl_list == NULL) { 491 assert(uu_error() == UU_ERROR_NO_MEMORY); 492 (void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error"); 493 changelist_free(clp); 494 return (NULL); 495 } 496 497 /* 498 * If this is a rename or the 'zoned' property, we pretend we're 499 * changing the mountpoint and flag it so we can catch all children in 500 * change_one(). 501 * 502 * Flag cl_alldependents to catch all children plus the 503 * dependents (clones) that are not in the hierarchy. 504 */ 505 if (prop == ZFS_PROP_NAME) { 506 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 507 clp->cl_alldependents = B_TRUE; 508 } else if (prop == ZFS_PROP_ZONED) { 509 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 510 clp->cl_allchildren = B_TRUE; 511 } else if (prop == ZFS_PROP_CANMOUNT) { 512 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 513 } else { 514 clp->cl_prop = prop; 515 } 516 clp->cl_realprop = prop; 517 518 if (clp->cl_prop != ZFS_PROP_MOUNTPOINT && 519 clp->cl_prop != ZFS_PROP_SHARENFS) 520 return (clp); 521 522 if (clp->cl_alldependents) { 523 if (zfs_iter_dependents(zhp, B_TRUE, change_one, clp) != 0) { 524 changelist_free(clp); 525 return (NULL); 526 } 527 } else if (zfs_iter_children(zhp, change_one, clp) != 0) { 528 changelist_free(clp); 529 return (NULL); 530 } 531 532 /* 533 * We have to re-open ourselves because we auto-close all the handles 534 * and can't tell the difference. 535 */ 536 if ((temp = zfs_open(zhp->zfs_hdl, zfs_get_name(zhp), 537 ZFS_TYPE_ANY)) == NULL) { 538 changelist_free(clp); 539 return (NULL); 540 } 541 542 /* 543 * Always add ourself to the list. We add ourselves to the end so that 544 * we're the last to be unmounted. 545 */ 546 if ((cn = zfs_alloc(zhp->zfs_hdl, 547 sizeof (prop_changenode_t))) == NULL) { 548 zfs_close(temp); 549 changelist_free(clp); 550 return (NULL); 551 } 552 553 cn->cn_handle = temp; 554 cn->cn_mounted = zfs_is_mounted(temp, NULL); 555 cn->cn_shared = zfs_is_shared(temp, NULL); 556 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); 557 558 uu_list_node_init(cn, &cn->cn_listnode, clp->cl_pool); 559 if (clp->cl_sorted) { 560 uu_list_index_t idx; 561 (void) uu_list_find(clp->cl_list, cn, NULL, &idx); 562 uu_list_insert(clp->cl_list, cn, idx); 563 } else { 564 verify(uu_list_insert_after(clp->cl_list, 565 uu_list_last(clp->cl_list), cn) == 0); 566 } 567 568 /* 569 * If the property was previously 'legacy' or 'none', record this fact, 570 * as the behavior of changelist_postfix() will be different. 571 */ 572 if (zfs_prop_get(zhp, prop, property, sizeof (property), 573 NULL, NULL, 0, B_FALSE) == 0 && 574 (strcmp(property, "legacy") == 0 || strcmp(property, "none") == 0 || 575 strcmp(property, "off") == 0)) 576 clp->cl_waslegacy = B_TRUE; 577 578 return (clp); 579 } 580