ext4: let ext4_readdir handle inline data
authorTao Ma <boyu.mt@taobao.com>
Mon, 10 Dec 2012 19:05:59 +0000 (14:05 -0500)
committerTheodore Ts'o <tytso@mit.edu>
Mon, 10 Dec 2012 19:05:59 +0000 (14:05 -0500)
For "." and "..", we just call filldir by ourselves
instead of iterating the real dir entry.

Signed-off-by: Tao Ma <boyu.mt@taobao.com>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
fs/ext4/dir.c
fs/ext4/ext4.h
fs/ext4/inline.c
fs/ext4/xattr.h

index 7c9d08b0f2feff052dd8e2abcb7941f7ffb015b0..b8d877f6c1fa52991cb16d8bf76652e41e9dd72c 100644 (file)
 #include <linux/slab.h>
 #include <linux/rbtree.h>
 #include "ext4.h"
-
-static unsigned char ext4_filetype_table[] = {
-       DT_UNKNOWN, DT_REG, DT_DIR, DT_CHR, DT_BLK, DT_FIFO, DT_SOCK, DT_LNK
-};
+#include "xattr.h"
 
 static int ext4_dx_readdir(struct file *filp,
                           void *dirent, filldir_t filldir);
 
-static unsigned char get_dtype(struct super_block *sb, int filetype)
-{
-       if (!EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_FILETYPE) ||
-           (filetype >= EXT4_FT_MAX))
-               return DT_UNKNOWN;
-
-       return (ext4_filetype_table[filetype]);
-}
-
 /**
  * Check if the given dir-inode refers to an htree-indexed directory
  * (or a directory which chould potentially get coverted to use htree
@@ -68,6 +56,9 @@ static int is_dx_dir(struct inode *inode)
  * Return 0 if the directory entry is OK, and 1 if there is a problem
  *
  * Note: this is the opposite of what ext2 and ext3 historically returned...
+ *
+ * bh passed here can be an inode block or a dir data block, depending
+ * on the inode inline data flag.
  */
 int __ext4_check_dir_entry(const char *function, unsigned int line,
                           struct inode *dir, struct file *filp,
@@ -124,6 +115,14 @@ static int ext4_readdir(struct file *filp,
        int ret = 0;
        int dir_has_error = 0;
 
+       if (ext4_has_inline_data(inode)) {
+               int has_inline_data = 1;
+               ret = ext4_read_inline_dir(filp, dirent, filldir,
+                                          &has_inline_data);
+               if (has_inline_data)
+                       return ret;
+       }
+
        if (is_dx_dir(inode)) {
                err = ext4_dx_readdir(filp, dirent, filldir);
                if (err != ERR_BAD_DX_DIR) {
index 689ce1d696b8e8488e6ff97da79dbc6091bb6777..e3a74658c63cb87f2d12bcbb34d73bfec21ba1ba 100644 (file)
@@ -1989,6 +1989,18 @@ static inline void ext4_update_dx_flag(struct inode *inode)
                                     EXT4_FEATURE_COMPAT_DIR_INDEX))
                ext4_clear_inode_flag(inode, EXT4_INODE_INDEX);
 }
+static unsigned char ext4_filetype_table[] = {
+       DT_UNKNOWN, DT_REG, DT_DIR, DT_CHR, DT_BLK, DT_FIFO, DT_SOCK, DT_LNK
+};
+
+static inline  unsigned char get_dtype(struct super_block *sb, int filetype)
+{
+       if (!EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_FILETYPE) ||
+           (filetype >= EXT4_FT_MAX))
+               return DT_UNKNOWN;
+
+       return ext4_filetype_table[filetype];
+}
 
 /* fsync.c */
 extern int ext4_sync_file(struct file *, loff_t, loff_t, int);
index bf732281873803f9e836fa23d43a41b044a87226..471504133c76065fe3638965e78a7b0ba81a77e2 100644 (file)
@@ -1288,6 +1288,142 @@ out:
        return ret;
 }
 
