Directory seek support. Define the seek behaviour on the stored cache of dirents. Signed-off-by: Bharata B Rao <bharata@xxxxxxxxxxxxxxxxxx> --- fs/read_write.c | 11 --- fs/union.c | 171 +++++++++++++++++++++++++++++++++++++++++++++++++- include/linux/fs.h | 8 ++ include/linux/union.h | 25 +++++++ 4 files changed, 205 insertions(+), 10 deletions(-) --- a/fs/read_write.c +++ b/fs/read_write.c @@ -16,6 +16,7 @@ #include <linux/syscalls.h> #include <linux/pagemap.h> #include <linux/splice.h> +#include <linux/union.h> #include "read_write.h" #include <asm/uaccess.h> @@ -116,15 +117,7 @@ EXPORT_SYMBOL(default_llseek); loff_t vfs_llseek(struct file *file, loff_t offset, int origin) { - loff_t (*fn)(struct file *, loff_t, int); - - fn = no_llseek; - if (file->f_mode & FMODE_LSEEK) { - fn = default_llseek; - if (file->f_op && file->f_op->llseek) - fn = file->f_op->llseek; - } - return fn(file, offset, origin); + return do_llseek(file, offset, origin); } EXPORT_SYMBOL(vfs_llseek); --- a/fs/union.c +++ b/fs/union.c @@ -614,6 +614,7 @@ static int rdcache_add_entry(struct rdst this->dtype = d_type; INIT_LIST_HEAD(&this->list); list_add_tail(&this->list, list); + r->cur_dirent = this; return 0; } @@ -636,18 +637,96 @@ static int filldir_union(void *buf, cons if (rdcache_find_entry(&r->dirent_cache, name, namlen)) return 0; - err = cb->filldir(cb->buf, name, namlen, r->cur_off, + /* We come here with NULL cb->filldir from lseek path */ + if (cb->filldir) + err = cb->filldir(cb->buf, name, namlen, r->cur_off, ino, d_type); if (err >= 0) { rdcache_add_entry(r, &r->dirent_cache, name, namlen, offset, ino, d_type); r->cur_off = ++r->last_off; r->nr_dirents++; + if (r->cur_off == r->fill_off) { + /* We filled up to the required seek offset */ + r->fill_off = 0; + err = -EINVAL; + } } cb->error = err; return err; } +/* + * This is called when current offset in rdcache gets changed and when + * we need to change the current underlying directory in the rdstate + * to match the current offset. + */ +static void update_rdstate(struct file *file) +{ + struct rdstate *r = file->f_rdstate; + loff_t off = r->cur_off; + struct union_mount *um; + + if (!(r->flags & RDSTATE_NEED_UPDATE)) + return; + + spin_lock(&union_lock); + um = union_lookup(file->f_path.dentry, file->f_path.mnt); + spin_unlock(&union_lock); + if (!um) + goto out; + off -= um->nr_dirents; + path_put(&r->cur_path); + r->cur_path = file->f_path; + path_get(&r->cur_path); + + while (off > 0) { + spin_lock(&union_lock); + um = union_lookup(r->cur_path.dentry, r->cur_path.mnt); + spin_unlock(&union_lock); + if (!um) + goto out; + off -= um->nr_dirents; + path_put(&r->cur_path); + r->cur_path = um->u_next; + path_get(&r->cur_path); + } +out: + r->file_off = r->cur_dirent->off; +} + +/* + * Returns dirents from rdcache to userspace. + */ +static int readdir_rdcache(struct file *file, struct rdcache_callback *cb) +{ + struct rdstate *r = cb->rdstate; + struct rdcache_entry *tmp = r->cur_dirent; + int err = 0; + + BUG_ON(r->cur_off > r->last_off); + + /* If offsets already uptodate, just return */ + if (likely(r->cur_off == r->last_off)) + return 0; + + /* + * return the entries from cur_off till last_off from rdcache to + * user space. + */ + list_for_each_entry_from(tmp, &r->dirent_cache, list) { + err = cb->filldir(cb->buf, tmp->name.name, tmp->name.len, + r->cur_off, tmp->ino, tmp->dtype); + r->cur_dirent = tmp; + if (err < 0) + break; + r->cur_off++; + r->flags |= RDSTATE_NEED_UPDATE; + } + update_rdstate(file); + return err; +} + /* Called from last fput() */ void put_rdstate(struct rdstate *rdstate) { @@ -710,6 +789,10 @@ int readdir_union(struct file *file, voi cb.rdstate = rdstate; cb.error = 0; + err = readdir_rdcache(file, &cb); + if (err) + return err; + offset = rdstate->file_off; /* Read from the topmost directory */ @@ -796,6 +879,92 @@ out: return err; } +static void rdcache_rewind(struct file *file, struct rdstate *r, loff_t offset) +{ + struct rdcache_entry *tmp = r->cur_dirent; + + list_for_each_entry_reverse_from(tmp, &r->dirent_cache, list) { + if (r->cur_off == offset) + break; + r->cur_dirent = tmp; + r->cur_off--; + r->flags |= RDSTATE_NEED_UPDATE; + } + update_rdstate(file); +} + +static void rdcache_forward(struct file *file, struct rdstate *r, loff_t offset) +{ + struct rdcache_entry *tmp = r->cur_dirent; + + list_for_each_entry_continue(tmp, &r->dirent_cache, list) { + if (r->cur_off == offset) + break; + r->cur_dirent = tmp; + r->cur_off++; + r->flags |= RDSTATE_NEED_UPDATE; + } + update_rdstate(file); +} + +loff_t llseek_union(struct file *file, loff_t offset, int origin) +{ + loff_t err; + struct rdstate *r = file->f_rdstate; + loff_t orig_off = file->f_rdstate->cur_off; + + if (!r) + return -EINVAL; + + switch (origin) { + case SEEK_END: + /* + * To support this, we need to know the end of the directory, + * which may need reading _all_ the entries from the filesystem + * to readdir cache, which can be expensive. + * + * After we have the last entry in the cache, do + * offset += r->last_off; + */ + err = -EINVAL; + goto out; + case SEEK_CUR: + offset += r->cur_off; + break; + } + err = -EINVAL; + if (offset < 0) + goto out; + + if (offset > r->cur_off) { + /* Seek forward into the cache */ + rdcache_forward(file, r, offset); + if (offset > r->cur_off) { + /* + * Need to seek beyond the cache, read dirents from into + * underlying directories into rdcache. + */ + r->fill_off = offset; + err = readdir_union(file, NULL, NULL); + if (err < 0) + goto out; + + /* readdir() failed, restore the original offset. */ + if (offset != r->cur_off) { + rdcache_rewind(file, r, orig_off); + err = -EINVAL; + goto out; + } + err = offset; + } + } else { + rdcache_rewind(file, r, offset); + err = offset; + } +out: + return err; +} + /* * Union mount copyup support */ --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -803,9 +803,17 @@ struct rdstate { loff_t last_off; /* Offset to last dirent in rdcache */ loff_t nr_dirents; /* Number of entries from current underlying directory in rdcache */ + loff_t fill_off; /* Fill cache upto this offset. Used during + seek */ struct list_head dirent_cache; /* cache of directory entries */ + struct rdcache_entry *cur_dirent; /* pointer to current directory + entry in rdcache which corresponds to cur_off */ + int flags; }; +#define RDSTATE_STALE 0x01 +#define RDSTATE_NEED_UPDATE 0x02 + extern void put_rdstate(struct rdstate *rdstate); #else --- a/include/linux/union.h +++ b/include/linux/union.h @@ -55,6 +55,7 @@ extern int attach_mnt_union(struct vfsmo struct dentry *); extern void detach_mnt_union(struct vfsmount *); extern int readdir_union(struct file *, void *, filldir_t); +extern loff_t llseek_union(struct file *, loff_t, int); extern int last_union_is_root(struct path *); extern int is_dir_unioned(struct path *); extern int union_relookup_topmost(struct nameidata *, int); @@ -110,5 +111,29 @@ static inline int do_readdir(struct file return res; } +static inline loff_t do_llseek(struct file *file, loff_t offset, int origin) +{ + long long res; +#ifdef CONFIG_UNION_MOUNT + if (IS_MNT_UNION(file->f_path.mnt) && is_dir_unioned(&file->f_path)) { + mutex_lock(&union_rdmutex); + res = llseek_union(file, offset, origin); + mutex_unlock(&union_rdmutex); + } else +#endif + { + loff_t (*fn)(struct file *, loff_t, int); + + fn = no_llseek; + if (file->f_mode & FMODE_LSEEK) { + fn = default_llseek; + if (file->f_op && file->f_op->llseek) + fn = file->f_op->llseek; + } + res = fn(file, offset, origin); + } + return res; +} + #endif /* __KERNEL__ */ #endif /* __LINUX_UNION_H */ - 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