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

Commit 12e9b892 authored by Dmitry Monakhov's avatar Dmitry Monakhov Committed by Theodore Ts'o
Browse files

ext4: Use bitops to read/modify i_flags in struct ext4_inode_info

At several places we modify EXT4_I(inode)->i_flags without holding
i_mutex (ext4_do_update_inode, ...). These modifications are racy and
we can lose updates to i_flags. So convert handling of i_flags to use
bitops which are atomic.

https://bugzilla.kernel.org/show_bug.cgi?id=15792



Signed-off-by: default avatarDmitry Monakhov <dmonakhov@openvz.org>
Signed-off-by: default avatar"Theodore Ts'o" <tytso@mit.edu>
parent 24676da4
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -110,7 +110,7 @@ static int ext4_readdir(struct file *filp,

	if (EXT4_HAS_COMPAT_FEATURE(inode->i_sb,
				    EXT4_FEATURE_COMPAT_DIR_INDEX) &&
	    ((EXT4_I(inode)->i_flags & EXT4_INDEX_FL) ||
	    ((ext4_test_inode_flag(inode, EXT4_INODE_INDEX)) ||
	     ((inode->i_size >> sb->s_blocksize_bits) == 1))) {
		err = ext4_dx_readdir(filp, dirent, filldir);
		if (err != ERR_BAD_DX_DIR) {
@@ -121,7 +121,7 @@ static int ext4_readdir(struct file *filp,
		 * We don't set the inode dirty flag since it's not
		 * critical that it get flushed back to the disk.
		 */
		EXT4_I(filp->f_path.dentry->d_inode)->i_flags &= ~EXT4_INDEX_FL;
		ext4_clear_inode_flag(filp->f_path.dentry->d_inode, EXT4_INODE_INDEX);
	}
	stored = 0;
	offset = filp->f_pos & (sb->s_blocksize - 1);
+94 −15
Original line number Diff line number Diff line
@@ -344,6 +344,83 @@ static inline __u32 ext4_mask_flags(umode_t mode, __u32 flags)
		return flags & EXT4_OTHER_FLMASK;
}

/*
 * Inode flags used for atomic set/get
 */
enum {
	EXT4_INODE_SECRM	= 0,	/* Secure deletion */
	EXT4_INODE_UNRM		= 1,	/* Undelete */
	EXT4_INODE_COMPR	= 2,	/* Compress file */
	EXT4_INODE_SYNC		= 3,	/* Synchronous updates */
	EXT4_INODE_IMMUTABLE	= 4,	/* Immutable file */
	EXT4_INODE_APPEND	= 5,	/* writes to file may only append */
	EXT4_INODE_NODUMP	= 6,	/* do not dump file */
	EXT4_INODE_NOATIME	= 7,	/* do not update atime */
/* Reserved for compression usage... */
	EXT4_INODE_DIRTY	= 8,
	EXT4_INODE_COMPRBLK	= 9,	/* One or more compressed clusters */
	EXT4_INODE_NOCOMPR	= 10,	/* Don't compress */
	EXT4_INODE_ECOMPR	= 11,	/* Compression error */
/* End compression flags --- maybe not all used */
	EXT4_INODE_INDEX	= 12,	/* hash-indexed directory */
	EXT4_INODE_IMAGIC	= 13,	/* AFS directory */
	EXT4_INODE_JOURNAL_DATA	= 14,	/* file data should be journaled */
	EXT4_INODE_NOTAIL	= 15,	/* file tail should not be merged */
	EXT4_INODE_DIRSYNC	= 16,	/* dirsync behaviour (directories only) */
	EXT4_INODE_TOPDIR	= 17,	/* Top of directory hierarchies*/
	EXT4_INODE_HUGE_FILE	= 18,	/* Set to each huge file */
	EXT4_INODE_EXTENTS	= 19,	/* Inode uses extents */
	EXT4_INODE_EA_INODE	= 21,	/* Inode used for large EA */
	EXT4_INODE_EOFBLOCKS	= 22,	/* Blocks allocated beyond EOF */
	EXT4_INODE_RESERVED	= 31,	/* reserved for ext4 lib */
};

#define TEST_FLAG_VALUE(FLAG) (EXT4_##FLAG##_FL == (1 << EXT4_INODE_##FLAG))
#define CHECK_FLAG_VALUE(FLAG) if (!TEST_FLAG_VALUE(FLAG)) { \
	printk(KERN_EMERG "EXT4 flag fail: " #FLAG ": %d %d\n", \
		EXT4_##FLAG##_FL, EXT4_INODE_##FLAG); BUG_ON(1); }

/*
 * Since it's pretty easy to mix up bit numbers and hex values, and we
 * can't do a compile-time test for ENUM values, we use a run-time
 * test to make sure that EXT4_XXX_FL is consistent with respect to
 * EXT4_INODE_XXX.  If all is well the printk and BUG_ON will all drop
 * out so it won't cost any extra space in the compiled kernel image.
 * But it's important that these values are the same, since we are
 * using EXT4_INODE_XXX to test for the flag values, but EXT4_XX_FL
 * must be consistent with the values of FS_XXX_FL defined in
 * include/linux/fs.h and the on-disk values found in ext2, ext3, and
 * ext4 filesystems, and of course the values defined in e2fsprogs.
 *
 * It's not paranoia if the Murphy's Law really *is* out to get you.  :-)
 */
static inline void ext4_check_flag_values(void)
{
	CHECK_FLAG_VALUE(SECRM);
	CHECK_FLAG_VALUE(UNRM);
	CHECK_FLAG_VALUE(COMPR);
	CHECK_FLAG_VALUE(SYNC);
	CHECK_FLAG_VALUE(IMMUTABLE);
	CHECK_FLAG_VALUE(APPEND);
	CHECK_FLAG_VALUE(NODUMP);
	CHECK_FLAG_VALUE(NOATIME);
	CHECK_FLAG_VALUE(DIRTY);
	CHECK_FLAG_VALUE(COMPRBLK);
	CHECK_FLAG_VALUE(NOCOMPR);
	CHECK_FLAG_VALUE(ECOMPR);
	CHECK_FLAG_VALUE(INDEX);
	CHECK_FLAG_VALUE(IMAGIC);
	CHECK_FLAG_VALUE(JOURNAL_DATA);
	CHECK_FLAG_VALUE(NOTAIL);
	CHECK_FLAG_VALUE(DIRSYNC);
	CHECK_FLAG_VALUE(TOPDIR);
	CHECK_FLAG_VALUE(HUGE_FILE);
	CHECK_FLAG_VALUE(EXTENTS);
	CHECK_FLAG_VALUE(EA_INODE);
	CHECK_FLAG_VALUE(EOFBLOCKS);
	CHECK_FLAG_VALUE(RESERVED);
}

/* Used to pass group descriptor data when online resize is done */
struct ext4_new_group_input {
	__u32 group;		/* Group number for this data */
@@ -639,9 +716,8 @@ struct ext4_ext_cache {
 */
struct ext4_inode_info {
	__le32	i_data[15];	/* unconverted */
	__u32	i_flags;
	ext4_fsblk_t	i_file_acl;
	__u32	i_dtime;
	ext4_fsblk_t	i_file_acl;

	/*
	 * i_block_group is the number of the block group which contains
@@ -652,6 +728,7 @@ struct ext4_inode_info {
	 */
	ext4_group_t	i_block_group;
	unsigned long	i_state_flags;		/* Dynamic state flags */
	unsigned long	i_flags;

	ext4_lblk_t		i_dir_start_lookup;
#ifdef CONFIG_EXT4_FS_XATTR
@@ -1087,20 +1164,22 @@ enum {
	EXT4_STATE_DIO_UNWRITTEN,	/* need convert on dio done*/
};

static inline int ext4_test_inode_state(struct inode *inode, int bit)
{
	return test_bit(bit, &EXT4_I(inode)->i_state_flags);
}

static inline void ext4_set_inode_state(struct inode *inode, int bit)
{
	set_bit(bit, &EXT4_I(inode)->i_state_flags);
#define EXT4_INODE_BIT_FNS(name, field)					\
static inline int ext4_test_inode_##name(struct inode *inode, int bit)	\
{									\
	return test_bit(bit, &EXT4_I(inode)->i_##field);		\
}									\
static inline void ext4_set_inode_##name(struct inode *inode, int bit)	\
{									\
	set_bit(bit, &EXT4_I(inode)->i_##field);			\
}									\
static inline void ext4_clear_inode_##name(struct inode *inode, int bit) \
{									\
	clear_bit(bit, &EXT4_I(inode)->i_##field);			\
}

static inline void ext4_clear_inode_state(struct inode *inode, int bit)
{
	clear_bit(bit, &EXT4_I(inode)->i_state_flags);
}
EXT4_INODE_BIT_FNS(flag, flags)
EXT4_INODE_BIT_FNS(state, state_flags)
#else
/* Assume that user mode programs are passing in an ext4fs superblock, not
 * a kernel struct super_block.  This will allow us to call the feature-test
@@ -1287,7 +1366,7 @@ struct ext4_dir_entry_2 {

#define is_dx(dir) (EXT4_HAS_COMPAT_FEATURE(dir->i_sb, \
				      EXT4_FEATURE_COMPAT_DIR_INDEX) && \
		      (EXT4_I(dir)->i_flags & EXT4_INDEX_FL))
		    ext4_test_inode_flag((dir), EXT4_INODE_INDEX))
#define EXT4_DIR_LINK_MAX(dir) (!is_dx(dir) && (dir)->i_nlink >= EXT4_LINK_MAX)
#define EXT4_DIR_LINK_EMPTY(dir) ((dir)->i_nlink == 2 || (dir)->i_nlink == 1)

+4 −4
Original line number Diff line number Diff line
@@ -273,7 +273,7 @@ static inline int ext4_should_journal_data(struct inode *inode)
		return 1;
	if (test_opt(inode->i_sb, DATA_FLAGS) == EXT4_MOUNT_JOURNAL_DATA)
		return 1;
	if (EXT4_I(inode)->i_flags & EXT4_JOURNAL_DATA_FL)
	if (ext4_test_inode_flag(inode, EXT4_INODE_JOURNAL_DATA))
		return 1;
	return 0;
}
@@ -284,7 +284,7 @@ static inline int ext4_should_order_data(struct inode *inode)
		return 0;
	if (!S_ISREG(inode->i_mode))
		return 0;
	if (EXT4_I(inode)->i_flags & EXT4_JOURNAL_DATA_FL)
	if (ext4_test_inode_flag(inode, EXT4_INODE_JOURNAL_DATA))
		return 0;
	if (test_opt(inode->i_sb, DATA_FLAGS) == EXT4_MOUNT_ORDERED_DATA)
		return 1;
@@ -297,7 +297,7 @@ static inline int ext4_should_writeback_data(struct inode *inode)
		return 0;
	if (EXT4_JOURNAL(inode) == NULL)
		return 1;
	if (EXT4_I(inode)->i_flags & EXT4_JOURNAL_DATA_FL)
	if (ext4_test_inode_flag(inode, EXT4_INODE_JOURNAL_DATA))
		return 0;
	if (test_opt(inode->i_sb, DATA_FLAGS) == EXT4_MOUNT_WRITEBACK_DATA)
		return 1;
@@ -321,7 +321,7 @@ static inline int ext4_should_dioread_nolock(struct inode *inode)
		return 0;
	if (!S_ISREG(inode->i_mode))
		return 0;
	if (!(EXT4_I(inode)->i_flags & EXT4_EXTENTS_FL))
	if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)))
		return 0;
	if (ext4_should_journal_data(inode))
		return 0;
+5 −5
Original line number Diff line number Diff line
@@ -3498,7 +3498,7 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
			map->m_flags |= EXT4_MAP_UNINIT;
	}

	if (unlikely(EXT4_I(inode)->i_flags & EXT4_EOFBLOCKS_FL)) {
	if (unlikely(ext4_test_inode_flag(inode, EXT4_INODE_EOFBLOCKS))) {
		if (unlikely(!eh->eh_entries)) {
			EXT4_ERROR_INODE(inode,
					 "eh->eh_entries == 0 ee_block %d",
@@ -3509,7 +3509,7 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
		last_ex = EXT_LAST_EXTENT(eh);
		if (map->m_lblk + ar.len > le32_to_cpu(last_ex->ee_block)
		    + ext4_ext_get_actual_len(last_ex))
			EXT4_I(inode)->i_flags &= ~EXT4_EOFBLOCKS_FL;
			ext4_clear_inode_flag(inode, EXT4_INODE_EOFBLOCKS);
	}
	err = ext4_ext_insert_extent(handle, inode, path, &newex, flags);
	if (err) {
@@ -3650,7 +3650,7 @@ static void ext4_falloc_update_inode(struct inode *inode,
		 * can proceed even if the new size is the same as i_size.
		 */
		if (new_size > i_size_read(inode))
			EXT4_I(inode)->i_flags |= EXT4_EOFBLOCKS_FL;
			ext4_set_inode_flag(inode, EXT4_INODE_EOFBLOCKS);
	}

}
@@ -3677,7 +3677,7 @@ long ext4_fallocate(struct inode *inode, int mode, loff_t offset, loff_t len)
	 * currently supporting (pre)allocate mode for extent-based
	 * files _only_
	 */
	if (!(EXT4_I(inode)->i_flags & EXT4_EXTENTS_FL))
	if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)))
		return -EOPNOTSUPP;

	/* preallocation to directories is currently not supported */
@@ -3922,7 +3922,7 @@ int ext4_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
	int error = 0;

	/* fallback to generic here if not in extents fmt */
	if (!(EXT4_I(inode)->i_flags & EXT4_EXTENTS_FL))
	if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)))
		return generic_block_fiemap(inode, fieinfo, start, len,
			ext4_get_block);

+1 −1
Original line number Diff line number Diff line
@@ -66,7 +66,7 @@ ext4_file_write(struct kiocb *iocb, const struct iovec *iov,
	 * is smaller than s_maxbytes, which is for extent-mapped files.
	 */

	if (!(EXT4_I(inode)->i_flags & EXT4_EXTENTS_FL)) {
	if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) {
		struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
		size_t length = iov_length(iov, nr_segs);

Loading