xref: /illumos-gate/usr/src/lib/libzfs/common/libzfs_changelist.c (revision 4de2612967d06c4fdbf524a62556a1e8118a006f)
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