From 5a41c8e24c17e597e4bdbea2ca3a952163c27445 Mon Sep 17 00:00:00 2001 From: Tom Caputi Date: Thu, 5 Sep 2019 19:22:05 -0400 Subject: [PATCH] Fix noop receive of raw send stream Currently, the noop receive code fails to work with raw send streams and resuming send streams. This happens because zfs_receive_impl() reads the DRR_BEGIN payload without reading the payload itself. Normally, the kernel expects to read this itself, but in this case the recv_skip() code runs instead and it is not prepared to handle the stream being left at any place other than the beginning of a record. This patch resolves this issue by manually reading the DRR_BEGIN payload in the dry-run case. This patch also includes a number of small fixups in this code path. Reviewed-by: George Melikov Reviewed-by: Brian Behlendorf Reviewed-by: Paul Dagnelie Signed-off-by: Tom Caputi Closes #9221 Closes #9173 --- lib/libzfs/libzfs_sendrecv.c | 39 ++++++++++++++++--- .../cli_root/zfs_receive/zfs_receive_raw.ksh | 3 ++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c index 12a4b500ed1e..20a59ef6cffe 100644 --- a/lib/libzfs/libzfs_sendrecv.c +++ b/lib/libzfs/libzfs_sendrecv.c @@ -3438,10 +3438,11 @@ recv_skip(libzfs_handle_t *hdl, int fd, boolean_t byteswap) { dmu_replay_record_t *drr; void *buf = zfs_alloc(hdl, SPA_MAXBLOCKSIZE); + uint64_t payload_size; char errbuf[1024]; (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, - "cannot receive:")); + "cannot receive")); /* XXX would be great to use lseek if possible... */ drr = buf; @@ -3468,9 +3469,14 @@ recv_skip(libzfs_handle_t *hdl, int fd, boolean_t byteswap) drr->drr_u.drr_object.drr_bonuslen = BSWAP_32(drr->drr_u.drr_object. drr_bonuslen); + drr->drr_u.drr_object.drr_raw_bonuslen = + BSWAP_32(drr->drr_u.drr_object. + drr_raw_bonuslen); } - (void) recv_read(hdl, fd, buf, - P2ROUNDUP(drr->drr_u.drr_object.drr_bonuslen, 8), + + payload_size = + DRR_OBJECT_PAYLOAD_SIZE(&drr->drr_u.drr_object); + (void) recv_read(hdl, fd, buf, payload_size, B_FALSE, NULL); break; @@ -3483,7 +3489,7 @@ recv_skip(libzfs_handle_t *hdl, int fd, boolean_t byteswap) BSWAP_64( drr->drr_u.drr_write.drr_compressed_size); } - uint64_t payload_size = + payload_size = DRR_WRITE_PAYLOAD_SIZE(&drr->drr_u.drr_write); (void) recv_read(hdl, fd, buf, payload_size, B_FALSE, NULL); @@ -3492,9 +3498,15 @@ recv_skip(libzfs_handle_t *hdl, int fd, boolean_t byteswap) if (byteswap) { drr->drr_u.drr_spill.drr_length = BSWAP_64(drr->drr_u.drr_spill.drr_length); + drr->drr_u.drr_spill.drr_compressed_size = + BSWAP_64(drr->drr_u.drr_spill. + drr_compressed_size); } - (void) recv_read(hdl, fd, buf, - drr->drr_u.drr_spill.drr_length, B_FALSE, NULL); + + payload_size = + DRR_SPILL_PAYLOAD_SIZE(&drr->drr_u.drr_spill); + (void) recv_read(hdl, fd, buf, payload_size, + B_FALSE, NULL); break; case DRR_WRITE_EMBEDDED: if (byteswap) { @@ -4232,6 +4244,21 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, } if (flags->dryrun) { + void *buf = zfs_alloc(hdl, SPA_MAXBLOCKSIZE); + + /* + * We have read the DRR_BEGIN record, but we have + * not yet read the payload. For non-dryrun sends + * this will be done by the kernel, so we must + * emulate that here, before attempting to read + * more records. + */ + err = recv_read(hdl, infd, buf, drr->drr_payloadlen, + flags->byteswap, NULL); + free(buf); + if (err != 0) + goto out; + err = recv_skip(hdl, infd, flags->byteswap); goto out; } diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_raw.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_raw.ksh index 7d5606acea09..9740caf72508 100755 --- a/tests/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_raw.ksh +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_raw.ksh @@ -36,6 +36,7 @@ # 9. Verify the key is unavailable # 10. Attempt to load the key and mount the dataset # 11. Verify the checksum of the file is the same as the original +# 12. Verify 'zfs receive -n' works with the raw stream # verify_runnable "both" @@ -88,4 +89,6 @@ typeset cksum2=$(md5digest /$TESTPOOL/$TESTFS1/c1/$TESTFILE0) [[ "$cksum2" == "$checksum" ]] || \ log_fail "Checksums differ ($cksum2 != $checksum)" +log_must eval "zfs send -w $snap | zfs receive -n $TESTPOOL/$TESTFS3" + log_pass "ZFS can receive streams from raw sends"