mirror_zfs/lib/libzfs/libzfs_iter.c
Matthew Ahrens 330d06f90d Illumos #1644, #1645, #1646, #1647, #1708
1644 add ZFS "clones" property
1645 add ZFS "written" and "written@..." properties
1646 "zfs send" should estimate size of stream
1647 "zfs destroy" should determine space reclaimed by
     destroying multiple snapshots
1708 adjust size of zpool history data

References:
  https://www.illumos.org/issues/1644
  https://www.illumos.org/issues/1645
  https://www.illumos.org/issues/1646
  https://www.illumos.org/issues/1647
  https://www.illumos.org/issues/1708

This commit modifies the user to kernel space ioctl ABI.  Extra
care should be taken when updating to ensure both the kernel
modules and utilities are updated.  This change has reordered
all of the new ioctl()s to the end of the list.  This should
help minimize this issue in the future.

Reviewed by: Richard Lowe <richlowe@richlowe.net>
Reviewed by: George Wilson <gwilson@zfsmail.com>
Reviewed by: Albert Lee <trisk@opensolaris.org>
Approved by: Garrett D'Amore <garret@nexenta.com>

Ported by: Martin Matuska <martin@matuska.org>
Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Closes #826
Closes #664
2012-07-31 09:25:30 -07:00

469 lines
11 KiB
C

