diff options
6 files changed, 71 insertions, 13 deletions
diff --git a/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c b/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c index fbad837..f56106a 100644 --- a/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c +++ b/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c @@ -3957,14 +3957,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/cddl/contrib/opensolaris/lib/libzfs_core/common/libzfs_core.c b/cddl/contrib/opensolaris/lib/libzfs_core/common/libzfs_core.c index 27a4144..c38c8c6 100644 --- a/cddl/contrib/opensolaris/lib/libzfs_core/common/libzfs_core.c +++ b/cddl/contrib/opensolaris/lib/libzfs_core/common/libzfs_core.c @@ -765,6 +765,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. */ @@ -788,6 +791,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/cddl/contrib/opensolaris/lib/libzfs_core/common/libzfs_core.h b/cddl/contrib/opensolaris/lib/libzfs_core/common/libzfs_core.h index fd18d5f..ea0e752 100644 --- a/cddl/contrib/opensolaris/lib/libzfs_core/common/libzfs_core.h +++ b/cddl/contrib/opensolaris/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 *); #ifdef __cplusplus } diff --git a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/dsl_dataset.c b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/dsl_dataset.c index 74bb9bb..6b9ddc8 100644 --- a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/dsl_dataset.c +++ b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/dsl_dataset.c @@ -2259,6 +2259,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; @@ -2300,6 +2301,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)); @@ -2401,11 +2414,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/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/sys/dsl_dataset.h b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/sys/dsl_dataset.h index fc5117b..3da68ca 100644 --- a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/sys/dsl_dataset.h +++ b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/sys/dsl_dataset.h @@ -334,7 +334,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/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_ioctl.c b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_ioctl.c index 849b448..29f2411 100644 --- a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_ioctl.c +++ b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_ioctl.c @@ -3779,18 +3779,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; @@ -3799,7 +3810,8 @@ 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; } @@ -3809,7 +3821,7 @@ zfs_ioc_rollback(const char *fsname, nvlist_t *args, nvlist_t *outnvl) vfs_unbusy(zfsvfs->z_vfs); #endif } else { - error = dsl_dataset_rollback(fsname, NULL, outnvl); + error = dsl_dataset_rollback(fsname, target, NULL, outnvl); } return (error); } |