btrfs-progs: extent_io: Init eb->lru to avoid NULL pointer dereference
[platform/upstream/btrfs-progs.git] / file.c
diff --git a/file.c b/file.c
index 0e9253e..f5e645c 100644 (file)
--- a/file.c
+++ b/file.c
@@ -18,6 +18,8 @@
 
 #include <sys/stat.h>
 #include "ctree.h"
+#include "utils.h"
+#include "disk-io.h"
 #include "transaction.h"
 #include "kerncompat.h"
 
@@ -52,7 +54,7 @@ int btrfs_get_extent(struct btrfs_trans_handle *trans,
        if (ret <= 0)
                goto out;
        if (ret > 0) {
-               /* Check preivous file extent */
+               /* Check previous file extent */
                ret = btrfs_previous_item(root, path, ino,
                                          BTRFS_EXTENT_DATA_KEY);
                if (ret < 0)
@@ -111,7 +113,7 @@ check_next:
                not_found = 1;
 
        /*
-        * To keep the search hehavior consistent with search_slot(),
+        * To keep the search behavior consistent with search_slot(),
         * we need to go back to the prev leaf's nritem slot if
         * we are at the first slot of the leaf.
         */
@@ -160,3 +162,170 @@ out:
        btrfs_free_path(path);
        return ret;
 }
+
+/*
+ * Read out content of one inode.
+ *
+ * @root:  fs/subvolume root containing the inode
+ * @ino:   inode number
+ * @start: offset inside the file, aligned to sectorsize
+ * @len:   length to read, aligned to sectorisize
+ * @dest:  where data will be stored
+ *
+ * NOTE:
+ * 1) compression data is not supported yet
+ * 2) @start and @len must be aligned to sectorsize
+ * 3) data read out is also aligned to sectorsize, not truncated to inode size
+ *
+ * Return < 0 for fatal error during read.
+ * Otherwise return the number of succesfully read data in bytes.
+ */
+int btrfs_read_file(struct btrfs_root *root, u64 ino, u64 start, int len,
+                   char *dest)
+{
+       struct btrfs_fs_info *fs_info = root->fs_info;
+       struct btrfs_key key;
+       struct btrfs_path path;
+       struct extent_buffer *leaf;
+       struct btrfs_inode_item *ii;
+       u64 isize;
+       int no_holes = btrfs_fs_incompat(fs_info, NO_HOLES);
+       int slot;
+       int read = 0;
+       int ret;
+
+       if (!IS_ALIGNED(start, fs_info->sectorsize) ||
+           !IS_ALIGNED(len, fs_info->sectorsize)) {
+               warning("@start and @len must be aligned to %u for function %s",
+                       fs_info->sectorsize, __func__);
+               return -EINVAL;
+       }
+
+       btrfs_init_path(&path);
+       key.objectid = ino;
+       key.offset = start;
+       key.type = BTRFS_EXTENT_DATA_KEY;
+
+       ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
+       if (ret < 0)
+               goto out;
+
+       if (ret > 0) {
+               ret = btrfs_previous_item(root, &path, ino, BTRFS_EXTENT_DATA_KEY);
+               if (ret > 0) {
+                       ret = -ENOENT;
+                       goto out;
+               }
+       }
+
+       /*
+        * Reset @dest to all 0, so we don't need to care about holes in
+        * no_hole mode, but focus on reading non-hole part.
+        */
+       memset(dest, 0, len);
+       while (1) {
+               struct btrfs_file_extent_item *fi;
+               u64 extent_start;
+               u64 extent_len;
+               u64 read_start;
+               u64 read_len;
+               u64 read_len_ret;
+               u64 disk_bytenr;
+
+               leaf = path.nodes[0];
+               slot = path.slots[0];
+
+               btrfs_item_key_to_cpu(leaf, &key, slot);
+               if (key.objectid > ino)
+                       break;
+               if (key.type != BTRFS_EXTENT_DATA_KEY || key.objectid != ino)
+                       goto next;
+
+               extent_start = key.offset;
+               if (extent_start >= start + len)
+                       break;
+
+               fi = btrfs_item_ptr(leaf, slot, struct btrfs_file_extent_item);
+               if (btrfs_file_extent_compression(leaf, fi) !=
+                   BTRFS_COMPRESS_NONE) {
+                       ret = -ENOTTY;
+                       break;
+               }
+
+               /* Inline extent, one inode should only one inline extent */
+               if (btrfs_file_extent_type(leaf, fi) ==
+                   BTRFS_FILE_EXTENT_INLINE) {
+                       extent_len = btrfs_file_extent_inline_len(leaf, slot,
+                                                                 fi);
+                       if (extent_start + extent_len <= start)
+                               goto next;
+                       read_extent_buffer(leaf, dest,
+                               btrfs_file_extent_inline_start(fi), extent_len);
+                       read += round_up(extent_len, fs_info->sectorsize);
+                       break;
+               }
+
+               extent_len = btrfs_file_extent_num_bytes(leaf, fi);
+               if (extent_start + extent_len <= start)
+                       goto next;
+
+               read_start = max(start, extent_start);
+               read_len = min(start + len, extent_start + extent_len) -
+                          read_start;
+
+               /* We have already zeroed @dest, nothing to do */
+               if (btrfs_file_extent_type(leaf, fi) ==
+                   BTRFS_FILE_EXTENT_PREALLOC ||
+                   btrfs_file_extent_disk_num_bytes(leaf, fi) == 0) {
+                       read += read_len;
+                       goto next;
+               }
+
+               disk_bytenr = btrfs_file_extent_disk_bytenr(leaf, fi) +
+                             btrfs_file_extent_offset(leaf, fi);
+               read_len_ret = read_len;
+               ret = read_extent_data(fs_info, dest + read_start - start, disk_bytenr,
+                                      &read_len_ret, 0);
+               if (ret < 0)
+                       break;
+               /* Short read, something went wrong */
+               if (read_len_ret != read_len)
+                       return -EIO;
+               read += read_len;
+next:
+               ret = btrfs_next_item(root, &path);
+               if (ret > 0) {
+                       ret = 0;
+                       break;
+               }
+       }
+
+       /*
+        * Special trick for no_holes, since for no_holes we don't have good
+        * method to account skipped and tailling holes, we used
+        * min(inode size, len) as return value
+        */
+       if (no_holes) {
+               btrfs_release_path(&path);
+               key.objectid = ino;
+               key.offset = 0;
+               key.type = BTRFS_INODE_ITEM_KEY;
+               ret = btrfs_lookup_inode(NULL, root, &path, &key, 0);
+               if (ret < 0)
+                       goto out;
+               if (ret > 0) {
+                       ret = -ENOENT;
+                       goto out;
+               }
+               ii = btrfs_item_ptr(path.nodes[0], path.slots[0],
+                                   struct btrfs_inode_item);
+               isize = round_up(btrfs_inode_size(path.nodes[0], ii),
+                                fs_info->sectorsize);
+               read = min_t(u64, isize - start, len);
+       }
+out:
+       btrfs_release_path(&path);
+       if (!ret)
+               ret = read;
+       return ret;
+}