Skip to contentSkip to conversation

ext2_readdir() filp->f_pos fix

PATCH In review Jan Blunck 3 participants 4 messages Jul 29, 2004 – Jul 30, 2004 ext2 file system

lore
Share & tools
lore

Jan Blunck

author
#0 lore raw

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
Index: testing-2.5/fs/ext2/dir.c
===================================================================
@@ -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;
 }
#1 lore raw
Quoted 2 lines · Jan Blunck

If the whole inode is read, ext2_readdir() sets the f_pos to a multiple

^^ directory

Quoted 3 lines

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.

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.

#2 lore raw
Quoted 4 lines · Andrew Morton

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.

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.

author
#3 lore raw
In reply to Andrew Morton
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 advance f_pos to 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;
 }