Skip to content

Commit

Permalink
ext4: let ext4_readdir handle inline data
Browse files Browse the repository at this point in the history
For "." and "..", we just call filldir by ourselves
instead of iterating the real dir entry.

Signed-off-by: Tao Ma <[email protected]>
Signed-off-by: "Theodore Ts'o" <[email protected]>
  • Loading branch information
taoma-tm authored and tytso committed Dec 10, 2012
1 parent 3c47d54 commit 65d165d
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 13 deletions.
25 changes: 12 additions & 13 deletions fs/ext4/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,11 @@
#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
Expand All @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
12 changes: 12 additions & 0 deletions fs/ext4/ext4.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
136 changes: 136 additions & 0 deletions fs/ext4/inline.c
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,142 @@ int ext4_try_add_inline_entry(handle_t *handle, struct dentry *dentry,
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.
Expand Down
9 changes: 9 additions & 0 deletions fs/ext4/xattr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 65d165d

Please sign in to comment.