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 }; 81 82 /* 83 * If the property is 'mountpoint', go through and unmount filesystems as 84 * necessary. We don't do the same for 'sharenfs', because we can just re-share 85 * with different options without interrupting service. 86 */ 87 int 88 changelist_prefix(prop_changelist_t *clp) 89 { 90 prop_changenode_t *cn; 91 int ret = 0; 92 93 if (clp->cl_prop != ZFS_PROP_MOUNTPOINT) 94 return (0); 95 96 for (cn = uu_list_first(clp->cl_list); cn != NULL; 97 cn = uu_list_next(clp->cl_list, cn)) { 98 /* 99 * if we are in a global zone, but this dataset is exported to 100 * a local zone, do nothing. 101 */ 102 if ((getzoneid() == GLOBAL_ZONEID) && cn->cn_zoned) 103 continue; 104 105 /* 106 * If we have a volume and this was a rename, remove the 107 * /dev/zvol links 108 */ 109 if (cn->cn_handle->zfs_volblocksize && 110 clp->cl_realprop == ZFS_PROP_NAME) { 111 if (zvol_remove_link(cn->cn_handle->zfs_hdl, 112 cn->cn_handle->zfs_name) != 0) 113 ret = -1; 114 } else if (zfs_unmount(cn->cn_handle, NULL, clp->cl_flags) != 0) 115 ret = -1; 116 } 117 118 return (ret); 119 } 120 121 /* 122 * If the proeprty is 'mountpoint' or 'sharenfs', go through and remount and/or 123 * reshare the filesystems as necessary. In changelist_gather() we recorded 124 * whether the filesystem was previously shared or mounted. The action we take 125 * depends on the previous state, and whether the value was previously 'legacy'. 126 * For non-legacy properties, we only remount/reshare the filesystem if it was 127 * previously mounted/shared. Otherwise, we always remount/reshare the 128 * filesystem. 129 */ 130 int 131 changelist_postfix(prop_changelist_t *clp) 132 { 133 prop_changenode_t *cn; 134 int ret = 0; 135 136 /* 137 * If we're changing the mountpoint, attempt to destroy the underlying 138 * mountpoint. All other datasets will have inherited from this dataset 139 * (in which case their mountpoints exist in the filesystem in the new 140 * location), or have explicit mountpoints set (in which case they won't 141 * be in the changelist). 142 */ 143 if ((cn = uu_list_last(clp->cl_list)) == NULL) 144 return (0); 145 146 if (clp->cl_prop == ZFS_PROP_MOUNTPOINT) 147 remove_mountpoint(cn->cn_handle); 148 149 /* 150 * We walk the datasets in reverse, because we want to mount any parent 151 * datasets before mounting the children. 152 */ 153 for (cn = uu_list_last(clp->cl_list); cn != NULL; 154 cn = uu_list_prev(clp->cl_list, cn)) { 155 /* 156 * if we are in a global zone, but this dataset is exported to 157 * a local zone, do nothing. 158 */ 159 if ((getzoneid() == GLOBAL_ZONEID) && cn->cn_zoned) 160 continue; 161 162 zfs_refresh_properties(cn->cn_handle); 163 164 /* 165 * If this is a volume and we're doing a rename, recreate the 166 * /dev/zvol links. 167 */ 168 if (cn->cn_handle->zfs_volblocksize && 169 clp->cl_realprop == ZFS_PROP_NAME) { 170 if (zvol_create_link(cn->cn_handle->zfs_hdl, 171 cn->cn_handle->zfs_name) != 0) 172 ret = -1; 173 continue; 174 } 175 176 if ((clp->cl_waslegacy || cn->cn_mounted) && 177 !zfs_is_mounted(cn->cn_handle, NULL) && 178 zfs_mount(cn->cn_handle, NULL, 0) != 0) 179 ret = -1; 180 181 /* 182 * We always re-share even if the filesystem is currently 183 * shared, so that we can adopt any new options. 184 */ 185 if ((cn->cn_shared || 186 (clp->cl_prop == ZFS_PROP_SHARENFS && clp->cl_waslegacy))) { 187 char shareopts[ZFS_MAXPROPLEN]; 188 if (zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARENFS, 189 shareopts, sizeof (shareopts), NULL, NULL, 0, 190 B_FALSE) == 0 && strcmp(shareopts, "off") == 0) 191 ret = zfs_unshare(cn->cn_handle, NULL); 192 else 193 ret = zfs_share(cn->cn_handle); 194 } 195 } 196 197 return (ret); 198 } 199 200 /* 201 * Is this "dataset" a child of "parent"? 202 */ 203 static boolean_t 204 isa_child_of(char *dataset, const char *parent) 205 { 206 int len; 207 208 /* snapshot does not have a child */ 209 if (strchr(parent, '@')) 210 return (B_FALSE); 211 212 len = strlen(parent); 213 214 if (strncmp(dataset, parent, len) == 0 && 215 (dataset[len] == '/' || dataset[len] == '\0')) 216 return (B_TRUE); 217 else 218 return (B_FALSE); 219 220 } 221 222 /* 223 * If we rename a filesystem, and child filesystem handles are no longer valid, 224 * since we identify datasets by their name in the ZFS namespace. So, we have 225 * to go through and fix up all the names appropriately. We could do this 226 * automatically if libzfs kept track of all open handles, but this is a lot 227 * less work. 228 */ 229 void 230 changelist_rename(prop_changelist_t *clp, const char *src, const char *dst) 231 { 232 prop_changenode_t *cn; 233 char newname[ZFS_MAXNAMELEN]; 234 235 for (cn = uu_list_first(clp->cl_list); cn != NULL; 236 cn = uu_list_next(clp->cl_list, cn)) { 237 /* 238 * Do not rename a clone that's not in the source hierarchy. 239 */ 240 if (!isa_child_of(cn->cn_handle->zfs_name, src)) 241 continue; 242 243 /* 244 * Destroy the previous mountpoint if needed. 245 */ 246 remove_mountpoint(cn->cn_handle); 247 248 (void) strlcpy(newname, dst, sizeof (newname)); 249 (void) strcat(newname, cn->cn_handle->zfs_name + strlen(src)); 250 251 (void) strlcpy(cn->cn_handle->zfs_name, newname, 252 sizeof (cn->cn_handle->zfs_name)); 253 } 254 } 255 256 /* 257 * Given a gathered changelist for the "sharenfs" property, 258 * unshare all the nodes in the list. 259 */ 260 int 261 changelist_unshare(prop_changelist_t *clp) 262 { 263 prop_changenode_t *cn; 264 int ret = 0; 265 266 if (clp->cl_prop != ZFS_PROP_SHARENFS) 267 return (0); 268 269 for (cn = uu_list_first(clp->cl_list); cn != NULL; 270 cn = uu_list_next(clp->cl_list, cn)) { 271 272 if (zfs_unshare(cn->cn_handle, NULL) != 0) 273 ret = -1; 274 } 275 276 return (ret); 277 } 278 279 /* 280 * Check if there is any child exported to a local zone in a 281 * given changelist. This information has already been recorded 282 * while gathering the changelist via changelist_gather(). 283 */ 284 int 285 changelist_haszonedchild(prop_changelist_t *clp) 286 { 287 return (clp->cl_haszonedchild); 288 } 289 290 /* 291 * Remove a node from a gathered list. 292 */ 293 void 294 changelist_remove(zfs_handle_t *zhp, prop_changelist_t *clp) 295 { 296 prop_changenode_t *cn; 297 298 for (cn = uu_list_first(clp->cl_list); cn != NULL; 299 cn = uu_list_next(clp->cl_list, cn)) { 300 301 if (strcmp(cn->cn_handle->zfs_name, zhp->zfs_name) == 0) { 302 uu_list_remove(clp->cl_list, cn); 303 zfs_close(cn->cn_handle); 304 free(cn); 305 return; 306 } 307 } 308 } 309 310 /* 311 * Release any memory associated with a changelist. 312 */ 313 void 314 changelist_free(prop_changelist_t *clp) 315 { 316 prop_changenode_t *cn; 317 uu_list_walk_t *walk; 318 319 if (clp->cl_list) { 320 verify((walk = uu_list_walk_start(clp->cl_list, 321 UU_WALK_ROBUST)) != NULL); 322 323 while ((cn = uu_list_walk_next(walk)) != NULL) { 324 325 uu_list_remove(clp->cl_list, cn); 326 327 zfs_close(cn->cn_handle); 328 free(cn); 329 } 330 331 uu_list_walk_end(walk); 332 333 uu_list_destroy(clp->cl_list); 334 } 335 if (clp->cl_pool) 336 uu_list_pool_destroy(clp->cl_pool); 337 338 free(clp); 339 } 340 341 static int 342 change_one(zfs_handle_t *zhp, void *data) 343 { 344 prop_changelist_t *clp = data; 345 char property[ZFS_MAXPROPLEN]; 346 char where[64]; 347 prop_changenode_t *cn; 348 zfs_source_t sourcetype; 349 350 /* 351 * We only want to unmount/unshare those filesystems which may 352 * inherit from the target filesystem. If we find any filesystem 353 * with a locally set mountpoint, we ignore any children since changing 354 * the property will not affect them. If this is a rename, we iterate 355 * over all children regardless, since we need them unmounted in order 356 * to do the rename. Also, if this is a volume and we're doing a 357 * rename, then always add it to the changelist. 358 */ 359 360 if (!(zhp->zfs_volblocksize && clp->cl_realprop == ZFS_PROP_NAME) && 361 zfs_prop_get(zhp, clp->cl_prop, property, 362 sizeof (property), &sourcetype, where, sizeof (where), 363 B_FALSE) != 0) { 364 zfs_close(zhp); 365 return (0); 366 } 367 368 if (clp->cl_alldependents || clp->cl_allchildren || 369 sourcetype == ZFS_SRC_DEFAULT || sourcetype == ZFS_SRC_INHERITED) { 370 if ((cn = zfs_alloc(zfs_get_handle(zhp), 371 sizeof (prop_changenode_t))) == NULL) { 372 zfs_close(zhp); 373 return (-1); 374 } 375 376 cn->cn_handle = zhp; 377 cn->cn_mounted = zfs_is_mounted(zhp, NULL); 378 cn->cn_shared = zfs_is_shared(zhp, NULL); 379 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); 380 381 /* indicate if any child is exported to a local zone */ 382 if ((getzoneid() == GLOBAL_ZONEID) && cn->cn_zoned) 383 clp->cl_haszonedchild = B_TRUE; 384 385 uu_list_node_init(cn, &cn->cn_listnode, clp->cl_pool); 386 387 if (clp->cl_alldependents) { 388 verify(uu_list_insert_after(clp->cl_list, 389 uu_list_last(clp->cl_list), cn) == 0); 390 return (0); 391 } else { 392 verify(uu_list_insert_before(clp->cl_list, 393 uu_list_first(clp->cl_list), cn) == 0); 394 return (zfs_iter_children(zhp, change_one, data)); 395 } 396 } else { 397 zfs_close(zhp); 398 } 399 400 return (0); 401 } 402 403 404 /* 405 * Given a ZFS handle and a property, construct a complete list of datasets that 406 * need to be modified as part of this process. For anything but the 407 * 'mountpoint' and 'sharenfs' properties, this just returns an empty list. 408 * Otherwise, we iterate over all children and look for any datasets which 409 * inherit this property. For each such dataset, we add it to the list and mark 410 * whether it was shared beforehand. 411 */ 412 prop_changelist_t * 413 changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int flags) 414 { 415 prop_changelist_t *clp; 416 prop_changenode_t *cn; 417 zfs_handle_t *temp; 418 char property[ZFS_MAXPROPLEN]; 419 420 if ((clp = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changelist_t))) == NULL) 421 return (NULL); 422 423 clp->cl_pool = uu_list_pool_create("changelist_pool", 424 sizeof (prop_changenode_t), 425 offsetof(prop_changenode_t, cn_listnode), 426 NULL, 0); 427 if (clp->cl_pool == NULL) { 428 assert(uu_error() == UU_ERROR_NO_MEMORY); 429 (void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error"); 430 changelist_free(clp); 431 return (NULL); 432 } 433 434 clp->cl_list = uu_list_create(clp->cl_pool, NULL, 0); 435 clp->cl_flags = flags; 436 437 if (clp->cl_list == NULL) { 438 assert(uu_error() == UU_ERROR_NO_MEMORY); 439 (void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error"); 440 changelist_free(clp); 441 return (NULL); 442 } 443 444 /* 445 * If this is a rename or the 'zoned' property, we pretend we're 446 * changing the mountpoint and flag it so we can catch all children in 447 * change_one(). 448 * 449 * Flag cl_alldependents to catch all children plus the 450 * dependents (clones) that are not in the hierarchy. 451 */ 452 if (prop == ZFS_PROP_NAME) { 453 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 454 clp->cl_alldependents = B_TRUE; 455 } else if (prop == ZFS_PROP_ZONED) { 456 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 457 clp->cl_allchildren = B_TRUE; 458 } else { 459 clp->cl_prop = prop; 460 } 461 clp->cl_realprop = prop; 462 463 if (clp->cl_prop != ZFS_PROP_MOUNTPOINT && 464 clp->cl_prop != ZFS_PROP_SHARENFS) 465 return (clp); 466 467 if (clp->cl_alldependents) { 468 if (zfs_iter_dependents(zhp, change_one, clp) != 0) { 469 changelist_free(clp); 470 return (NULL); 471 } 472 } else if (zfs_iter_children(zhp, change_one, clp) != 0) { 473 changelist_free(clp); 474 return (NULL); 475 } 476 477 /* 478 * We have to re-open ourselves because we auto-close all the handles 479 * and can't tell the difference. 480 */ 481 if ((temp = zfs_open(zhp->zfs_hdl, zfs_get_name(zhp), 482 ZFS_TYPE_ANY)) == NULL) { 483 changelist_free(clp); 484 return (NULL); 485 } 486 487 /* 488 * Always add ourself to the list. We add ourselves to the end so that 489 * we're the last to be unmounted. 490 */ 491 if ((cn = zfs_alloc(zhp->zfs_hdl, 492 sizeof (prop_changenode_t))) == NULL) { 493 zfs_close(temp); 494 changelist_free(clp); 495 return (NULL); 496 } 497 498 cn->cn_handle = temp; 499 cn->cn_mounted = zfs_is_mounted(temp, NULL); 500 cn->cn_shared = zfs_is_shared(temp, NULL); 501 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); 502 503 uu_list_node_init(cn, &cn->cn_listnode, clp->cl_pool); 504 verify(uu_list_insert_after(clp->cl_list, 505 uu_list_last(clp->cl_list), cn) == 0); 506 507 /* 508 * If the property was previously 'legacy' or 'none', record this fact, 509 * as the behavior of changelist_postfix() will be different. 510 */ 511 if (zfs_prop_get(zhp, prop, property, sizeof (property), 512 NULL, NULL, 0, B_FALSE) == 0 && 513 (strcmp(property, "legacy") == 0 || strcmp(property, "none") == 0 || 514 strcmp(property, "off") == 0)) 515 clp->cl_waslegacy = B_TRUE; 516 517 return (clp); 518 } 519