mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2024-11-17 10:01:01 +03:00
330d06f90d
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
469 lines
11 KiB
C
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));
|
|
}
|