diff --git a/module/zfs/dmu_object.c b/module/zfs/dmu_object.c index 488ca2155d..3e3efe0c13 100644 --- a/module/zfs/dmu_object.c +++ b/module/zfs/dmu_object.c @@ -237,28 +237,39 @@ int dmu_object_next(objset_t *os, uint64_t *objectp, boolean_t hole, uint64_t txg) { uint64_t offset; - dmu_object_info_t doi; + uint64_t start_obj; struct dsl_dataset *ds = os->os_dsl_dataset; - int dnodesize; int error; - /* - * Avoid expensive dnode hold if this dataset doesn't use large dnodes. - */ - if (ds && ds->ds_feature_inuse[SPA_FEATURE_LARGE_DNODE]) { - error = dmu_object_info(os, *objectp, &doi); - if (error && !(error == EINVAL && *objectp == 0)) - return (SET_ERROR(error)); - else - dnodesize = doi.doi_dnodesize; + if (*objectp == 0) { + start_obj = 1; + } else if (ds && ds->ds_feature_inuse[SPA_FEATURE_LARGE_DNODE]) { + /* + * For large_dnode datasets, scan from the beginning of the + * dnode block to find the starting offset. This is needed + * because objectp could be part of a large dnode so we can't + * assume it's a hole even if dmu_object_info() returns ENOENT. + */ + int epb = DNODE_BLOCK_SIZE >> DNODE_SHIFT; + int skip; + uint64_t i; + + for (i = *objectp & ~(epb - 1); i <= *objectp; i += skip) { + dmu_object_info_t doi; + + error = dmu_object_info(os, i, &doi); + if (error) + skip = 1; + else + skip = doi.doi_dnodesize >> DNODE_SHIFT; + } + + start_obj = i; } else { - dnodesize = DNODE_MIN_SIZE; + start_obj = *objectp + 1; } - if (*objectp == 0) - offset = 1 << DNODE_SHIFT; - else - offset = (*objectp << DNODE_SHIFT) + dnodesize; + offset = start_obj << DNODE_SHIFT; error = dnode_next_offset(DMU_META_DNODE(os), (hole ? DNODE_FIND_HOLE : 0), &offset, 0, DNODES_PER_BLOCK, txg); diff --git a/module/zfs/dnode.c b/module/zfs/dnode.c index 45bb958cd4..cbd54be04d 100644 --- a/module/zfs/dnode.c +++ b/module/zfs/dnode.c @@ -1184,6 +1184,7 @@ dnode_is_free(dmu_buf_impl_t *db, int idx, int slots) * errors: * EINVAL - invalid object number. * ENOSPC - hole too small to fulfill "slots" request + * ENOENT - the requested dnode is not allocated * EIO - i/o error. * succeeds even for free dnodes. */ diff --git a/tests/zfs-tests/tests/functional/features/large_dnode/large_dnode_005_pos.ksh b/tests/zfs-tests/tests/functional/features/large_dnode/large_dnode_005_pos.ksh index e03d1274fa..1117e849d5 100755 --- a/tests/zfs-tests/tests/functional/features/large_dnode/large_dnode_005_pos.ksh +++ b/tests/zfs-tests/tests/functional/features/large_dnode/large_dnode_005_pos.ksh @@ -27,9 +27,11 @@ verify_runnable "both" TEST_SEND_FS=$TESTPOOL/send_large_dnode TEST_RECV_FS=$TESTPOOL/recv_large_dnode TEST_SNAP=$TEST_SEND_FS@ldnsnap +TEST_SNAPINCR=$TEST_SEND_FS@ldnsnap_incr TEST_STREAM=$TESTDIR/ldnsnap +TEST_STREAMINCR=$TESTDIR/ldnsnap_incr TEST_FILE=foo - +TEST_FILEINCR=bar function cleanup { @@ -42,6 +44,7 @@ function cleanup fi rm -f $TEST_STREAM + rm -f $TEST_STREAMINCR } log_onexit cleanup @@ -49,10 +52,13 @@ log_onexit cleanup log_assert "zfs send stream with large dnodes accepted by new pool" log_must $ZFS create -o dnodesize=1k $TEST_SEND_FS -log_must touch /$TEST_SEND_FS/$TEST_FILE -log_must $ZFS umount $TEST_SEND_FS +log_must $TOUCH /$TEST_SEND_FS/$TEST_FILE log_must $ZFS snap $TEST_SNAP log_must $ZFS send $TEST_SNAP > $TEST_STREAM +log_must $RM -f /$TEST_SEND_FS/$TEST_FILE +log_must $TOUCH /$TEST_SEND_FS/$TEST_FILEINCR +log_must $ZFS snap $TEST_SNAPINCR +log_must $ZFS send -i $TEST_SNAP $TEST_SNAPINCR > $TEST_STREAMINCR log_must eval "$ZFS recv $TEST_RECV_FS < $TEST_STREAM" inode=$(ls -li /$TEST_RECV_FS/$TEST_FILE | awk '{print $1}') @@ -61,4 +67,9 @@ if [[ "$dnsize" != "1K" ]]; then log_fail "dnode size is $dnsize (expected 1K)" fi +log_must eval "$ZFS recv -F $TEST_RECV_FS < $TEST_STREAMINCR" +log_must $DIFF -r /$TEST_SEND_FS /$TEST_RECV_FS +log_must $ZFS umount $TEST_SEND_FS +log_must $ZFS umount $TEST_RECV_FS + log_pass