summaryrefslogtreecommitdiff
path: root/usr/src
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src')
-rw-r--r--usr/src/lib/libzfs/common/libzfs_dataset.c19
-rw-r--r--usr/src/lib/libzfs_core/common/libzfs_core.c24
-rw-r--r--usr/src/lib/libzfs_core/common/libzfs_core.h1
-rw-r--r--usr/src/lib/libzfs_core/common/mapfile-vers1
-rw-r--r--usr/src/uts/common/fs/zfs/dsl_dataset.c17
-rw-r--r--usr/src/uts/common/fs/zfs/sys/dsl_dataset.h3
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_ioctl.c20
7 files changed, 72 insertions, 13 deletions
diff --git a/usr/src/lib/libzfs/common/libzfs_dataset.c b/usr/src/lib/libzfs/common/libzfs_dataset.c
index 73e81c6069..9f55741bc9 100644
--- a/usr/src/lib/libzfs/common/libzfs_dataset.c
+++ b/usr/src/lib/libzfs/common/libzfs_dataset.c
@@ -3996,14 +3996,19 @@ zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
}
/*
- * We rely on zfs_iter_children() to verify that there are no
- * newer snapshots for the given dataset. Therefore, we can
- * simply pass the name on to the ioctl() call. There is still
- * an unlikely race condition where the user has taken a
- * snapshot since we verified that this was the most recent.
+ * Pass both the filesystem and the wanted snapshot names,
+ * we would get an error back if the snapshot is destroyed or
+ * a new snapshot is created before this request is processed.
*/
- err = lzc_rollback(zhp->zfs_name, NULL, 0);
- if (err != 0) {
+ err = lzc_rollback_to(zhp->zfs_name, snap->zfs_name);
+ if (err == EXDEV) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is not the latest snapshot"), snap->zfs_name);
+ (void) zfs_error_fmt(zhp->zfs_hdl, EZFS_BUSY,
+ dgettext(TEXT_DOMAIN, "cannot rollback '%s'"),
+ zhp->zfs_name);
+ return (err);
+ } else if (err != 0) {
(void) zfs_standard_error_fmt(zhp->zfs_hdl, errno,
dgettext(TEXT_DOMAIN, "cannot rollback '%s'"),
zhp->zfs_name);
diff --git a/usr/src/lib/libzfs_core/common/libzfs_core.c b/usr/src/lib/libzfs_core/common/libzfs_core.c
index 5d92d3b1fa..c17ebcc58d 100644
--- a/usr/src/lib/libzfs_core/common/libzfs_core.c
+++ b/usr/src/lib/libzfs_core/common/libzfs_core.c
@@ -736,6 +736,9 @@ lzc_receive_with_header(const char *snapname, nvlist_t *props,
* Roll back this filesystem or volume to its most recent snapshot.
* If snapnamebuf is not NULL, it will be filled in with the name
* of the most recent snapshot.
+ * Note that the latest snapshot may change if a new one is concurrently
+ * created or the current one is destroyed. lzc_rollback_to can be used
+ * to roll back to a specific latest snapshot.
*
* Return 0 on success or an errno on failure.
*/
@@ -759,6 +762,27 @@ lzc_rollback(const char *fsname, char *snapnamebuf, int snapnamelen)
}
/*
+ * Roll back this filesystem or volume to the specified snapshot,
+ * if possible.
+ *
+ * Return 0 on success or an errno on failure.
+ */
+int
+lzc_rollback_to(const char *fsname, const char *snapname)
+{
+ nvlist_t *args;
+ nvlist_t *result;
+ int err;
+
+ args = fnvlist_alloc();
+ fnvlist_add_string(args, "target", snapname);
+ err = lzc_ioctl(ZFS_IOC_ROLLBACK, fsname, args, &result);
+ nvlist_free(args);
+ nvlist_free(result);
+ return (err);
+}
+
+/*
* Creates bookmarks.
*
* The bookmarks nvlist maps from name of the bookmark (e.g. "pool/fs#bmark") to
diff --git a/usr/src/lib/libzfs_core/common/libzfs_core.h b/usr/src/lib/libzfs_core/common/libzfs_core.h
index caa6dcb2a6..2dcb1f639d 100644
--- a/usr/src/lib/libzfs_core/common/libzfs_core.h
+++ b/usr/src/lib/libzfs_core/common/libzfs_core.h
@@ -84,6 +84,7 @@ int lzc_receive_with_header(const char *, nvlist_t *, const char *, boolean_t,
boolean_t lzc_exists(const char *);
int lzc_rollback(const char *, char *, int);
+int lzc_rollback_to(const char *, const char *);
int lzc_channel_program(const char *, const char *, uint64_t, uint64_t,
nvlist_t *, nvlist_t **);
diff --git a/usr/src/lib/libzfs_core/common/mapfile-vers b/usr/src/lib/libzfs_core/common/mapfile-vers
index aa1bf945d6..9361062ba8 100644
--- a/usr/src/lib/libzfs_core/common/mapfile-vers
+++ b/usr/src/lib/libzfs_core/common/mapfile-vers
@@ -58,6 +58,7 @@ SYMBOL_VERSION ILLUMOS_0.1 {
lzc_receive_with_header;
lzc_release;
lzc_rollback;
+ lzc_rollback_to;
lzc_send;
lzc_send_resume;
lzc_send_space;
diff --git a/usr/src/uts/common/fs/zfs/dsl_dataset.c b/usr/src/uts/common/fs/zfs/dsl_dataset.c
index cf2f290f47..81798db2fc 100644
--- a/usr/src/uts/common/fs/zfs/dsl_dataset.c
+++ b/usr/src/uts/common/fs/zfs/dsl_dataset.c
@@ -2454,6 +2454,7 @@ dsl_dataset_handoff_check(dsl_dataset_t *ds, void *owner, dmu_tx_t *tx)
typedef struct dsl_dataset_rollback_arg {
const char *ddra_fsname;
+ const char *ddra_tosnap;
void *ddra_owner;
nvlist_t *ddra_result;
} dsl_dataset_rollback_arg_t;
@@ -2495,6 +2496,18 @@ dsl_dataset_rollback_check(void *arg, dmu_tx_t *tx)
return (SET_ERROR(EAGAIN));
}
+ /*
+ * If the expected target snapshot is specified, then check that
+ * the latest snapshot is it.
+ */
+ if (ddra->ddra_tosnap != NULL) {
+ char namebuf[ZFS_MAX_DATASET_NAME_LEN];
+
+ dsl_dataset_name(ds->ds_prev, namebuf);
+ if (strcmp(namebuf, ddra->ddra_tosnap) != 0)
+ return (SET_ERROR(EXDEV));
+ }
+
/* must not have any bookmarks after the most recent snapshot */
nvlist_t *proprequest = fnvlist_alloc();
fnvlist_add_boolean(proprequest, zfs_prop_to_name(ZFS_PROP_CREATETXG));
@@ -2596,11 +2609,13 @@ dsl_dataset_rollback_sync(void *arg, dmu_tx_t *tx)
* notes above zfs_suspend_fs() for further details.
*/
int
-dsl_dataset_rollback(const char *fsname, void *owner, nvlist_t *result)
+dsl_dataset_rollback(const char *fsname, const char *tosnap, void *owner,
+ nvlist_t *result)
{
dsl_dataset_rollback_arg_t ddra;
ddra.ddra_fsname = fsname;
+ ddra.ddra_tosnap = tosnap;
ddra.ddra_owner = owner;
ddra.ddra_result = result;
diff --git a/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h b/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h
index f709395883..214d5919ff 100644
--- a/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h
+++ b/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h
@@ -375,7 +375,8 @@ void dsl_dataset_set_refreservation_sync_impl(dsl_dataset_t *ds,
void dsl_dataset_zapify(dsl_dataset_t *ds, dmu_tx_t *tx);
boolean_t dsl_dataset_is_zapified(dsl_dataset_t *ds);
boolean_t dsl_dataset_has_resume_receive_state(dsl_dataset_t *ds);
-int dsl_dataset_rollback(const char *fsname, void *owner, nvlist_t *result);
+int dsl_dataset_rollback(const char *fsname, const char *tosnap, void *owner,
+ nvlist_t *result);
void dsl_dataset_deactivate_feature(uint64_t dsobj,
spa_feature_t f, dmu_tx_t *tx);
diff --git a/usr/src/uts/common/fs/zfs/zfs_ioctl.c b/usr/src/uts/common/fs/zfs/zfs_ioctl.c
index b8302e0a28..5ffda31f24 100644
--- a/usr/src/uts/common/fs/zfs/zfs_ioctl.c
+++ b/usr/src/uts/common/fs/zfs/zfs_ioctl.c
@@ -3678,18 +3678,29 @@ zfs_ioc_destroy(zfs_cmd_t *zc)
/*
* fsname is name of dataset to rollback (to most recent snapshot)
*
- * innvl is not used.
+ * innvl may contain name of expected target snapshot
*
* outnvl: "target" -> name of most recent snapshot
* }
*/
/* ARGSUSED */
static int
-zfs_ioc_rollback(const char *fsname, nvlist_t *args, nvlist_t *outnvl)
+zfs_ioc_rollback(const char *fsname, nvlist_t *innvl, nvlist_t *outnvl)
{
zfsvfs_t *zfsvfs;
+ char *target = NULL;
int error;
+ (void) nvlist_lookup_string(innvl, "target", &target);
+ if (target != NULL) {
+ int fslen = strlen(fsname);
+
+ if (strncmp(fsname, target, fslen) != 0)
+ return (SET_ERROR(EINVAL));
+ if (target[fslen] != '@')
+ return (SET_ERROR(EINVAL));
+ }
+
if (getzfsvfs(fsname, &zfsvfs) == 0) {
dsl_dataset_t *ds;
@@ -3698,13 +3709,14 @@ zfs_ioc_rollback(const char *fsname, nvlist_t *args, nvlist_t *outnvl)
if (error == 0) {
int resume_err;
- error = dsl_dataset_rollback(fsname, zfsvfs, outnvl);
+ error = dsl_dataset_rollback(fsname, target, zfsvfs,
+ outnvl);
resume_err = zfs_resume_fs(zfsvfs, ds);
error = error ? error : resume_err;
}
VFS_RELE(zfsvfs->z_vfs);
} else {
- error = dsl_dataset_rollback(fsname, NULL, outnvl);
+ error = dsl_dataset_rollback(fsname, target, NULL, outnvl);
}
return (error);
}