fs: btrfs: Implement btrfs_file_read()
authorQu Wenruo <wqu@suse.com>
Wed, 24 Jun 2020 16:03:11 +0000 (18:03 +0200)
committerTom Rini <trini@konsulko.com>
Tue, 8 Sep 2020 01:00:36 +0000 (21:00 -0400)
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>
fs/btrfs/btrfs.c
fs/btrfs/btrfs.h
fs/btrfs/inode.c

index 7eb01c4..f5d4752 100644 (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) {
+               ret = btrfs_size(file, &real_size);
+               if (ret < 0) {
+                       error("Failed to get inode size: %s", file);
+                       return ret;
+               }
+               len = real_size;
+       }
 
-       if (len > inode.size - offset)
-               len = inode.size - offset;
+       if (len > real_size - offset)
+               len = real_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;
+       ret = btrfs_file_read(root, ino, offset, len, buf);
+       if (ret < 0) {
+               error("An error occured while reading file %s", file);
+               return ret;
        }
 
-       *actread = rd;
+       *actread = len;
        return 0;
 }
 
index 32ea2fc..268ca07 100644 (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);
index 11eb30c..0c2b2b5 100644 (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;
+}