]> granicus.if.org Git - zfs/commitdiff
OpenZFS 6111 - zfs send should ignore datasets created after the ending snapshot
authorkernelOfTruth aka. kOT, Gentoo user <kerneloftruth@gmail.com>
Fri, 23 Sep 2016 00:22:37 +0000 (02:22 +0200)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Fri, 23 Sep 2016 00:22:37 +0000 (17:22 -0700)
Authored by: Alex Deiter <alex.deiter@gmail.com>
Reviewed by: Alex Aizman alex.aizman@nexenta.com
Reviewed by: Alek Pinchuk alek.pinchuk@nexenta.com
Reviewed by: Roman Strashkin roman.strashkin@nexenta.com
Reviewed by: Matthew Ahrens <mahrens@delphix.com>
Reviewed by: Paul Dagnelie <pcd@delphix.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Approved by: Garrett D'Amore <garrett@damore.org>
Ported-by: kernelOfTruth <kerneloftruth@gmail.com>
OpenZFS-issue: https://www.illumos.org/issues/6111
OpenZFS-commit: https://github.com/illumos/illumos-gate/commit/4a20c933
Closes #5110

Porting notes:

There were changes from upstream due to the following commits:
- zfs send -p send properties only for snapshots that are actually sent
  https://github.com/zfsonlinux/zfs/commit/057485504e3a4502c265813ab58e9ec8ffc2a3be
- Produce a full snapshot list for zfs send -p
  https://github.com/zfsonlinux/zfs/commit/e890dd85a7522730ad46daf68150aafd3952d0c1
- Implement zfs_ioc_recv_new() for OpenZFS 2605
  https://github.com/zfsonlinux/zfs/commit/43e52eddb13d8accbd052fac9a242ce979531aa4
- OpenZFS 6314 - buffer overflow in dsl_dataset_name
  ZFS_MAXNAMELEN was changed to the now used ZFS_MAX_DATASET_NAME_LEN since
  https://github.com/zfsonlinux/zfs/commit/eca7b76001a7d33f78bd98884aef8325bdbf98e7

lib/libzfs/libzfs_sendrecv.c

