]> git.itanic.dy.fi Git - linux-stable/commitdiff
ext4: avoid ext4_error()'s caused by ENOMEM in the truncate path
authorTheodore Ts'o <tytso@mit.edu>
Thu, 7 May 2020 17:50:28 +0000 (10:50 -0700)
committerTheodore Ts'o <tytso@mit.edu>
Thu, 4 Jun 2020 03:16:51 +0000 (23:16 -0400)
We can't fail in the truncate path without requiring an fsck.
Add work around for this by using a combination of retry loops
and the __GFP_NOFAIL flag.

From: Theodore Ts'o <tytso@mit.edu>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Signed-off-by: Anna Pendleton <pendleton@google.com>
Reviewed-by: Harshad Shirwadkar <harshadshirwadkar@gmail.com>
Link: https://lore.kernel.org/r/20200507175028.15061-1-pendleton@google.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
fs/ext4/ext4.h
fs/ext4/extents.c

index 884ce3086486d86880d3762c707ce210f335379a..5d901bf92ce9d41586e0259706b68471ae4a2c00 100644 (file)
@@ -630,6 +630,7 @@ enum {
  */
 #define EXT4_EX_NOCACHE                                0x40000000
 #define EXT4_EX_FORCE_CACHE                    0x20000000
+#define EXT4_EX_NOFAIL                         0x10000000
 
 /*
  * Flags used by ext4_free_blocks
index 3ca797faa86b438b8611c616d29cce470e9a5004..ff7eeb5a77efbff06369bc73a3056895f8f7c14f 100644 (file)
@@ -297,11 +297,14 @@ ext4_force_split_extent_at(handle_t *handle, struct inode *inode,
 {
        struct ext4_ext_path *path = *ppath;
        int unwritten = ext4_ext_is_unwritten(path[path->p_depth].p_ext);
+       int flags = EXT4_EX_NOCACHE | EXT4_GET_BLOCKS_PRE_IO;
+
+       if (nofail)
+               flags |= EXT4_GET_BLOCKS_METADATA_NOFAIL | EXT4_EX_NOFAIL;
 
        return ext4_split_extent_at(handle, inode, ppath, lblk, unwritten ?
                        EXT4_EXT_MARK_UNWRIT1|EXT4_EXT_MARK_UNWRIT2 : 0,
-                       EXT4_EX_NOCACHE | EXT4_GET_BLOCKS_PRE_IO |
-                       (nofail ? EXT4_GET_BLOCKS_METADATA_NOFAIL:0));
+                       flags);
 }
 
 static int
@@ -487,8 +490,12 @@ __read_extent_tree_block(const char *function, unsigned int line,
 {
        struct buffer_head              *bh;
        int                             err;
+       gfp_t                           gfp_flags = __GFP_MOVABLE | GFP_NOFS;
+
+       if (flags & EXT4_EX_NOFAIL)
+               gfp_flags |= __GFP_NOFAIL;
 
-       bh = sb_getblk_gfp(inode->i_sb, pblk, __GFP_MOVABLE | GFP_NOFS);
+       bh = sb_getblk_gfp(inode->i_sb, pblk, gfp_flags);
        if (unlikely(!bh))
                return ERR_PTR(-ENOMEM);
 
@@ -837,6 +844,10 @@ ext4_find_extent(struct inode *inode, ext4_lblk_t block,
        struct ext4_ext_path *path = orig_path ? *orig_path : NULL;
        short int depth, i, ppos = 0;
        int ret;
+       gfp_t gfp_flags = GFP_NOFS;
+
+       if (flags & EXT4_EX_NOFAIL)
+               gfp_flags |= __GFP_NOFAIL;
 
        eh = ext_inode_hdr(inode);
        depth = ext_depth(inode);
@@ -857,7 +868,7 @@ ext4_find_extent(struct inode *inode, ext4_lblk_t block,
        if (!path) {
                /* account possible depth increase */
                path = kcalloc(depth + 2, sizeof(struct ext4_ext_path),
-                               GFP_NOFS);
+                               gfp_flags);
                if (unlikely(!path))
                        return ERR_PTR(-ENOMEM);
                path[0].p_maxdepth = depth + 1;
