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 verify((walk = uu_list_walk_start(clp->cl_list, 320 UU_WALK_ROBUST)) != NULL); 321 322 while ((cn = uu_list_walk_next(walk)) != NULL) { 323 324 uu_list_remove(clp->cl_list, cn); 325 326 zfs_close(cn->cn_handle); 327 free(cn); 328 } 329 330 uu_list_walk_end(walk); 331 332 uu_list_destroy(clp->cl_list); 333 uu_list_pool_destroy(clp->cl_pool); 334 335 free(clp); 336 } 337 338 static int 339 change_one(zfs_handle_t *zhp, void *data) 340 { 341 prop_changelist_t *clp = data; 342 char property[ZFS_MAXPROPLEN]; 343 char where[64]; 344 prop_changenode_t *cn; 345 zfs_source_t sourcetype; 346 347 /* 348 * We only want to unmount/unshare those filesystems which may 349 * inherit from the target filesystem. If we find any filesystem 350 * with a locally set mountpoint, we ignore any children since changing 351 * the property will not affect them. If this is a rename, we iterate 352 * over all children regardless, since we need them unmounted in order 353 * to do the rename. Also, if this is a volume and we're doing a 354 * rename, then always add it to the changelist. 355 */ 356 357 if (!(zhp->zfs_volblocksize && clp->cl_realprop == ZFS_PROP_NAME) && 358 zfs_prop_get(zhp, clp->cl_prop, property, 359 sizeof (property), &sourcetype, where, sizeof (where), 360 B_FALSE) != 0) { 361 zfs_close(zhp); 362 return (0); 363 } 364 365 if (clp->cl_alldependents || clp->cl_allchildren || 366 sourcetype == ZFS_SRC_DEFAULT || sourcetype == ZFS_SRC_INHERITED) { 367 if ((cn = zfs_alloc(zfs_get_handle(zhp), 368 sizeof (prop_changenode_t))) == NULL) { 369 zfs_close(zhp); 370 return (-1); 371 } 372 373 cn->cn_handle = zhp; 374 cn->cn_mounted = zfs_is_mounted(zhp, NULL); 375 cn->cn_shared = zfs_is_shared(zhp, NULL); 376 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); 377 378 /* indicate if any child is exported to a local zone */ 379 if ((getzoneid() == GLOBAL_ZONEID) && cn->cn_zoned) 380 clp->cl_haszonedchild = B_TRUE; 381 382 uu_list_node_init(cn, &cn->cn_listnode, clp->cl_pool); 383 384 if (clp->cl_alldependents) { 385 verify(uu_list_insert_after(clp->cl_list, 386 uu_list_last(clp->cl_list), cn) == 0); 387 return (0); 388 } else { 389 verify(uu_list_insert_before(clp->cl_list, 390 uu_list_first(clp->cl_list), cn) == 0); 391 return (zfs_iter_children(zhp, change_one, data)); 392 } 393 } else { 394 zfs_close(zhp); 395 } 396 397 return (0); 398 } 399 400 401 /* 402 * Given a ZFS handle and a property, construct a complete list of datasets that 403 * need to be modified as part of this process. For anything but the 404 * 'mountpoint' and 'sharenfs' properties, this just returns an empty list. 405 * Otherwise, we iterate over all children and look for any datasets which 406 * inherit this property. For each such dataset, we add it to the list and mark 407 * whether it was shared beforehand. 408 */ 409 prop_changelist_t * 410 changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int flags) 411 { 412 prop_changelist_t *clp; 413 prop_changenode_t *cn; 414 zfs_handle_t *temp; 415 char property[ZFS_MAXPROPLEN]; 416 417 if ((clp = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changelist_t))) == NULL) 418 return (NULL); 419 420 clp->cl_pool = uu_list_pool_create("changelist_pool", 421 sizeof (prop_changenode_t), 422 offsetof(prop_changenode_t, cn_listnode), 423 NULL, 0); 424 assert(clp->cl_pool != NULL); 425 426 clp->cl_list = uu_list_create(clp->cl_pool, NULL, 0); 427 clp->cl_flags = flags; 428 429 /* 430 * If this is a rename or the 'zoned' property, we pretend we're 431 * changing the mountpoint and flag it so we can catch all children in 432 * change_one(). 433 * 434 * Flag cl_alldependents to catch all children plus the 435 * dependents (clones) that are not in the hierarchy. 436 */ 437 if (prop == ZFS_PROP_NAME) { 438 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 439 clp->cl_alldependents = B_TRUE; 440 } else if (prop == ZFS_PROP_ZONED) { 441 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 442 clp->cl_allchildren = B_TRUE; 443 } else { 444 clp->cl_prop = prop; 445 } 446 clp->cl_realprop = prop; 447 448 if (clp->cl_prop != ZFS_PROP_MOUNTPOINT && 449 clp->cl_prop != ZFS_PROP_SHARENFS) 450 return (clp); 451 452 if (clp->cl_alldependents) { 453 if (zfs_iter_dependents(zhp, change_one, clp) != 0) { 454 changelist_free(clp); 455 return (NULL); 456 } 457 } else if (zfs_iter_children(zhp, change_one, clp) != 0) { 458 changelist_free(clp); 459 return (NULL); 460 } 461 462 /* 463 * We have to re-open ourselves because we auto-close all the handles 464 * and can't tell the difference. 465 */ 466 if ((temp = zfs_open(zhp->zfs_hdl, zfs_get_name(zhp), 467 ZFS_TYPE_ANY)) == NULL) { 468 changelist_free(clp); 469 return (NULL); 470 } 471 472 /* 473 * Always add ourself to the list. We add ourselves to the end so that 474 * we're the last to be unmounted. 475 */ 476 if ((cn = zfs_alloc(zhp->zfs_hdl, 477 sizeof (prop_changenode_t))) == NULL) { 478 zfs_close(temp); 479 changelist_free(clp); 480 return (NULL); 481 } 482 483 cn->cn_handle = temp; 484 cn->cn_mounted = zfs_is_mounted(temp, NULL); 485 cn->cn_shared = zfs_is_shared(temp, NULL); 486 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); 487 488 uu_list_node_init(cn, &cn->cn_listnode, clp->cl_pool); 489 verify(uu_list_insert_after(clp->cl_list, 490 uu_list_last(clp->cl_list), cn) == 0); 491 492 /* 493 * If the property was previously 'legacy' or 'none', record this fact, 494 * as the behavior of changelist_postfix() will be different. 495 */ 496 if (zfs_prop_get(zhp, prop, property, sizeof (property), 497 NULL, NULL, 0, B_FALSE) == 0 && 498 (strcmp(property, "legacy") == 0 || strcmp(property, "none") == 0 || 499 strcmp(property, "off") == 0)) 500 clp->cl_waslegacy = B_TRUE; 501 502 return (clp); 503 } 504