Commit 73c384c0 authored by Theodore Ts'o's avatar Theodore Ts'o

ext4: avoid ext4_error()'s caused by ENOMEM in the truncate path

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: default avatarTheodore Ts'o <tytso@mit.edu>
Signed-off-by: default avatarAnna Pendleton <pendleton@google.com>
Reviewed-by: default avatarHarshad Shirwadkar <harshadshirwadkar@gmail.com>
Link: https://lore.kernel.org/r/20200507175028.15061-1-pendleton@google.comSigned-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
parent 08adf452
...@@ -630,6 +630,7 @@ enum { ...@@ -630,6 +630,7 @@ enum {
*/ */
#define EXT4_EX_NOCACHE 0x40000000 #define EXT4_EX_NOCACHE 0x40000000
#define EXT4_EX_FORCE_CACHE 0x20000000 #define EXT4_EX_FORCE_CACHE 0x20000000
#define EXT4_EX_NOFAIL 0x10000000
/* /*
* Flags used by ext4_free_blocks * Flags used by ext4_free_blocks
......
...@@ -297,11 +297,14 @@ ext4_force_split_extent_at(handle_t *handle, struct inode *inode, ...@@ -297,11 +297,14 @@ ext4_force_split_extent_at(handle_t *handle, struct inode *inode,
{ {
struct ext4_ext_path *path = *ppath; struct ext4_ext_path *path = *ppath;
int unwritten = ext4_ext_is_unwritten(path[path->p_depth].p_ext); 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 ? return ext4_split_extent_at(handle, inode, ppath, lblk, unwritten ?
EXT4_EXT_MARK_UNWRIT1|EXT4_EXT_MARK_UNWRIT2 : 0, EXT4_EXT_MARK_UNWRIT1|EXT4_EXT_MARK_UNWRIT2 : 0,
EXT4_EX_NOCACHE | EXT4_GET_BLOCKS_PRE_IO | flags);
(nofail ? EXT4_GET_BLOCKS_METADATA_NOFAIL:0));
} }
static int static int
...@@ -487,8 +490,12 @@ __read_extent_tree_block(const char *function, unsigned int line, ...@@ -487,8 +490,12 @@ __read_extent_tree_block(const char *function, unsigned int line,
{ {
struct buffer_head *bh; struct buffer_head *bh;
int err; int err;
gfp_t gfp_flags = __GFP_MOVABLE | GFP_NOFS;
bh = sb_getblk_gfp(inode->i_sb, pblk, __GFP_MOVABLE | GFP_NOFS); if (flags & EXT4_EX_NOFAIL)
gfp_flags |= __GFP_NOFAIL;
bh = sb_getblk_gfp(inode->i_sb, pblk, gfp_flags);
if (unlikely(!bh)) if (unlikely(!bh))
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
...@@ -837,6 +844,10 @@ ext4_find_extent(struct inode *inode, ext4_lblk_t block, ...@@ -837,6 +844,10 @@ ext4_find_extent(struct inode *inode, ext4_lblk_t block,
struct ext4_ext_path *path = orig_path ? *orig_path : NULL; struct ext4_ext_path *path = orig_path ? *orig_path : NULL;
short int depth, i, ppos = 0; short int depth, i, ppos = 0;
int ret; int ret;
gfp_t gfp_flags = GFP_NOFS;
if (flags & EXT4_EX_NOFAIL)
gfp_flags |= __GFP_NOFAIL;
eh = ext_inode_hdr(inode); eh = ext_inode_hdr(inode);
depth = ext_depth(inode); depth = ext_depth(inode);
...@@ -857,7 +868,7 @@ ext4_find_extent(struct inode *inode, ext4_lblk_t block, ...@@ -857,7 +868,7 @@ ext4_find_extent(struct inode *inode, ext4_lblk_t block,
if (!path) { if (!path) {
/* account possible depth increase */ /* account possible depth increase */
path = kcalloc(depth + 2, sizeof(struct ext4_ext_path), path = kcalloc(depth + 2, sizeof(struct ext4_ext_path),
GFP_NOFS); gfp_flags);
if (unlikely(!path)) if (unlikely(!path))
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
path[0].p_maxdepth = depth + 1; path[0].p_maxdepth = depth + 1;
...@@ -1007,9 +1018,13 @@ static int ext4_ext_split(handle_t *handle, struct inode *inode, ...@@ -1007,9 +1018,13 @@ static int ext4_ext_split(handle_t *handle, struct inode *inode,
ext4_fsblk_t newblock, oldblock; ext4_fsblk_t newblock, oldblock;
__le32 border; __le32 border;
ext4_fsblk_t *ablocks = NULL; /* array of allocated blocks */ ext4_fsblk_t *ablocks = NULL; /* array of allocated blocks */
gfp_t gfp_flags = GFP_NOFS;
int err = 0; int err = 0;
size_t ext_size = 0; size_t ext_size = 0;
if (flags & EXT4_EX_NOFAIL)
gfp_flags |= __GFP_NOFAIL;
/* make decision: where to split? */ /* make decision: where to split? */
/* FIXME: now decision is simplest: at current extent */ /* FIXME: now decision is simplest: at current extent */
...@@ -1043,7 +1058,7 @@ static int ext4_ext_split(handle_t *handle, struct inode *inode, ...@@ -1043,7 +1058,7 @@ static int ext4_ext_split(handle_t *handle, struct inode *inode,
* We need this to handle errors and free blocks * We need this to handle errors and free blocks
* upon them. * upon them.
*/ */
ablocks = kcalloc(depth, sizeof(ext4_fsblk_t), GFP_NOFS); ablocks = kcalloc(depth, sizeof(ext4_fsblk_t), gfp_flags);
if (!ablocks) if (!ablocks)
return -ENOMEM; return -ENOMEM;
...@@ -2019,7 +2034,7 @@ int ext4_ext_insert_extent(handle_t *handle, struct inode *inode, ...@@ -2019,7 +2034,7 @@ int ext4_ext_insert_extent(handle_t *handle, struct inode *inode,
if (next != EXT_MAX_BLOCKS) { if (next != EXT_MAX_BLOCKS) {
ext_debug("next leaf block - %u\n", next); ext_debug("next leaf block - %u\n", next);
BUG_ON(npath != NULL); 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)) if (IS_ERR(npath))
return PTR_ERR(npath); return PTR_ERR(npath);
BUG_ON(npath->p_depth != path->p_depth); BUG_ON(npath->p_depth != path->p_depth);
...@@ -2792,7 +2807,8 @@ int ext4_ext_remove_space(struct inode *inode, ext4_lblk_t start, ...@@ -2792,7 +2807,8 @@ int ext4_ext_remove_space(struct inode *inode, ext4_lblk_t start,
ext4_fsblk_t pblk; ext4_fsblk_t pblk;
/* find extent for or closest extent to this block */ /* 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)) { if (IS_ERR(path)) {
ext4_journal_stop(handle); ext4_journal_stop(handle);
return PTR_ERR(path); return PTR_ERR(path);
...@@ -2878,7 +2894,7 @@ int ext4_ext_remove_space(struct inode *inode, ext4_lblk_t start, ...@@ -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; le16_to_cpu(path[k].p_hdr->eh_entries)+1;
} else { } else {
path = kcalloc(depth + 1, sizeof(struct ext4_ext_path), path = kcalloc(depth + 1, sizeof(struct ext4_ext_path),
GFP_NOFS); GFP_NOFS | __GFP_NOFAIL);
if (path == NULL) { if (path == NULL) {
ext4_journal_stop(handle); ext4_journal_stop(handle);
return -ENOMEM; return -ENOMEM;
...@@ -3303,7 +3319,7 @@ static int ext4_split_extent(handle_t *handle, ...@@ -3303,7 +3319,7 @@ static int ext4_split_extent(handle_t *handle,
* Update path is required because previous ext4_split_extent_at() may * Update path is required because previous ext4_split_extent_at() may
* result in split of original leaf or extent zeroout. * 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)) if (IS_ERR(path))
return PTR_ERR(path); return PTR_ERR(path);
depth = ext_depth(inode); depth = ext_depth(inode);
...@@ -4365,7 +4381,14 @@ int ext4_ext_truncate(handle_t *handle, struct inode *inode) ...@@ -4365,7 +4381,14 @@ int ext4_ext_truncate(handle_t *handle, struct inode *inode)
} }
if (err) if (err)
return 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, static int ext4_alloc_file_blocks(struct file *file, ext4_lblk_t offset,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment