]> granicus.if.org Git - zfs/commitdiff
OpenZFS 4986 - receiving replication stream fails if any snapshot exceeds refquota
authorDan McDonald <danmcd@omniti.com>
Thu, 9 Jun 2016 19:24:29 +0000 (12:24 -0700)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Tue, 28 Jun 2016 20:47:03 +0000 (13:47 -0700)
Authored by: Dan McDonald <danmcd@omniti.com>
Reviewed by: John Kennedy <john.kennedy@delphix.com>
Reviewed by: Matthew Ahrens <mahrens@delphix.com>
Approved by: Gordon Ross <gordon.ross@nexenta.com>
Ported-by: Brian Behlendorf <behlendorf1@llnl.gov>
OpenZFS-issue: https://www.illumos.org/issues/4986
OpenZFS-commit: https://github.com/openzfs/openzfs/commit/5878fad

lib/libzfs/libzfs_sendrecv.c
module/zfs/zfs_ioctl.c

index 1bf8bf49aeda5e896f6d7e65b41a1ea267c61851..6231cf0e47846232dbfc9813c3353c77159a695f 100644 (file)
@@ -26,6 +26,7 @@
  * Copyright (c) 2012 Pawel Jakub Dawidek <pawel@dawidek.net>.
  * All rights reserved
  * Copyright (c) 2013 Steven Hartland. All rights reserved.
+ * Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved.
  */
 
 #include <assert.h>
@@ -66,7 +67,7 @@ extern void zfs_setprop_error(libzfs_handle_t *, zfs_prop_t, int, char *);
 
 static int zfs_receive_impl(libzfs_handle_t *, const char *, const char *,
     recvflags_t *, int, const char *, nvlist_t *, avl_tree_t *, char **, int,
-    uint64_t *);
+    uint64_t *, const char *);
 static int guid_to_name(libzfs_handle_t *, const char *,
     uint64_t, boolean_t, char *);
 
@@ -2621,6 +2622,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
        nvlist_t *stream_nv = NULL;
        avl_tree_t *stream_avl = NULL;
        char *fromsnap = NULL;
+       char *sendsnap = NULL;
        char *cp;
        char tofs[ZFS_MAXNAMELEN];
        char sendfs[ZFS_MAXNAMELEN];
@@ -2769,8 +2771,16 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
         */
        (void) strlcpy(sendfs, drr->drr_u.drr_begin.drr_toname,
            ZFS_MAXNAMELEN);
