linux-fsdevel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Tao Ma <tm@tao.ma>
To: linux-ext4@vger.kernel.org
Cc: adilger@dilger.ca, tytso@mit.edu, linux-kernel@vger.kernel.org,
	linux-fsdevel@vger.kernel.org
Subject: [PATCH V3 11/21] ext4: let add_dir_entry handle inline data properly.
Date: Sun, 18 Dec 2011 22:24:28 +0800	[thread overview]
Message-ID: <1324218278-2460-11-git-send-email-tm@tao.ma> (raw)
In-Reply-To: <1324218278-2460-1-git-send-email-tm@tao.ma>

From: Tao Ma <boyu.mt@taobao.com>

This patch let add_dir_entry handle the inline data case. So the
dir is initialized as inline dir first and then we can try to add
some files to it, when the inline space can't hold all the entries,
a dir block will be created and the dir entry will be moved to it.

Signed-off-by: Tao Ma <boyu.mt@taobao.com>
---
 fs/ext4/ext4.h   |    2 +
 fs/ext4/inline.c |  276 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/ext4/namei.c  |   19 ++++
 fs/ext4/xattr.h  |   19 ++++
 4 files changed, 316 insertions(+), 0 deletions(-)

diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index e5847b4..e61c967 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -2262,6 +2262,8 @@ static inline void ext4_mark_super_dirty(struct super_block *sb)
 
 /* dir.c */
 extern const struct file_operations ext4_dir_operations;
+extern void ext4_show_inline_dir(struct inode *dir, struct buffer_head *bh,
+				 void *inline_start, int inline_size);
 
 /* file.c */
 extern const struct inode_operations ext4_file_inode_operations;
diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c
index eb9018b..db70d3e 100644
--- a/fs/ext4/inline.c
+++ b/fs/ext4/inline.c
@@ -927,6 +927,282 @@ int ext4_da_write_inline_data_end(struct inode *inode, loff_t pos,
 	return copied;
 }
 
