Illumos 4368, 4369.

4369 implement zfs bookmarks
4368 zfs send filesystems from readonly pools
Reviewed by: Christopher Siden <christopher.siden@delphix.com>
Reviewed by: George Wilson <george.wilson@delphix.com>
Approved by: Garrett D'Amore <garrett@damore.org>

References:
  https://www.illumos.org/issues/4369
  https://www.illumos.org/issues/4368
  https://github.com/illumos/illumos-gate/commit/78f1710

Ported by: Tim Chase <tim@chase2k.com>
Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Closes #2530
This commit is contained in:
Matthew Ahrens
2013-12-11 14:33:41 -08:00
committed by Brian Behlendorf
parent b0bc7a84d9
commit da536844d5
33 changed files with 1677 additions and 270 deletions
+1
View File
@@ -25,6 +25,7 @@ $(MODULE)-objs += @top_srcdir@/module/zfs/dnode_sync.o
$(MODULE)-objs += @top_srcdir@/module/zfs/dsl_dataset.o
$(MODULE)-objs += @top_srcdir@/module/zfs/dsl_deadlist.o
$(MODULE)-objs += @top_srcdir@/module/zfs/dsl_deleg.o
$(MODULE)-objs += @top_srcdir@/module/zfs/dsl_bookmark.o
$(MODULE)-objs += @top_srcdir@/module/zfs/dsl_dir.o
$(MODULE)-objs += @top_srcdir@/module/zfs/dsl_pool.o
$(MODULE)-objs += @top_srcdir@/module/zfs/dsl_prop.o
+1 -1
View File
@@ -187,7 +187,7 @@ dmu_diff(const char *tosnap_name, const char *fromsnap_name,
return (error);
}
if (!dsl_dataset_is_before(tosnap, fromsnap)) {
if (!dsl_dataset_is_before(tosnap, fromsnap, 0)) {
dsl_dataset_rele(fromsnap, FTAG);
dsl_dataset_rele(tosnap, FTAG);
dsl_pool_rele(dp, FTAG);
+91 -35
View File
@@ -50,6 +50,7 @@
#include <sys/zfs_onexit.h>
#include <sys/dmu_send.h>
#include <sys/dsl_destroy.h>
#include <sys/dsl_bookmark.h>
/* Set this tunable to TRUE to replace corrupt data with 0x2f5baddb10c */
int zfs_send_corrupt_data = B_FALSE;
@@ -385,6 +386,12 @@ backup_cb(spa_t *spa, zilog_t *zilog, const blkptr_t *bp,
if (zb->zb_object != DMU_META_DNODE_OBJECT &&
DMU_OBJECT_IS_SPECIAL(zb->zb_object)) {
return (0);
} else if (zb->zb_level == ZB_ZIL_LEVEL) {
/*
* If we are sending a non-snapshot (which is allowed on
* read-only pools), it may have a ZIL, which must be ignored.
*/
return (0);
} else if (BP_IS_HOLE(bp) &&
zb->zb_object == DMU_META_DNODE_OBJECT) {
uint64_t span = BP_SPAN(dnp, zb->zb_level);
@@ -433,6 +440,7 @@ backup_cb(spa_t *spa, zilog_t *zilog, const blkptr_t *bp,
arc_buf_t *abuf;
int blksz = BP_GET_LSIZE(bp);
ASSERT0(zb->zb_level);
if (arc_read(NULL, spa, bp, arc_getbuf_func, &abuf,
ZIO_PRIORITY_ASYNC_READ, ZIO_FLAG_CANFAIL,
&aflags, zb) != 0) {
@@ -460,11 +468,12 @@ backup_cb(spa_t *spa, zilog_t *zilog, const blkptr_t *bp,
}
/*
* Releases dp, ds, and fromds, using the specified tag.
* Releases dp using the specified tag.
*/
static int
dmu_send_impl(void *tag, dsl_pool_t *dp, dsl_dataset_t *ds,
dsl_dataset_t *fromds, int outfd, vnode_t *vp, offset_t *off)
zfs_bookmark_phys_t *fromzb, boolean_t is_clone, int outfd,
vnode_t *vp, offset_t *off)
{
objset_t *os;
dmu_replay_record_t *drr;
@@ -472,18 +481,8 @@ dmu_send_impl(void *tag, dsl_pool_t *dp, dsl_dataset_t *ds,
int err;
uint64_t fromtxg = 0;
if (fromds != NULL && !dsl_dataset_is_before(ds, fromds)) {
dsl_dataset_rele(fromds, tag);
dsl_dataset_rele(ds, tag);
dsl_pool_rele(dp, tag);
return (SET_ERROR(EXDEV));
}
err = dmu_objset_from_ds(ds, &os);
if (err != 0) {
if (fromds != NULL)
dsl_dataset_rele(fromds, tag);
dsl_dataset_rele(ds, tag);
dsl_pool_rele(dp, tag);
return (err);
}
@@ -499,9 +498,6 @@ dmu_send_impl(void *tag, dsl_pool_t *dp, dsl_dataset_t *ds,
uint64_t version;
if (zfs_get_zplprop(os, ZFS_PROP_VERSION, &version) != 0) {
kmem_free(drr, sizeof (dmu_replay_record_t));
if (fromds != NULL)
dsl_dataset_rele(fromds, tag);
dsl_dataset_rele(ds, tag);
dsl_pool_rele(dp, tag);
return (SET_ERROR(EINVAL));
}
@@ -516,20 +512,20 @@ dmu_send_impl(void *tag, dsl_pool_t *dp, dsl_dataset_t *ds,
drr->drr_u.drr_begin.drr_creation_time =
ds->ds_phys->ds_creation_time;
drr->drr_u.drr_begin.drr_type = dmu_objset_type(os);
if (fromds != NULL && ds->ds_dir != fromds->ds_dir)
if (is_clone)
drr->drr_u.drr_begin.drr_flags |= DRR_FLAG_CLONE;
drr->drr_u.drr_begin.drr_toguid = ds->ds_phys->ds_guid;
if (ds->ds_phys->ds_flags & DS_FLAG_CI_DATASET)
drr->drr_u.drr_begin.drr_flags |= DRR_FLAG_CI_DATA;
if (fromds != NULL)
drr->drr_u.drr_begin.drr_fromguid = fromds->ds_phys->ds_guid;
if (fromzb != NULL) {
drr->drr_u.drr_begin.drr_fromguid = fromzb->zbm_guid;
fromtxg = fromzb->zbm_creation_txg;
}
dsl_dataset_name(ds, drr->drr_u.drr_begin.drr_toname);
if (fromds != NULL) {
fromtxg = fromds->ds_phys->ds_creation_txg;
dsl_dataset_rele(fromds, tag);
fromds = NULL;
if (!dsl_dataset_is_snapshot(ds)) {
(void) strlcat(drr->drr_u.drr_begin.drr_toname, "@--head--",
sizeof (drr->drr_u.drr_begin.drr_toname));
}
dsp = kmem_zalloc(sizeof (dmu_sendarg_t), KM_SLEEP);
@@ -543,7 +539,7 @@ dmu_send_impl(void *tag, dsl_pool_t *dp, dsl_dataset_t *ds,
dsp->dsa_toguid = ds->ds_phys->ds_guid;
ZIO_SET_CHECKSUM(&dsp->dsa_zc, 0, 0, 0, 0);
dsp->dsa_pending_op = PENDING_NONE;
dsp->dsa_incremental = (fromtxg != 0);
dsp->dsa_incremental = (fromzb != NULL);
mutex_enter(&ds->ds_sendstream_lock);
list_insert_head(&ds->ds_sendstreams, dsp);
@@ -589,7 +585,6 @@ out:
kmem_free(dsp, sizeof (dmu_sendarg_t));
dsl_dataset_long_rele(ds, FTAG);
dsl_dataset_rele(ds, tag);
return (err);
}
@@ -614,15 +609,30 @@ dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
}
if (fromsnap != 0) {
zfs_bookmark_phys_t zb;
boolean_t is_clone;
err = dsl_dataset_hold_obj(dp, fromsnap, FTAG, &fromds);
if (err != 0) {
dsl_dataset_rele(ds, FTAG);
dsl_pool_rele(dp, FTAG);
return (err);
}
if (!dsl_dataset_is_before(ds, fromds, 0))
err = SET_ERROR(EXDEV);
zb.zbm_creation_time = fromds->ds_phys->ds_creation_time;
zb.zbm_creation_txg = fromds->ds_phys->ds_creation_txg;
zb.zbm_guid = fromds->ds_phys->ds_guid;
is_clone = (fromds->ds_dir != ds->ds_dir);
dsl_dataset_rele(fromds, FTAG);
err = dmu_send_impl(FTAG, dp, ds, &zb, is_clone,
outfd, vp, off);
} else {
err = dmu_send_impl(FTAG, dp, ds, NULL, B_FALSE,
outfd, vp, off);
}
return (dmu_send_impl(FTAG, dp, ds, fromds, outfd, vp, off));
dsl_dataset_rele(ds, FTAG);
return (err);
}
int
@@ -631,33 +641,79 @@ dmu_send(const char *tosnap, const char *fromsnap,
{
dsl_pool_t *dp;
dsl_dataset_t *ds;
dsl_dataset_t *fromds = NULL;
int err;
boolean_t owned = B_FALSE;
if (strchr(tosnap, '@') == NULL)
return (SET_ERROR(EINVAL));
if (fromsnap != NULL && strchr(fromsnap, '@') == NULL)
if (fromsnap != NULL && strpbrk(fromsnap, "@#") == NULL)
return (SET_ERROR(EINVAL));
err = dsl_pool_hold(tosnap, FTAG, &dp);
if (err != 0)
return (err);
err = dsl_dataset_hold(dp, tosnap, FTAG, &ds);
if (strchr(tosnap, '@') == NULL && spa_writeable(dp->dp_spa)) {
/*
* We are sending a filesystem or volume. Ensure
* that it doesn't change by owning the dataset.
*/
err = dsl_dataset_own(dp, tosnap, FTAG, &ds);
owned = B_TRUE;
} else {
err = dsl_dataset_hold(dp, tosnap, FTAG, &ds);
}
if (err != 0) {
dsl_pool_rele(dp, FTAG);
return (err);
}
if (fromsnap != NULL) {
err = dsl_dataset_hold(dp, fromsnap, FTAG, &fromds);
zfs_bookmark_phys_t zb;
boolean_t is_clone = B_FALSE;
int fsnamelen = strchr(tosnap, '@') - tosnap;
/*
* If the fromsnap is in a different filesystem, then
* mark the send stream as a clone.
*/
if (strncmp(tosnap, fromsnap, fsnamelen) != 0 ||
(fromsnap[fsnamelen] != '@' &&
fromsnap[fsnamelen] != '#')) {
is_clone = B_TRUE;
}
if (strchr(fromsnap, '@')) {
dsl_dataset_t *fromds;
err = dsl_dataset_hold(dp, fromsnap, FTAG, &fromds);
if (err == 0) {
if (!dsl_dataset_is_before(ds, fromds, 0))
err = SET_ERROR(EXDEV);
zb.zbm_creation_time =
fromds->ds_phys->ds_creation_time;
zb.zbm_creation_txg =
fromds->ds_phys->ds_creation_txg;
zb.zbm_guid = fromds->ds_phys->ds_guid;
is_clone = (ds->ds_dir != fromds->ds_dir);
dsl_dataset_rele(fromds, FTAG);
}
} else {
err = dsl_bookmark_lookup(dp, fromsnap, ds, &zb);
}
if (err != 0) {
dsl_dataset_rele(ds, FTAG);
dsl_pool_rele(dp, FTAG);
return (err);
}
err = dmu_send_impl(FTAG, dp, ds, &zb, is_clone,
outfd, vp, off);
} else {
err = dmu_send_impl(FTAG, dp, ds, NULL, B_FALSE,
outfd, vp, off);
}
return (dmu_send_impl(FTAG, dp, ds, fromds, outfd, vp, off));
if (owned)
dsl_dataset_disown(ds, FTAG);
else
dsl_dataset_rele(ds, FTAG);
return (err);
}
int
@@ -677,7 +733,7 @@ dmu_send_estimate(dsl_dataset_t *ds, dsl_dataset_t *fromds, uint64_t *sizep)
* fromsnap must be an earlier snapshot from the same fs as tosnap,
* or the origin's fs.
*/
if (fromds != NULL && !dsl_dataset_is_before(ds, fromds))
if (fromds != NULL && !dsl_dataset_is_before(ds, fromds, 0))
return (SET_ERROR(EXDEV));
/* Get uncompressed size estimate of changed data. */
+459
View File
@@ -0,0 +1,459 @@
/*
* CDDL HEADER START
*
* This file and its contents are supplied under the terms of the
* Common Development and Distribution License ("CDDL"), version 1.0.
* You may only use this file in accordance with the terms of version
* 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this
* source. A copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2013 by Delphix. All rights reserved.
*/
#include <sys/zfs_context.h>
#include <sys/dsl_dataset.h>
#include <sys/dsl_dir.h>
#include <sys/dsl_prop.h>
#include <sys/dsl_synctask.h>
#include <sys/dmu_impl.h>
#include <sys/dmu_tx.h>
#include <sys/arc.h>
#include <sys/zap.h>
#include <sys/zfeature.h>
#include <sys/spa.h>
#include <sys/dsl_bookmark.h>
#include <zfs_namecheck.h>
static int
dsl_bookmark_hold_ds(dsl_pool_t *dp, const char *fullname,
dsl_dataset_t **dsp, void *tag, char **shortnamep)
{
char buf[MAXNAMELEN];
char *hashp;
if (strlen(fullname) >= MAXNAMELEN)
return (SET_ERROR(ENAMETOOLONG));
hashp = strchr(fullname, '#');
if (hashp == NULL)
return (SET_ERROR(EINVAL));
*shortnamep = hashp + 1;
if (zfs_component_namecheck(*shortnamep, NULL, NULL))
return (SET_ERROR(EINVAL));
(void) strlcpy(buf, fullname, hashp - fullname + 1);
return (dsl_dataset_hold(dp, buf, tag, dsp));
}
/*
* Returns ESRCH if bookmark is not found.
*/
static int
dsl_dataset_bmark_lookup(dsl_dataset_t *ds, const char *shortname,
zfs_bookmark_phys_t *bmark_phys)
{
objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
uint64_t bmark_zapobj = ds->ds_bookmarks;
matchtype_t mt;
int err;
if (bmark_zapobj == 0)
return (SET_ERROR(ESRCH));
if (ds->ds_phys->ds_flags & DS_FLAG_CI_DATASET)
mt = MT_FIRST;
else
mt = MT_EXACT;
err = zap_lookup_norm(mos, bmark_zapobj, shortname, sizeof (uint64_t),
sizeof (*bmark_phys) / sizeof (uint64_t), bmark_phys, mt,
NULL, 0, NULL);
return (err == ENOENT ? ESRCH : err);
}
/*
* If later_ds is non-NULL, this will return EXDEV if the the specified bookmark
* does not represents an earlier point in later_ds's timeline.
*
* Returns ENOENT if the dataset containing the bookmark does not exist.
* Returns ESRCH if the dataset exists but the bookmark was not found in it.
*/
int
dsl_bookmark_lookup(dsl_pool_t *dp, const char *fullname,
dsl_dataset_t *later_ds, zfs_bookmark_phys_t *bmp)
{
char *shortname;
dsl_dataset_t *ds;
int error;
error = dsl_bookmark_hold_ds(dp, fullname, &ds, FTAG, &shortname);
if (error != 0)
return (error);
error = dsl_dataset_bmark_lookup(ds, shortname, bmp);
if (error == 0 && later_ds != NULL) {
if (!dsl_dataset_is_before(later_ds, ds, bmp->zbm_creation_txg))
error = SET_ERROR(EXDEV);
}
dsl_dataset_rele(ds, FTAG);
return (error);
}
typedef struct dsl_bookmark_create_arg {
nvlist_t *dbca_bmarks;
nvlist_t *dbca_errors;
} dsl_bookmark_create_arg_t;
static int
dsl_bookmark_create_check_impl(dsl_dataset_t *snapds, const char *bookmark_name,
dmu_tx_t *tx)
{
dsl_pool_t *dp = dmu_tx_pool(tx);
dsl_dataset_t *bmark_fs;
char *shortname;
int error;
zfs_bookmark_phys_t bmark_phys;
if (!dsl_dataset_is_snapshot(snapds))
return (SET_ERROR(EINVAL));
error = dsl_bookmark_hold_ds(dp, bookmark_name,
&bmark_fs, FTAG, &shortname);
if (error != 0)
return (error);
if (!dsl_dataset_is_before(bmark_fs, snapds, 0)) {
dsl_dataset_rele(bmark_fs, FTAG);
return (SET_ERROR(EINVAL));
}
error = dsl_dataset_bmark_lookup(bmark_fs, shortname,
&bmark_phys);
dsl_dataset_rele(bmark_fs, FTAG);
if (error == 0)
return (SET_ERROR(EEXIST));
if (error == ESRCH)
return (0);
return (error);
}
static int
dsl_bookmark_create_check(void *arg, dmu_tx_t *tx)
{
dsl_bookmark_create_arg_t *dbca = arg;
dsl_pool_t *dp = dmu_tx_pool(tx);
int rv = 0;
nvpair_t *pair;
if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS))
return (SET_ERROR(ENOTSUP));
for (pair = nvlist_next_nvpair(dbca->dbca_bmarks, NULL);
pair != NULL; pair = nvlist_next_nvpair(dbca->dbca_bmarks, pair)) {
dsl_dataset_t *snapds;
int error;
/* note: validity of nvlist checked by ioctl layer */
error = dsl_dataset_hold(dp, fnvpair_value_string(pair),
FTAG, &snapds);
if (error == 0) {
error = dsl_bookmark_create_check_impl(snapds,
nvpair_name(pair), tx);
dsl_dataset_rele(snapds, FTAG);
}
if (error != 0) {
fnvlist_add_int32(dbca->dbca_errors,
nvpair_name(pair), error);
rv = error;
}
}
return (rv);
}
static void
dsl_bookmark_create_sync(void *arg, dmu_tx_t *tx)
{
dsl_bookmark_create_arg_t *dbca = arg;
dsl_pool_t *dp = dmu_tx_pool(tx);
objset_t *mos = dp->dp_meta_objset;
nvpair_t *pair;
ASSERT(spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS));
for (pair = nvlist_next_nvpair(dbca->dbca_bmarks, NULL);
pair != NULL; pair = nvlist_next_nvpair(dbca->dbca_bmarks, pair)) {
dsl_dataset_t *snapds, *bmark_fs;
zfs_bookmark_phys_t bmark_phys;
char *shortname;
VERIFY0(dsl_dataset_hold(dp, fnvpair_value_string(pair),
FTAG, &snapds));
VERIFY0(dsl_bookmark_hold_ds(dp, nvpair_name(pair),
&bmark_fs, FTAG, &shortname));
if (bmark_fs->ds_bookmarks == 0) {
bmark_fs->ds_bookmarks =
zap_create_norm(mos, U8_TEXTPREP_TOUPPER,
DMU_OTN_ZAP_METADATA, DMU_OT_NONE, 0, tx);
spa_feature_incr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx);
dsl_dataset_zapify(bmark_fs, tx);
VERIFY0(zap_add(mos, bmark_fs->ds_object,
DS_FIELD_BOOKMARK_NAMES,
sizeof (bmark_fs->ds_bookmarks), 1,
&bmark_fs->ds_bookmarks, tx));
}
bmark_phys.zbm_guid = snapds->ds_phys->ds_guid;
bmark_phys.zbm_creation_txg = snapds->ds_phys->ds_creation_txg;
bmark_phys.zbm_creation_time =
snapds->ds_phys->ds_creation_time;
VERIFY0(zap_add(mos, bmark_fs->ds_bookmarks,
shortname, sizeof (uint64_t),
sizeof (zfs_bookmark_phys_t) / sizeof (uint64_t),
&bmark_phys, tx));
spa_history_log_internal_ds(bmark_fs, "bookmark", tx,
"name=%s creation_txg=%llu target_snap=%llu",
shortname,
(longlong_t)bmark_phys.zbm_creation_txg,
(longlong_t)snapds->ds_object);
dsl_dataset_rele(bmark_fs, FTAG);
dsl_dataset_rele(snapds, FTAG);
}
}
/*
* The bookmarks must all be in the same pool.
*/
int
dsl_bookmark_create(nvlist_t *bmarks, nvlist_t *errors)
{
nvpair_t *pair;
dsl_bookmark_create_arg_t dbca;
pair = nvlist_next_nvpair(bmarks, NULL);
if (pair == NULL)
return (0);
dbca.dbca_bmarks = bmarks;
dbca.dbca_errors = errors;
return (dsl_sync_task(nvpair_name(pair), dsl_bookmark_create_check,
dsl_bookmark_create_sync, &dbca, fnvlist_num_pairs(bmarks)));
}
int
dsl_get_bookmarks_impl(dsl_dataset_t *ds, nvlist_t *props, nvlist_t *outnvl)
{
int err = 0;
zap_cursor_t zc;
zap_attribute_t attr;
dsl_pool_t *dp = ds->ds_dir->dd_pool;
uint64_t bmark_zapobj = ds->ds_bookmarks;
if (bmark_zapobj == 0)
return (0);
for (zap_cursor_init(&zc, dp->dp_meta_objset, bmark_zapobj);
zap_cursor_retrieve(&zc, &attr) == 0;
zap_cursor_advance(&zc)) {
nvlist_t *out_props;
char *bmark_name = attr.za_name;
zfs_bookmark_phys_t bmark_phys;
err = dsl_dataset_bmark_lookup(ds, bmark_name, &bmark_phys);
ASSERT3U(err, !=, ENOENT);
if (err != 0)
break;
out_props = fnvlist_alloc();
if (nvlist_exists(props,
zfs_prop_to_name(ZFS_PROP_GUID))) {
dsl_prop_nvlist_add_uint64(out_props,
ZFS_PROP_GUID, bmark_phys.zbm_guid);
}
if (nvlist_exists(props,
zfs_prop_to_name(ZFS_PROP_CREATETXG))) {
dsl_prop_nvlist_add_uint64(out_props,
ZFS_PROP_CREATETXG, bmark_phys.zbm_creation_txg);
}
if (nvlist_exists(props,
zfs_prop_to_name(ZFS_PROP_CREATION))) {
dsl_prop_nvlist_add_uint64(out_props,
ZFS_PROP_CREATION, bmark_phys.zbm_creation_time);
}
fnvlist_add_nvlist(outnvl, bmark_name, out_props);
fnvlist_free(out_props);
}
zap_cursor_fini(&zc);
return (err);
}
/*
* Retrieve the bookmarks that exist in the specified dataset, and the
* requested properties of each bookmark.
*
* The "props" nvlist specifies which properties are requested.
* See lzc_get_bookmarks() for the list of valid properties.
*/
int
dsl_get_bookmarks(const char *dsname, nvlist_t *props, nvlist_t *outnvl)
{
dsl_pool_t *dp;
dsl_dataset_t *ds;
int err;
err = dsl_pool_hold(dsname, FTAG, &dp);
if (err != 0)
return (err);
err = dsl_dataset_hold(dp, dsname, FTAG, &ds);
if (err != 0) {
dsl_pool_rele(dp, FTAG);
return (err);
}
err = dsl_get_bookmarks_impl(ds, props, outnvl);
dsl_dataset_rele(ds, FTAG);
dsl_pool_rele(dp, FTAG);
return (err);
}
typedef struct dsl_bookmark_destroy_arg {
nvlist_t *dbda_bmarks;
nvlist_t *dbda_success;
nvlist_t *dbda_errors;
} dsl_bookmark_destroy_arg_t;
static int
dsl_dataset_bookmark_remove(dsl_dataset_t *ds, const char *name, dmu_tx_t *tx)
{
objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
uint64_t bmark_zapobj = ds->ds_bookmarks;
matchtype_t mt;
if (ds->ds_phys->ds_flags & DS_FLAG_CI_DATASET)
mt = MT_FIRST;
else
mt = MT_EXACT;
return (zap_remove_norm(mos, bmark_zapobj, name, mt, tx));
}
static int
dsl_bookmark_destroy_check(void *arg, dmu_tx_t *tx)
{
dsl_bookmark_destroy_arg_t *dbda = arg;
dsl_pool_t *dp = dmu_tx_pool(tx);
int rv = 0;
nvpair_t *pair;
if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS))
return (0);
for (pair = nvlist_next_nvpair(dbda->dbda_bmarks, NULL);
pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_bmarks, pair)) {
const char *fullname = nvpair_name(pair);
dsl_dataset_t *ds;
zfs_bookmark_phys_t bm;
int error;
char *shortname;
error = dsl_bookmark_hold_ds(dp, fullname, &ds,
FTAG, &shortname);
if (error == ENOENT) {
/* ignore it; the bookmark is "already destroyed" */
continue;
}
if (error == 0) {
error = dsl_dataset_bmark_lookup(ds, shortname, &bm);
dsl_dataset_rele(ds, FTAG);
if (error == ESRCH) {
/*
* ignore it; the bookmark is
* "already destroyed"
*/
continue;
}
}
if (error == 0) {
fnvlist_add_boolean(dbda->dbda_success, fullname);
} else {
fnvlist_add_int32(dbda->dbda_errors, fullname, error);
rv = error;
}
}
return (rv);
}
static void
dsl_bookmark_destroy_sync(void *arg, dmu_tx_t *tx)
{
dsl_bookmark_destroy_arg_t *dbda = arg;
dsl_pool_t *dp = dmu_tx_pool(tx);
objset_t *mos = dp->dp_meta_objset;
nvpair_t *pair;
for (pair = nvlist_next_nvpair(dbda->dbda_success, NULL);
pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_success, pair)) {
dsl_dataset_t *ds;
char *shortname;
uint64_t zap_cnt;
VERIFY0(dsl_bookmark_hold_ds(dp, nvpair_name(pair),
&ds, FTAG, &shortname));
VERIFY0(dsl_dataset_bookmark_remove(ds, shortname, tx));
/*
* If all of this dataset's bookmarks have been destroyed,
* free the zap object and decrement the feature's use count.
*/
VERIFY0(zap_count(mos, ds->ds_bookmarks,
&zap_cnt));
if (zap_cnt == 0) {
dmu_buf_will_dirty(ds->ds_dbuf, tx);
VERIFY0(zap_destroy(mos, ds->ds_bookmarks, tx));
ds->ds_bookmarks = 0;
spa_feature_decr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx);
VERIFY0(zap_remove(mos, ds->ds_object,
DS_FIELD_BOOKMARK_NAMES, tx));
}
spa_history_log_internal_ds(ds, "remove bookmark", tx,
"name=%s", shortname);
dsl_dataset_rele(ds, FTAG);
}
}
/*
* The bookmarks must all be in the same pool.
*/
int
dsl_bookmark_destroy(nvlist_t *bmarks, nvlist_t *errors)
{
int rv;
dsl_bookmark_destroy_arg_t dbda;
nvpair_t *pair = nvlist_next_nvpair(bmarks, NULL);
if (pair == NULL)
return (0);
dbda.dbda_bmarks = bmarks;
dbda.dbda_errors = errors;
dbda.dbda_success = fnvlist_alloc();
rv = dsl_sync_task(nvpair_name(pair), dsl_bookmark_destroy_check,
dsl_bookmark_destroy_sync, &dbda, fnvlist_num_pairs(bmarks));
fnvlist_free(dbda.dbda_success);
return (rv);
}
+44 -4
View File
@@ -48,6 +48,7 @@
#include <sys/dsl_deadlist.h>
#include <sys/dsl_destroy.h>
#include <sys/dsl_userhold.h>
#include <sys/dsl_bookmark.h>
#define SWITCH64(x, y) \
{ \
@@ -404,6 +405,14 @@ dsl_dataset_hold_obj(dsl_pool_t *dp, uint64_t dsobj, void *tag,
ds->ds_phys->ds_prev_snap_obj,
ds, &ds->ds_prev);
}
if (doi.doi_type == DMU_OTN_ZAP_METADATA) {
int zaperr = zap_lookup(mos, ds->ds_object,
DS_FIELD_BOOKMARK_NAMES,
sizeof (ds->ds_bookmarks), 1,
&ds->ds_bookmarks);
if (zaperr != ENOENT)
VERIFY0(zaperr);
}
} else {
if (zfs_flags & ZFS_DEBUG_SNAPNAMES)
err = dsl_dataset_get_snapname(ds);
@@ -1734,6 +1743,8 @@ dsl_dataset_rollback_check(void *arg, dmu_tx_t *tx)
dsl_dataset_t *ds;
int64_t unused_refres_delta;
int error;
nvpair_t *pair;
nvlist_t *proprequest, *bookmarks;
error = dsl_dataset_hold(dp, ddra->ddra_fsname, FTAG, &ds);
if (error != 0)
@@ -1751,6 +1762,28 @@ dsl_dataset_rollback_check(void *arg, dmu_tx_t *tx)
return (SET_ERROR(EINVAL));
}
/* must not have any bookmarks after the most recent snapshot */
proprequest = fnvlist_alloc();
fnvlist_add_boolean(proprequest, zfs_prop_to_name(ZFS_PROP_CREATETXG));
bookmarks = fnvlist_alloc();
error = dsl_get_bookmarks_impl(ds, proprequest, bookmarks);
fnvlist_free(proprequest);
if (error != 0)
return (error);
for (pair = nvlist_next_nvpair(bookmarks, NULL);
pair != NULL; pair = nvlist_next_nvpair(bookmarks, pair)) {
nvlist_t *valuenv =
fnvlist_lookup_nvlist(fnvpair_value_nvlist(pair),
zfs_prop_to_name(ZFS_PROP_CREATETXG));
uint64_t createtxg = fnvlist_lookup_uint64(valuenv, "value");
if (createtxg > ds->ds_phys->ds_prev_snap_txg) {
fnvlist_free(bookmarks);
dsl_dataset_rele(ds, FTAG);
return (SET_ERROR(EEXIST));
}
}
fnvlist_free(bookmarks);
error = dsl_dataset_handoff_check(ds, ddra->ddra_owner, tx);
if (error != 0) {
dsl_dataset_rele(ds, FTAG);
@@ -2972,9 +3005,12 @@ dsl_dataset_space_wouldfree(dsl_dataset_t *firstsnap,
* 'earlier' is before 'later'. Or 'earlier' could be the origin of
* 'later's filesystem. Or 'earlier' could be an older snapshot in the origin's
* filesystem. Or 'earlier' could be the origin's origin.
*
* If non-zero, earlier_txg is used instead of earlier's ds_creation_txg.
*/
boolean_t
dsl_dataset_is_before(dsl_dataset_t *later, dsl_dataset_t *earlier)
dsl_dataset_is_before(dsl_dataset_t *later, dsl_dataset_t *earlier,
uint64_t earlier_txg)
{
dsl_pool_t *dp = later->ds_dir->dd_pool;
int error;
@@ -2982,9 +3018,13 @@ dsl_dataset_is_before(dsl_dataset_t *later, dsl_dataset_t *earlier)
dsl_dataset_t *origin;
ASSERT(dsl_pool_config_held(dp));
ASSERT(dsl_dataset_is_snapshot(earlier) || earlier_txg != 0);
if (earlier->ds_phys->ds_creation_txg >=
later->ds_phys->ds_creation_txg)
if (earlier_txg == 0)
earlier_txg = earlier->ds_phys->ds_creation_txg;
if (dsl_dataset_is_snapshot(later) &&
earlier_txg >= later->ds_phys->ds_creation_txg)
return (B_FALSE);
if (later->ds_dir == earlier->ds_dir)
@@ -2998,7 +3038,7 @@ dsl_dataset_is_before(dsl_dataset_t *later, dsl_dataset_t *earlier)
later->ds_dir->dd_phys->dd_origin_obj, FTAG, &origin);
if (error != 0)
return (B_FALSE);
ret = dsl_dataset_is_before(origin, earlier);
ret = dsl_dataset_is_before(origin, earlier, earlier_txg);
dsl_dataset_rele(origin, FTAG);
return (ret);
}
+6
View File
@@ -816,6 +816,12 @@ dsl_destroy_head_sync_impl(dsl_dataset_t *ds, dmu_tx_t *tx)
ASSERT(ds->ds_phys->ds_snapnames_zapobj != 0);
VERIFY0(zap_destroy(mos, ds->ds_phys->ds_snapnames_zapobj, tx));
if (ds->ds_bookmarks != 0) {
VERIFY0(zap_destroy(mos,
ds->ds_bookmarks, tx));
spa_feature_decr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx);
}
spa_prop_clear_bootfs(dp->dp_spa, ds->ds_object, tx);
ASSERT0(ds->ds_phys->ds_next_clones_obj);
+1 -1
View File
@@ -428,7 +428,7 @@ spa_lookup(const char *name)
* If it's a full dataset name, figure out the pool name and
* just use that.
*/
cp = strpbrk(search.spa_name, "/@");
cp = strpbrk(search.spa_name, "/@#");
if (cp != NULL)
*cp = '\0';
+16 -2
View File
@@ -187,8 +187,10 @@ zpool_feature_init(void)
B_FALSE, NULL);
{
static spa_feature_t hole_birth_deps[] = { SPA_FEATURE_ENABLED_TXG,
SPA_FEATURE_NONE };
static const spa_feature_t hole_birth_deps[] = {
SPA_FEATURE_ENABLED_TXG,
SPA_FEATURE_NONE
};
zfeature_register(SPA_FEATURE_HOLE_BIRTH,
"com.delphix:hole_birth", "hole_birth",
"Retain hole birth txg for more precise zfs send",
@@ -199,4 +201,16 @@ zpool_feature_init(void)
"com.delphix:extensible_dataset", "extensible_dataset",
"Enhanced dataset functionality, used by other features.",
B_FALSE, B_FALSE, B_FALSE, NULL);
{
static const spa_feature_t bookmarks_deps[] = {
SPA_FEATURE_EXTENSIBLE_DATASET,
SPA_FEATURE_NONE
};
zfeature_register(SPA_FEATURE_BOOKMARKS,
"com.delphix:bookmarks", "bookmarks",
"\"zfs bookmark\" command",
B_TRUE, B_FALSE, B_FALSE, bookmarks_deps);
}
}
+2 -2
View File
@@ -354,7 +354,7 @@ zfsctl_snapshot_zname(struct inode *ip, const char *name, int len, char *zname)
{
objset_t *os = ITOZSB(ip)->z_os;
if (snapshot_namecheck(name, NULL, NULL) != 0)
if (zfs_component_namecheck(name, NULL, NULL) != 0)
return (SET_ERROR(EILSEQ));
dmu_objset_name(os, zname);
@@ -630,7 +630,7 @@ zfsctl_snapdir_mkdir(struct inode *dip, char *dirname, vattr_t *vap,
dsname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
if (snapshot_namecheck(dirname, NULL, NULL) != 0) {
if (zfs_component_namecheck(dirname, NULL, NULL) != 0) {
error = SET_ERROR(EILSEQ);
goto out;
}
+191 -35
View File
@@ -180,6 +180,7 @@
#include <sys/dmu_send.h>
#include <sys/dsl_destroy.h>
#include <sys/dsl_bookmark.h>
#include <sys/dsl_userhold.h>
#include <sys/zfeature.h>
@@ -812,22 +813,9 @@ zfs_secpolicy_destroy_snaps(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
return (SET_ERROR(EINVAL));
for (pair = nvlist_next_nvpair(snaps, NULL); pair != NULL;
pair = nextpair) {
dsl_pool_t *dp;
dsl_dataset_t *ds;
error = dsl_pool_hold(nvpair_name(pair), FTAG, &dp);
if (error != 0)
break;
nextpair = nvlist_next_nvpair(snaps, pair);
error = dsl_dataset_hold(dp, nvpair_name(pair), FTAG, &ds);
if (error == 0)
dsl_dataset_rele(ds, FTAG);
dsl_pool_rele(dp, FTAG);
if (error == 0) {
error = zfs_secpolicy_destroy_perms(nvpair_name(pair),
cr);
} else if (error == ENOENT) {
error = zfs_secpolicy_destroy_perms(nvpair_name(pair), cr);
if (error == ENOENT) {
/*
* Ignore any snapshots that don't exist (we consider
* them "already destroyed"). Remove the name from the
@@ -986,6 +974,76 @@ zfs_secpolicy_snapshot(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
return (error);
}
/*
* Check for permission to create each snapshot in the nvlist.
*/
/* ARGSUSED */
static int
zfs_secpolicy_bookmark(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
{
int error = 0;
nvpair_t *pair;
for (pair = nvlist_next_nvpair(innvl, NULL);
pair != NULL; pair = nvlist_next_nvpair(innvl, pair)) {
char *name = nvpair_name(pair);
char *hashp = strchr(name, '#');
if (hashp == NULL) {
error = SET_ERROR(EINVAL);
break;
}
*hashp = '\0';
error = zfs_secpolicy_write_perms(name,
ZFS_DELEG_PERM_BOOKMARK, cr);
*hashp = '#';
if (error != 0)
break;
}
return (error);
}
/* ARGSUSED */
static int
zfs_secpolicy_destroy_bookmarks(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
{
nvpair_t *pair, *nextpair;
int error = 0;
for (pair = nvlist_next_nvpair(innvl, NULL); pair != NULL;
pair = nextpair) {
char *name = nvpair_name(pair);
char *hashp = strchr(name, '#');
nextpair = nvlist_next_nvpair(innvl, pair);
if (hashp == NULL) {
error = SET_ERROR(EINVAL);
break;
}
*hashp = '\0';
error = zfs_secpolicy_write_perms(name,
ZFS_DELEG_PERM_DESTROY, cr);
*hashp = '#';
if (error == ENOENT) {
/*
* Ignore any filesystems that don't exist (we consider
* their bookmarks "already destroyed"). Remove
* the name from the nvl here in case the filesystem
* is created between now and when we try to destroy
* the bookmark (in which case we don't want to
* destroy it since we haven't checked for permission).
*/
fnvlist_remove_nvpair(innvl, pair);
error = 0;
}
if (error != 0)
break;
}
return (error);
}
/* ARGSUSED */
static int
zfs_secpolicy_log_history(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
@@ -2551,7 +2609,6 @@ zfs_check_userprops(const char *fsname, nvlist_t *nvl)
while ((pair = nvlist_next_nvpair(nvl, pair)) != NULL) {
const char *propname = nvpair_name(pair);
char *valstr;
if (!zfs_prop_user(propname) ||
nvpair_type(pair) != DATA_TYPE_STRING)
@@ -2564,8 +2621,7 @@ zfs_check_userprops(const char *fsname, nvlist_t *nvl)
if (strlen(propname) >= ZAP_MAXNAMELEN)
return (SET_ERROR(ENAMETOOLONG));
VERIFY(nvpair_value_string(pair, &valstr) == 0);
if (strlen(valstr) >= ZAP_MAXVALUELEN)
if (strlen(fnvpair_value_string(pair)) >= ZAP_MAXVALUELEN)
return (SET_ERROR(E2BIG));
}
return (0);
@@ -3242,7 +3298,8 @@ zfs_ioc_snapshot(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
* The snap name must contain an @, and the part after it must
* contain only valid characters.
*/
if (cp == NULL || snapshot_namecheck(cp + 1, NULL, NULL) != 0)
if (cp == NULL ||
zfs_component_namecheck(cp + 1, NULL, NULL) != 0)
return (SET_ERROR(EINVAL));
/*
@@ -3396,10 +3453,10 @@ zfs_destroy_unmount_origin(const char *fsname)
*
* outnvl: snapshot -> error code (int32)
*/
/* ARGSUSED */
static int
zfs_ioc_destroy_snaps(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
{
int error, poollen;
nvlist_t *snaps;
nvpair_t *pair;
boolean_t defer;
@@ -3408,27 +3465,112 @@ zfs_ioc_destroy_snaps(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
return (SET_ERROR(EINVAL));
defer = nvlist_exists(innvl, "defer");
poollen = strlen(poolname);
for (pair = nvlist_next_nvpair(snaps, NULL); pair != NULL;
pair = nvlist_next_nvpair(snaps, pair)) {
const char *name = nvpair_name(pair);
/*
* The snap must be in the specified pool.
*/
if (strncmp(name, poolname, poollen) != 0 ||
(name[poollen] != '/' && name[poollen] != '@'))
return (SET_ERROR(EXDEV));
error = zfs_unmount_snap(name);
if (error != 0)
return (error);
(void) zvol_remove_minor(name);
(void) zfs_unmount_snap(nvpair_name(pair));
(void) zvol_remove_minor(nvpair_name(pair));
}
return (dsl_destroy_snapshots_nvl(snaps, defer, outnvl));
}
/*
* Create bookmarks. Bookmark names are of the form <fs>#<bmark>.
* All bookmarks must be in the same pool.
*
* innvl: {
* bookmark1 -> snapshot1, bookmark2 -> snapshot2
* }
*
* outnvl: bookmark -> error code (int32)
*
*/
/* ARGSUSED */
static int
zfs_ioc_bookmark(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
{
nvpair_t *pair, *pair2;
for (pair = nvlist_next_nvpair(innvl, NULL);
pair != NULL; pair = nvlist_next_nvpair(innvl, pair)) {
char *snap_name;
/*
* Verify the snapshot argument.
*/
if (nvpair_value_string(pair, &snap_name) != 0)
return (SET_ERROR(EINVAL));
/* Verify that the keys (bookmarks) are unique */
for (pair2 = nvlist_next_nvpair(innvl, pair);
pair2 != NULL; pair2 = nvlist_next_nvpair(innvl, pair2)) {
if (strcmp(nvpair_name(pair), nvpair_name(pair2)) == 0)
return (SET_ERROR(EINVAL));
}
}
return (dsl_bookmark_create(innvl, outnvl));
}
/*
* innvl: {
* property 1, property 2, ...
* }
*
* outnvl: {
* bookmark name 1 -> { property 1, property 2, ... },
* bookmark name 2 -> { property 1, property 2, ... }
* }
*
*/
static int
zfs_ioc_get_bookmarks(const char *fsname, nvlist_t *innvl, nvlist_t *outnvl)
{
return (dsl_get_bookmarks(fsname, innvl, outnvl));
}
/*
* innvl: {
* bookmark name 1, bookmark name 2
* }
*
* outnvl: bookmark -> error code (int32)
*
*/
static int
zfs_ioc_destroy_bookmarks(const char *poolname, nvlist_t *innvl,
nvlist_t *outnvl)
{
int error, poollen;
nvpair_t *pair;
poollen = strlen(poolname);
for (pair = nvlist_next_nvpair(innvl, NULL);
pair != NULL; pair = nvlist_next_nvpair(innvl, pair)) {
const char *name = nvpair_name(pair);
const char *cp = strchr(name, '#');
/*
* The bookmark name must contain an #, and the part after it
* must contain only valid characters.
*/
if (cp == NULL ||
zfs_component_namecheck(cp + 1, NULL, NULL) != 0)
return (SET_ERROR(EINVAL));
/*
* The bookmark must be in the specified pool.
*/
if (strncmp(name, poolname, poollen) != 0 ||
(name[poollen] != '/' && name[poollen] != '#'))
return (SET_ERROR(EXDEV));
}
error = dsl_bookmark_destroy(innvl, outnvl);
return (error);
}
/*
* inputs:
* zc_name name of dataset to destroy
@@ -4097,7 +4239,8 @@ out:
* zc_guid if set, estimate size of stream only. zc_cookie is ignored.
* output size in zc_objset_type.
*
* outputs: none
* outputs:
* zc_objset_type estimated size, if zc_guid is set
*/
static int
zfs_ioc_send(zfs_cmd_t *zc)
@@ -5272,6 +5415,19 @@ zfs_ioctl_init(void)
zfs_ioc_rollback, zfs_secpolicy_rollback, DATASET_NAME,
POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY, B_FALSE, B_TRUE);
zfs_ioctl_register("bookmark", ZFS_IOC_BOOKMARK,
zfs_ioc_bookmark, zfs_secpolicy_bookmark, POOL_NAME,
POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY, B_TRUE, B_TRUE);
zfs_ioctl_register("get_bookmarks", ZFS_IOC_GET_BOOKMARKS,
zfs_ioc_get_bookmarks, zfs_secpolicy_read, DATASET_NAME,
POOL_CHECK_SUSPENDED, B_FALSE, B_FALSE);
zfs_ioctl_register("destroy_bookmarks", ZFS_IOC_DESTROY_BOOKMARKS,
zfs_ioc_destroy_bookmarks, zfs_secpolicy_destroy_bookmarks,
POOL_NAME,
POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY, B_TRUE, B_TRUE);
/* IOCTLS that use the legacy function signature */
zfs_ioctl_register_legacy(ZFS_IOC_POOL_FREEZE, zfs_ioc_pool_freeze,