btrfs: fix offset when reading compressed extents
authorDominique Martinet <dominique.martinet@atmark-techno.com>
Tue, 18 Apr 2023 06:41:55 +0000 (15:41 +0900)
committerTom Rini <trini@konsulko.com>
Mon, 8 May 2023 13:23:53 +0000 (09:23 -0400)
btrfs_read_extent_reg correctly computed the extent offset in the
BTRFS_COMPRESS_NONE case, but did not account for the 'offset - key.offset'
part correctly in the compressed case, making the function read
incorrect data.

In the case I examined, the last 4k of a file was corrupted and
contained data from a few blocks prior, e.g. reading a 10k file with a
single extent:
btrfs_file_read()
 -> btrfs_read_extent_reg
    (aligned part loop, until 8k)
 -> read_and_truncate_page
   -> btrfs_read_extent_reg
      (re-reads the last extent from 8k to the end,
      incorrectly reading the first 2k of data)

This can be reproduced as follow:
$ truncate -s 200M btr
$ mount btr -o compress /mnt
$ pat() { dd if=/dev/zero bs=1M count=$1 iflag=count_bytes status=none | tr '\0' "\\$2"; }
$ { pat 4K 1; pat 4K 2; pat 2K 3; }  > /mnt/file
$ sync
$ filefrag -v /mnt/file
File size of /mnt/file is 10240 (3 blocks of 4096 bytes)
 ext:     logical_offset:        physical_offset: length:   expected: flags:
   0:        0..       2:       3328..      3330:      3:             last,encoded,eof
$ umount /mnt

Then in u-boot:
=> load scsi 0 2000000 file
10240 bytes read in 3 ms (3.3 MiB/s)
=> md 2001ff0
02001ff002020202 02020202 02020202 02020202  ................
0200200001010101 01010101 01010101 01010101  ................
0200201001010101 01010101 01010101 01010101  ................

(02002000 onwards should contain '03' pattern but went back to 01,
start of the extent)

After patch, data is read properly:
=> md 2001ff0
02001ff002020202 02020202 02020202 02020202  ................
0200200003030303 03030303 03030303 03030303  ................
0200201003030303 03030303 03030303 03030303  ................

Note that the code previously (before commit e3427184f38a ("fs: btrfs:
Implement btrfs_file_read()")) did not split that read in two, so
this is a regression even if the previous code might not have been
handling offsets correctly either (something that booted now fails to
boot)

Fixes: a26a6bedafcf ("fs: btrfs: Introduce btrfs_read_extent_inline() and btrfs_read_extent_reg()")
Signed-off-by: Dominique Martinet <dominique.martinet@atmark-techno.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
fs/btrfs/inode.c

index 4002566..38e285b 100644 (file)
@@ -511,7 +511,9 @@ int btrfs_read_extent_reg(struct btrfs_path *path,
        if (ret < dsize)
                memset(dbuf + ret, 0, dsize - ret);
        /* Then copy the needed part */
-       memcpy(dest, dbuf + btrfs_file_extent_offset(leaf, fi), len);
+       memcpy(dest,
+              dbuf + btrfs_file_extent_offset(leaf, fi) + offset - key.offset,
+              len);
        ret = len;
 out:
        free(cbuf);