vfs: allow custom EOF in generic_file_llseek code
Subsystems (2)
PATCH 1/1 vfs: allow custom EOF in generic_file_llseek code
For ext3/4 htree directories, using the vfs llseek function with SEEK_END goes to i_size like for any other file, but in reality we want the maximum possible hash value. Recent changes
in ext4 have cut & pasted generic_file_llseek() back into fs/ext4/dir.c, but replicating this core code seems like a bad idea, especially since the copy has already diverged from the vfs.
This patch implements a version of generic_file_llseek which can accept both a custom maximum offset, and a custom EOF position. With this in place, ext4_dir_llseek can pass in the appropriate maximum hash position for both maxsize and eof, and get what it wants.
As far as I know, this does not fix any bugs - nfs in the kernel doesn't use SEEK_END, and I don't know of any user who does. But some ext4 folks seem keen on doing the right thing here, and I can't really argue.
(Patch also fixes up some comments slightly)
---
fs/read_write.c +34 -11
@@ -51,14 +51,15 @@ static loff_t lseek_execute(struct file *file, struct inode *inode, }/** -* generic_file_llseek_size - generic llseek implementation for regular files +* generic_file_llseek_size_eof - generic llseek implementation for regular files * @file: file structure to seek on * @offset: file offset to seek to * @origin: type of seek -* @size: max size of file system +* @size: max size of this file in file system +* @eof: offset used for SEEK_END position * * This is a variant of generic_file_llseek that allows passing in a custom -* file size. +* maximum file size and a custom EOF position, for e.g. hashed directories * * Synchronization: * SEEK_SET and SEEK_END are unsynchronized (but atomic on 64bit platforms)
@@ -66,14 +67,14 @@ static loff_t lseek_execute(struct file *file, struct inode *inode,* read/writes behave like SEEK_SET against seeks. */ loff_t -generic_file_llseek_size (struct file *file,loff_t offset,int origin, -loff_t maxsize) +generic_file_llseek_size_eof (struct file *file,loff_t offset,int origin, +loff_t maxsize,loff_t eof) {struct inode *inode = file->f_mapping->host;switch (origin) {case SEEK_END : - offset +=i_size_read (inode); + offset += eof;break ;case SEEK_CUR :/*
@@ -99,7 +100,7 @@ generic_file_llseek_size(struct file *file, loff_t offset, int origin,* In the generic case the entire file is data, so as long as * offset isn't at the end of the file then the offset is data. */ -if (offset >=i_size_read (inode)) +if (offset >= eof)return -ENXIO ;break ;case SEEK_HOLE :
@@ -107,14 +108,35 @@ generic_file_llseek_size(struct file *file, loff_t offset, int origin,* There is a virtual hole at the end of the file, so as long as * offset isn't i_size or larger, return i_size. */ -if (offset >=i_size_read (inode)) +if (offset >= eof)return -ENXIO ; - offset =i_size_read (inode); + offset = eof;break ; }return lseek_execute (file, inode, offset, maxsize); } +EXPORT_SYMBOL (generic_file_llseek_size_eof); + +/** +* generic_file_llseek_size - generic llseek implementation for regular files +* @file: file structure to seek on +* @offset: file offset to seek to +* @origin: type of seek +* @size: max size of this file in file system +* +* This is a variant of generic_file_llseek that allows passing in a custom +* maximum file size. +*/ +loff_t +generic_file_llseek_size (struct file *file,loff_t offset,int origin, +loff_t maxsize) +{ +struct inode *inode = file->f_mapping->host; + +return generic_file_llseek_size_eof (file, offset, origin, maxsize, +i_size_read (inode)); +}EXPORT_SYMBOL (generic_file_llseek_size);/**
@@ -131,8 +153,9 @@ loff_t generic_file_llseek(struct file *file, loff_t offset, int origin) {struct inode *inode = file->f_mapping->host; -return generic_file_llseek_size (file, offset, origin, - inode->i_sb->s_maxbytes); +return generic_file_llseek_size_eof (file, offset, origin, + inode->i_sb->s_maxbytes, +i_size_read (inode)); }EXPORT_SYMBOL (generic_file_llseek);
include/linux/fs.h +2 -0
@@ -2402,6 +2402,8 @@ extern loff_t no_llseek(struct file *file, loff_t offset, int origin);extern loff_t generic_file_llseek (struct file *file,loff_t offset,int origin);extern loff_t generic_file_llseek_size (struct file *file,loff_t offset,int origin,loff_t maxsize); +extern loff_t generic_file_llseek_size_eof (struct file *file,loff_t offset, +int origin,loff_t maxsize,loff_t eof);extern int generic_file_open (struct inode * inode,struct file * filp);extern int nonseekable_open (struct inode * inode,struct file * filp);
Quoted 12 lines · Eric Sandeen
For ext3/4 htree directories, using the vfs llseek function with
SEEK_ENDgoes toi_sizelike for any other file, but in reality we want the maximum possible hash value. Recent changes
in ext4 have cut & pastedgeneric_file_llseek()back intofs/ext4/dir.c, but replicating this core code seems like a bad idea, especially since the copy has already diverged from the vfs.This patch implements a version of
generic_file_llseekwhich can accept both a custom maximum offset, and a custom EOF position. With this in place,ext4_dir_llseekcan pass in the appropriate maximum hash position for both maxsize and eof, and get what it wants.
Quoted 8 lines
As far as I know, this does not fix any bugs - nfs in the kernel doesn't use
SEEK_END, and I don't know of any user who does. But some ext4 folks seem keen on doing the right thing here, and I can't really argue.(Patch also fixes up some comments slightly)
Signed-off-by Eric Sandeen <[email protected]>
I guess I should ahev done a patch series, although the ext4 patch is so messy it's hard to read as a patch. With the new framework in place, ext4_dir_llseek can just be:
/*
* ext4_dir_llseek() calls generic_file_llseek_size to handle htree
* directories, where the "offset" is in terms of the filename hash
* value instead of the byte offset.
*
* Because we may return a 64-bit hash that is well beyond offset limits,
* we need to pass the max hash as the maximum allowable offset in
* the htree directory case.
*
* For non-htree, ext4_llseek already chooses the proper max offset. */loff_t ext4_dir_llseek(struct file *file, loff_t offset, int origin)
{
struct inode *inode = file->f_mapping->host;
int dx_dir = is_dx_dir(inode);
loff_t htree_max = ext4_get_htree_eof(file);
if (likely(dx_dir))
return generic_file_llseek_size_eof(file, offset, origin,
htree_max, htree_max);
else
return ext4_llseek(file, offset, origin);
}-Eric
Quoted 52 lines · Eric Sandeen
For ext3/4 htree directories, using the vfs llseek function with
SEEK_ENDgoes toi_sizelike for any other file, but in reality we want the maximum possible hash value. Recent changes
in ext4 have cut & pastedgeneric_file_llseek()back intofs/ext4/dir.c, but replicating this core code seems like a bad idea, especially since the copy has already diverged from the vfs.This patch implements a version of
generic_file_llseekwhich can accept both a custom maximum offset, and a custom EOF position. With this in place,ext4_dir_llseekcan pass in the appropriate maximum hash position for both maxsize and eof, and get what it wants.As far as I know, this does not fix any bugs - nfs in the kernel doesn't use
SEEK_END, and I don't know of any user who does. But some ext4 folks seem keen on doing the right thing here, and I can't really argue.(Patch also fixes up some comments slightly)
Signed-off-by Eric Sandeen <[email protected]>---
fs/read_write.c +4
-5@@ -51,14 +51,15 @@ static loff_t lseek_execute(struct file *file, struct inode *inode, }/** -* generic_file_llseek_size - generic llseek implementation for regular files +* generic_file_llseek_size_eof - generic llseek implementation for regular files * @file: file structure to seek on * @offset: file offset to seek to * @origin: type of seek -* @size: max size of file system +* @size: max size of this file in file system +* @eof: offset used for SEEK_END position * * This is a variant of generic_file_llseek that allows passing in a custom -* file size. +* maximum file size and a custom EOF position, for e.g. hashed directories * * Synchronization: * SEEK_SET and SEEK_END are unsynchronized (but atomic on 64bit platforms) @@ -66,14 +67,14 @@ static loff_t lseek_execute(struct file *file, struct inode *inode,* read/writes behave like SEEK_SET against seeks. */ loff_t -generic_file_llseek_size (struct file *file,loff_t offset,int origin, -loff_t maxsize)
fs/read_write.c static loff_t lseek_execute(struct file *file, struct inode *inode, · line 78
Quoted 10 lines
fs/read_write.c +3
-1+generic_file_llseek_size_eof(struct file *file, loff_t offset, int origin, + loff_t maxsize, loff_t eof) { struct inode *inode = file->f_mapping->host;switch (origin) {case SEEK_END : - offset +=i_size_read (inode); + offset += eof;break ;
Here is the only glitch I can see. As Andreas already said before, it might overflow here. Do we need do care about that? As you already said, SEEK_END is unlikely to be ever called for directories. But then we also cannot keep user space from doing weird calls...
Quoted 66 lines
fs/read_write.c +27
-5case SEEK_CUR :/* @@ -99,7 +100,7 @@ generic_file_llseek_size(struct file *file, loff_t offset, int origin,* In the generic case the entire file is data, so as long as * offset isn't at the end of the file then the offset is data. */ -if (offset >=i_size_read (inode)) +if (offset >= eof)return -ENXIO ;break ;case SEEK_HOLE :@@ -107,14 +108,35 @@ generic_file_llseek_size(struct file *file, loff_t offset, int origin,* There is a virtual hole at the end of the file, so as long as * offset isn't i_size or larger, return i_size. */ -if (offset >=i_size_read (inode)) +if (offset >= eof)return -ENXIO ; - offset =i_size_read (inode); + offset = eof;break ; }return lseek_execute (file, inode, offset, maxsize); } +EXPORT_SYMBOL (generic_file_llseek_size_eof); + +/** +* generic_file_llseek_size - generic llseek implementation for regular files +* @file: file structure to seek on +* @offset: file offset to seek to +* @origin: type of seek +* @size: max size of this file in file system +* +* This is a variant of generic_file_llseek that allows passing in a custom +* maximum file size. +*/ +loff_t +generic_file_llseek_size (struct file *file,loff_t offset,int origin, +loff_t maxsize) +{ +struct inode *inode = file->f_mapping->host; + +return generic_file_llseek_size_eof (file, offset, origin, maxsize, +i_size_read (inode)); +}EXPORT_SYMBOL (generic_file_llseek_size);/** @@ -131,8 +153,9 @@ loff_t generic_file_llseek(struct file *file, loff_t offset, int origin) {struct inode *inode = file->f_mapping->host; -return generic_file_llseek_size (file, offset, origin, - inode->i_sb->s_maxbytes); +return generic_file_llseek_size_eof (file, offset, origin, + inode->i_sb->s_maxbytes, +i_size_read (inode)); }EXPORT_SYMBOL (generic_file_llseek);include/linux/fs.h +0
-0
include/linux/fs.h extern loff_t no_llseek(struct file *file, loff_t offset, int origin); · line 2408
Quoted 7 lines
include/linux/fs.h +2
-0@@ -2402,6 +2402,8 @@ extern loff_t no_llseek(struct file *file, loff_t offset, int origin);extern loff_t generic_file_llseek (struct file *file,loff_t offset,int origin);extern loff_t generic_file_llseek_size (struct file *file,loff_t offset,int origin,loff_t maxsize); +extern loff_t generic_file_llseek_size_eof (struct file *file,loff_t offset, +int origin,loff_t maxsize,loff_t eof);extern int generic_file_open (struct inode * inode,struct file * filp);extern int nonseekable_open (struct inode * inode,struct file * filp);
Another question, wouldn't it be better to entirely move generic_file_llseek_size() and generic_file_llseek() into fs.h to make sure it gets inlined?
Cheers,
Bernd
Quoted 1 line · Bernd Schubert
On 04/27/2012 06:21 PM, Eric Sandeen wrote:
...
Quoted 5 lines
Quoted 5 lines
fs/read_write.c +2
-0+generic_file_llseek_size_eof(struct file *file, loff_t offset, int origin, + loff_t maxsize, loff_t eof) { struct inode *inode = file->f_mapping->host;
Quoted 10 lines
fs/read_write.c static loff_t lseek_execute(struct file *file, struct inode *inode, · line 78
Quoted 5 lines
fs/read_write.c +1
-1switch (origin) {case SEEK_END : - offset +=i_size_read (inode); + offset += eof;break ;Here is the only glitch I can see. As Andreas already said before, it might overflow here. Do we need do care about that? As you already said,
SEEK_ENDis unlikely to be ever called for directories. But then we also cannot keep user space from doing weird calls...
It can happen already today, for a sufficiently large file offset.
# ls -l reallybigfile -rw-r--r--. 1 root root 9223372036854775807 Apr 27 18:02 reallybigfile
(that's 2^63 - 1)
so overflow protection may be warranted in here, but I think it's a separate problem.
...
Quoted 8 lines
Quoted 8 lines
include/linux/fs.h +0
-0@@ -2402,6 +2402,8 @@ extern loff_t no_llseek(struct file *file, loff_t offset, int origin);extern loff_t generic_file_llseek (struct file *file,loff_t offset,int origin);extern loff_t generic_file_llseek_size (struct file *file,loff_t offset,int origin,loff_t maxsize);
Quoted 8 lines
include/linux/fs.h extern loff_t no_llseek(struct file *file, loff_t offset, int origin); · line 2408
Quoted 4 lines
include/linux/fs.h +2
-0+extern loff_t generic_file_llseek_size_eof (struct file *file,loff_t offset, +int origin,loff_t maxsize,loff_t eof);extern int generic_file_open (struct inode * inode,struct file * filp);extern int nonseekable_open (struct inode * inode,struct file * filp);Another question, wouldn't it be better to entirely move
generic_file_llseek_size()andgeneric_file_llseek()intofs.hto make sure it gets inlined?
Hm, perhaps. It wasn't done for generic_file_llseek_size() so I just followed that example, for now.
-Eric
Quoted 4 lines · Eric Sandeen
As far as I know, this does not fix any bugs - nfs in the kernel doesn't use
SEEK_END, and I don't know of any user who does. But some ext4 folks seem keen on doing the right thing here, and I can't really argue.
I like it. In particular it removes a lot of calls to i_size_read() which may have some nice benefits on 32-bit systems.
However, there is only one call to generic_file_llseek_size() in the kernel (and it's in ext4!) I would suggest simply changing the prototype of generic_file_llseek_size ... or if you insist, just renaming it to generic_file_llseek_size_eof().
Signature · 4 lines
Matthew Wilcox Intel Open Source Technology Centre "Bill, look, we understand that you're interested in selling us this operating system, but compare it to ours. We can't possibly take such a retrograde step."
Quoted 6 lines · Matthew Wilcox
Quoted 4 lines · Eric Sandeen
As far as I know, this does not fix any bugs - nfs in the kernel doesn't use
SEEK_END, and I don't know of any user who does. But some ext4 folks seem keen on doing the right thing here, and I can't really argue.
Quoted 7 lines
I like it. In particular it removes a lot of calls to
i_size_read()which may have some nice benefits on 32-bit systems.However, there is only one call to
generic_file_llseek_size()in the kernel (and it's in ext4!) I would suggest simply changing the prototype ofgeneric_file_llseek_size... or if you insist, just renaming it togeneric_file_llseek_size_eof().
Ok, if both users are only in ext* you're right, probably no need to have both variants with 2 kinds of special sauce. Is it cool to change the prototype of the existing function, or should I rename it? I guess since it'll properly break any current users in obvious ways, I could just add a new argument to the _size() variant. I'll send a V2.
Thanks,
-Eric
PATCH v2 1/3 vfs: allow custom EOF in generic_file_llseek code
In reply to Matthew WilcoxFor ext3/4 htree directories, using the vfs llseek function with SEEK_END goes to i_size like for any other file, but in reality we want the maximum possible hash value. Recent changes
in ext4 have cut & pasted generic_file_llseek() back into fs/ext4/dir.c, but replicating this core code seems like a bad idea, especially since the copy has already diverged from the vfs.
This patch updates generic_file_llseek_size to accept both a custom maximum offset, and a custom EOF position. With this in place, ext4_dir_llseek can pass in the appropriate maximum hash position for both maxsize and eof, and get what it wants.
As far as I know, this does not fix any bugs - nfs in the kernel doesn't use SEEK_END, and I don't know of any user who does. But some ext4 folks seem keen on doing the right thing here, and I can't really argue.
(Patch also fixes up some comments slightly)
---
V2: Just add a new argument to the existing generic_file_llseek_size() function, per Matthew Wilcox's suggestion.
fs/read_write.c +10 -8
@@ -55,10 +55,11 @@ static loff_t lseek_execute(struct file *file, struct inode *inode,* @file: file structure to seek on * @offset: file offset to seek to * @origin: type of seek -* @size: max size of file system +* @size: max size of this file in file system +* @eof: offset used for SEEK_END position * * This is a variant of generic_file_llseek that allows passing in a custom -* file size. +* maximum file size and a custom EOF position, for e.g. hashed directories * * Synchronization: * SEEK_SET and SEEK_END are unsynchronized (but atomic on 64bit platforms)
@@ -67,13 +68,13 @@ static loff_t lseek_execute(struct file *file, struct inode *inode,*/ loff_t generic_file_llseek_size (struct file *file,loff_t offset,int origin, -loff_t maxsize) +loff_t maxsize,loff_t eof) {struct inode *inode = file->f_mapping->host;switch (origin) {case SEEK_END : - offset +=i_size_read (inode); + offset += eof;break ;case SEEK_CUR :/*
@@ -99,7 +100,7 @@ generic_file_llseek_size(struct file *file, loff_t offset, int origin,* In the generic case the entire file is data, so as long as * offset isn't at the end of the file then the offset is data. */ -if (offset >=i_size_read (inode)) +if (offset >= eof)return -ENXIO ;break ;case SEEK_HOLE :
@@ -107,9 +108,9 @@ generic_file_llseek_size(struct file *file, loff_t offset, int origin,* There is a virtual hole at the end of the file, so as long as * offset isn't i_size or larger, return i_size. */ -if (offset >=i_size_read (inode)) +if (offset >= eof)return -ENXIO ; - offset =i_size_read (inode); + offset = eof;break ; }
@@ -132,7 +133,8 @@ loff_t generic_file_llseek(struct file *file, loff_t offset, int origin)struct inode *inode = file->f_mapping->host;return generic_file_llseek_size (file, offset, origin, - inode->i_sb->s_maxbytes); + inode->i_sb->s_maxbytes, +i_size_read (inode)); }EXPORT_SYMBOL (generic_file_llseek);
include/linux/fs.h +1 -1
@@ -2401,7 +2401,7 @@ extern loff_t noop_llseek(struct file *file, loff_t offset, int origin);extern loff_t no_llseek (struct file *file,loff_t offset,int origin);extern loff_t generic_file_llseek (struct file *file,loff_t offset,int origin);extern loff_t generic_file_llseek_size (struct file *file,loff_t offset, -int origin,loff_t maxsize); +int origin,loff_t maxsize,loff_t eof);extern int generic_file_open (struct inode * inode,struct file * filp);extern int nonseekable_open (struct inode * inode,struct file * filp);
PATCH 2/3 ext4: use core vfs llseek code for dir seeks
In reply to Matthew WilcoxUse the new functionality in generic_file_llseek_size() to accept a custom EOF position, and un-cut-and-paste all the vfs llseek code from ext4.
Also fix up comments on ext4_llseek() to reflect reality.
---
fs/ext4/dir.c +14 -61
@@ -312,74 +312,27 @@ static inline loff_t ext4_get_htree_eof(struct file *filp)/* -* ext4_dir_llseek() based on generic_file_llseek() to handle both -* non-htree and htree directories, where the "offset" is in terms -* of the filename hash value instead of the byte offset. +* ext4_dir_llseek() calls generic_file_llseek_size to handle htree +* directories, where the "offset" is in terms of the filename hash +* value instead of the byte offset. * -* NOTE: offsets obtained *before* ext4_set_inode_flag(dir, EXT4_INODE_INDEX) -* will be invalid once the directory was converted into a dx directory +* Because we may return a 64-bit hash that is well beyond offset limits, +* we need to pass the max hash as the maximum allowable offset in +* the htree directory case. +* +* For non-htree, ext4_llseek already chooses the proper max offset. */ loff_t ext4_dir_llseek (struct file *file,loff_t offset,int origin) {struct inode *inode = file->f_mapping->host; -loff_t ret = -EINVAL ;int dx_dir =is_dx_dir (inode); +loff_t htree_max =ext4_get_htree_eof (file); -mutex_lock (&inode->i_mutex); - -/* NOTE: relative offsets with dx directories might not work -* as expected, as it is difficult to figure out the -* correct offset between dx hashes */ - -switch (origin) { -case SEEK_END : -if (unlikely (offset >0 )) - goto out_err;/* not supported for directories */ - -/* so only negative offsets are left, does that have a -* meaning for directories at all? */ -if (dx_dir) - offset +=ext4_get_htree_eof (file); -else - offset += inode->i_size; -break ; -case SEEK_CUR : -/* -* Here we special-case the lseek(fd, 0, SEEK_CUR) -* position-querying operation. Avoid rewriting the "same" -* f_pos value back to the file because a concurrent read(), -* write() or lseek() might have altered it -*/ -if (offset ==0 ) { - offset = file->f_pos; - goto out_ok; - } - - offset += file->f_pos; -break ; - } - -if (unlikely (offset <0 )) - goto out_err; - -if (!dx_dir) { -if (offset > inode->i_sb->s_maxbytes) - goto out_err; - }else if (offset >ext4_get_htree_eof (file)) - goto out_err; - -/* Special lock needed here? */ -if (offset != file->f_pos) { - file->f_pos = offset; - file->f_version =0 ; - } - -out_ok: - ret = offset; -out_err: -mutex_unlock (&inode->i_mutex); - -return ret; +if (likely (dx_dir)) +return generic_file_llseek_size (file, offset, origin, + htree_max, htree_max); +else +return ext4_llseek (file, offset, origin); }/*
fs/ext4/file.c +5 -4
@@ -211,9 +211,9 @@ static int ext4_file_open(struct inode * inode, struct file * filp) }/* -* ext4_llseek() copied from generic_file_llseek() to handle both -* block-mapped and extent-mapped maxbytes values. This should -* otherwise be identical with generic_file_llseek(). +* ext4_llseek() handles both block-mapped and extent-mapped maxbytes values +* by calling generic_file_llseek_size() with the appropriate maxbytes +* value for each. */ loff_t ext4_llseek (struct file *file,loff_t offset,int origin) {
@@ -225,7 +225,8 @@ loff_t ext4_llseek(struct file *file, loff_t offset, int origin)else maxbytes = inode->i_sb->s_maxbytes; -return generic_file_llseek_size (file, offset, origin, maxbytes); +return generic_file_llseek_size (file, offset, origin, + maxbytes,i_size_read (inode)); }const struct file_operations ext4_file_operations = {
PATCH 3/3 ext3: pass custom EOF to generic_file_llseek_size()
In reply to Matthew WilcoxUse the new custom EOF argument to generic_file_llseek_size so that SEEK_END will go to the max hash value for htree dirs in ext3 rather than to i_size_read()
---
fs/ext3/dir.c +2 -1
@@ -300,10 +300,11 @@ loff_t ext3_dir_llseek(struct file *file, loff_t offset, int origin) {struct inode *inode = file->f_mapping->host;int dx_dir =is_dx_dir (inode); +loff_t htree_max =ext3_get_htree_eof (file);if (likely (dx_dir))return generic_file_llseek_size (file, offset, origin, -ext3_get_htree_eof (file)); + htree_max, htree_max);else return generic_file_llseek (file, offset, origin); }
Re: [PATCH 3/3] ext3: pass custom EOF to generic_file_llseek_size()
Quoted 5 lines · Eric Sandeen
Use the new custom EOF argument to
generic_file_llseek_sizeso thatSEEK_ENDwill go to the max hash value for htree dirs in ext3 rather than toi_size_read()Signed-off-by Eric Sandeen <[email protected]>
Looks good. I suppose you want to merge all three patches at once so:
Honza
Quoted 21 lines
---
fs/ext3/dir.c +2
-1@@ -300,10 +300,11 @@ loff_t ext3_dir_llseek(struct file *file, loff_t offset, int origin) {struct inode *inode = file->f_mapping->host;int dx_dir =is_dx_dir (inode); +loff_t htree_max =ext3_get_htree_eof (file);if (likely (dx_dir))return generic_file_llseek_size (file, offset, origin, -ext3_get_htree_eof (file)); + htree_max, htree_max);else return generic_file_llseek (file, offset, origin); }
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR