summaryrefslogtreecommitdiffstats
path: root/module
diff options
context:
space:
mode:
authorMatthew Ahrens <mahrens@delphix.com>2013-12-11 14:33:41 -0800
committerBrian Behlendorf <behlendorf1@llnl.gov>2014-07-29 10:55:29 -0700
commitda536844d55b2f3aaefdaebd36fb97bb867494aa (patch)
tree2b0b5bbad2595189476a8084e24f26e6106bbb4d /module
parentb0bc7a84d90dcbf5321d48c5b24ed771c5a128b0 (diff)
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
Diffstat (limited to 'module')
-rw-r--r--module/zcommon/zfs_deleg.c56
-rw-r--r--module/zcommon/zfs_namecheck.c9
-rw-r--r--module/zcommon/zfs_prop.c13
-rw-r--r--module/zfs/Makefile.in1
-rw-r--r--module/zfs/dmu_diff.c2
-rw-r--r--module/zfs/dmu_send.c126
-rw-r--r--module/zfs/dsl_bookmark.c459
-rw-r--r--module/zfs/dsl_dataset.c48
-rw-r--r--module/zfs/dsl_destroy.c6
-rw-r--r--module/zfs/spa_misc.c2
-rw-r--r--module/zfs/zfeature_common.c18
-rw-r--r--module/zfs/zfs_ctldir.c4
-rw-r--r--module/zfs/zfs_ioctl.c216
13 files changed, 845 insertions, 115 deletions
diff --git a/module/zcommon/zfs_deleg.c b/module/zcommon/zfs_deleg.c
index 9de61790c..a152b4e76 100644
--- a/module/zcommon/zfs_deleg.c
+++ b/module/zcommon/zfs_deleg.c
@@ -21,8 +21,11 @@
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2010 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2013 by Delphix. All rights reserved.
*/
+#include <sys/zfs_context.h>
+
#if defined(_KERNEL)
#include <sys/systm.h>
#include <sys/sunddi.h>
@@ -34,43 +37,34 @@
#include <libnvpair.h>
#include <ctype.h>
#endif
-/* XXX includes zfs_context.h, so why bother with the above? */
#include <sys/dsl_deleg.h>
#include "zfs_prop.h"
#include "zfs_deleg.h"
#include "zfs_namecheck.h"
-/*
- * permission table
- *
- * Keep this table in sorted order
- *
- * This table is used for displaying all permissions for
- * zfs allow
- */
-
zfs_deleg_perm_tab_t zfs_deleg_perm_tab[] = {
- {ZFS_DELEG_PERM_ALLOW, ZFS_DELEG_NOTE_ALLOW},
- {ZFS_DELEG_PERM_CLONE, ZFS_DELEG_NOTE_CLONE },
- {ZFS_DELEG_PERM_CREATE, ZFS_DELEG_NOTE_CREATE },
- {ZFS_DELEG_PERM_DESTROY, ZFS_DELEG_NOTE_DESTROY },
- {ZFS_DELEG_PERM_MOUNT, ZFS_DELEG_NOTE_MOUNT },
- {ZFS_DELEG_PERM_PROMOTE, ZFS_DELEG_NOTE_PROMOTE },
- {ZFS_DELEG_PERM_RECEIVE, ZFS_DELEG_NOTE_RECEIVE },
- {ZFS_DELEG_PERM_RENAME, ZFS_DELEG_NOTE_RENAME },
- {ZFS_DELEG_PERM_ROLLBACK, ZFS_DELEG_NOTE_ROLLBACK },
- {ZFS_DELEG_PERM_SNAPSHOT, ZFS_DELEG_NOTE_SNAPSHOT },
- {ZFS_DELEG_PERM_SHARE, ZFS_DELEG_NOTE_SHARE },
- {ZFS_DELEG_PERM_SEND, ZFS_DELEG_NOTE_SEND },
- {ZFS_DELEG_PERM_USERPROP, ZFS_DELEG_NOTE_USERPROP },
- {ZFS_DELEG_PERM_USERQUOTA, ZFS_DELEG_NOTE_USERQUOTA },
- {ZFS_DELEG_PERM_GROUPQUOTA, ZFS_DELEG_NOTE_GROUPQUOTA },
- {ZFS_DELEG_PERM_USERUSED, ZFS_DELEG_NOTE_USERUSED },
- {ZFS_DELEG_PERM_GROUPUSED, ZFS_DELEG_NOTE_GROUPUSED },
- {ZFS_DELEG_PERM_HOLD, ZFS_DELEG_NOTE_HOLD },
- {ZFS_DELEG_PERM_RELEASE, ZFS_DELEG_NOTE_RELEASE },
- {ZFS_DELEG_PERM_DIFF, ZFS_DELEG_NOTE_DIFF},
- {NULL, ZFS_DELEG_NOTE_NONE }
+ {ZFS_DELEG_PERM_ALLOW},
+ {ZFS_DELEG_PERM_BOOKMARK},
+ {ZFS_DELEG_PERM_CLONE},
+ {ZFS_DELEG_PERM_CREATE},
+ {ZFS_DELEG_PERM_DESTROY},
+ {ZFS_DELEG_PERM_DIFF},
+ {ZFS_DELEG_PERM_MOUNT},
+ {ZFS_DELEG_PERM_PROMOTE},
+ {ZFS_DELEG_PERM_RECEIVE},
+ {ZFS_DELEG_PERM_RENAME},
+ {ZFS_DELEG_PERM_ROLLBACK},
+ {ZFS_DELEG_PERM_SNAPSHOT},
+ {ZFS_DELEG_PERM_SHARE},
+ {ZFS_DELEG_PERM_SEND},
+ {ZFS_DELEG_PERM_USERPROP},
+ {ZFS_DELEG_PERM_USERQUOTA},
+ {ZFS_DELEG_PERM_GROUPQUOTA},
+ {ZFS_DELEG_PERM_USERUSED},
+ {ZFS_DELEG_PERM_GROUPUSED},
+ {ZFS_DELEG_PERM_HOLD},
+ {ZFS_DELEG_PERM_RELEASE},
+ {NULL}
};
static int
diff --git a/module/zcommon/zfs_namecheck.c b/module/zcommon/zfs_namecheck.c
index 8508bc734..ff724be58 100644
--- a/module/zcommon/zfs_namecheck.c
+++ b/module/zcommon/zfs_namecheck.c
@@ -22,6 +22,9 @@
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
+/*
+ * Copyright (c) 2013 by Delphix. All rights reserved.
+ */
/*
* Common name validation routines for ZFS. These routines are shared by the
@@ -62,7 +65,7 @@ valid_char(char c)
* [-_.: ]
*/
int
-snapshot_namecheck(const char *path, namecheck_err_t *why, char *what)
+zfs_component_namecheck(const char *path, namecheck_err_t *why, char *what)
{
const char *loc;
@@ -113,7 +116,7 @@ permset_namecheck(const char *path, namecheck_err_t *why, char *what)
return (-1);
}
- return (snapshot_namecheck(&path[1], why, what));
+ return (zfs_component_namecheck(&path[1], why, what));
}
/*
@@ -372,7 +375,7 @@ pool_namecheck(const char *pool, namecheck_err_t *why, char *what)
}
#if defined(_KERNEL) && defined(HAVE_SPL)
-EXPORT_SYMBOL(snapshot_namecheck);
EXPORT_SYMBOL(pool_namecheck);
EXPORT_SYMBOL(dataset_namecheck);
+EXPORT_SYMBOL(zfs_component_namecheck);
#endif
diff --git a/module/zcommon/zfs_prop.c b/module/zcommon/zfs_prop.c
index d81ff3bc6..92cfa282b 100644
--- a/module/zcommon/zfs_prop.c
+++ b/module/zcommon/zfs_prop.c
@@ -20,7 +20,7 @@
*/
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2013 by Delphix. All rights reserved.
* Copyright (c) 2013 by Saso Kiselkov. All rights reserved.
*/
@@ -327,7 +327,8 @@ zfs_prop_init(void)
PROP_INHERIT, ZFS_TYPE_FILESYSTEM, "on | off | share(1M) options",
"SHARENFS");
zprop_register_string(ZFS_PROP_TYPE, "type", NULL, PROP_READONLY,
- ZFS_TYPE_DATASET, "filesystem | volume | snapshot", "TYPE");
+ ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK,
+ "filesystem | volume | snapshot | bookmark", "TYPE");
zprop_register_string(ZFS_PROP_SHARESMB, "sharesmb", "off",
PROP_INHERIT, ZFS_TYPE_FILESYSTEM,
"on | off | sharemgr(1M) options", "SHARESMB");
@@ -405,18 +406,18 @@ zfs_prop_init(void)
/* hidden properties */
zprop_register_hidden(ZFS_PROP_CREATETXG, "createtxg", PROP_TYPE_NUMBER,
- PROP_READONLY, ZFS_TYPE_DATASET, "CREATETXG");
+ PROP_READONLY, ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK, "CREATETXG");
zprop_register_hidden(ZFS_PROP_NUMCLONES, "numclones", PROP_TYPE_NUMBER,
PROP_READONLY, ZFS_TYPE_SNAPSHOT, "NUMCLONES");
zprop_register_hidden(ZFS_PROP_NAME, "name", PROP_TYPE_STRING,
- PROP_READONLY, ZFS_TYPE_DATASET, "NAME");
+ PROP_READONLY, ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK, "NAME");
zprop_register_hidden(ZFS_PROP_ISCSIOPTIONS, "iscsioptions",
PROP_TYPE_STRING, PROP_INHERIT, ZFS_TYPE_VOLUME, "ISCSIOPTIONS");
zprop_register_hidden(ZFS_PROP_STMF_SHAREINFO, "stmf_sbd_lu",
PROP_TYPE_STRING, PROP_INHERIT, ZFS_TYPE_VOLUME,
"STMF_SBD_LU");
zprop_register_hidden(ZFS_PROP_GUID, "guid", PROP_TYPE_NUMBER,
- PROP_READONLY, ZFS_TYPE_DATASET, "GUID");
+ PROP_READONLY, ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK, "GUID");
zprop_register_hidden(ZFS_PROP_USERACCOUNTING, "useraccounting",
PROP_TYPE_NUMBER, PROP_READONLY, ZFS_TYPE_DATASET,
"USERACCOUNTING");
@@ -436,7 +437,7 @@ zfs_prop_init(void)
/* oddball properties */
zprop_register_impl(ZFS_PROP_CREATION, "creation", PROP_TYPE_NUMBER, 0,
- NULL, PROP_READONLY, ZFS_TYPE_DATASET,
+ NULL, PROP_READONLY, ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK,
"<date>", "CREATION", B_FALSE, B_TRUE, NULL);
}
diff --git a/module/zfs/Makefile.in b/module/zfs/Makefile.in
index 9701ff2bb..56ecd4918 100644
--- a/module/zfs/Makefile.in
+++ b/module/zfs/Makefile.in
@@ -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
diff --git a/module/zfs/dmu_diff.c b/module/zfs/dmu_diff.c
index 8d7385539..a2130b131 100644
--- a/module/zfs/dmu_diff.c
+++ b/module/zfs/dmu_diff.c
@@ -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);
diff --git a/module/zfs/dmu_send.c b/module/zfs/dmu_send.c
index 15abdeacb..885da23ba 100644
--- a/module/zfs/dmu_send.c
+++ b/module/zfs/dmu_send.c
@@ -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. */
diff --git a/module/zfs/dsl_bookmark.c b/module/zfs/dsl_bookmark.c
new file mode 100644
index 000000000..2cae5cd4d
--- /dev/null
+++ b/module/zfs/dsl_bookmark.c
@@ -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);
+}
diff --git a/module/zfs/dsl_dataset.c b/module/zfs/dsl_dataset.c
index cae56534d..2fe6b858d 100644
--- a/module/zfs/dsl_dataset.c
+++ b/module/zfs/dsl_dataset.c
@@ -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_txg == 0)
+ earlier_txg = earlier->ds_phys->ds_creation_txg;
- if (earlier->ds_phys->ds_creation_txg >=
- later->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);
}
diff --git a/module/zfs/dsl_destroy.c b/module/zfs/dsl_destroy.c
index cbe89739f..113b7261b 100644
--- a/module/zfs/dsl_destroy.c
+++ b/module/zfs/dsl_destroy.c
@@ -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);
diff --git a/module/zfs/spa_misc.c b/module/zfs/spa_misc.c
index 88f9e34e3..02ccb13a2 100644
--- a/module/zfs/spa_misc.c
+++ b/module/zfs/spa_misc.c
@@ -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';
diff --git a/module/zfs/zfeature_common.c b/module/zfs/zfeature_common.c
index 35e662ee2..2ea220245 100644
--- a/module/zfs/zfeature_common.c
+++ b/module/zfs/zfeature_common.c
@@ -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);
+ }
}
diff --git a/module/zfs/zfs_ctldir.c b/module/zfs/zfs_ctldir.c
index 96520545a..59405de82 100644
--- a/module/zfs/zfs_ctldir.c
+++ b/module/zfs/zfs_ctldir.c
@@ -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;
}
diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c
index 453571598..409d2c737 100644
--- a/module/zfs/zfs_ioctl.c
+++ b/module/zfs/zfs_ioctl.c
@@ -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,25 +3465,110 @@ 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)) {
+ (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 snap must be in the specified pool.
+ * 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] != '@'))
+ (name[poollen] != '/' && name[poollen] != '#'))
return (SET_ERROR(EXDEV));
-
- error = zfs_unmount_snap(name);
- if (error != 0)
- return (error);
- (void) zvol_remove_minor(name);
}
- return (dsl_destroy_snapshots_nvl(snaps, defer, outnvl));
+ error = dsl_bookmark_destroy(innvl, outnvl);
+ return (error);
}
/*
@@ -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,