summaryrefslogtreecommitdiffstats
path: root/fs/ext2
diff options
context:
space:
mode:
authorAl Viro <viro@ftp.linux.org.uk>2006-03-15 21:41:59 +0000
committerLinus Torvalds <torvalds@g5.osdl.org>2006-03-15 16:31:51 -0800
commit2d7f2ea9c989853310c7f6e8be52cc090cc8e66b (patch)
treea4fab82e393dc525c1f5d7549c0cbd6758e7523e /fs/ext2
parentf13b83580acef03a36c785dccc534ccdd7e43084 (diff)
downloadlinux-2d7f2ea9c989853310c7f6e8be52cc090cc8e66b.tar.gz
linux-2d7f2ea9c989853310c7f6e8be52cc090cc8e66b.tar.bz2
linux-2d7f2ea9c989853310c7f6e8be52cc090cc8e66b.zip
[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>
Diffstat (limited to 'fs/ext2')
-rw-r--r--fs/ext2/dir.c28
1 files changed, 12 insertions, 16 deletions
diff --git a/fs/ext2/dir.c b/fs/ext2/dir.c
index 7442bdd1267a..b3dbd716cd3a 100644
--- a/fs/ext2/dir.c
+++ b/fs/ext2/dir.c
@@ -256,11 +256,10 @@ ext2_readdir (struct file * filp, void * dirent, filldir_t filldir)
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 * dirent, filldir_t filldir)
"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 * dirent, filldir_t filldir)
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 * dirent, filldir_t filldir)
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;
}
/*