Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 65d165d9 authored by Tao Ma's avatar Tao Ma Committed by Theodore Ts'o
Browse files

ext4: let ext4_readdir handle inline data



For "." and "..", we just call filldir by ourselves
instead of iterating the real dir entry.

Signed-off-by: default avatarTao Ma <boyu.mt@taobao.com>
Signed-off-by: default avatar"Theodore Ts'o" <tytso@mit.edu>
parent 3c47d541
Loading
Loading
Loading
Loading
+12 −13
Original line number Diff line number Diff line
@@ -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
@@ -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) {
+12 −0
Original line number Diff line number Diff line
@@ -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);
+136 −0
Original line number Diff line number Diff line
@@ -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.
+9 −0
Original line number Diff line number Diff line
@@ -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