Hello, This is my first pass at implementing SEEK_HOLE/SEEK_DATA. This has been in solaris for about a year now, and is described here http://docs.sun.com/app/docs/doc/819-2241/lseek-2?l=en&a=view&q=SEEK_HOLE http://blogs.sun.com/roller/page/bonwick?entry=seek_hole_and_seek_data I've added a file operation to allow filesystems to override the default seek_hole_data function, which just loops through bmap looking for either a hole or data. I've tested this and it seems to work well. I ran my testcase on a solaris box to make sure I got consistent results (I just ran my test script on the solaris box, I haven't looked at any of their code in case thats a concern). All comments welcome. Thank you, Josef diff --git a/fs/read_write.c b/fs/read_write.c index ea1f94c..cf61e1e 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -31,10 +31,32 @@ const struct file_operations generic_ro_fops = { EXPORT_SYMBOL(generic_ro_fops); +static loff_t generic_seek_hole_data(struct file *file, loff_t offset, + int origin) +{ + loff_t retval = -ENXIO; + struct inode *inode = file->f_mapping->host; + sector_t block, found_block; + sector_t last_block = (i_size_read(inode) - 1) >> inode->i_blkbits; + int want = (origin == SEEK_HOLE) ? 0 : 1; + + for (block = offset >> inode->i_blkbits; block <= last_block; block++) { + found_block = bmap(inode, block); + + if (!!found_block == want) { + retval = block << inode->i_blkbits; + break; + } + } + + return retval; +} + loff_t generic_file_llseek(struct file *file, loff_t offset, int origin) { long long retval; struct inode *inode = file->f_mapping->host; + loff_t (*fn)(struct file *, loff_t, int); mutex_lock(&inode->i_mutex); switch (origin) { @@ -43,15 +65,24 @@ loff_t generic_file_llseek(struct file *file, loff_t offset, int origin) break; case SEEK_CUR: offset += file->f_pos; + break; + case SEEK_HOLE: + case SEEK_DATA: + fn = generic_seek_hole_data; + if (file->f_op->seek_hole_data) + fn = file->f_op->seek_hole_data; + offset = fn(file, offset, origin); } retval = -EINVAL; if (offset>=0 && offset<=inode->i_sb->s_maxbytes) { - if (offset != file->f_pos) { + if (offset != file->f_pos && origin != SEEK_HOLE) { file->f_pos = offset; file->f_version = 0; } retval = offset; - } + } else if (offset < 0) + retval = offset; + mutex_unlock(&inode->i_mutex); return retval; } diff --git a/include/linux/fs.h b/include/linux/fs.h index b3ec4a4..a55d68e 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -30,7 +30,9 @@ #define SEEK_SET 0 /* seek relative to beginning of file */ #define SEEK_CUR 1 /* seek relative to current file position */ #define SEEK_END 2 /* seek relative to end of file */ -#define SEEK_MAX SEEK_END +#define SEEK_HOLE 3 /* seek relative to the next hole */ +#define SEEK_DATA 4 /* seek relative to the next block with data */ +#define SEEK_MAX SEEK_DATA /* And dynamically-tunable limits and defaults: */ struct files_stat_struct { @@ -1163,6 +1165,7 @@ typedef int (*read_actor_t)(read_descriptor_t *, struct page *, unsigned long, u struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); + loff_t (*seek_hole_data) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); - To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html