diff options
author | Paul Dagnelie <[email protected]> | 2023-03-10 09:52:44 -0800 |
---|---|---|
committer | GitHub <[email protected]> | 2023-03-10 09:52:44 -0800 |
commit | da19d919a853ad05ef300fe000e6c96c4db84bcf (patch) | |
tree | 9a4e6fab24e3779a81eb82873067a23779d0fe81 /module | |
parent | 589f59b52af86455cf71421e63c34557c7ee6f91 (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.c | 35 |
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. |