-       if ((cp = strchr(sendfs, '@')) != NULL)
+       if ((cp = strchr(sendfs, '@')) != NULL) {
                *cp = '\0';
+               /*
+                * Find the "sendsnap", the final snapshot in a replication
+                * stream.  zfs_receive_one() handles certain errors
+                * differently, depending on if the contained stream is the
+                * last one or not.
+                */
+               sendsnap = (cp + 1);
+       }
 
        /* Finally, receive each contained stream */
        do {
@@ -2783,7 +2793,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
                 */
                error = zfs_receive_impl(hdl, destname, NULL, flags, fd,
                    sendfs, stream_nv, stream_avl, top_zfs, cleanup_fd,
-                   action_handlep);
+                   action_handlep, sendsnap);
                if (error == ENODATA) {
                        error = 0;
                        break;
@@ -2948,7 +2958,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
     const char *originsnap, recvflags_t *flags, dmu_replay_record_t *drr,
     dmu_replay_record_t *drr_noswap, const char *sendfs, nvlist_t *stream_nv,
     avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd,
-    uint64_t *action_handlep)
+    uint64_t *action_handlep, const char *finalsnap)
 {
        zfs_cmd_t zc = {"\0"};
        time_t begin_time;
@@ -2965,6 +2975,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
        nvlist_t *snapprops_nvlist = NULL;
        zprop_errflags_t prop_errflags;
        boolean_t recursive;
+       char *snapname = NULL;
 
        begin_time = time(NULL);
 
@@ -2975,7 +2986,6 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
            ENOENT);
 
        if (stream_avl != NULL) {
-               char *snapname;
                nvlist_t *fs = fsavl_find(stream_avl, drrb->drr_toguid,
                    &snapname);
                nvlist_t *props;
@@ -3316,7 +3326,21 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                            ZPROP_N_MORE_ERRORS) == 0) {
                                trunc_prop_errs(intval);
                                break;
-                       } else {
+                       } else if (snapname == NULL || finalsnap == NULL ||
+                           strcmp(finalsnap, snapname) == 0 ||
+                           strcmp(nvpair_name(prop_err),
+                           zfs_prop_to_name(ZFS_PROP_REFQUOTA)) != 0) {
+                               /*
+                                * Skip the special case of, for example,
+                                * "refquota", errors on intermediate
+                                * snapshots leading up to a final one.
+                                * That's why we have all of the checks above.
+                                *
+                                * See zfs_ioctl.c's extract_delay_props() for
+                                * a list of props which can fail on
+                                * intermediate snapshots, but shouldn't
+                                * affect the overall receive.
+                                */
                                (void) snprintf(tbuf, sizeof (tbuf),
                                    dgettext(TEXT_DOMAIN,
                                    "cannot receive %s property on %s"),
@@ -3501,7 +3525,7 @@ static int
 zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap,
     const char *originsnap, recvflags_t *flags, int infd, const char *sendfs,
     nvlist_t *stream_nv, avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd,
-    uint64_t *action_handlep)
+    uint64_t *action_handlep, const char *finalsnap)
 {
        int err;
        dmu_replay_record_t drr, drr_noswap;
@@ -3597,10 +3621,11 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap,
                        if ((cp = strchr(nonpackage_sendfs, '@')) != NULL)
                                *cp = '\0';
                        sendfs = nonpackage_sendfs;
+                       VERIFY(finalsnap == NULL);
                }
                return (zfs_receive_one(hdl, infd, tosnap, originsnap, flags,
                    &drr, &drr_noswap, sendfs, stream_nv, stream_avl, top_zfs,
-                   cleanup_fd, action_handlep));
+                   cleanup_fd, action_handlep, finalsnap));
        } else {
                assert(DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo) ==
                    DMU_COMPOUNDSTREAM);
@@ -3678,7 +3703,7 @@ zfs_receive(libzfs_handle_t *hdl, const char *tosnap, nvlist_t *props,
        VERIFY(cleanup_fd >= 0);
 
        err = zfs_receive_impl(hdl, tosnap, originsnap, flags, infd, NULL, NULL,
-           stream_avl, &top_zfs, cleanup_fd, &action_handle);
+           stream_avl, &top_zfs, cleanup_fd, &action_handle, NULL);
 
        VERIFY(0 == close(cleanup_fd));
 
index 825e838470e68df1894b916820264a8a7ddea3a2..dd3ce09e344266b0b1a26c35f27728b603db175c 100644 (file)
@@ -22,6 +22,7 @@
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
  * Portions Copyright 2011 Martin Matuska
+ * Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved.
  * Portions Copyright 2012 Pawel Jakub Dawidek <pawel@dawidek.net>
  * Copyright (c) 2012, Joyent, Inc. All rights reserved.
  * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
@@ -3990,6 +3991,56 @@ next:
        }
 }
 