+int ext4_read_inline_dir(struct file *filp,
+                        void *dirent, filldir_t filldir,
+                        int *has_inline_data)
+{
+       int error = 0;
+       unsigned int offset, parent_ino;
+       int i, stored;
+       struct ext4_dir_entry_2 *de;
+       struct super_block *sb;
+       struct inode *inode = filp->f_path.dentry->d_inode;
+       int ret, inline_size = 0;
+       struct ext4_iloc iloc;
+       void *dir_buf = NULL;
+
+       ret = ext4_get_inode_loc(inode, &iloc);
+       if (ret)
+               return ret;
+
+       down_read(&EXT4_I(inode)->xattr_sem);
+       if (!ext4_has_inline_data(inode)) {
+               up_read(&EXT4_I(inode)->xattr_sem);
+               *has_inline_data = 0;
+               goto out;
+       }
+
+       inline_size = ext4_get_inline_size(inode);
+       dir_buf = kmalloc(inline_size, GFP_NOFS);
+       if (!dir_buf) {
+               ret = -ENOMEM;
+               up_read(&EXT4_I(inode)->xattr_sem);
+               goto out;
+       }
+
+       ret = ext4_read_inline_data(inode, dir_buf, inline_size, &iloc);
+       up_read(&EXT4_I(inode)->xattr_sem);
+       if (ret < 0)
+               goto out;
+
+       sb = inode->i_sb;
+       stored = 0;
+       parent_ino = le32_to_cpu(((struct ext4_dir_entry_2 *)dir_buf)->inode);
+
+       while (!error && !stored && filp->f_pos < inode->i_size) {
+revalidate:
+               /*
+                * If the version has changed since the last call to
+                * readdir(2), then we might be pointing to an invalid
+                * dirent right now.  Scan from the start of the inline
+                * dir to make sure.
+                */
+               if (filp->f_version != inode->i_version) {
+                       for (i = 0;
+                            i < inode->i_size && i < offset;) {
+                               if (!i) {
+                                       /* skip "." and ".." if needed. */
+                                       i += EXT4_INLINE_DOTDOT_SIZE;
+                                       continue;
+                               }
+                               de = (struct ext4_dir_entry_2 *)
+                                       (dir_buf + i);
+                               /* It's too expensive to do a full
+                                * dirent test each time round this
+                                * loop, but we do have to test at
+                                * least that it is non-zero.  A
+                                * failure will be detected in the
+                                * dirent test below. */
+                               if (ext4_rec_len_from_disk(de->rec_len,
+                                       inline_size) < EXT4_DIR_REC_LEN(1))
+                                       break;
+                               i += ext4_rec_len_from_disk(de->rec_len,
+                                                           inline_size);
+                       }
+                       offset = i;
+                       filp->f_pos = offset;
+                       filp->f_version = inode->i_version;
+               }
+
+               while (!error && filp->f_pos < inode->i_size) {
+                       if (filp->f_pos == 0) {
+                               error = filldir(dirent, ".", 1, 0, inode->i_ino,
+                                               DT_DIR);
+                               if (error)
+                                       break;
+                               stored++;
+
+                               error = filldir(dirent, "..", 2, 0, parent_ino,
+                                               DT_DIR);
+                               if (error)
+                                       break;
+                               stored++;
+
+                               filp->f_pos = offset = EXT4_INLINE_DOTDOT_SIZE;
+                               continue;
+                       }
+
+                       de = (struct ext4_dir_entry_2 *)(dir_buf + offset);
+                       if (ext4_check_dir_entry(inode, filp, de,
+                                                iloc.bh, dir_buf,
+                                                inline_size, offset)) {
+                               ret = stored;
+                               goto out;
+                       }
+                       offset += ext4_rec_len_from_disk(de->rec_len,
+                                                        inline_size);
+                       if (le32_to_cpu(de->inode)) {
+                               /* We might block in the next section
+                                * if the data destination is
+                                * currently swapped out.  So, use a
+                                * version stamp to detect whether or
+                                * not the directory has been modified
+                                * during the copy operation.
+                                */
+                               u64 version = filp->f_version;
+
+                               error = filldir(dirent, de->name,
+                                               de->name_len,
+                                               filp->f_pos,
+                                               le32_to_cpu(de->inode),
+                                               get_dtype(sb, de->file_type));
+                               if (error)
+                                       break;
+                               if (version != filp->f_version)
+                                       goto revalidate;
+                               stored++;
+                       }
+                       filp->f_pos += ext4_rec_len_from_disk(de->rec_len,
+                                                             inline_size);
+               }
+               offset = 0;
+       }
+out:
+       kfree(dir_buf);
+       brelse(iloc.bh);
+       return ret;
+}
+
 /*
  * Try to create the inline data for the new dir.
  * If it succeeds, return 0, otherwise return the error.
index 397ef4bbaf1e7756f4ec6adcfa7837f6c115fceb..539e6a08c95f46ab83bdb13808ad5bca5558bba2 100644 (file)
@@ -168,6 +168,9 @@ extern int ext4_try_add_inline_entry(handle_t *handle, struct dentry *dentry,
 extern int ext4_try_create_inline_dir(handle_t *handle,
                                      struct inode *parent,
                                      struct inode *inode);
+extern int ext4_read_inline_dir(struct file *filp,
+                               void *dirent, filldir_t filldir,
+                               int *has_inline_data);
 # else  /* CONFIG_EXT4_FS_XATTR */
 
 static inline int
@@ -346,6 +349,12 @@ static inline int ext4_try_create_inline_dir(handle_t *handle,
 {
        return 0;
 }
+static inline int ext4_read_inline_dir(struct file *filp,
+                                      void *dirent, filldir_t filldir,
+                                      int *has_inline_data)
+{
+       return 0;
+}
 # endif  /* CONFIG_EXT4_FS_XATTR */
 
 #ifdef CONFIG_EXT4_FS_SECURITY