ext2_readdir() filp->f_pos fix
PATCH 1/1 ext2_readdir() filp->f_pos fix
If the whole inode is read, ext2_readdir() sets the f_pos to a multiple of the page size (because of the conditions of the outer for loop). This sets the wrong f_pos for directory inodes on ext2 partitions with a block size differing from the page size.
dir.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-)
fs/ext2/dir.c +2 -2
@@ -251,7 +251,7 @@loff_t pos = filp->f_pos;struct inode *inode = filp->f_dentry->d_inode;struct super_block *sb = inode->i_sb; -unsigned offset = pos & ~PAGE_CACHE_MASK ; +unsigned int offset = pos & ~PAGE_CACHE_MASK ;unsigned long n = pos >>PAGE_CACHE_SHIFT ;unsigned long npages =dir_pages (inode);unsigned chunk_mask = ~(ext2_chunk_size (inode)-1 );
@@ -303,6 +303,7 @@ goto success; } } + filp->f_pos +=le16_to_cpu (de->rec_len); }ext2_put_page (page); }
@@ -310,7 +311,6 @@ success: ret =0 ; done: - filp->f_pos = (n <<PAGE_CACHE_SHIFT ) | offset; filp->f_version = inode->i_version;return ret; }
Quoted 2 lines · Jan Blunck
If the whole inode is read,
ext2_readdir()sets thef_posto a multiple
^^ directory
Quoted 3 lines
of the page size (because of the conditions of the outer for loop). This sets the wrong
f_posfor directory inodes on ext2 partitions with a block size differing from the page size.
Interesting. How did you actually notice this? Is the same problem not present in 2.4?
If the IS_ERR(page) returns true, should we not advance f_pos to skip this page?
If the filldir() call returns non-zero your patch will leave f_pos pointing at the problematic directory entry. I'm not sure whether this is desirable.
hmm, ext2_readir() isn't propagating EFAULT back up to the caller.
Quoted 4 lines · Andrew Morton
If the
filldir()call returns non-zero your patch will leavef_pospointing at the problematic directory entry. I'm not sure whether this is desirable.hmm,
ext2_readir()isn't propagating EFAULT back up to the caller.
filldir callback does that. Please, read through fs/readdir.c and take a look at the way error value is generated.
Return value of filldir has only one meaning - should we stop or should we go on. It's boolean, not an error value.
Errors are stored in data we are passing to filldir and picked by caller of vfs_readdir() once it's done.
Quoted 2 lines · Andrew Morton
Interesting. How did you actually notice this? Is the same problem not present in 2.4?
I noticed this problem because I'm "abusing" the f_pos in a VFS based implementation of something like union-mount. There are already some things done but I still need time until its ready for being posted here.
Didn't checked that for 2.4 cause I'm only working on 2.6 at the moment.
Quoted 2 lines
If the
IS_ERR(page)returns true, should we not advancef_posto skip this page?
Ok. I changed that in the attached patch. So when there is a bad page it is possible that the f_pos points somewhere to the beginning of the next page which might be out of range of the i_size. But I think that is ok for a error condition.
dir.c | 11 ++++++++--- 1 files changed, 8 insertions(+), 3 deletions(-)
fs/ext2/dir.c +8 -3
@@ -251,7 +251,7 @@loff_t pos = filp->f_pos;struct inode *inode = filp->f_dentry->d_inode;struct super_block *sb = inode->i_sb; -unsigned offset = pos & ~PAGE_CACHE_MASK ; +unsigned int offset = pos & ~PAGE_CACHE_MASK ;unsigned long n = pos >>PAGE_CACHE_SHIFT ;unsigned long npages =dir_pages (inode);unsigned chunk_mask = ~(ext2_chunk_size (inode)-1 );
@@ -270,8 +270,13 @@ext2_dirent *de;struct page *page =ext2_get_page (inode, n); -if (IS_ERR (page)) +if (IS_ERR (page)) { +ext2_error (sb, __FUNCTION__, +"bad page in #%lu" , + inode->i_ino); + filp->f_pos +=PAGE_CACHE_SIZE - offset;continue ; + } kaddr =page_address (page);if (need_revalidate) { offset =ext2_validate_entry (kaddr, offset, chunk_mask);
@@ -303,6 +308,7 @@ goto success; } } + filp->f_pos +=le16_to_cpu (de->rec_len); }ext2_put_page (page); }
@@ -310,7 +316,6 @@ success: ret =0 ; done: - filp->f_pos = (n <<PAGE_CACHE_SHIFT ) | offset; filp->f_version = inode->i_version;return ret; }