f2fs: avoid needless lock for node pages when fsyncing a file
Subsystems (2)
Jaegeuk Kim
maintainerPATCH 1/7 f2fs: avoid needless lock for node pages when fsyncing a file
When fsync is called, sync_node_pages finds a proper direct node pages to flush. But, it locks unrelated direct node pages together unnecessarily.
--- fs/f2fs/node.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
fs/f2fs/node.c +7 -3
@@ -1272,10 +1272,14 @@ next_step:* we should not skip writing node pages. */ lock_node: -if (ino &&ino_of_node (page) == ino) -lock_page (page); -else if (!trylock_page (page)) +if (ino) { +if (ino_of_node (page) == ino) +lock_page (page); +else +continue ; + }else if (!trylock_page (page)) {continue ; + }if (unlikely (page->mapping !=NODE_MAPPING (sbi))) { continue_unlock:
Signature · 1 line
2.6.3
Jaegeuk Kim
maintainerPATCH 2/7 f2fs: avoid writing 0'th page in volatile writes
The first page of volatile writes usually contains a sort of header information which will be used for recovery.
(e.g., journal header of sqlite)
If this is written without other journal data, user needs to handle the stale journal information.
--- fs/f2fs/data.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
fs/f2fs/data.c +4 -2
@@ -1177,8 +1177,10 @@ write: goto redirty_out;if (f2fs_is_drop_cache (inode)) goto out; -if (f2fs_is_volatile_file (inode) && !wbc->for_reclaim && -available_free_memory (sbi,BASE_CHECK )) +/* we should not write 0'th page having journal header */ +if (f2fs_is_volatile_file (inode) && (!page->index || + (!wbc->for_reclaim && +available_free_memory (sbi,BASE_CHECK )))) goto redirty_out;/* Dentry blocks are controlled by checkpoint */
Signature · 1 line
2.6.3
Jaegeuk Kim
maintainerPATCH 3/7 f2fs: split sync_node_pages with fsync_node_pages
In reply to Jaegeuk KimThis patch splits the existing sync_node_pages into (f)sync_node_pages. The fsync_node_pages is used for f2fs_sync_file only.
--- fs/f2fs/checkpoint.c | 2 +- fs/f2fs/f2fs.h | 3 +- fs/f2fs/file.c | 2 +- fs/f2fs/gc.c | 2 +- fs/f2fs/node.c | 108 +++++++++++++++++++++++++++++++++++++-------------- 5 files changed, 84 insertions(+), 33 deletions(-)
fs/f2fs/checkpoint.c +1 -1
@@ -892,7 +892,7 @@ retry_flush_nodes:if (get_pages (sbi,F2FS_DIRTY_NODES )) {up_write (&sbi->node_write); - err =sync_node_pages (sbi,0 , &wbc); + err =sync_node_pages (sbi, &wbc);if (err) {f2fs_unlock_all (sbi); goto out;
fs/f2fs/f2fs.h +2 -1
@@ -1784,7 +1784,8 @@ void ra_node_page(struct f2fs_sb_info *, nid_t);struct page *get_node_page (struct f2fs_sb_info *,pgoff_t );struct page *get_node_page_ra (struct page *,int );void sync_inode_page (struct dnode_of_data *); -int sync_node_pages (struct f2fs_sb_info *,nid_t ,struct writeback_control *); +int fsync_node_pages (struct f2fs_sb_info *,nid_t ,struct writeback_control *); +int sync_node_pages (struct f2fs_sb_info *,struct writeback_control *);bool alloc_nid (struct f2fs_sb_info *,nid_t *);void alloc_nid_done (struct f2fs_sb_info *,nid_t );void alloc_nid_failed (struct f2fs_sb_info *,nid_t );
fs/f2fs/file.c +1 -1
@@ -256,7 +256,7 @@ go_write: goto out; } sync_nodes: -sync_node_pages (sbi, ino, &wbc); +fsync_node_pages (sbi, ino, &wbc);/* if cp_error was enabled, we should avoid infinite loop */ if (unlikely (f2fs_cp_error (sbi))) {
fs/f2fs/gc.c +1 -1
@@ -841,7 +841,7 @@ static int do_garbage_collect(struct f2fs_sb_info *sbi, .nr_to_write = LONG_MAX, .for_reclaim = 0, }; -sync_node_pages (sbi,0 , &wbc); +sync_node_pages (sbi, &wbc); }else {f2fs_submit_merged_bio (sbi,DATA ,WRITE ); }
fs/f2fs/node.c +79 -29
150 lines not on this page · raw message
Signature · 1 line
2.6.3
Jaegeuk Kim
maintainerPATCH 4/7 f2fs: report unwritten status in fsync_node_pages
In reply to Jaegeuk KimThe fsync_node_pages should return pass or failure so that user could know fsync is completed or not.
--- fs/f2fs/file.c | 4 +++- fs/f2fs/node.c | 13 ++++++------- 2 files changed, 9 insertions(+), 8 deletions(-)
fs/f2fs/file.c +3 -1
@@ -256,7 +256,9 @@ go_write: goto out; } sync_nodes: -fsync_node_pages (sbi, ino, &wbc); + ret =fsync_node_pages (sbi, ino, &wbc); +if (ret) + goto out;/* if cp_error was enabled, we should avoid infinite loop */ if (unlikely (f2fs_cp_error (sbi))) {
fs/f2fs/node.c +6 -7
@@ -1227,7 +1227,7 @@ int fsync_node_pages(struct f2fs_sb_info *sbi, nid_t ino, {pgoff_t index, end;struct pagevec pvec; -int nwritten =0 ; +int ret =0 ;pagevec_init (&pvec,0 ); index =0 ;
@@ -1278,21 +1278,20 @@ continue_unlock:if (IS_INODE (page))set_dentry_mark (page,need_dentry_mark (sbi, ino)); - nwritten++; -if (NODE_MAPPING (sbi)->a_ops->writepage (page, wbc)) + ret =NODE_MAPPING (sbi)->a_ops->writepage (page, wbc); +if (ret) {unlock_page (page); - -if (--wbc->nr_to_write ==0 )break ; + } }pagevec_release (&pvec);cond_resched (); -if (wbc->nr_to_write ==0 ) +if (ret)break ; } -return nwritten; +return ret ? -EIO :0 ; }int sync_node_pages (struct f2fs_sb_info *sbi,struct writeback_control *wbc)
Signature · 1 line
2.6.3
Jaegeuk Kim
maintainerPATCH 5/7 f2fs: set fsync mark only for the last dnode
In reply to Jaegeuk KimIn order to give atomic writes, we should consider power failure during sync_node_pages in fsync.
So, this patch marks fsync flag only in the last dnode block.
--- fs/f2fs/f2fs.h | 4 +- fs/f2fs/file.c | 14 +++++-- fs/f2fs/node.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++++---- fs/f2fs/recovery.c | 9 ++--- 4 files changed, 113 insertions(+), 20 deletions(-)
fs/f2fs/f2fs.h +2 -2
@@ -159,7 +159,6 @@ struct fsync_inode_entry {struct inode *inode;/* vfs inode pointer */ block_t blkaddr;/* block address locating the last fsync */ block_t last_dentry;/* block address locating the last dentry */ -block_t last_inode;/* block address locating the last inode */ };#define nats_in_cursum (jnl) (le16_to_cpu(jnl->n_nats))
@@ -1784,7 +1783,8 @@ void ra_node_page(struct f2fs_sb_info *, nid_t);struct page *get_node_page (struct f2fs_sb_info *,pgoff_t );struct page *get_node_page_ra (struct page *,int );void sync_inode_page (struct dnode_of_data *); -int fsync_node_pages (struct f2fs_sb_info *,nid_t ,struct writeback_control *); +int fsync_node_pages (struct f2fs_sb_info *,nid_t ,struct writeback_control *, +bool );int sync_node_pages (struct f2fs_sb_info *,struct writeback_control *);bool alloc_nid (struct f2fs_sb_info *,nid_t *);void alloc_nid_done (struct f2fs_sb_info *,nid_t );
fs/f2fs/file.c +10 -4
@@ -182,7 +182,8 @@ static void try_to_fix_pino(struct inode *inode) } } -int f2fs_sync_file (struct file *file,loff_t start,loff_t end,int datasync) +static int f2fs_do_sync_file (struct file *file,loff_t start,loff_t end, +int datasync,bool atomic) {struct inode *inode = file->f_mapping->host;struct f2fs_inode_info *fi =F2FS_I (inode);
@@ -256,7 +257,7 @@ go_write: goto out; } sync_nodes: - ret =fsync_node_pages (sbi, ino, &wbc); + ret =fsync_node_pages (sbi, ino, &wbc, atomic);if (ret) goto out;
@@ -290,6 +291,11 @@ out:return ret; } +int f2fs_sync_file (struct file *file,loff_t start,loff_t end,int datasync) +{ +return f2fs_do_sync_file (file, start, end, datasync, false); +} +static pgoff_t __get_first_dirty_index (struct address_space *mapping,pgoff_t pgofs,int whence) {
@@ -1407,7 +1413,7 @@ static int f2fs_ioc_commit_atomic_write(struct file *filp) } } - ret =f2fs_sync_file (filp,0 ,LLONG_MAX ,0 ); + ret =f2fs_do_sync_file (filp,0 ,LLONG_MAX ,0 , true); err_out:mnt_drop_write_file (filp);return ret;
@@ -1465,7 +1471,7 @@ static int f2fs_ioc_abort_volatile_write(struct file *filp)drop_inmem_pages (inode);if (f2fs_is_volatile_file (inode)) {clear_inode_flag (F2FS_I (inode),FI_VOLATILE_FILE ); - ret =f2fs_sync_file (filp,0 ,LLONG_MAX ,0 ); + ret =f2fs_do_sync_file (filp,0 ,LLONG_MAX ,0 , true); }mnt_drop_write_file (filp);
fs/f2fs/node.c +98 -8
150 lines not on this page · raw message
fs/f2fs/recovery.c +3 -6
@@ -257,11 +257,8 @@ static int find_fsync_dnodes(struct f2fs_sb_info *sbi, struct list_head *head) } entry->blkaddr = blkaddr; -if (IS_INODE (page)) { - entry->last_inode = blkaddr; -if (is_dent_dnode (page)) - entry->last_dentry = blkaddr; - } +if (IS_INODE (page) &&is_dent_dnode (page)) + entry->last_dentry = blkaddr; next:/* check next segment */ blkaddr =next_blkaddr_of_node (page);
@@ -521,7 +518,7 @@ static int recover_data(struct f2fs_sb_info *sbi, struct list_head *head)* In this case, we can lose the latest inode(x). * So, call recover_inode for the inode update. */ -if (entry->last_inode == blkaddr) +if (IS_INODE (page))recover_inode (entry->inode, page);if (entry->last_dentry == blkaddr) { err =recover_dentry (entry->inode, page);
Signature · 1 line
2.6.3
Jaegeuk Kim
maintainerPATCH 6/7 f2fs: issue cache flush on direct IO
In reply to Jaegeuk KimUnder direct IO path with O_(D)SYNC, it needs to set proper APPEND or UPDATE flags, so taht f2fs_sync_file can make its data safe.
--- fs/f2fs/data.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-)
fs/f2fs/data.c +9 -2
@@ -673,6 +673,9 @@ next_block: err =reserve_new_block (&dn); }else { err =__allocate_data_block (&dn); +if (!err) +set_inode_flag (F2FS_I (inode), +FI_APPEND_WRITE ); }if (err) goto sync_out;
@@ -1685,8 +1688,12 @@ static ssize_t f2fs_direct_IO(struct kiocb *iocb, struct iov_iter *iter,trace_f2fs_direct_IO_enter (inode, offset, count,iov_iter_rw (iter)); err =blockdev_direct_IO (iocb, inode, iter, offset, get_data_block_dio); -if (err <0 &&iov_iter_rw (iter) ==WRITE ) -f2fs_write_failed (mapping, offset + count); +if (iov_iter_rw (iter) ==WRITE ) { +if (err >0 ) +set_inode_flag (F2FS_I (inode),FI_UPDATE_WRITE ); +else if (err <0 ) +f2fs_write_failed (mapping, offset + count); + }trace_f2fs_direct_IO_exit (inode, offset, count,iov_iter_rw (iter), err);
Signature · 1 line
2.6.3
Jaegeuk Kim
maintainerPATCH 7/7 f2fs: should check the remaining dentry bits
In reply to Jaegeuk KimLet's consider a race condition between f2fs_add_regular_entry and find_target_dentry.
1.
- f2fs_add_regular_entry updated len: 24 first.
|
Bits: 0 0 0 1
Lens: 24 0 0 3 (name: foo)
|->- find_target_dentry checks the first bit to find "foo", then ++pointer.
2.
- f2fs_add_regular_entry updates bits.
|>|>|
Bits: 1 1 1 1
Lens: 24 0 0 3 (name: foo)
|- find_target_dentry is checking second bit, but it's len is zero, which makes the process being terminated.
In this case, user can add additional dentry named "foo" accordingly. This patch enables to check the remaining bits.
--- fs/f2fs/dir.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
fs/f2fs/dir.c +3 -3
@@ -149,9 +149,9 @@ struct f2fs_dir_entry *find_target_dentry(struct fscrypt_name *fname,/* remain bug on condition */ if (unlikely (!de->name_len)) - d->max =-1 ; - - bit_pos +=GET_DENTRY_SLOTS (le16_to_cpu (de->name_len)); + bit_pos++; +else + bit_pos +=GET_DENTRY_SLOTS (le16_to_cpu (de->name_len)); } de =NULL ;
Signature · 1 line
2.6.3
Jaegeuk Kim
maintainerRe: [PATCH v2 7/7] f2fs: should check the remaining dentry bits
Change log from v1:
o remove wron f2fs_bug_on()
Quoted 1 line
From
545c0c9055b0d8dc5d134d9340b3cd80eeecdafaMon Sep 17 00:00:00 2001
From: Jaegeuk Kim <[email protected]>
Date: Mon, 25 Apr 2016 14:24:44 -0700
Subject: [PATCH] f2fs: should check the remaining dentry bits
Let's consider a race condition between f2fs_add_regular_entry and find_target_dentry.
1.
- f2fs_add_regular_entry updated len: 24 first.
|
Bits: 0 0 0 1
Lens: 24 0 0 3 (name: foo)
|->- find_target_dentry checks the first bit to find "foo", then ++pointer.
2.
- f2fs_add_regular_entry updates bits.
|>|>|
Bits: 1 1 1 1
Lens: 24 0 0 3 (name: foo)
|- find_target_dentry is checking second bit, but it's len is zero, which makes the process being terminated.
In this case, user can add additional dentry named "foo" accordingly. This patch enables to check the remaining bits.
--- fs/f2fs/dir.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-)
fs/f2fs/dir.c +3 -9
@@ -100,12 +100,6 @@ static struct f2fs_dir_entry *find_in_block(struct page *dentry_page, *res_page = dentry_page; elsekunmap (dentry_page ); - -/* -* For the most part, it should be a bug when name_len is zero. -* We stop here for figuring out where the bugs has occurred. -*/ -f2fs_bug_on (F2FS_P_SB (dentry_page), d.max <0 );return de; }
@@ -149,9 +143,9 @@ struct f2fs_dir_entry *find_target_dentry(struct fscrypt_name *fname,/* remain bug on condition */ if (unlikely (!de->name_len)) - d->max =-1 ; - - bit_pos +=GET_DENTRY_SLOTS (le16_to_cpu (de->name_len)); + bit_pos++; +else + bit_pos +=GET_DENTRY_SLOTS (le16_to_cpu (de->name_len)); } de =NULL ;
Signature · 1 line
2.6.3
Chao Yu
maintainerRe: [f2fs-dev] [PATCH 7/7] f2fs: should check the remaining dentry bits
In reply to Jaegeuk KimHi Jaegeuk, Yunlei,
Quoted 11 lines · Jaegeuk Kim
Let's consider a race condition between
f2fs_add_regular_entryandfind_target_dentry.1.
-f2fs_add_regular_entryupdated len: 24 first.| Bits: 0 0 0 1 Lens: 24 0 0 3 (name: foo) |->-
find_target_dentrychecks the first bit to find "foo", then ++pointer.
Quoted 8 lines
2.
-f2fs_add_regular_entryupdates bits.|>|>| Bits: 1 1 1 1 Lens: 24 0 0 3 (name: foo) |-
find_target_dentryis checking second bit, but it's len is zero, which makes the process being terminated.
As Pengyang reminded, there are no racing condition between find_target_dentry and f2fs_add_regular_entry since i_mutex lock make each of operations being atomical. So seems above condition can not happen.
But still we should handle dirent with zero-sized length correctly, as it may cause deadloop. So how do you think of following patch?
From: Chao Yu <[email protected]>
Subject: [PATCH] f2fs: be aware of invalid filename length
The filename length in dirent of may become zero-sized after random junk data injection, once encounter such dirent, find_target_dentry or f2fs_add_inline_entries will run into an infinite loop. So let f2fs being aware of that to avoid deadloop.
--- fs/f2fs/dir.c | 14 +++++--------- fs/f2fs/inline.c | 14 ++++++-------- 2 files changed, 11 insertions(+), 17 deletions(-)
fs/f2fs/dir.c +0 -0
@@ -101,11 +101,6 @@ static struct f2fs_dir_entry *find_in_block(struct page
*dentry_page,
else
kunmap(dentry_page);
- /*
- * For the most part, it should be a bug when name_len is zero.
- * We stop here for figuring out where the bugs has occurred.
- */
- f2fs_bug_on(F2FS_P_SB(dentry_page), d.max < 0);
return de;
}
@@ -130,6 +125,11 @@ struct f2fs_dir_entry *find_target_dentry(struct fscrypt_name *fname,
de = &d->dentry[bit_pos];
+ if (unlikely(!de->name_len)) {
+ bit_pos++;
+ continue;
+ }
+
/* encrypted case */
de_name.name = d->filename[bit_pos]; de_name.len = le16_to_cpu(de->name_len);
@@ -147,10 +147,6 @@ struct f2fs_dir_entry *find_target_dentry(struct fscrypt_name *fname,
*max_slots = max_len;
max_len = 0;
- /* remain bug on condition */
- if (unlikely(!de->name_len))
- d->max = -1;
-
bit_pos += GET_DENTRY_SLOTS(le16_to_cpu(de->name_len));
}
fs/f2fs/inline.c +6 -8
@@ -303,11 +303,6 @@ struct f2fs_dir_entry *find_in_inline_dir(struct inode *dir,else f2fs_put_page (ipage,0 ); -/* -* For the most part, it should be a bug when name_len is zero. -* We stop here for figuring out where the bugs has occurred. -*/ -f2fs_bug_on (sbi, d.max <0 );return de; }
@@ -437,6 +432,12 @@ static int f2fs_add_inline_entries(struct inode *dir, } de = &d.dentry[bit_pos]; + +if (unlikely (!de->name_len)) { + bit_pos++; +continue ; + } + new_name.name = d.filename[bit_pos]; new_name.len = de->name_len;
@@ -448,9 +449,6 @@ static int f2fs_add_inline_entries(struct inode *dir,if (err) goto punch_dentry_pages; -if (unlikely (!de->name_len)) - d.max =-1 ; - bit_pos +=GET_DENTRY_SLOTS (le16_to_cpu (de->name_len)); }return 0 ;
Signature · 1 line
2.7.2
Jaegeuk Kim
maintainerRe: [f2fs-dev] [PATCH 7/7] f2fs: should check the remaining dentry bits
Looks better.
Merged. :)
Thanks,
Quoted 127 lines · Chao Yu
Hi Jaegeuk, Yunlei,
Quoted 11 lines · Jaegeuk Kim
Let's consider a race condition between
f2fs_add_regular_entryandfind_target_dentry.1.
-f2fs_add_regular_entryupdated len: 24 first.| Bits: 0 0 0 1 Lens: 24 0 0 3 (name: foo) |->-
find_target_dentrychecks the first bit to find "foo", then ++pointer.Quoted 8 lines
2.
-f2fs_add_regular_entryupdates bits.|>|>| Bits: 1 1 1 1 Lens: 24 0 0 3 (name: foo) |-
find_target_dentryis checking second bit, but it's len is zero, which makes the process being terminated.As Pengyang reminded, there are no racing condition between
find_target_dentryandf2fs_add_regular_entrysincei_mutexlock make each of operations being atomical. So seems above condition can not happen.But still we should handle dirent with zero-sized length correctly, as it may cause deadloop. So how do you think of following patch?
From: Chao Yu <[email protected]>
Subject: [PATCH] f2fs: be aware of invalid filename lengthThe filename length in dirent of may become zero-sized after random junk data injection, once encounter such dirent,
find_target_dentryorf2fs_add_inline_entrieswill run into an infinite loop. So let f2fs being aware of that to avoid deadloop.Signed-off-by Chao Yu <[email protected]>--- fs/f2fs/dir.c | 14 +++++--------- fs/f2fs/inline.c | 14 ++++++-------- 2 files changed, 11 insertions(+), 17 deletions(-)fs/f2fs/dir.c +5
-5@@ -101,11 +101,6 @@ static struct f2fs_dir_entry *find_in_block(struct page *dentry_page, elsekunmap (dentry_page); -/* -* For the most part, it should be a bug when name_len is zero. -* We stop here for figuring out where the bugs has occurred. -*/ -f2fs_bug_on (F2FS_P_SB (dentry_page), d.max <0 );return de; }@@ -130,6 +125,11 @@ struct f2fs_dir_entry *find_target_dentry(structfscrypt_name *fname, de = &d->dentry[bit_pos]; +if (unlikely (!de->name_len)) { + bit_pos++; +continue ; + } +/* encrypted case */ de_name.name = d->filename[bit_pos];fs/f2fs/dir.c +0
-4de_name.len =le16_to_cpu (de->name_len);@@ -147,10 +147,6 @@ struct f2fs_dir_entry *find_target_dentry(structfscrypt_name *fname, *max_slots = max_len; max_len =0 ; -/* remain bug on condition */ -if (unlikely (!de->name_len)) - d->max =-1 ; - bit_pos +=GET_DENTRY_SLOTS (le16_to_cpu (de->name_len)); }fs/f2fs/inline.c +6
-8@@ -303,11 +303,6 @@ struct f2fs_dir_entry *find_in_inline_dir(struct inode *dir,else f2fs_put_page (ipage,0 ); -/* -* For the most part, it should be a bug when name_len is zero. -* We stop here for figuring out where the bugs has occurred. -*/ -f2fs_bug_on (sbi, d.max <0 );return de; }@@ -437,6 +432,12 @@ static int f2fs_add_inline_entries(struct inode *dir, } de = &d.dentry[bit_pos]; + +if (unlikely (!de->name_len)) { + bit_pos++; +continue ; + } + new_name.name = d.filename[bit_pos]; new_name.len = de->name_len;@@ -448,9 +449,6 @@ static int f2fs_add_inline_entries(struct inode *dir,if (err) goto punch_dentry_pages; -if (unlikely (!de->name_len)) - d.max =-1 ; - bit_pos +=GET_DENTRY_SLOTS (le16_to_cpu (de->name_len)); }return 0 ;Signature · 1 line
2.7.2
Hi Jaegeuk,
Quoted 4 lines · Jaegeuk Kim
When fsync is called,
sync_node_pagesfinds a proper direct node pages to flush. But, it locks unrelated direct node pages together unnecessarily.Signed-off-by Jaegeuk Kim <[email protected]>
I've reviewed this patchset, it looks good to me, please add: