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