fs: btrfs: Implement btrfs_file_read()

This version of btrfs_file_read() has the following new features:
- Tries all mirrors
- More handling on unaligned size
- Better compressed extent handling
  The old implementation doesn't handle compressed extent with offset
  properly: we need to read out the whole compressed extent, then
  decompress the whole extent, and only then copy the requested part.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Marek Behún <marek.behun@nic.cz>
This commit is contained in:
Qu Wenruo 2020-06-24 18:03:11 +02:00 committed by Tom Rini
parent 01347f64d5
commit e3427184f3
3 changed files with 178 additions and 22 deletions

View File

@ -253,37 +253,45 @@ out:
int btrfs_read(const char *file, void *buf, loff_t offset, loff_t len,
loff_t *actread)
{
struct __btrfs_root root = btrfs_info.fs_root;
struct btrfs_inode_item inode;
u64 inr, rd;
struct btrfs_fs_info *fs_info = current_fs_info;
struct btrfs_root *root;
loff_t real_size = 0;
u64 ino;
u8 type;
int ret;
inr = __btrfs_lookup_path(&root, root.root_dirid, file, &type, &inode,
40);
if (inr == -1ULL) {
printf("Cannot lookup file %s\n", file);
return -1;
ASSERT(fs_info);
ret = btrfs_lookup_path(fs_info->fs_root, BTRFS_FIRST_FREE_OBJECTID,
file, &root, &ino, &type, 40);
if (ret < 0) {
error("Cannot lookup file %s", file);
return ret;
}
if (type != BTRFS_FT_REG_FILE) {
printf("Not a regular file: %s\n", file);
return -1;
error("Not a regular file: %s", file);
return -EINVAL;
}
if (!len)
len = inode.size;
if (len > inode.size - offset)
len = inode.size - offset;
rd = __btrfs_file_read(&root, inr, offset, len, buf);
if (rd == -1ULL) {
printf("An error occured while reading file %s\n", file);
return -1;
if (!len) {
ret = btrfs_size(file, &real_size);
if (ret < 0) {
error("Failed to get inode size: %s", file);
return ret;
}
len = real_size;
}
*actread = rd;
if (len > real_size - offset)
len = real_size - offset;
ret = btrfs_file_read(root, ino, offset, len, buf);
if (ret < 0) {
error("An error occured while reading file %s", file);
return ret;
}
*actread = len;
return 0;
}

View File

@ -59,6 +59,8 @@ int btrfs_readlink(struct btrfs_root *root, u64 ino, char *target);
u64 __btrfs_lookup_path(struct __btrfs_root *, u64, const char *, u8 *,
struct btrfs_inode_item *, int);
u64 __btrfs_file_read(const struct __btrfs_root *, u64, u64, u64, char *);
int btrfs_file_read(struct btrfs_root *root, u64 ino, u64 file_offset, u64 len,
char *dest);
/* subvolume.c */
u64 btrfs_get_default_subvol_objectid(void);

View File

@ -926,3 +926,149 @@ check_next:
*next_offset = key.offset;
return 1;
}
static int read_and_truncate_page(struct btrfs_path *path,
struct btrfs_file_extent_item *fi,
int start, int len, char *dest)
{
struct extent_buffer *leaf = path->nodes[0];
struct btrfs_fs_info *fs_info = leaf->fs_info;
u64 aligned_start = round_down(start, fs_info->sectorsize);
u8 extent_type;
char *buf;
int page_off = start - aligned_start;
int page_len = fs_info->sectorsize - page_off;
int ret;
ASSERT(start + len <= aligned_start + fs_info->sectorsize);
buf = malloc_cache_aligned(fs_info->sectorsize);
if (!buf)
return -ENOMEM;
extent_type = btrfs_file_extent_type(leaf, fi);
if (extent_type == BTRFS_FILE_EXTENT_INLINE) {
ret = btrfs_read_extent_inline(path, fi, buf);
memcpy(dest, buf + page_off, min(page_len, ret));
free(buf);
return len;
}
ret = btrfs_read_extent_reg(path, fi,
round_down(start, fs_info->sectorsize),
fs_info->sectorsize, buf);
if (ret < 0) {
free(buf);
return ret;
}
memcpy(dest, buf + page_off, page_len);
free(buf);
return len;
}
int btrfs_file_read(struct btrfs_root *root, u64 ino, u64 file_offset, u64 len,
char *dest)
{
struct btrfs_fs_info *fs_info = root->fs_info;
struct btrfs_file_extent_item *fi;
struct btrfs_path path;
struct btrfs_key key;
u64 aligned_start = round_down(file_offset, fs_info->sectorsize);
u64 aligned_end = round_down(file_offset + len, fs_info->sectorsize);
u64 next_offset;
u64 cur = aligned_start;
int ret = 0;
btrfs_init_path(&path);
/* Set the whole dest all zero, so we won't need to bother holes */
memset(dest, 0, len);
/* Read out the leading unaligned part */
if (aligned_start != file_offset) {
ret = lookup_data_extent(root, &path, ino, aligned_start,
&next_offset);
if (ret < 0)
goto out;
if (ret == 0) {
/* Read the unaligned part out*/
fi = btrfs_item_ptr(path.nodes[0], path.slots[0],
struct btrfs_file_extent_item);
ret = read_and_truncate_page(&path, fi, file_offset,
round_up(file_offset, fs_info->sectorsize) -
file_offset, dest);
if (ret < 0)
goto out;
cur += fs_info->sectorsize;
} else {
/* The whole file is a hole */
if (!next_offset) {
memset(dest, 0, len);
return len;
}
cur = next_offset;
}
}
/* Read the aligned part */
while (cur < aligned_end) {
u64 extent_num_bytes;
u8 type;
btrfs_release_path(&path);
ret = lookup_data_extent(root, &path, ino, cur, &next_offset);
if (ret < 0)
goto out;
if (ret > 0) {
/* No next, direct exit */
if (!next_offset) {
ret = 0;
goto out;
}
}
fi = btrfs_item_ptr(path.nodes[0], path.slots[0],
struct btrfs_file_extent_item);
btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]);
type = btrfs_file_extent_type(path.nodes[0], fi);
if (type == BTRFS_FILE_EXTENT_INLINE) {
ret = btrfs_read_extent_inline(&path, fi, dest);
goto out;
}
/* Skip holes, as we have zeroed the dest */
if (type == BTRFS_FILE_EXTENT_PREALLOC ||
btrfs_file_extent_disk_bytenr(path.nodes[0], fi) == 0) {
cur = key.offset + btrfs_file_extent_num_bytes(
path.nodes[0], fi);
continue;
}
/* Read the remaining part of the extent */
extent_num_bytes = btrfs_file_extent_num_bytes(path.nodes[0],
fi);
ret = btrfs_read_extent_reg(&path, fi, cur,
min(extent_num_bytes, aligned_end - cur),
dest + cur - file_offset);
if (ret < 0)
goto out;
cur += min(extent_num_bytes, aligned_end - cur);
}
/* Read the tailing unaligned part*/
if (file_offset + len != aligned_end) {
btrfs_release_path(&path);
ret = lookup_data_extent(root, &path, ino, aligned_end,
&next_offset);
/* <0 is error, >0 means no extent */
if (ret)
goto out;
fi = btrfs_item_ptr(path.nodes[0], path.slots[0],
struct btrfs_file_extent_item);
ret = read_and_truncate_page(&path, fi, aligned_end,
file_offset + len - aligned_end,
dest + aligned_end - file_offset);
}
out:
btrfs_release_path(&path);
if (ret < 0)
return ret;
return len;
}