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