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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 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 uu_list_node_t cn_listnode; 69 } prop_changenode_t; 70 71 struct prop_changelist { 72 zfs_prop_t cl_prop; 73 zfs_prop_t cl_realprop; 74 uu_list_pool_t *cl_pool; 75 uu_list_t *cl_list; 76 int cl_waslegacy; 77 int cl_allchildren; 78 int cl_alldependents; 79 int cl_flags; 80 int cl_haszonedchild; 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 (cn->cn_handle->zfs_volblocksize && 111 clp->cl_realprop == ZFS_PROP_NAME) { 112 if (zvol_remove_link(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_name) != 0) 171 ret = -1; 172 continue; 173 } 174 175 if ((clp->cl_waslegacy || cn->cn_mounted) && 176 !zfs_is_mounted(cn->cn_handle, NULL) && 177 zfs_mount(cn->cn_handle, NULL, 0) != 0) 178 ret = -1; 179 180 /* 181 * We always re-share even if the filesystem is currently 182 * shared, so that we can adopt any new options. 183 */ 184 if ((cn->cn_shared || 185 (clp->cl_prop == ZFS_PROP_SHARENFS && clp->cl_waslegacy))) { 186 char shareopts[ZFS_MAXPROPLEN]; 187 if (zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARENFS, 188 shareopts, sizeof (shareopts), NULL, NULL, 0, 189 FALSE) == 0 && strcmp(shareopts, "off") == 0) 190 ret = zfs_unshare(cn->cn_handle, NULL); 191 else 192 ret = zfs_share(cn->cn_handle); 193 } 194 } 195 196 return (ret); 197 } 198 199 /* 200 * Is this "dataset" a child of "parent"? 201 */ 202 static int 203 isa_child_of(char *dataset, const char *parent) 204 { 205 int len; 206 207 /* snapshot does not have a child */ 208 if (strchr(parent, '@')) 209 return (FALSE); 210 211 len = strlen(parent); 212 213 if (strncmp(dataset, parent, len) == 0 && 214 (dataset[len] == '/' || dataset[len] == '\0')) 215 return (TRUE); 216 else 217 return (FALSE); 218 219 } 220 221 /* 222 * If we rename a filesystem, and child filesystem handles are no longer valid, 223 * since we identify datasets by their name in the ZFS namespace. So, we have 224 * to go through and fix up all the names appropriately. We could do this 225 * automatically if libzfs kept track of all open handles, but this is a lot 226 * less work. 227 */ 228 void 229 changelist_rename(prop_changelist_t *clp, const char *src, const char *dst) 230 { 231 prop_changenode_t *cn; 232 char newname[ZFS_MAXNAMELEN]; 233 234 for (cn = uu_list_first(clp->cl_list); cn != NULL; 235 cn = uu_list_next(clp->cl_list, cn)) { 236 /* 237 * Do not rename a clone that's not in the source hierarchy. 238 */ 239 if (!isa_child_of(cn->cn_handle->zfs_name, src)) 240 continue; 241 242 /* 243 * Destroy the previous mountpoint if needed. 244 */ 245 remove_mountpoint(cn->cn_handle); 246 247 (void) strlcpy(newname, dst, sizeof (newname)); 248 (void) strcat(newname, cn->cn_handle->zfs_name + strlen(src)); 249 250 (void) strlcpy(cn->cn_handle->zfs_name, newname, 251 sizeof (cn->cn_handle->zfs_name)); 252 } 253 } 254 255 /* 256 * Given a gathered changelist for the "sharenfs" property, 257 * unshare all the nodes in the list. 258 */ 259 int 260 changelist_unshare(prop_changelist_t *clp) 261 { 262 prop_changenode_t *cn; 263 int ret = 0; 264 265 if (clp->cl_prop != ZFS_PROP_SHARENFS) 266 return (0); 267 268 for (cn = uu_list_first(clp->cl_list); cn != NULL; 269 cn = uu_list_next(clp->cl_list, cn)) { 270 271 if (zfs_unshare(cn->cn_handle, NULL) != 0) 272 ret = -1; 273 } 274 275 return (ret); 276 } 277 278 /* 279 * Check if there is any child exported to a local zone in a 280 * given changelist. This information has already been recorded 281 * while gathering the changelist via changelist_gather(). 282 */ 283 int 284 changelist_haszonedchild(prop_changelist_t *clp) 285 { 286 return (clp->cl_haszonedchild); 287 } 288 289 /* 290 * Remove a node from a gathered list. 291 */ 292 void 293 changelist_remove(zfs_handle_t *zhp, prop_changelist_t *clp) 294 { 295 prop_changenode_t *cn; 296 297 for (cn = uu_list_first(clp->cl_list); cn != NULL; 298 cn = uu_list_next(clp->cl_list, cn)) { 299 300 if (strcmp(cn->cn_handle->zfs_name, zhp->zfs_name) == 0) { 301 uu_list_remove(clp->cl_list, cn); 302 zfs_close(cn->cn_handle); 303 free(cn); 304 return; 305 } 306 } 307 } 308 309 /* 310 * Release any memory associated with a changelist. 311 */ 312 void 313 changelist_free(prop_changelist_t *clp) 314 { 315 prop_changenode_t *cn; 316 uu_list_walk_t *walk; 317 318 verify((walk = uu_list_walk_start(clp->cl_list, 319 UU_WALK_ROBUST)) != NULL); 320 321 while ((cn = uu_list_walk_next(walk)) != NULL) { 322 323 uu_list_remove(clp->cl_list, cn); 324 325 zfs_close(cn->cn_handle); 326 free(cn); 327 } 328 329 uu_list_pool_destroy(clp->cl_pool); 330 331 free(clp); 332 } 333 334 static int 335 change_one(zfs_handle_t *zhp, void *data) 336 { 337 prop_changelist_t *clp = data; 338 char property[ZFS_MAXPROPLEN]; 339 char where[64]; 340 prop_changenode_t *cn; 341 zfs_source_t sourcetype; 342 343 /* 344 * We only want to unmount/unshare those filesystems which may 345 * inherit from the target filesystem. If we find any filesystem 346 * with a locally set mountpoint, we ignore any children since changing 347 * the property will not affect them. If this is a rename, we iterate 348 * over all children regardless, since we need them unmounted in order 349 * to do the rename. Also, if this is a volume and we're doing a 350 * rename, then always add it to the changelist. 351 */ 352 353 if (!(zhp->zfs_volblocksize && clp->cl_realprop == ZFS_PROP_NAME) && 354 zfs_prop_get(zhp, clp->cl_prop, property, 355 sizeof (property), &sourcetype, where, sizeof (where), 356 FALSE) != 0) 357 return (0); 358 359 if (clp->cl_alldependents || clp->cl_allchildren || 360 sourcetype == ZFS_SRC_DEFAULT || sourcetype == ZFS_SRC_INHERITED) { 361 cn = zfs_malloc(sizeof (prop_changenode_t)); 362 363 cn->cn_handle = zhp; 364 cn->cn_mounted = zfs_is_mounted(zhp, NULL); 365 cn->cn_shared = zfs_is_shared(zhp, NULL); 366 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); 367 368 /* indicate if any child is exported to a local zone */ 369 if ((getzoneid() == GLOBAL_ZONEID) && cn->cn_zoned) 370 clp->cl_haszonedchild = TRUE; 371 372 uu_list_node_init(cn, &cn->cn_listnode, clp->cl_pool); 373 374 if (clp->cl_alldependents) { 375 verify(uu_list_insert_after(clp->cl_list, 376 uu_list_last(clp->cl_list), cn) == 0); 377 return (0); 378 } else { 379 verify(uu_list_insert_before(clp->cl_list, 380 uu_list_first(clp->cl_list), cn) == 0); 381 return (zfs_iter_children(zhp, change_one, data)); 382 } 383 } else { 384 zfs_close(zhp); 385 } 386 387 return (0); 388 } 389 390 391 /* 392 * Given a ZFS handle and a property, construct a complete list of datasets that 393 * need to be modified as part of this process. For anything but the 394 * 'mountpoint' and 'sharenfs' properties, this just returns an empty list. 395 * Otherwise, we iterate over all children and look for any datasets which 396 * inherit this property. For each such dataset, we add it to the list and mark 397 * whether it was shared beforehand. 398 */ 399 prop_changelist_t * 400 changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int flags) 401 { 402 prop_changelist_t *clp = zfs_malloc(sizeof (prop_changelist_t)); 403 prop_changenode_t *cn; 404 zfs_handle_t *temp; 405 char property[ZFS_MAXPROPLEN]; 406 407 clp->cl_pool = uu_list_pool_create("changelist_pool", 408 sizeof (prop_changenode_t), 409 offsetof(prop_changenode_t, cn_listnode), 410 NULL, 0); 411 assert(clp->cl_pool != NULL); 412 413 clp->cl_list = uu_list_create(clp->cl_pool, NULL, 0); 414 clp->cl_flags = flags; 415 416 /* 417 * If this is a rename or the 'zoned' property, we pretend we're 418 * changing the mountpoint and flag it so we can catch all children in 419 * change_one(). 420 * 421 * Flag cl_alldependents to catch all children plus the 422 * dependents (clones) that are not in the hierarchy. 423 */ 424 if (prop == ZFS_PROP_NAME) { 425 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 426 clp->cl_alldependents = TRUE; 427 } else if (prop == ZFS_PROP_ZONED) { 428 clp->cl_prop = ZFS_PROP_MOUNTPOINT; 429 clp->cl_allchildren = TRUE; 430 } else { 431 clp->cl_prop = prop; 432 } 433 clp->cl_realprop = prop; 434 435 if (clp->cl_prop != ZFS_PROP_MOUNTPOINT && 436 clp->cl_prop != ZFS_PROP_SHARENFS) 437 return (clp); 438 439 if (clp->cl_alldependents) { 440 if (zfs_iter_dependents(zhp, change_one, clp) != 0) { 441 changelist_free(clp); 442 return (NULL); 443 } 444 } else if (zfs_iter_children(zhp, change_one, clp) != 0) { 445 changelist_free(clp); 446 return (NULL); 447 } 448 449 /* 450 * We have to re-open ourselves because we auto-close all the handles 451 * and can't tell the difference. 452 */ 453 if ((temp = zfs_open(zfs_get_name(zhp), ZFS_TYPE_ANY)) == NULL) { 454 free(clp); 455 return (NULL); 456 } 457 458 /* 459 * Always add ourself to the list. We add ourselves to the end so that 460 * we're the last to be unmounted. 461 */ 462 cn = zfs_malloc(sizeof (prop_changenode_t)); 463 cn->cn_handle = temp; 464 cn->cn_mounted = zfs_is_mounted(temp, NULL); 465 cn->cn_shared = zfs_is_shared(temp, NULL); 466 cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); 467 468 uu_list_node_init(cn, &cn->cn_listnode, clp->cl_pool); 469 verify(uu_list_insert_after(clp->cl_list, 470 uu_list_last(clp->cl_list), cn) == 0); 471 472 /* 473 * If the property was previously 'legacy' or 'none', record this fact, 474 * as the behavior of changelist_postfix() will be different. 475 */ 476 if (zfs_prop_get(zhp, prop, property, sizeof (property), 477 NULL, NULL, 0, FALSE) == 0 && 478 (strcmp(property, "legacy") == 0 || strcmp(property, "none") == 0 || 479 strcmp(property, "off") == 0)) 480 clp->cl_waslegacy = TRUE; 481 482 return (clp); 483 } 484