/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2010 Nexenta Systems, Inc. All rights reserved.
* Copyright (c) 2011 by Delphix. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <stddef.h>
#include <libintl.h>
#include <libzfs.h>
#include "libzfs_impl.h"
int
zfs_iter_clones(zfs_handle_t *zhp, zfs_iter_f func, void *data)
{
nvlist_t *nvl = zfs_get_clones_nvl(zhp);
nvpair_t *pair;
if (nvl == NULL)
return (0);
for (pair = nvlist_next_nvpair(nvl, NULL); pair != NULL;
pair = nvlist_next_nvpair(nvl, pair)) {
zfs_handle_t *clone = zfs_open(zhp->zfs_hdl, nvpair_name(pair),
ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
if (clone != NULL) {
int err = func(clone, data);
if (err != 0)
return (err);
}
}
return (0);
}
static int
zfs_do_list_ioctl(zfs_handle_t *zhp, int arg, zfs_cmd_t *zc)
{
int rc;
uint64_t orig_cookie;
orig_cookie = zc->zc_cookie;
top:
(void) strlcpy(zc->zc_name, zhp->zfs_name, sizeof (zc->zc_name));
rc = ioctl(zhp->zfs_hdl->libzfs_fd, arg, zc);
if (rc == -1) {
switch (errno) {
case ENOMEM:
/* expand nvlist memory and try again */
if (zcmd_expand_dst_nvlist(zhp->zfs_hdl, zc) != 0) {
zcmd_free_nvlists(zc);
return (-1);
}
zc->zc_cookie = orig_cookie;
goto top;
/*
* An errno value of ESRCH indicates normal completion.
* If ENOENT is returned, then the underlying dataset
* has been removed since we obtained the handle.
*/
case ESRCH:
case ENOENT:
rc = 1;
break;
default:
rc = zfs_standard_error(zhp->zfs_hdl, errno,
dgettext(TEXT_DOMAIN,
"cannot iterate filesystems"));
break;
}
}
return (rc);
}
/*
* Iterate over all child filesystems
*/
int
zfs_iter_filesystems(zfs_handle_t *zhp, zfs_iter_f func, void *data)
{
zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
zfs_handle_t *nzhp;
int ret;
if (zhp->zfs_type != ZFS_TYPE_FILESYSTEM)
return (0);
if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0)
return (-1);
while ((ret = zfs_do_list_ioctl(zhp, ZFS_IOC_DATASET_LIST_NEXT,
&zc)) == 0) {
/*
* Silently ignore errors, as the only plausible explanation is
* that the pool has since been removed.
*/
if ((nzhp = make_dataset_handle_zc(zhp->zfs_hdl,
&zc)) == NULL) {
continue;
}
if ((ret = func(nzhp, data)) != 0) {
zcmd_free_nvlists(&zc);
return (ret);
}
}
zcmd_free_nvlists(&zc);
return ((ret < 0) ? ret : 0);
}
/*
* Iterate over all snapshots
*/
int
zfs_iter_snapshots(zfs_handle_t *zhp, boolean_t simple, zfs_iter_f func,
void *data)
{
zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
zfs_handle_t *nzhp;
int ret;
if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT)
return (0);
zc.zc_simple = simple;
if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0)
return (-1);
while ((ret = zfs_do_list_ioctl(zhp, ZFS_IOC_SNAPSHOT_LIST_NEXT,
&zc)) == 0) {
if (simple)
nzhp = make_dataset_simple_handle_zc(zhp, &zc);
else
nzhp = make_dataset_handle_zc(zhp->zfs_hdl, &zc);
if (nzhp == NULL)
continue;
if ((ret = func(nzhp, data)) != 0) {
zcmd_free_nvlists(&zc);
return (ret);
}
}
zcmd_free_nvlists(&zc);
return ((ret < 0) ? ret : 0);
}
/*
* Routines for dealing with the sorted snapshot functionality
*/
typedef struct zfs_node {
zfs_handle_t *zn_handle;
avl_node_t zn_avlnode;
} zfs_node_t;
static int
zfs_sort_snaps(zfs_handle_t *zhp, void *data)
{
avl_tree_t *avl = data;
zfs_node_t *node;
zfs_node_t search;
search.zn_handle = zhp;
node = avl_find(avl, &search, NULL);
if (node) {
/*
* If this snapshot was renamed while we were creating the
* AVL tree, it's possible that we already inserted it under
* its old name. Remove the old handle before adding the new
* one.
*/
zfs_close(node->zn_handle);
avl_remove(avl, node);
free(node);
}
node = zfs_alloc(zhp->zfs_hdl, sizeof (zfs_node_t));
node->zn_handle = zhp;
avl_add(avl, node);
return (0);
}
static int
zfs_snapshot_compare(const void *larg, const void *rarg)
{
zfs_handle_t *l = ((zfs_node_t *)larg)->zn_handle;
zfs_handle_t *r = ((zfs_node_t *)rarg)->zn_handle;
uint64_t lcreate, rcreate;
/*
* Sort them according to creation time. We use the hidden
* CREATETXG property to get an absolute ordering of snapshots.
*/
lcreate = zfs_prop_get_int(l, ZFS_PROP_CREATETXG);
rcreate = zfs_prop_get_int(r, ZFS_PROP_CREATETXG);
if (lcreate < rcreate)
return (-1);
else if (lcreate > rcreate)
return (+1);
else
return (0);
}
int
zfs_iter_snapshots_sorted(zfs_handle_t *zhp, zfs_iter_f callback, void *data)
{
int ret = 0;
zfs_node_t *node;
avl_tree_t avl;
void *cookie = NULL;
avl_create(&avl, zfs_snapshot_compare,
sizeof (zfs_node_t), offsetof(zfs_node_t, zn_avlnode));
ret = zfs_iter_snapshots(zhp, B_FALSE, zfs_sort_snaps, &avl);
for (node = avl_first(&avl); node != NULL; node = AVL_NEXT(&avl, node))
ret |= callback(node->zn_handle, data);
while ((node = avl_destroy_nodes(&avl, &cookie)) != NULL)
free(node);
avl_destroy(&avl);
return (ret);
}
typedef struct {
char *ssa_first;
char *ssa_last;
boolean_t ssa_seenfirst;
boolean_t ssa_seenlast;
zfs_iter_f ssa_func;
void *ssa_arg;
} snapspec_arg_t;
static int
snapspec_cb(zfs_handle_t *zhp, void *arg) {
snapspec_arg_t *ssa = arg;
char *shortsnapname;
int err = 0;
if (ssa->ssa_seenlast)
return (0);
shortsnapname = zfs_strdup(zhp->zfs_hdl,
strchr(zfs_get_name(zhp), '@') + 1);
if (!ssa->ssa_seenfirst && strcmp(shortsnapname, ssa->ssa_first) == 0)
ssa->ssa_seenfirst = B_TRUE;
if (ssa->ssa_seenfirst) {
err = ssa->ssa_func(zhp, ssa->ssa_arg);
} else {
zfs_close(zhp);
}
if (strcmp(shortsnapname, ssa->ssa_last) == 0)
ssa->ssa_seenlast = B_TRUE;
free(shortsnapname);
return (err);
}
/*
* spec is a string like "A,B%C,D"
*
* <snaps>, where <snaps> can be:
* <snap> (single snapshot)
* <snap>%<snap> (range of snapshots, inclusive)
* %<snap> (range of snapshots, starting with earliest)
* <snap>% (range of snapshots, ending with last)
* % (all snapshots)
* <snaps>[,...] (comma separated list of the above)
*
* If a snapshot can not be opened, continue trying to open the others, but
* return ENOENT at the end.
*/
int
zfs_iter_snapspec(zfs_handle_t *fs_zhp, const char *spec_orig,
zfs_iter_f func, void *arg)
{
char buf[ZFS_MAXNAMELEN];
char *comma_separated, *cp;
int err = 0;
int ret = 0;
(void) strlcpy(buf, spec_orig, sizeof (buf));
cp = buf;
while ((comma_separated = strsep(&cp, ",")) != NULL) {
char *pct = strchr(comma_separated, '%');
if (pct != NULL) {
snapspec_arg_t ssa = { 0 };
ssa.ssa_func = func;
ssa.ssa_arg = arg;
if (pct == comma_separated)
ssa.ssa_seenfirst = B_TRUE;
else
ssa.ssa_first = comma_separated;
*pct = '\0';
ssa.ssa_last = pct + 1;
/*
* If there is a lastname specified, make sure it
* exists.
*/
if (ssa.ssa_last[0] != '\0') {
char snapname[ZFS_MAXNAMELEN];
(void) snprintf(snapname, sizeof (snapname),
"%s@%s", zfs_get_name(fs_zhp),
ssa.ssa_last);
if (!zfs_dataset_exists(fs_zhp->zfs_hdl,
snapname, ZFS_TYPE_SNAPSHOT)) {
ret = ENOENT;
continue;
}
}
err = zfs_iter_snapshots_sorted(fs_zhp,
snapspec_cb, &ssa);
if (ret == 0)
ret = err;
if (ret == 0 && (!ssa.ssa_seenfirst ||
(ssa.ssa_last[0] != '\0' && !ssa.ssa_seenlast))) {
ret = ENOENT;
}
} else {
char snapname[ZFS_MAXNAMELEN];
zfs_handle_t *snap_zhp;
(void) snprintf(snapname, sizeof (snapname), "%s@%s",
zfs_get_name(fs_zhp), comma_separated);
snap_zhp = make_dataset_handle(fs_zhp->zfs_hdl,
snapname);
if (snap_zhp == NULL) {
ret = ENOENT;
continue;
}
err = func(snap_zhp, arg);
if (ret == 0)
ret = err;
}
}
return (ret);
}
/*
* Iterate over all children, snapshots and filesystems
*/
int
zfs_iter_children(zfs_handle_t *zhp, zfs_iter_f func, void *data)
{
int ret;
if ((ret = zfs_iter_filesystems(zhp, func, data)) != 0)
return (ret);
return (zfs_iter_snapshots(zhp, B_FALSE, func, data));
}
typedef struct iter_stack_frame {
struct iter_stack_frame *next;
zfs_handle_t *zhp;
} iter_stack_frame_t;
typedef struct iter_dependents_arg {
boolean_t first;
boolean_t allowrecursion;
iter_stack_frame_t *stack;
zfs_iter_f func;
void *data;
} iter_dependents_arg_t;
static int
iter_dependents_cb(zfs_handle_t *zhp, void *arg)
{
iter_dependents_arg_t *ida = arg;
int err;
boolean_t first = ida->first;
ida->first = B_FALSE;
if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) {
err = zfs_iter_clones(zhp, iter_dependents_cb, ida);
} else {
iter_stack_frame_t isf;
iter_stack_frame_t *f;
/*
* check if there is a cycle by seeing if this fs is already
* on the stack.
*/
for (f = ida->stack; f != NULL; f = f->next) {
if (f->zhp->zfs_dmustats.dds_guid ==
zhp->zfs_dmustats.dds_guid) {
if (ida->allowrecursion) {
zfs_close(zhp);
return (0);
} else {
zfs_error_aux(zhp->zfs_hdl,
dgettext(TEXT_DOMAIN,
"recursive dependency at '%s'"),
zfs_get_name(zhp));
err = zfs_error(zhp->zfs_hdl,
EZFS_RECURSIVE,
dgettext(TEXT_DOMAIN,
"cannot determine dependent "
"datasets"));
zfs_close(zhp);
return (err);
}
}
}
isf.zhp = zhp;
isf.next = ida->stack;
ida->stack = &isf;
err = zfs_iter_filesystems(zhp, iter_dependents_cb, ida);
if (err == 0)
err = zfs_iter_snapshots(zhp, B_FALSE,
iter_dependents_cb, ida);
ida->stack = isf.next;
}
if (!first && err == 0)
err = ida->func(zhp, ida->data);
return (err);
}
int
zfs_iter_dependents(zfs_handle_t *zhp, boolean_t allowrecursion,
zfs_iter_f func, void *data)
{
iter_dependents_arg_t ida;
ida.allowrecursion = allowrecursion;
ida.stack = NULL;
ida.func = func;
ida.data = data;
ida.first = B_TRUE;
return (iter_dependents_cb(zfs_handle_dup(zhp), &ida));
}