ext4: remove while loop in mpage_map_and_submit_extent()
Theodore Ts'o
maintainerPATCH 1/2 ext4: remove while loop in mpage_map_and_submit_extent()
The while loop in mpage_map_and_submit_extent() is pointless; this function is only called if mpd.map.m_len is non-zero, and at the end of the while loop, mpage_map_and_submit_buffers sets mpd.map.m_len to be zero on success. (And on a failure, we return out of the while loop anyway.)
Hence, the body of the while loop is guaranteed to execute once and exactly once. This also fixes a gcc warning complaining that err might be left uninitialized.
--- fs/ext4/inode.c | 66 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 34 deletions(-)
fs/ext4/inode.c +32 -34
@@ -2157,44 +2157,42 @@ static int mpage_map_and_submit_extent(handle_t *handle, mpd->io_submit.io_end->offset = ((loff_t )map->m_lblk) << inode->i_blkbits; -while (map->m_len) { - err =mpage_map_one_extent (handle, mpd); -if (err <0 ) { -struct super_block *sb = inode->i_sb; + err =mpage_map_one_extent (handle, mpd); +if (err <0 ) { +struct super_block *sb = inode->i_sb; -/* -* Need to commit transaction to free blocks. Let upper -* layers sort it out. -*/ -if (err == -ENOSPC &&ext4_count_free_clusters (sb)) -return -ENOSPC ; - -if (!(EXT4_SB (sb)->s_mount_flags &EXT4_MF_FS_ABORTED )) { -ext4_msg (sb,KERN_CRIT , -"Delayed block allocation failed for " -"inode %lu at logical offset %llu with" -" max blocks %u with error %d" , - inode->i_ino, - (unsigned long long )map->m_lblk, - (unsigned )map->m_len, err); -ext4_msg (sb,KERN_CRIT , -"This should not happen!! Data will " -"be lost\n" ); -if (err == -ENOSPC ) -ext4_print_free_blocks (inode); - } -/* invalidate all the pages */ -mpage_release_unused_pages (mpd, true); -return err; - }/* -* Update buffer state, submit mapped pages, and get us new -* extent to map +* Need to commit transaction to free blocks. Let upper +* layers sort it out. */ - err =mpage_map_and_submit_buffers (mpd); -if (err <0 ) -return err; +if (err == -ENOSPC &&ext4_count_free_clusters (sb)) +return -ENOSPC ; + +if (!(EXT4_SB (sb)->s_mount_flags &EXT4_MF_FS_ABORTED )) { +ext4_msg (sb,KERN_CRIT , +"Delayed block allocation failed for " +"inode %lu at logical offset %llu with" +" max blocks %u with error %d" , + inode->i_ino, + (unsigned long long )map->m_lblk, + (unsigned )map->m_len, err); +ext4_msg (sb,KERN_CRIT , +"This should not happen!! Data will " +"be lost\n" ); +if (err == -ENOSPC ) +ext4_print_free_blocks (inode); + } +/* invalidate all the pages */ +mpage_release_unused_pages (mpd, true); +return err; } +/* +* Update buffer state, submit mapped pages, and get us new +* extent to map +*/ + err =mpage_map_and_submit_buffers (mpd); +if (err <0 ) +return err;/* Update on-disk size after IO is submitted */ disksize = ((loff_t )mpd->first_page) <<PAGE_CACHE_SHIFT ;
Signature · 1 line
1.7.12.rc0.22.gcdd159b
Theodore Ts'o
maintainerPATCH 2/2 ext4: fix up error handling for mpage_map_and_submit_extent()
The function mpage_released_unused_page() must only be called once; otherwise the kernel will BUG() when the second call to mpage_released_unused_page() tries to unlock the pages which had been unlocked by the first call.
Also restructure the error handling so that we only give up on writing the dirty pages in the case of ENOSPC where retrying the allocation won't help. Otherwise, a transient failure, such as a kmalloc() failure in calling ext4_map_blocks() might cause us to give up on those pages, leading to a scary message in /var/log/messages plus data loss.
--- fs/ext4/inode.c | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-)
fs/ext4/inode.c +22 -22
@@ -2148,7 +2148,8 @@ static int mpage_map_one_extent(handle_t *handle, struct mpage_da_data *mpd)* guaranteed). After mapping we submit all mapped pages for IO. */ static int mpage_map_and_submit_extent (handle_t *handle, -struct mpage_da_data *mpd) +struct mpage_da_data *mpd, +bool *give_up_on_write) {struct inode *inode = mpd->inode;struct ext4_map_blocks *map = &mpd->map;
@@ -2161,29 +2162,26 @@ static int mpage_map_and_submit_extent(handle_t *handle,if (err <0 ) {struct super_block *sb = inode->i_sb; +if (EXT4_SB (sb)->s_mount_flags &EXT4_MF_FS_ABORTED ) { +/* invalidate all the pages */ + *give_up_on_write = true; +return err; + } +if ((err != -ENOSPC ) ||ext4_count_free_clusters (sb)) +return err;/* * Need to commit transaction to free blocks. Let upper * layers sort it out. */ -if (err == -ENOSPC &&ext4_count_free_clusters (sb)) -return -ENOSPC ; - -if (!(EXT4_SB (sb)->s_mount_flags &EXT4_MF_FS_ABORTED )) { -ext4_msg (sb,KERN_CRIT , -"Delayed block allocation failed for " -"inode %lu at logical offset %llu with" -" max blocks %u with error %d" , - inode->i_ino, - (unsigned long long )map->m_lblk, - (unsigned )map->m_len, err); -ext4_msg (sb,KERN_CRIT , -"This should not happen!! Data will " -"be lost\n" ); -if (err == -ENOSPC ) -ext4_print_free_blocks (inode); - } -/* invalidate all the pages */ -mpage_release_unused_pages (mpd, true); +ext4_msg (sb,KERN_CRIT , +"Delayed block allocation failed for inode %lu at " +"logical offset %llu with max blocks %u" , + inode->i_ino, (unsigned long long )map->m_lblk, + (unsigned )map->m_len); +ext4_msg (sb,KERN_CRIT , +"This should not happen!! Data will be lost\n" ); +ext4_print_free_blocks (inode); + *give_up_on_write = true;/* invalidate all the pages */ return err; }/*
@@ -2370,6 +2368,7 @@ static int ext4_writepages(struct address_space *mapping,struct ext4_sb_info *sbi =EXT4_SB (mapping->host->i_sb);bool done;struct blk_plug plug; +bool give_up_on_write = false;trace_ext4_writepages (inode, wbc);
@@ -2487,7 +2486,8 @@ retry: ret =mpage_prepare_extent_to_map (&mpd);if (!ret) {if (mpd.map.m_len) - ret =mpage_map_and_submit_extent (handle, &mpd); + ret =mpage_map_and_submit_extent (handle, &mpd, + &give_up_on_write);else {/* * We scanned the whole range (or exhausted
@@ -2502,7 +2502,7 @@ retry:/* Submit prepared bio */ ext4_io_submit (&mpd.io_submit);/* Unlock pages we didn't use */ -mpage_release_unused_pages (&mpd, false); +mpage_release_unused_pages (&mpd, give_up_on_write);/* Drop our io_end reference we got from init */ ext4_put_io_end (mpd.io_submit.io_end);
Signature · 1 line
1.7.12.rc0.22.gcdd159b
Re: [PATCH 2/2] ext4: fix up error handling for mpage_map_and_submit_extent()
Quoted 12 lines · Ted Tso
The function
mpage_released_unused_page()must only be called once; otherwise the kernel willBUG()when the second call tompage_released_unused_page()tries to unlock the pages which had been unlocked by the first call.Also restructure the error handling so that we only give up on writing the dirty pages in the case of ENOSPC where retrying the allocation won't help. Otherwise, a transient failure, such as a
kmalloc()failure in callingext4_map_blocks()might cause us to give up on those pages, leading to a scary message in/var/log/messagesplus data loss.
Quoted 2 lines
Signed-off-by "Theodore Ts'o" <[email protected]>Cc Jan Kara <[email protected]>
Yeah, this looks nicer. You can add:
Honza
Quoted 94 lines
--- fs/ext4/inode.c | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-)fs/ext4/inode.c +22
-22@@ -2148,7 +2148,8 @@ static int mpage_map_one_extent(handle_t *handle, struct mpage_da_data *mpd)* guaranteed). After mapping we submit all mapped pages for IO. */ static int mpage_map_and_submit_extent (handle_t *handle, -struct mpage_da_data *mpd) +struct mpage_da_data *mpd, +bool *give_up_on_write) {struct inode *inode = mpd->inode;struct ext4_map_blocks *map = &mpd->map;@@ -2161,29 +2162,26 @@ static int mpage_map_and_submit_extent(handle_t *handle,if (err <0 ) {struct super_block *sb = inode->i_sb; +if (EXT4_SB (sb)->s_mount_flags &EXT4_MF_FS_ABORTED ) { +/* invalidate all the pages */ + *give_up_on_write = true; +return err; + } +if ((err != -ENOSPC ) ||ext4_count_free_clusters (sb)) +return err;/* * Need to commit transaction to free blocks. Let upper * layers sort it out. */ -if (err == -ENOSPC &&ext4_count_free_clusters (sb)) -return -ENOSPC ; - -if (!(EXT4_SB (sb)->s_mount_flags &EXT4_MF_FS_ABORTED )) { -ext4_msg (sb,KERN_CRIT , -"Delayed block allocation failed for " -"inode %lu at logical offset %llu with" -" max blocks %u with error %d" , - inode->i_ino, - (unsigned long long )map->m_lblk, - (unsigned )map->m_len, err); -ext4_msg (sb,KERN_CRIT , -"This should not happen!! Data will " -"be lost\n" ); -if (err == -ENOSPC ) -ext4_print_free_blocks (inode); - } -/* invalidate all the pages */ -mpage_release_unused_pages (mpd, true); +ext4_msg (sb,KERN_CRIT , +"Delayed block allocation failed for inode %lu at " +"logical offset %llu with max blocks %u" , + inode->i_ino, (unsigned long long )map->m_lblk, + (unsigned )map->m_len); +ext4_msg (sb,KERN_CRIT , +"This should not happen!! Data will be lost\n" ); +ext4_print_free_blocks (inode); + *give_up_on_write = true;/* invalidate all the pages */ return err; }/* @@ -2370,6 +2368,7 @@ static int ext4_writepages(struct address_space *mapping,struct ext4_sb_info *sbi =EXT4_SB (mapping->host->i_sb);bool done;struct blk_plug plug; +bool give_up_on_write = false;trace_ext4_writepages (inode, wbc);@@ -2487,7 +2486,8 @@ retry: ret =mpage_prepare_extent_to_map (&mpd);if (!ret) {if (mpd.map.m_len) - ret =mpage_map_and_submit_extent (handle, &mpd); + ret =mpage_map_and_submit_extent (handle, &mpd, + &give_up_on_write);else {/* * We scanned the whole range (or exhausted @@ -2502,7 +2502,7 @@ retry:/* Submit prepared bio */ ext4_io_submit (&mpd.io_submit);/* Unlock pages we didn't use */ -mpage_release_unused_pages (&mpd, false); +mpage_release_unused_pages (&mpd, give_up_on_write);/* Drop our io_end reference we got from init */ ext4_put_io_end (mpd.io_submit.io_end);Signature · 1 line
1.7.12.rc0.22.gcdd159b
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Theodore Ts'o
maintainerPATCH 1/1 ext4: fix up error handling for mpage_map_and_submit_extent()
Since I dropped the earlier patch, I had to fix up this patch a wee bit.
Quoted 1 line
From
1cd6c381602e86eb8c8064bcb212c9c8aeee062bMon Sep 17 00:00:00 2001
From: Theodore Ts'o <[email protected]>
Date: Fri, 21 Jun 2013 23:49:09 -0400
Subject: [PATCH] ext4: fix up error handling for mpage_map_and_submit_extent()
The function mpage_released_unused_page() must only be called once; otherwise the kernel will BUG() when the second call to mpage_released_unused_page() tries to unlock the pages which had been unlocked by the first call.
Also restructure the error handling so that we only give up on writing the dirty pages in the case of ENOSPC where retrying the allocation won't help. Otherwise, a transient failure, such as a kmalloc() failure in calling ext4_map_blocks() might cause us to give up on those pages, leading to a scary message in /var/log/messages plus data loss.
--- fs/ext4/inode.c | 52 ++++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 24 deletions(-)
fs/ext4/inode.c +28 -24
@@ -2153,7 +2153,8 @@ static int mpage_map_one_extent(handle_t *handle, struct mpage_da_data *mpd)* guaranteed). After mapping we submit all mapped pages for IO. */ static int mpage_map_and_submit_extent (handle_t *handle, -struct mpage_da_data *mpd) +struct mpage_da_data *mpd, +bool *give_up_on_write) {struct inode *inode = mpd->inode;struct ext4_map_blocks *map = &mpd->map;
@@ -2167,29 +2168,30 @@ static int mpage_map_and_submit_extent(handle_t *handle,if (err <0 ) {struct super_block *sb = inode->i_sb; +if (EXT4_SB (sb)->s_mount_flags &EXT4_MF_FS_ABORTED ) + goto invalidate_dirty_pages;/* -* Need to commit transaction to free blocks. Let upper -* layers sort it out. +* Let the uper layers retry transient errors. +* In the case of ENOSPC, if ext4_count_free_blocks() +* is non-zero, a commit should free up blocks. */ -if (err == -ENOSPC &&ext4_count_free_clusters (sb)) -return -ENOSPC ; - -if (!(EXT4_SB (sb)->s_mount_flags &EXT4_MF_FS_ABORTED )) { -ext4_msg (sb,KERN_CRIT , -"Delayed block allocation failed for " -"inode %lu at logical offset %llu with" -" max blocks %u with error %d" , - inode->i_ino, - (unsigned long long )map->m_lblk, - (unsigned )map->m_len, err); -ext4_msg (sb,KERN_CRIT , -"This should not happen!! Data will " -"be lost\n" ); -if (err == -ENOSPC ) -ext4_print_free_blocks (inode); - } -/* invalidate all the pages */ -mpage_release_unused_pages (mpd, true); +if ((err == -ENOMEM ) || + (err == -ENOSPC &&ext4_count_free_clusters (sb))) +return err; +ext4_msg (sb,KERN_CRIT , +"Delayed block allocation failed for " +"inode %lu at logical offset %llu with" +" max blocks %u with error %d" , + inode->i_ino, + (unsigned long long )map->m_lblk, + (unsigned )map->m_len, -err); +ext4_msg (sb,KERN_CRIT , +"This should not happen!! Data will " +"be lost\n" ); +if (err == -ENOSPC ) +ext4_print_free_blocks (inode); + invalidate_dirty_pages: + *give_up_on_write = true;return err; }/*
@@ -2377,6 +2379,7 @@ static int ext4_writepages(struct address_space *mapping,struct ext4_sb_info *sbi =EXT4_SB (mapping->host->i_sb);bool done;struct blk_plug plug; +bool give_up_on_write = false;trace_ext4_writepages (inode, wbc);
@@ -2494,7 +2497,8 @@ retry: ret =mpage_prepare_extent_to_map (&mpd);if (!ret) {if (mpd.map.m_len) - ret =mpage_map_and_submit_extent (handle, &mpd); + ret =mpage_map_and_submit_extent (handle, &mpd, + &give_up_on_write);else {/* * We scanned the whole range (or exhausted
@@ -2509,7 +2513,7 @@ retry:/* Submit prepared bio */ ext4_io_submit (&mpd.io_submit);/* Unlock pages we didn't use */ -mpage_release_unused_pages (&mpd, false); +mpage_release_unused_pages (&mpd, give_up_on_write);/* Drop our io_end reference we got from init */ ext4_put_io_end (mpd.io_submit.io_end);
Signature · 1 line
1.7.12.rc0.22.gcdd159b
Jan Kara
reviewerQuoted 5 lines · Ted Tso
The while loop in
mpage_map_and_submit_extent()is pointless; this function is only called if mpd.map.m_len is non-zero, and at the end of the while loop,mpage_map_and_submit_bufferssets mpd.map.m_len to be zero on success. (And on a failure, we return out of the while loop anyway.)
This is not true. In case blocksize < pagesize and we were not able to
map a full page, mpage_map_and_submit_buffers() changes mpd->map to point to the next extent inside the page that needs mapping. In that case we can take several iterations of the loop. Maybe this fact deserves a comment before the loop but there's a comment before the callsite of mpage_map_and_submit_buffers() which mentions that it will get us next extent to map and also the documentation of mpage_map_and_submit_buffers() explicitely tells that it can update mpd->map.
Honza
Quoted 94 lines
Hence, the body of the while loop is guaranteed to execute once and exactly once. This also fixes a gcc warning complaining that err might be left uninitialized.
Signed-off-by "Theodore Ts'o" <[email protected]>Cc Jan Kara <[email protected]>--- fs/ext4/inode.c | 66 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 34 deletions(-)fs/ext4/inode.c +32
-34@@ -2157,44 +2157,42 @@ static int mpage_map_and_submit_extent(handle_t *handle, mpd->io_submit.io_end->offset = ((loff_t )map->m_lblk) << inode->i_blkbits; -while (map->m_len) { - err =mpage_map_one_extent (handle, mpd); -if (err <0 ) { -struct super_block *sb = inode->i_sb; + err =mpage_map_one_extent (handle, mpd); +if (err <0 ) { +struct super_block *sb = inode->i_sb; -/* -* Need to commit transaction to free blocks. Let upper -* layers sort it out. -*/ -if (err == -ENOSPC &&ext4_count_free_clusters (sb)) -return -ENOSPC ; - -if (!(EXT4_SB (sb)->s_mount_flags &EXT4_MF_FS_ABORTED )) { -ext4_msg (sb,KERN_CRIT , -"Delayed block allocation failed for " -"inode %lu at logical offset %llu with" -" max blocks %u with error %d" , - inode->i_ino, - (unsigned long long )map->m_lblk, - (unsigned )map->m_len, err); -ext4_msg (sb,KERN_CRIT , -"This should not happen!! Data will " -"be lost\n" ); -if (err == -ENOSPC ) -ext4_print_free_blocks (inode); - } -/* invalidate all the pages */ -mpage_release_unused_pages (mpd, true); -return err; - }/* -* Update buffer state, submit mapped pages, and get us new -* extent to map +* Need to commit transaction to free blocks. Let upper +* layers sort it out. */ - err =mpage_map_and_submit_buffers (mpd); -if (err <0 ) -return err; +if (err == -ENOSPC &&ext4_count_free_clusters (sb)) +return -ENOSPC ; + +if (!(EXT4_SB (sb)->s_mount_flags &EXT4_MF_FS_ABORTED )) { +ext4_msg (sb,KERN_CRIT , +"Delayed block allocation failed for " +"inode %lu at logical offset %llu with" +" max blocks %u with error %d" , + inode->i_ino, + (unsigned long long )map->m_lblk, + (unsigned )map->m_len, err); +ext4_msg (sb,KERN_CRIT , +"This should not happen!! Data will " +"be lost\n" ); +if (err == -ENOSPC ) +ext4_print_free_blocks (inode); + } +/* invalidate all the pages */ +mpage_release_unused_pages (mpd, true); +return err; } +/* +* Update buffer state, submit mapped pages, and get us new +* extent to map +*/ + err =mpage_map_and_submit_buffers (mpd); +if (err <0 ) +return err;/* Update on-disk size after IO is submitted */ disksize = ((loff_t )mpd->first_page) <<PAGE_CACHE_SHIFT ;Signature · 1 line
1.7.12.rc0.22.gcdd159b
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Theodore Ts'o
maintainerQuoted 8 lines · Jan Kara
This is not true. In case blocksize < pagesize and we were not able to
map a full page,mpage_map_and_submit_buffers()changesmpd->mapto point to the next extent inside the page that needs mapping. In that case we can take several iterations of the loop. Maybe this fact deserves a comment before the loop but there's a comment before the callsite ofmpage_map_and_submit_buffers()which mentions that it will get us next extent to map and also the documentation ofmpage_map_and_submit_buffers()explicitely tells that it can updatempd->map.
Ah, right. Thanks, I missed where that was happening the first time I was analyzing that function.
I've dropped this patch.
- Ted