diff options
Diffstat (limited to 'fs/ext4/inline.c')
| -rw-r--r-- | fs/ext4/inline.c | 377 | 
1 files changed, 377 insertions, 0 deletions
diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c index 65f7ffb5437..bf732281873 100644 --- a/fs/ext4/inline.c +++ b/fs/ext4/inline.c @@ -18,6 +18,7 @@  #define EXT4_XATTR_SYSTEM_DATA	"data"  #define EXT4_MIN_INLINE_DATA_SIZE	((sizeof(__le32) * EXT4_N_BLOCKS)) +#define EXT4_INLINE_DOTDOT_SIZE	4  int ext4_get_inline_size(struct inode *inode)  { @@ -949,6 +950,382 @@ 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: off %u rlen %u name %*.s nlen %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; +} + +static 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 inode *dir, +				  struct ext4_iloc *iloc) +{ +	int ret; +	int old_size = EXT4_I(dir)->i_inline_size - EXT4_MIN_INLINE_DATA_SIZE; +	int new_size = get_max_inline_xattr_value_size(dir, iloc); + +	if (new_size - old_size <= EXT4_DIR_REC_LEN(1)) +		return -ENOSPC; + +	ret = ext4_update_inline_data(handle, dir, +				      new_size + EXT4_MIN_INLINE_DATA_SIZE); +	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; +} + +static void ext4_restore_inline_data(handle_t *handle, struct inode *inode, +				     struct ext4_iloc *iloc, +				     void *buf, int inline_size) +{ +	ext4_create_inline_data(handle, inode, inline_size); +	ext4_write_inline_data(inode, iloc, buf, 0, inline_size); +	ext4_set_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA); +} + +static int ext4_finish_convert_inline_dir(handle_t *handle, +					  struct inode *inode, +					  struct buffer_head *dir_block, +					  void *buf, +					  int inline_size) +{ +	int err, csum_size = 0, header_size = 0; +	struct ext4_dir_entry_2 *de; +	struct ext4_dir_entry_tail *t; +	void *target = dir_block->b_data; + +	/* +	 * First create "." and ".." and then copy the dir information +	 * back to the block. +	 */ +	de = (struct ext4_dir_entry_2 *)target; +	de = ext4_init_dot_dotdot(inode, de, +		inode->i_sb->s_blocksize, csum_size, +		le32_to_cpu(((struct ext4_dir_entry_2 *)buf)->inode), 1); +	header_size = (void *)de - target; + +	memcpy((void *)de, buf + EXT4_INLINE_DOTDOT_SIZE, +		inline_size - EXT4_INLINE_DOTDOT_SIZE); + +	if (EXT4_HAS_RO_COMPAT_FEATURE(inode->i_sb, +				       EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) +		csum_size = sizeof(struct ext4_dir_entry_tail); + +	inode->i_size = inode->i_sb->s_blocksize; +	i_size_write(inode, inode->i_sb->s_blocksize); +	EXT4_I(inode)->i_disksize = inode->i_sb->s_blocksize; +	ext4_update_final_de(dir_block->b_data, +			inline_size - EXT4_INLINE_DOTDOT_SIZE + header_size, +			inode->i_sb->s_blocksize - csum_size); + +	if (csum_size) { +		t = EXT4_DIRENT_TAIL(dir_block->b_data, +				     inode->i_sb->s_blocksize); +		initialize_dirent_tail(t, inode->i_sb->s_blocksize); +	} +	set_buffer_uptodate(dir_block); +	err = ext4_handle_dirty_dirent_node(handle, inode, dir_block); +	if (err) +		goto out; +	set_buffer_verified(dir_block); +out: +	return err; +} + +static int ext4_convert_inline_data_nolock(handle_t *handle, +					   struct inode *inode, +					   struct ext4_iloc *iloc) +{ +	int error; +	void *buf = NULL; +	struct buffer_head *data_bh = NULL; +	struct ext4_map_blocks map; +	int inline_size; + +	inline_size = ext4_get_inline_size(inode); +	buf = kmalloc(inline_size, GFP_NOFS); +	if (!buf) { +		error = -ENOMEM; +		goto out; +	} + +	error = ext4_read_inline_data(inode, buf, inline_size, iloc); +	if (error < 0) +		goto out; + +	error = ext4_destroy_inline_data_nolock(handle, inode); +	if (error) +		goto out; + +	map.m_lblk = 0; +	map.m_len = 1; +	map.m_flags = 0; +	error = ext4_map_blocks(handle, inode, &map, EXT4_GET_BLOCKS_CREATE); +	if (error < 0) +		goto out_restore; +	if (!(map.m_flags & EXT4_MAP_MAPPED)) { +		error = -EIO; +		goto out_restore; +	} + +	data_bh = sb_getblk(inode->i_sb, map.m_pblk); +	if (!data_bh) { +		error = -EIO; +		goto out_restore; +	} + +	lock_buffer(data_bh); +	error = ext4_journal_get_create_access(handle, data_bh); +	if (error) { +		unlock_buffer(data_bh); +		error = -EIO; +		goto out_restore; +	} +	memset(data_bh->b_data, 0, inode->i_sb->s_blocksize); + +	if (!S_ISDIR(inode->i_mode)) { +		memcpy(data_bh->b_data, buf, inline_size); +		set_buffer_uptodate(data_bh); +		error = ext4_handle_dirty_metadata(handle, +						   inode, data_bh); +	} else { +		error = ext4_finish_convert_inline_dir(handle, inode, data_bh, +						       buf, inline_size); +	} + +	unlock_buffer(data_bh); +out_restore: +	if (error) +		ext4_restore_inline_data(handle, inode, iloc, buf, inline_size); + +out: +	brelse(data_bh); +	kfree(buf); +	return error; +} + +/* + * 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; +	struct ext4_iloc iloc; +	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 = (void *)ext4_raw_inode(&iloc)->i_block + +						 EXT4_INLINE_DOTDOT_SIZE; +	inline_size = EXT4_MIN_INLINE_DATA_SIZE - EXT4_INLINE_DOTDOT_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) { +		/* Try to use the xattr space.*/ +		ret = ext4_update_inline_dir(handle, dir, &iloc); +		if (ret && ret != -ENOSPC) +			goto out; + +		inline_size = EXT4_I(dir)->i_inline_size - +				EXT4_MIN_INLINE_DATA_SIZE; +	} + +	if (inline_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. +	 */ +	ret = ext4_convert_inline_data_nolock(handle, dir, &iloc); + +out: +	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; + +	/* +	 * For inline dir, we only save the inode information for the ".." +	 * and create a fake dentry to cover the left space. +	 */ +	de = (struct ext4_dir_entry_2 *)ext4_raw_inode(&iloc)->i_block; +	de->inode = cpu_to_le32(parent->i_ino); +	de = (struct ext4_dir_entry_2 *)((void *)de + EXT4_INLINE_DOTDOT_SIZE); +	de->inode = 0; +	de->rec_len = ext4_rec_len_to_disk( +				inline_size - EXT4_INLINE_DOTDOT_SIZE, +				inline_size); +	set_nlink(inode, 2); +	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;  |