index 448ee15ec21366e7f920def2caadc02e1239a623..95998638913a6e7fb78a4bbf8d15eec21d96eeb5 100644 (file)
@@ -574,13 +574,30 @@ fsavl_create(nvlist_t *fss)
  * Routines for dealing with the giant nvlist of fs-nvlists, etc.
  */
 typedef struct send_data {
+       /*
+        * assigned inside every recursive call,
+        * restored from *_save on return:
+        *
+        * guid of fromsnap snapshot in parent dataset
+        * txg of fromsnap snapshot in current dataset
+        * txg of tosnap snapshot in current dataset
+        */
+
        uint64_t parent_fromsnap_guid;
+       uint64_t fromsnap_txg;
+       uint64_t tosnap_txg;
+
+       /* the nvlists get accumulated during depth-first traversal */
        nvlist_t *parent_snaps;
        nvlist_t *fss;
        nvlist_t *snapprops;
+
+       /* send-receive configuration, does not change during traversal */
+       const char *fsname;
        const char *fromsnap;
        const char *tosnap;
        boolean_t recursive;
+       boolean_t verbose;
        boolean_t seenfrom;
        boolean_t seento;
 
@@ -615,6 +632,7 @@ send_iterate_snap(zfs_handle_t *zhp, void *arg)
 {
        send_data_t *sd = arg;
        uint64_t guid = zhp->zfs_dmustats.dds_guid;
+       uint64_t txg = zhp->zfs_dmustats.dds_creation_txg;
        char *snapname;
        nvlist_t *nv;
        boolean_t isfromsnap, istosnap, istosnapwithnofrom;
@@ -625,6 +643,17 @@ send_iterate_snap(zfs_handle_t *zhp, void *arg)
        istosnap = (sd->tosnap != NULL && (strcmp(sd->tosnap, snapname) == 0));
        istosnapwithnofrom = (istosnap && sd->fromsnap == NULL);
 
+       if (sd->tosnap_txg != 0 && txg > sd->tosnap_txg) {
+               if (sd->verbose) {
+                       (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+                           "skipping snapshot %s because it was created "
+                           "after the destination snapshot (%s)\n"),
+                           zhp->zfs_name, sd->tosnap);
+               }
+               zfs_close(zhp);
+               return (0);
+       }
+
        VERIFY(0 == nvlist_add_uint64(sd->parent_snaps, snapname, guid));
        /*
         * NB: if there is no fromsnap here (it's a newly created fs in
@@ -731,6 +760,31 @@ send_iterate_prop(zfs_handle_t *zhp, nvlist_t *nv)
        }
 }
 
+/*
+ * returns snapshot creation txg
+ * and returns 0 if the snapshot does not exist
+ */
+static uint64_t
+get_snap_txg(libzfs_handle_t *hdl, const char *fs, const char *snap)
+{
+       char name[ZFS_MAX_DATASET_NAME_LEN];
+       uint64_t txg = 0;
+
+       if (fs == NULL || fs[0] == '\0' || snap == NULL || snap[0] == '\0')
+               return (txg);
+
+       (void) snprintf(name, sizeof (name), "%s@%s", fs, snap);
+       if (zfs_dataset_exists(hdl, name, ZFS_TYPE_SNAPSHOT)) {
+               zfs_handle_t *zhp = zfs_open(hdl, name, ZFS_TYPE_SNAPSHOT);
+               if (zhp != NULL) {
+                       txg = zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG);
+                       zfs_close(zhp);
+               }
+       }
+
+       return (txg);
+}
+
 /*
  * recursively generate nvlists describing datasets.  See comment
  * for the data structure send_data_t above for description of contents
@@ -743,9 +797,48 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
        nvlist_t *nvfs, *nv;
        int rv = 0;
        uint64_t parent_fromsnap_guid_save = sd->parent_fromsnap_guid;
+       uint64_t fromsnap_txg_save = sd->fromsnap_txg;
+       uint64_t tosnap_txg_save = sd->tosnap_txg;
+       uint64_t txg = zhp->zfs_dmustats.dds_creation_txg;
        uint64_t guid = zhp->zfs_dmustats.dds_guid;
+       uint64_t fromsnap_txg, tosnap_txg;
        char guidstring[64];
 
+       fromsnap_txg = get_snap_txg(zhp->zfs_hdl, zhp->zfs_name, sd->fromsnap);
+       if (fromsnap_txg != 0)
+               sd->fromsnap_txg = fromsnap_txg;
+
+       tosnap_txg = get_snap_txg(zhp->zfs_hdl, zhp->zfs_name, sd->tosnap);
+       if (tosnap_txg != 0)
+               sd->tosnap_txg = tosnap_txg;
+
+       /*
+        * on the send side, if the current dataset does not have tosnap,
+        * perform two additional checks:
+        *
+        * - skip sending the current dataset if it was created later than
+        *   the parent tosnap
+        * - return error if the current dataset was created earlier than
+        *   the parent tosnap
+        */
+       if (sd->tosnap != NULL && tosnap_txg == 0) {
+               if (sd->tosnap_txg != 0 && txg > sd->tosnap_txg) {
+                       if (sd->verbose) {
+                               (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+                                   "skipping dataset %s: snapshot %s does "
+                                   "not exist\n"), zhp->zfs_name, sd->tosnap);
+                       }
+               } else {
+                       (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+                           "cannot send %s@%s%s: snapshot %s@%s does not "
+                           "exist\n"), sd->fsname, sd->tosnap, sd->recursive ?
+                           dgettext(TEXT_DOMAIN, " recursively") : "",
+                           zhp->zfs_name, sd->tosnap);
+                       rv = -1;
+               }
+               goto out;
+       }
+
        VERIFY(0 == nvlist_alloc(&nvfs, NV_UNIQUE_NAME, 0));
        VERIFY(0 == nvlist_add_string(nvfs, "name", zhp->zfs_name));
        VERIFY(0 == nvlist_add_uint64(nvfs, "parentfromsnap",
@@ -754,8 +847,10 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
        if (zhp->zfs_dmustats.dds_origin[0]) {
                zfs_handle_t *origin = zfs_open(zhp->zfs_hdl,
                    zhp->zfs_dmustats.dds_origin, ZFS_TYPE_SNAPSHOT);
-               if (origin == NULL)
-                       return (-1);
+               if (origin == NULL) {
+                       rv = -1;
+                       goto out;
+               }
                VERIFY(0 == nvlist_add_uint64(nvfs, "origin",
                    origin->zfs_dmustats.dds_guid));
        }
@@ -786,7 +881,10 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
        if (sd->recursive)
                rv = zfs_iter_filesystems(zhp, send_iterate_fs, sd);
 
+out:
        sd->parent_fromsnap_guid = parent_fromsnap_guid_save;
+       sd->fromsnap_txg = fromsnap_txg_save;
+       sd->tosnap_txg = tosnap_txg_save;
 
        zfs_close(zhp);
        return (rv);
@@ -794,7 +892,8 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
 
 static int
 gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
-    const char *tosnap, boolean_t recursive, nvlist_t **nvlp, avl_tree_t **avlp)
+    const char *tosnap, boolean_t recursive, boolean_t verbose,
+    nvlist_t **nvlp, avl_tree_t **avlp)
 {
        zfs_handle_t *zhp;
        send_data_t sd = { 0 };
@@ -805,9 +904,11 @@ gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
                return (EZFS_BADTYPE);
 
        VERIFY(0 == nvlist_alloc(&sd.fss, NV_UNIQUE_NAME, 0));
+       sd.fsname = fsname;
        sd.fromsnap = fromsnap;
        sd.tosnap = tosnap;
        sd.recursive = recursive;
+       sd.verbose = verbose;
 
        if ((error = send_iterate_fs(zhp, &sd)) != 0) {
                nvlist_free(sd.fss);
@@ -1722,7 +1823,8 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                        }
 
                        err = gather_nvlist(zhp->zfs_hdl, zhp->zfs_name,
-                           fromsnap, tosnap, flags->replicate, &fss, &fsavl);
+                           fromsnap, tosnap, flags->replicate, flags->verbose,
+                           &fss, &fsavl);
                        if (err)
                                goto err_out;
                        VERIFY(0 == nvlist_add_nvlist(hdrnv, "fss", fss));
@@ -2337,7 +2439,7 @@ again:
        VERIFY(0 == nvlist_alloc(&deleted, NV_UNIQUE_NAME, 0));
 
        if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL,
-           recursive, &local_nv, &local_avl)) != 0)
+           recursive, B_FALSE, &local_nv, &local_avl)) != 0)
                return (error);
 
        /*
@@ -3386,7 +3488,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                 */
                *cp = '\0';
                if (gather_nvlist(hdl, destsnap, NULL, NULL, B_FALSE,
-                   &local_nv, &local_avl) == 0) {
+                   B_FALSE, &local_nv, &local_avl) == 0) {
                        *cp = '@';
                        fs = fsavl_find(local_avl, drrb->drr_toguid, NULL);
                        fsavl_destroy(local_avl);