@@ -1007,9 +1018,13 @@ static int ext4_ext_split(handle_t *handle, struct inode *inode,
        ext4_fsblk_t newblock, oldblock;
        __le32 border;
        ext4_fsblk_t *ablocks = NULL; /* array of allocated blocks */
+       gfp_t gfp_flags = GFP_NOFS;
        int err = 0;
        size_t ext_size = 0;
 
+       if (flags & EXT4_EX_NOFAIL)
+               gfp_flags |= __GFP_NOFAIL;
+
        /* make decision: where to split? */
        /* FIXME: now decision is simplest: at current extent */
 
@@ -1043,7 +1058,7 @@ static int ext4_ext_split(handle_t *handle, struct inode *inode,
         * We need this to handle errors and free blocks
         * upon them.
         */
-       ablocks = kcalloc(depth, sizeof(ext4_fsblk_t), GFP_NOFS);
+       ablocks = kcalloc(depth, sizeof(ext4_fsblk_t), gfp_flags);
        if (!ablocks)
                return -ENOMEM;
 
@@ -2019,7 +2034,7 @@ int ext4_ext_insert_extent(handle_t *handle, struct inode *inode,
        if (next != EXT_MAX_BLOCKS) {
                ext_debug("next leaf block - %u\n", next);
                BUG_ON(npath != NULL);
-               npath = ext4_find_extent(inode, next, NULL, 0);
+               npath = ext4_find_extent(inode, next, NULL, gb_flags);
                if (IS_ERR(npath))
                        return PTR_ERR(npath);
                BUG_ON(npath->p_depth != path->p_depth);
@@ -2792,7 +2807,8 @@ int ext4_ext_remove_space(struct inode *inode, ext4_lblk_t start,
                ext4_fsblk_t pblk;
 
                /* find extent for or closest extent to this block */
-               path = ext4_find_extent(inode, end, NULL, EXT4_EX_NOCACHE);
+               path = ext4_find_extent(inode, end, NULL,
+                                       EXT4_EX_NOCACHE | EXT4_EX_NOFAIL);
                if (IS_ERR(path)) {
                        ext4_journal_stop(handle);
                        return PTR_ERR(path);
@@ -2878,7 +2894,7 @@ int ext4_ext_remove_space(struct inode *inode, ext4_lblk_t start,
                                le16_to_cpu(path[k].p_hdr->eh_entries)+1;
        } else {
                path = kcalloc(depth + 1, sizeof(struct ext4_ext_path),
-                              GFP_NOFS);
+                              GFP_NOFS | __GFP_NOFAIL);
                if (path == NULL) {
                        ext4_journal_stop(handle);
                        return -ENOMEM;
@@ -3303,7 +3319,7 @@ static int ext4_split_extent(handle_t *handle,
         * Update path is required because previous ext4_split_extent_at() may
         * result in split of original leaf or extent zeroout.
         */
-       path = ext4_find_extent(inode, map->m_lblk, ppath, 0);
+       path = ext4_find_extent(inode, map->m_lblk, ppath, flags);
        if (IS_ERR(path))
                return PTR_ERR(path);
        depth = ext_depth(inode);
@@ -4365,7 +4381,14 @@ int ext4_ext_truncate(handle_t *handle, struct inode *inode)
        }
        if (err)
                return err;
-       return ext4_ext_remove_space(inode, last_block, EXT_MAX_BLOCKS - 1);
+retry_remove_space:
+       err = ext4_ext_remove_space(inode, last_block, EXT_MAX_BLOCKS - 1);
+       if (err == -ENOMEM) {
+               cond_resched();
+               congestion_wait(BLK_RW_ASYNC, HZ/50);
+               goto retry_remove_space;
+       }
+       return err;
 }
 
 static int ext4_alloc_file_blocks(struct file *file, ext4_lblk_t offset,