summaryrefslogtreecommitdiffstats
path: root/module
diff options
context:
space:
mode:
authorPaul Dagnelie <[email protected]>2023-03-10 09:52:44 -0800
committerGitHub <[email protected]>2023-03-10 09:52:44 -0800
commitda19d919a853ad05ef300fe000e6c96c4db84bcf (patch)
tree9a4e6fab24e3779a81eb82873067a23779d0fe81 /module
parent589f59b52af86455cf71421e63c34557c7ee6f91 (diff)
Fix incremental receive silently failing for recursive sends
The problem occurs because dmu_recv_begin pulls in the payload and next header from the input stream in order to use the contents of the begin record's nvlist. However, the change to do that before the other checks in dmu_recv_begin occur caused a regression where an empty send stream in a recursive send could have its END record consumed by this, which broke the logic of recv_skip. A test is also included to protect against this case in the future. Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Paul Dagnelie <[email protected]> Closes #12661 Closes #14568
Diffstat (limited to 'module')
-rw-r--r--module/zfs/dmu_recv.c35
1 files changed, 26 insertions, 9 deletions
diff --git a/module/zfs/dmu_recv.c b/module/zfs/dmu_recv.c
index 1684d9d92..3763cba02 100644
--- a/module/zfs/dmu_recv.c
+++ b/module/zfs/dmu_recv.c
@@ -1255,7 +1255,6 @@ dmu_recv_begin(char *tofs, char *tosnap, dmu_replay_record_t *drr_begin,
DMU_GET_FEATUREFLAGS(drc->drc_drrb->drr_versioninfo);
uint32_t payloadlen = drc->drc_drr_begin->drr_payloadlen;
- void *payload = NULL;
/*
* Since OpenZFS 2.0.0, we have enforced a 64MB limit in userspace
@@ -1266,16 +1265,23 @@ dmu_recv_begin(char *tofs, char *tosnap, dmu_replay_record_t *drr_begin,
if (payloadlen > (MIN((1U << 28), arc_all_memory() / 4)))
return (E2BIG);
- if (payloadlen != 0)
- payload = vmem_alloc(payloadlen, KM_SLEEP);
- err = receive_read_payload_and_next_header(drc, payloadlen,
- payload);
- if (err != 0) {
- vmem_free(payload, payloadlen);
- return (err);
- }
if (payloadlen != 0) {
+ void *payload = vmem_alloc(payloadlen, KM_SLEEP);
+ /*
+ * For compatibility with recursive send streams, we don't do
+ * this here if the stream could be part of a package. Instead,
+ * we'll do it in dmu_recv_stream. If we pull the next header
+ * too early, and it's the END record, we break the `recv_skip`
+ * logic.
+ */
+
+ err = receive_read_payload_and_next_header(drc, payloadlen,
+ payload);
+ if (err != 0) {
+ vmem_free(payload, payloadlen);
+ return (err);
+ }
err = nvlist_unpack(payload, payloadlen, &drc->drc_begin_nvl,
KM_SLEEP);
vmem_free(payload, payloadlen);
@@ -3316,6 +3322,17 @@ dmu_recv_stream(dmu_recv_cookie_t *drc, offset_t *voffp)
}
/*
+ * For compatibility with recursive send streams, we do this here,
+ * rather than in dmu_recv_begin. If we pull the next header too
+ * early, and it's the END record, we break the `recv_skip` logic.
+ */
+ if (drc->drc_drr_begin->drr_payloadlen == 0) {
+ err = receive_read_payload_and_next_header(drc, 0, NULL);
+ if (err != 0)
+ goto out;
+ }
+
+ /*
* If we failed before this point we will clean up any new resume
* state that was created. Now that we've gotten past the initial
* checks we are ok to retain that resume state.