+/*
+ * Extract properties that cannot be set PRIOR to the receipt of a dataset.
+ * For example, refquota cannot be set until after the receipt of a dataset,
+ * because in replication streams, an older/earlier snapshot may exceed the
+ * refquota.  We want to receive the older/earlier snapshot, but setting
+ * refquota pre-receipt will set the dsl's ACTUAL quota, which will prevent
+ * the older/earlier snapshot from being received (with EDQUOT).
+ *
+ * The ZFS test "zfs_receive_011_pos" demonstrates such a scenario.
+ *
+ * libzfs will need to be judicious handling errors encountered by props
+ * extracted by this function.
+ */
+static nvlist_t *
+extract_delay_props(nvlist_t *props)
+{
+       nvlist_t *delayprops;
+       nvpair_t *nvp, *tmp;
+       static const zfs_prop_t delayable[] = { ZFS_PROP_REFQUOTA, 0 };
+       int i;
+
+       VERIFY(nvlist_alloc(&delayprops, NV_UNIQUE_NAME, KM_SLEEP) == 0);
+
+       for (nvp = nvlist_next_nvpair(props, NULL); nvp != NULL;
+           nvp = nvlist_next_nvpair(props, nvp)) {
+               /*
+                * strcmp() is safe because zfs_prop_to_name() always returns
+                * a bounded string.
+                */
+               for (i = 0; delayable[i] != 0; i++) {
+                       if (strcmp(zfs_prop_to_name(delayable[i]),
+                           nvpair_name(nvp)) == 0) {
+                               break;
+                       }
+               }
+               if (delayable[i] != 0) {
+                       tmp = nvlist_prev_nvpair(props, nvp);
+                       VERIFY(nvlist_add_nvpair(delayprops, nvp) == 0);
+                       VERIFY(nvlist_remove_nvpair(props, nvp) == 0);
+                       nvp = tmp;
+               }
+       }
+
+       if (nvlist_empty(delayprops)) {
+               nvlist_free(delayprops);
+               delayprops = NULL;
+       }
+       return (delayprops);
+}
+
 #ifdef DEBUG
 static boolean_t zfs_ioc_recv_inject_err;
 #endif
@@ -4026,6 +4077,7 @@ zfs_ioc_recv(zfs_cmd_t *zc)
        offset_t off;
        nvlist_t *props = NULL; /* sent properties */
        nvlist_t *origprops = NULL; /* existing properties */
+       nvlist_t *delayprops = NULL; /* sent properties applied post-receive */
        char *origin = NULL;
        char *tosnap;
        char tofs[ZFS_MAXNAMELEN];
@@ -4106,21 +4158,12 @@ zfs_ioc_recv(zfs_cmd_t *zc)
                props_error = dsl_prop_set_hasrecvd(tofs);
 
                if (props_error == 0) {
+                       delayprops = extract_delay_props(props);
                        (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED,
                            props, errors);
                }
        }
 
-       if (zc->zc_nvlist_dst_size != 0 &&
-           (nvlist_smush(errors, zc->zc_nvlist_dst_size) != 0 ||
-           put_nvlist(zc, errors) != 0)) {
-               /*
-                * Caller made zc->zc_nvlist_dst less than the minimum expected
-                * size or supplied an invalid address.
-                */
-               props_error = SET_ERROR(EINVAL);
-       }
-
        off = fp->f_offset;
        error = dmu_recv_stream(&drc, fp->f_vnode, &off, zc->zc_cleanup_fd,
            &zc->zc_action_handle);
@@ -4145,6 +4188,40 @@ zfs_ioc_recv(zfs_cmd_t *zc)
                } else {
                        error = dmu_recv_end(&drc, NULL);
                }
+
+               /* Set delayed properties now, after we're done receiving. */
+               if (delayprops != NULL && error == 0) {
+                       (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED,
+                           delayprops, errors);
+               }
+       }
+
+       if (delayprops != NULL) {
+               /*
+                * Merge delayed props back in with initial props, in case
+                * we're DEBUG and zfs_ioc_recv_inject_err is set (which means
+                * we have to make sure clear_received_props() includes
+                * the delayed properties).
+                *
+                * Since zfs_ioc_recv_inject_err is only in DEBUG kernels,
+                * using ASSERT() will be just like a VERIFY.
+                */
+               ASSERT(nvlist_merge(props, delayprops, 0) == 0);
+               nvlist_free(delayprops);
+       }
+
+       /*
+        * Now that all props, initial and delayed, are set, report the prop
+        * errors to the caller.
+        */
+       if (zc->zc_nvlist_dst_size != 0 &&
+           (nvlist_smush(errors, zc->zc_nvlist_dst_size) != 0 ||
+           put_nvlist(zc, errors) != 0)) {
+               /*
+                * Caller made zc->zc_nvlist_dst less than the minimum expected
+                * size or supplied an invalid address.
+                */
+               props_error = SET_ERROR(EINVAL);
        }
 
        zc->zc_cookie = off - fp->f_offset;