| From nobody Mon Sep 17 00:00:00 2001 |
| From: Al Viro <viro@ftp.linux.org.uk> |
| Date: Wed Mar 15 21:41:59 2006 +0000 |
| Subject: [PATCH] Fix ext2 readdir f_pos re-validation logic |
| |
| This fixes not one, but _two_, silly (but admittedly hard to hit) bugs |
| in the ext2 filesystem "readdir()" function. It also cleans up the code |
| to avoid the unnecessary goto mess. |
| |
| The bugs were related to re-valiating the f_pos value after somebody had |
| either done an "lseek()" on the directory to an invalid offset, or when |
| the offset had become invalid due to a file being unlinked in the |
| directory. The code would not only set the f_version too eagerly, it |
| would also not update f_pos appropriately for when the offset fixup took |
| place. |
| |
| When that happened, we'd occasionally subsequently fail the readdir() |
| even when we shouldn't (no real harm done, but an ugly printk, and |
| obviously you would end up not necessarily seeing all entries). |
| |
| Thanks to Masoud Sharbiani <masouds@google.com> who noticed the problem |
| and had a test-case for it, and also fixed up a thinko in the first |
| version of this patch. |
| |
| Signed-off-by: Al Viro <viro@zeniv.linux.org.uk> |
| Acked-by: Masoud Sharbiani <masouds@google.com> |
| Signed-off-by: Linus Torvalds <torvalds@osdl.org> |
| Signed-off-by: Chris Wright <chrisw@sous-sol.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> |
| --- |
| |
| fs/ext2/dir.c | 28 ++++++++++++---------------- |
| 1 file changed, 12 insertions(+), 16 deletions(-) |
| |
| 2d7f2ea9c989853310c7f6e8be52cc090cc8e66b |
| --- linux-2.6.15.6.orig/fs/ext2/dir.c |
| +++ linux-2.6.15.6/fs/ext2/dir.c |
| @@ -256,11 +256,10 @@ ext2_readdir (struct file * filp, void * |
| unsigned long npages = dir_pages(inode); |
| unsigned chunk_mask = ~(ext2_chunk_size(inode)-1); |
| unsigned char *types = NULL; |
| - int need_revalidate = (filp->f_version != inode->i_version); |
| - int ret; |
| + int need_revalidate = filp->f_version != inode->i_version; |
| |
| if (pos > inode->i_size - EXT2_DIR_REC_LEN(1)) |
| - goto success; |
| + return 0; |
| |
| if (EXT2_HAS_INCOMPAT_FEATURE(sb, EXT2_FEATURE_INCOMPAT_FILETYPE)) |
| types = ext2_filetype_table; |
| @@ -275,12 +274,15 @@ ext2_readdir (struct file * filp, void * |
| "bad page in #%lu", |
| inode->i_ino); |
| filp->f_pos += PAGE_CACHE_SIZE - offset; |
| - ret = -EIO; |
| - goto done; |
| + return -EIO; |
| } |
| kaddr = page_address(page); |
| - if (need_revalidate) { |
| - offset = ext2_validate_entry(kaddr, offset, chunk_mask); |
| + if (unlikely(need_revalidate)) { |
| + if (offset) { |
| + offset = ext2_validate_entry(kaddr, offset, chunk_mask); |
| + filp->f_pos = (n<<PAGE_CACHE_SHIFT) + offset; |
| + } |
| + filp->f_version = inode->i_version; |
| need_revalidate = 0; |
| } |
| de = (ext2_dirent *)(kaddr+offset); |
| @@ -289,9 +291,8 @@ ext2_readdir (struct file * filp, void * |
| if (de->rec_len == 0) { |
| ext2_error(sb, __FUNCTION__, |
| "zero-length directory entry"); |
| - ret = -EIO; |
| ext2_put_page(page); |
| - goto done; |
| + return -EIO; |
| } |
| if (de->inode) { |
| int over; |
| @@ -306,19 +307,14 @@ ext2_readdir (struct file * filp, void * |
| le32_to_cpu(de->inode), d_type); |
| if (over) { |
| ext2_put_page(page); |
| - goto success; |
| + return 0; |
| } |
| } |
| filp->f_pos += le16_to_cpu(de->rec_len); |
| } |
| ext2_put_page(page); |
| } |
| - |
| -success: |
| - ret = 0; |
| -done: |
| - filp->f_version = inode->i_version; |
| - return ret; |
| + return 0; |
| } |
| |
| /* |