+#ifdef INLINE_DIR_DEBUG
+void ext4_show_inline_dir(struct inode *dir, struct buffer_head *bh,
+			  void *inline_start, int inline_size)
+{
+	int offset;
+	unsigned short de_len;
+	struct ext4_dir_entry_2 *de = inline_start;
+	void *dlimit = inline_start + inline_size;
+
+	trace_printk("inode %lu\n", dir->i_ino);
+	offset = 0;
+	while ((void *)de < dlimit) {
+		de_len = ext4_rec_len_from_disk(de->rec_len, inline_size);
+		trace_printk("de: offset %u reclen %u name %*.s "
+			     "namelen %u ino %u\n",
+			     offset, de_len, de->name_len, de->name,
+			     de->name_len, le32_to_cpu(de->inode));
+		if (ext4_check_dir_entry(dir, NULL, de, bh,
+					 inline_start, inline_size, offset))
+			BUG();
+
+		offset += de_len;
+		de = (struct ext4_dir_entry_2 *) ((char *) de + de_len);
+	}
+}
+#else
+#define ext4_show_inline_dir(dir, bh, inline_start, inline_size)
+#endif
+
+/*
+ * Add a new entry into a inline dir.
+ * It will return -ENOSPC if no space is available, and -EIO
+ * and -EEXIST if directory entry already exists.
+ */
+static int ext4_add_dirent_to_inline(handle_t *handle,
+				     struct dentry *dentry,
+				     struct inode *inode,
+				     struct ext4_iloc *iloc,
+				     void *inline_start, int inline_size)
+{
+	struct inode	*dir = dentry->d_parent->d_inode;
+	const char	*name = dentry->d_name.name;
+	int		namelen = dentry->d_name.len;
+	unsigned short	reclen;
+	int		err;
+	struct ext4_dir_entry_2 *de;
+
+	reclen = EXT4_DIR_REC_LEN(namelen);
+	err = ext4_find_dest_de(dir, inode, iloc->bh,
+				inline_start, inline_size,
+				name, namelen, &de);
+	if (err)
+		return err;
+
+	err = ext4_journal_get_write_access(handle, iloc->bh);
+	if (err)
+		return err;
+	ext4_insert_dentry(inode, de, inline_size, name, namelen);
+
+	ext4_show_inline_dir(dir, iloc->bh, inline_start, inline_size);
+
+	/*
+	 * XXX shouldn't update any times until successful
+	 * completion of syscall, but too many callers depend
+	 * on this.
+	 *
+	 * XXX similarly, too many callers depend on
+	 * ext4_new_inode() setting the times, but error
+	 * recovery deletes the inode, so the worst that can
+	 * happen is that the times are slightly out of date
+	 * and/or different from the directory change time.
+	 */
+	dir->i_mtime = dir->i_ctime = ext4_current_time(dir);
+	ext4_update_dx_flag(dir);
+	dir->i_version++;
+	ext4_mark_inode_dirty(handle, dir);
+	return 1;
+}
+
+void* ext4_get_inline_xattr_pos(struct inode *inode, struct ext4_iloc *iloc)
+{
+	struct ext4_xattr_entry *entry;
+	struct ext4_xattr_ibody_header *header;
+
+	BUG_ON(!EXT4_I(inode)->i_inline_off);
+
+	header = IHDR(inode, ext4_raw_inode(iloc));
+	entry = (struct ext4_xattr_entry *)((void *)ext4_raw_inode(iloc) +
+					    EXT4_I(inode)->i_inline_off);
+
+	return (void *)IFIRST(header) + le16_to_cpu(entry->e_value_offs);
+}
+
+/* Set the final de to cover the whole block. */
+static void ext4_update_final_de(void *de_buf, int old_size, int new_size)
+{
+	struct ext4_dir_entry_2 *de, *prev_de;
+	void *limit;
+	int de_len;
+
+	de = (struct ext4_dir_entry_2 *)de_buf;
+	if (old_size) {
+		limit = de_buf + old_size;
+		do {
+			prev_de = de;
+			de_len = ext4_rec_len_from_disk(de->rec_len, old_size);
+			de_buf += de_len;
+			de = (struct ext4_dir_entry_2 *)de_buf;
+		} while (de_buf < limit);
+
+		prev_de->rec_len = ext4_rec_len_to_disk(de_len + new_size -
+							old_size, new_size);
+	} else {
+		/* this is just created, so create an empty entry. */
+		de->inode = 0;
+		de->rec_len = ext4_rec_len_to_disk(new_size, new_size);
+	}
+}
+
+static int ext4_update_inline_dir(handle_t *handle, struct dentry *dentry,
+				  struct inode *dir,
+				  struct ext4_iloc *iloc)
+{
+	int ret, reclen = EXT4_DIR_REC_LEN(dentry->d_name.len);
+	int old_size = EXT4_I(dir)->i_inline_size - EXT4_MIN_INLINE_DATA_SIZE;
+
+	ret = ext4_update_inline_data(handle, dir,
+				EXT4_I(dir)->i_inline_size + reclen);
+	if (ret)
+		return ret;
+
+	ext4_update_final_de(ext4_get_inline_xattr_pos(dir, iloc), old_size,
+			     EXT4_I(dir)->i_inline_size -
+						EXT4_MIN_INLINE_DATA_SIZE);
+	dir->i_size = EXT4_I(dir)->i_disksize = EXT4_I(dir)->i_inline_size;
+	return 0;
+}
+
+/*
+ * Try to add the new entry to the inline data.
+ * If succeeds, return 0. If not, extended the inline dir and copied data to
+ * the new created block.
+ */
+int ext4_try_add_inline_entry(handle_t *handle, struct dentry *dentry,
+			      struct inode *inode)
+{
+	int ret, inline_size;
+	void *inline_start, *backup_buf = NULL;
+	struct buffer_head *dir_block = NULL;
+	struct ext4_iloc iloc;
+	int blocksize = inode->i_sb->s_blocksize;
+	struct inode *dir = dentry->d_parent->d_inode;
+
+	ret = ext4_get_inode_loc(dir, &iloc);
+	if (ret)
+		return ret;
+
+	down_write(&EXT4_I(dir)->xattr_sem);
+	if (!ext4_has_inline_data(dir))
+		goto out;
+
+	inline_start = ext4_raw_inode(&iloc)->i_block;
+	inline_size = EXT4_MIN_INLINE_DATA_SIZE;
+
+	ret = ext4_add_dirent_to_inline(handle, dentry, inode, &iloc,
+					inline_start, inline_size);
+	if (ret != -ENOSPC)
+		goto out;
+
+	/* check whether it can be inserted to inline xattr space. */
+	inline_size = EXT4_I(dir)->i_inline_size -
+			EXT4_MIN_INLINE_DATA_SIZE;
+	if (inline_size > 0) {
+		inline_start = ext4_get_inline_xattr_pos(dir, &iloc);
+
+		ret = ext4_add_dirent_to_inline(handle, dentry, inode, &iloc,
+						inline_start, inline_size);
+		if (ret != -ENOSPC)
+			goto out;
+	}
+
+	/* Try to add more xattr space.*/
+	ret = ext4_update_inline_dir(handle, dentry, dir, &iloc);
+	if (ret && ret != -ENOSPC)
+		goto out;
+	else if (!ret) {
+		inline_size = EXT4_I(dir)->i_inline_size -
+				EXT4_MIN_INLINE_DATA_SIZE;
+		inline_start = ext4_get_inline_xattr_pos(dir, &iloc);
+
+		ret = ext4_add_dirent_to_inline(handle, dentry, inode, &iloc,
+						inline_start, inline_size);
+		if (ret != -ENOSPC)
+			goto out;
+	}
+
+	/*
+	 * The inline space is filled up, so create a new block for it.
+	 * As the extent tree will be created, we have to save the inline
+	 * dir first.
+	 */
+	inline_size = EXT4_I(dir)->i_inline_size;
+	backup_buf = kmalloc(inline_size, GFP_NOFS);
+	if (!backup_buf) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	memcpy(backup_buf, (void *)ext4_raw_inode(&iloc)->i_block,
+	       EXT4_MIN_INLINE_DATA_SIZE);
+	if (inline_size > EXT4_MIN_INLINE_DATA_SIZE)
+		memcpy(backup_buf + EXT4_MIN_INLINE_DATA_SIZE,
+		       ext4_get_inline_xattr_pos(dir, &iloc),
+		       inline_size - EXT4_MIN_INLINE_DATA_SIZE);
+
+	/* clear the entry and the flag in dir now. */
+	ret = ext4_destroy_inline_data_nolock(handle, dir);
+	if (ret)
+		goto out;
+
+	dir->i_size = EXT4_I(dir)->i_disksize = blocksize;
+	dir_block = ext4_bread(handle, dir, 0, 1, &ret);
+	if (!dir_block)
+		goto out;
+
+	BUFFER_TRACE(dir_block, "get_write_access");
+	ret = ext4_journal_get_write_access(handle, dir_block);
+	if (ret)
+		goto out;
+	memcpy(dir_block->b_data, backup_buf, inline_size);
+
+	/* Set the final de to cover the whole block. */
+	ext4_update_final_de(dir_block->b_data, inline_size,
+			     blocksize);
+
+	BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+	ret = ext4_handle_dirty_metadata(handle, dir, dir_block);
+
+out:
+	kfree(backup_buf);
+	brelse(dir_block);
+	if (!ret || ret == 1)
+		ext4_mark_inode_dirty(handle, dir);
+	up_write(&EXT4_I(dir)->xattr_sem);
+	brelse(iloc.bh);
+	return ret;
+}
+
+/*
+ * Try to create the inline data for the new dir.
+ * If it succeeds, return 0, otherwise return the error.
+ * In case of ENOSPC, the caller should create the normal disk layout dir.
+ */
+int ext4_try_create_inline_dir(handle_t *handle, struct inode *parent,
+			       struct inode *inode)
+{
+	int ret, inline_size = EXT4_MIN_INLINE_DATA_SIZE;
+	struct ext4_iloc iloc;
+	struct ext4_dir_entry_2 *de;
+
+	ret = ext4_get_inode_loc(inode, &iloc);
+	if (ret)
+		return ret;
+
+	ret = ext4_prepare_inline_data(handle, inode, inline_size);
+	if (ret)
+		goto out;
+
+	de = (struct ext4_dir_entry_2 *)ext4_raw_inode(&iloc)->i_block;
+	ext4_init_dot_dotdot(parent, inode, de, inline_size);
+	inode->i_size = EXT4_I(inode)->i_disksize = inline_size;
+out:
+	brelse(iloc.bh);
+	return ret;
+}
+
 int ext4_destroy_inline_data(handle_t *handle, struct inode *inode)
 {
 	int ret;
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 21acbc9..77312dc 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -1486,6 +1486,17 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
 	blocksize = sb->s_blocksize;
 	if (!dentry->d_name.len)
 		return -EINVAL;
+
+	if (ext4_has_inline_data(dir)) {
+		retval = ext4_try_add_inline_entry(handle, dentry, inode);
+		if (retval < 0)
+			return retval;
+		if (retval == 1) {
+			retval = 0;
+			return retval;
+		}
+	}
+
 	if (is_dx(dir)) {
 		retval = ext4_dx_add_entry(handle, dentry, inode);
 		if (!retval || (retval != ERR_BAD_DX_DIR))
@@ -1862,6 +1873,14 @@ static int ext4_init_new_dir(handle_t *handle, struct inode *parent,
 	int err;
 	int blocksize = inode->i_sb->s_blocksize;
 
+	if (ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA)) {
+		err = ext4_try_create_inline_dir(handle, parent, inode);
+		if (err < 0 && err != ENOSPC)
+			goto out;
+		if (!err)
+			goto out;
+	}
+
 	inode->i_size = EXT4_I(inode)->i_disksize = blocksize;
 	dir_block = ext4_bread(handle, inode, 0, 1, &err);
 	if (!dir_block)
diff --git a/fs/ext4/xattr.h b/fs/ext4/xattr.h
index a0d43c7..0229a78 100644
--- a/fs/ext4/xattr.h
+++ b/fs/ext4/xattr.h
@@ -161,6 +161,11 @@ extern int ext4_da_write_inline_data_begin(struct address_space *mapping,
 extern int ext4_da_write_inline_data_end(struct inode *inode, loff_t pos,
 					 unsigned len, unsigned copied,
 					 struct page *page);
+extern int ext4_try_add_inline_entry(handle_t *handle, struct dentry *dentry,
+				     struct inode *inode);
+extern int ext4_try_create_inline_dir(handle_t *handle,
+				      struct inode *parent,
+				      struct inode *inode);
 # else  /* CONFIG_EXT4_FS_XATTR */
 
 static inline int
@@ -324,6 +329,20 @@ static inline int ext4_da_write_inline_data_end(struct inode *inode, loff_t pos,
 {
 	return 0;
 }
+
+static inline int ext4_try_add_inline_entry(handle_t *handle,
+					    struct dentry *dentry,
+					    struct inode *inode)
+{
+	return 0;
+}
+
+static inline int ext4_try_create_inline_dir(handle_t *handle,
+					     struct inode *parent,
+					     struct inode *inode)
+{
+	return 0;
+}
 # endif  /* CONFIG_EXT4_FS_XATTR */
 
 #ifdef CONFIG_EXT4_FS_SECURITY
-- 
1.7.0.4

  parent reply	other threads:[~2011-12-18 14:24 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-12-18 14:20 [PATCH V3 00/21] ext4: Add inline data support Tao Ma
2011-12-18 14:24 ` [PATCH V3 01/21] ext4: Move extra inode read to a new function Tao Ma
2011-12-18 14:24   ` [PATCH V3 02/21] ext4: export inline xattr functions Tao Ma
2011-12-18 14:24   ` [PATCH V3 03/21] ext4: Add the basic function for inline data support Tao Ma
2011-12-18 14:24   ` [PATCH V3 04/21] ext4: Add read support for inline data Tao Ma
2011-12-18 14:24   ` [PATCH V3 05/21] ext4: Add normal write " Tao Ma
2011-12-18 14:24   ` [PATCH V3 06/21] ext4: Add journalled " Tao Ma
2011-12-18 14:24   ` [PATCH V3 07/21] ext4: Add delalloc " Tao Ma
2011-12-18 14:24   ` [PATCH V3 08/21] ext4: Create a new function ext4_init_new_dir Tao Ma
2011-12-18 14:24   ` [PATCH V3 09/21] ext4: Refactor __ext4_check_dir_entry to accepts start and size Tao Ma
2011-12-18 14:24   ` [PATCH V3 10/21] ext4: Create __ext4_insert_dentry for dir entry insertion Tao Ma
2011-12-18 14:24   ` Tao Ma [this message]
2011-12-18 14:24   ` [PATCH V3 12/21] ext4: Let ext4_readdir handle inline data Tao Ma
2011-12-18 14:24   ` [PATCH V3 13/21] ext4: Create a new function search_dir Tao Ma
2011-12-18 14:24   ` [PATCH V3 14/21] ext4: let ext4_find_entry handle inline data Tao Ma
2011-12-18 14:24   ` [PATCH V3 15/21] ext4: make ext4_delete_entry generic Tao Ma
2011-12-18 14:24   ` [PATCH V3 16/21] ext4: let ext4_delete_entry handle inline data Tao Ma
2011-12-18 14:24   ` [PATCH V3 17/21] ext4: let empty_dir handle inline dir Tao Ma
2011-12-18 14:24   ` [PATCH V3 18/21] ext4: let ext4_rename " Tao Ma
2011-12-18 14:24   ` [PATCH V3 19/21] ext4: Let fiemap work with inline data Tao Ma
2011-12-18 14:24   ` [PATCH V3 20/21] ext4: Evict inline data out if we needs to strore xattr in inode Tao Ma
2011-12-18 14:24   ` [PATCH V3 21/21] ext4: Enable ext4 inline support Tao Ma
2011-12-19  8:32   ` [PATCH V3 01/21] ext4: Move extra inode read to a new function Andreas Dilger
2011-12-19 14:27     ` Tao Ma

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1324218278-2460-11-git-send-email-tm@tao.ma \
    --to=tm@tao.ma \
    --cc=adilger@dilger.ca \
    --cc=linux-ext4@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=tytso@mit.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).