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

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.com


Signed-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
parent 08adf452
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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
+33 −10
Original line number Diff line number Diff line
@@ -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;

	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))
		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,