]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - fs/ext4/inline.c
ext4: fix deadlock between inline_data and ext4_expand_extra_isize_ea()
[karo-tx-linux.git] / fs / ext4 / inline.c
index f74d5ee2cdec095c5406677014c6a7141d17cdb2..99a5312ced5205e05d35b72fc27a23bab546cc94 100644 (file)
@@ -299,6 +299,11 @@ static int ext4_create_inline_data(handle_t *handle,
        EXT4_I(inode)->i_inline_size = len + EXT4_MIN_INLINE_DATA_SIZE;
        ext4_clear_inode_flag(inode, EXT4_INODE_EXTENTS);
        ext4_set_inode_flag(inode, EXT4_INODE_INLINE_DATA);
+       /*
+        * Propagate changes to inode->i_flags as well - e.g. S_DAX may
+        * get cleared
+        */
+       ext4_set_inode_flags(inode);
        get_bh(is.iloc.bh);
        error = ext4_mark_iloc_dirty(handle, inode, &is.iloc);
 
@@ -336,8 +341,10 @@ static int ext4_update_inline_data(handle_t *handle, struct inode *inode,
 
        len -= EXT4_MIN_INLINE_DATA_SIZE;
        value = kzalloc(len, GFP_NOFS);
-       if (!value)
+       if (!value) {
+               error = -ENOMEM;
                goto out;
+       }
 
        error = ext4_xattr_ibody_get(inode, i.name_index, i.name,
                                     value, len);
@@ -374,7 +381,7 @@ out:
 static int ext4_prepare_inline_data(handle_t *handle, struct inode *inode,
                                    unsigned int len)
 {
-       int ret, size;
+       int ret, size, no_expand;
        struct ext4_inode_info *ei = EXT4_I(inode);
 
        if (!ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA))
@@ -384,15 +391,14 @@ static int ext4_prepare_inline_data(handle_t *handle, struct inode *inode,
        if (size < len)
                return -ENOSPC;
 
-       down_write(&EXT4_I(inode)->xattr_sem);
+       ext4_write_lock_xattr(inode, &no_expand);
 
        if (ei->i_inline_off)
                ret = ext4_update_inline_data(handle, inode, len);
        else
                ret = ext4_create_inline_data(handle, inode, len);
 
-       up_write(&EXT4_I(inode)->xattr_sem);
-
+       ext4_write_unlock_xattr(inode, &no_expand);
        return ret;
 }
 
@@ -442,6 +448,11 @@ static int ext4_destroy_inline_data_nolock(handle_t *handle,
                }
        }
        ext4_clear_inode_flag(inode, EXT4_INODE_INLINE_DATA);
+       /*
+        * Propagate changes to inode->i_flags as well - e.g. S_DAX may
+        * get set.
+        */
+       ext4_set_inode_flags(inode);
 
        get_bh(is.iloc.bh);
        error = ext4_mark_iloc_dirty(handle, inode, &is.iloc);
@@ -521,7 +532,7 @@ static int ext4_convert_inline_data_to_extent(struct address_space *mapping,
                                              struct inode *inode,
                                              unsigned flags)
 {
-       int ret, needed_blocks;
+       int ret, needed_blocks, no_expand;
        handle_t *handle = NULL;
        int retries = 0, sem_held = 0;
        struct page *page = NULL;
@@ -561,7 +572,7 @@ retry:
                goto out;
        }
 
-       down_write(&EXT4_I(inode)->xattr_sem);
+       ext4_write_lock_xattr(inode, &no_expand);
        sem_held = 1;
        /* If some one has already done this for us, just exit. */
        if (!ext4_has_inline_data(inode)) {
@@ -598,7 +609,7 @@ retry:
                put_page(page);
                page = NULL;
                ext4_orphan_add(handle, inode);
-               up_write(&EXT4_I(inode)->xattr_sem);
+               ext4_write_unlock_xattr(inode, &no_expand);
                sem_held = 0;
                ext4_journal_stop(handle);
                handle = NULL;
@@ -624,7 +635,7 @@ out:
                put_page(page);
        }
        if (sem_held)
-               up_write(&EXT4_I(inode)->xattr_sem);
+               ext4_write_unlock_xattr(inode, &no_expand);
        if (handle)
                ext4_journal_stop(handle);
        brelse(iloc.bh);
@@ -717,7 +728,7 @@ convert:
 int ext4_write_inline_data_end(struct inode *inode, loff_t pos, unsigned len,
                               unsigned copied, struct page *page)
 {
-       int ret;
+       int ret, no_expand;
        void *kaddr;
        struct ext4_iloc iloc;
 
@@ -735,7 +746,7 @@ int ext4_write_inline_data_end(struct inode *inode, loff_t pos, unsigned len,
                goto out;
        }
 
-       down_write(&EXT4_I(inode)->xattr_sem);
+       ext4_write_lock_xattr(inode, &no_expand);
        BUG_ON(!ext4_has_inline_data(inode));
 
        kaddr = kmap_atomic(page);
@@ -745,7 +756,7 @@ int ext4_write_inline_data_end(struct inode *inode, loff_t pos, unsigned len,
        /* clear page dirty so that writepages wouldn't work for us. */
        ClearPageDirty(page);
 
-       up_write(&EXT4_I(inode)->xattr_sem);
+       ext4_write_unlock_xattr(inode, &no_expand);
        brelse(iloc.bh);
 out:
        return copied;
@@ -756,7 +767,7 @@ ext4_journalled_write_inline_data(struct inode *inode,
                                  unsigned len,
                                  struct page *page)
 {
-       int ret;
+       int ret, no_expand;
        void *kaddr;
        struct ext4_iloc iloc;
 
@@ -766,11 +777,11 @@ ext4_journalled_write_inline_data(struct inode *inode,
                return NULL;
        }
 
-       down_write(&EXT4_I(inode)->xattr_sem);
+       ext4_write_lock_xattr(inode, &no_expand);
        kaddr = kmap_atomic(page);
        ext4_write_inline_data(inode, &iloc, kaddr, 0, len);
        kunmap_atomic(kaddr);
-       up_write(&EXT4_I(inode)->xattr_sem);
+       ext4_write_unlock_xattr(inode, &no_expand);
 
        return iloc.bh;
 }
@@ -1028,7 +1039,7 @@ static int ext4_add_dirent_to_inline(handle_t *handle,
         * 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);
+       dir->i_mtime = dir->i_ctime = current_time(dir);
        ext4_update_dx_flag(dir);
        dir->i_version++;
        ext4_mark_inode_dirty(handle, dir);
@@ -1247,7 +1258,7 @@ out:
 int ext4_try_add_inline_entry(handle_t *handle, struct ext4_filename *fname,
                              struct inode *dir, struct inode *inode)
 {
-       int ret, inline_size;
+       int ret, inline_size, no_expand;
        void *inline_start;
        struct ext4_iloc iloc;
 
@@ -1255,7 +1266,7 @@ int ext4_try_add_inline_entry(handle_t *handle, struct ext4_filename *fname,
        if (ret)
                return ret;
 
-       down_write(&EXT4_I(dir)->xattr_sem);
+       ext4_write_lock_xattr(dir, &no_expand);
        if (!ext4_has_inline_data(dir))
                goto out;
 
@@ -1301,7 +1312,7 @@ int ext4_try_add_inline_entry(handle_t *handle, struct ext4_filename *fname,
 
 out:
        ext4_mark_inode_dirty(handle, dir);
-       up_write(&EXT4_I(dir)->xattr_sem);
+       ext4_write_unlock_xattr(dir, &no_expand);
        brelse(iloc.bh);
        return ret;
 }
@@ -1661,7 +1672,7 @@ int ext4_delete_inline_entry(handle_t *handle,
                             struct buffer_head *bh,
                             int *has_inline_data)
 {
-       int err, inline_size;
+       int err, inline_size, no_expand;
        struct ext4_iloc iloc;
        void *inline_start;
 
@@ -1669,7 +1680,7 @@ int ext4_delete_inline_entry(handle_t *handle,
        if (err)
                return err;
 
-       down_write(&EXT4_I(dir)->xattr_sem);
+       ext4_write_lock_xattr(dir, &no_expand);
        if (!ext4_has_inline_data(dir)) {
                *has_inline_data = 0;
                goto out;
@@ -1703,7 +1714,7 @@ int ext4_delete_inline_entry(handle_t *handle,
 
        ext4_show_inline_dir(dir, iloc.bh, inline_start, inline_size);
 out:
-       up_write(&EXT4_I(dir)->xattr_sem);
+       ext4_write_unlock_xattr(dir, &no_expand);
        brelse(iloc.bh);
        if (err != -ENOENT)
                ext4_std_error(dir->i_sb, err);
@@ -1802,11 +1813,11 @@ out:
 
 int ext4_destroy_inline_data(handle_t *handle, struct inode *inode)
 {
-       int ret;
+       int ret, no_expand;
 
-       down_write(&EXT4_I(inode)->xattr_sem);
+       ext4_write_lock_xattr(inode, &no_expand);
        ret = ext4_destroy_inline_data_nolock(handle, inode);
-       up_write(&EXT4_I(inode)->xattr_sem);
+       ext4_write_unlock_xattr(inode, &no_expand);
 
        return ret;
 }
@@ -1891,7 +1902,7 @@ out:
 void ext4_inline_data_truncate(struct inode *inode, int *has_inline)
 {
        handle_t *handle;
-       int inline_size, value_len, needed_blocks;
+       int inline_size, value_len, needed_blocks, no_expand;
        size_t i_size;
        void *value = NULL;
        struct ext4_xattr_ibody_find is = {
@@ -1908,7 +1919,7 @@ void ext4_inline_data_truncate(struct inode *inode, int *has_inline)
        if (IS_ERR(handle))
                return;
 
-       down_write(&EXT4_I(inode)->xattr_sem);
+       ext4_write_lock_xattr(inode, &no_expand);
        if (!ext4_has_inline_data(inode)) {
                *has_inline = 0;
                ext4_journal_stop(handle);
@@ -1966,12 +1977,12 @@ out_error:
        up_write(&EXT4_I(inode)->i_data_sem);
 out:
        brelse(is.iloc.bh);
-       up_write(&EXT4_I(inode)->xattr_sem);
+       ext4_write_unlock_xattr(inode, &no_expand);
        kfree(value);
        if (inode->i_nlink)
                ext4_orphan_del(handle, inode);
 
-       inode->i_mtime = inode->i_ctime = ext4_current_time(inode);
+       inode->i_mtime = inode->i_ctime = current_time(inode);
        ext4_mark_inode_dirty(handle, inode);
        if (IS_SYNC(inode))
                ext4_handle_sync(handle);
@@ -1982,7 +1993,7 @@ out:
 
 int ext4_convert_inline_data(struct inode *inode)
 {
-       int error, needed_blocks;
+       int error, needed_blocks, no_expand;
        handle_t *handle;
        struct ext4_iloc iloc;
 
@@ -2004,15 +2015,10 @@ int ext4_convert_inline_data(struct inode *inode)
                goto out_free;
        }
 
-       down_write(&EXT4_I(inode)->xattr_sem);
-       if (!ext4_has_inline_data(inode)) {
-               up_write(&EXT4_I(inode)->xattr_sem);
-               goto out;
-       }
-
-       error = ext4_convert_inline_data_nolock(handle, inode, &iloc);
-       up_write(&EXT4_I(inode)->xattr_sem);
-out:
+       ext4_write_lock_xattr(inode, &no_expand);
+       if (ext4_has_inline_data(inode))
+               error = ext4_convert_inline_data_nolock(handle, inode, &iloc);
+       ext4_write_unlock_xattr(inode, &no_expand);
        ext4_journal_stop(handle);
 out_free:
        brelse(iloc.bh);