Changes to keep dirent cache uptodate.
Dirent cache stored as part of topmost directory's struct file needs to
be marked stale whenever there is a modification in any of the directories
that is part of the union. Modifications(like addition/deletion of new
entries) to a directory can occur from places like mkdir, rmdir, mknod etc.
Signed-off-by: Bharata B Rao <[email protected]>
---
fs/dcache.c | 1
fs/namei.c | 13 +++
fs/union.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++++-
include/linux/dcache.h | 4 -
include/linux/fs.h | 4 +
include/linux/union.h | 3
6 files changed, 201 insertions(+), 2 deletions(-)
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -974,6 +974,7 @@ struct dentry *d_alloc(struct dentry * p
#endif
#ifdef CONFIG_UNION_MOUNT
INIT_LIST_HEAD(&dentry->d_unions);
+ INIT_LIST_HEAD(&dentry->d_overlaid);
dentry->d_unionized = 0;
#endif
INIT_HLIST_NODE(&dentry->d_hash);
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2237,6 +2237,7 @@ static int __open_namei_create(struct na
nd->path.dentry = path->dentry;
if (error)
return error;
+ rdcache_invalidate(&nd->path);
/* Don't check for write permission, don't truncate */
return may_open(nd, 0, flag & ~O_TRUNC);
}
@@ -2682,6 +2683,8 @@ asmlinkage long sys_mknodat(int dfd, con
mode, 0);
break;
}
+ if (!error)
+ rdcache_invalidate(&nd.path);
mnt_drop_write(nd.path.mnt);
out_dput:
path_put_conditional(&path, &nd);
@@ -2757,6 +2760,8 @@ asmlinkage long sys_mkdirat(int dfd, con
if (error)
goto out_dput;
error = vfs_mkdir(nd.path.dentry->d_inode, path.dentry, mode);
+ if (!error)
+ rdcache_invalidate(&nd.path);
mnt_drop_write(nd.path.mnt);
out_dput:
path_put_conditional(&path, &nd);
@@ -3287,6 +3292,8 @@ static long do_rmdir(int dfd, const char
if (error)
goto exit3;
error = vfs_rmdir(nd.path.dentry->d_inode, path.dentry);
+ if (!error)
+ rdcache_invalidate(&nd.path);
mnt_drop_write(nd.path.mnt);
exit3:
path_put_conditional(&path, &nd);
@@ -3375,6 +3382,8 @@ static long do_unlinkat(int dfd, const c
if (error)
goto exit2;
error = vfs_unlink(nd.path.dentry->d_inode, path.dentry);
+ if (!error)
+ rdcache_invalidate(&nd.path);
mnt_drop_write(nd.path.mnt);
exit2:
path_put_conditional(&path, &nd);
@@ -3466,6 +3475,8 @@ asmlinkage long sys_symlinkat(const char
goto out_dput;
error = vfs_symlink(nd.path.dentry->d_inode, path.dentry, from,
S_IALLUGO);
+ if (!error)
+ rdcache_invalidate(&nd.path);
mnt_drop_write(nd.path.mnt);
out_dput:
path_put_conditional(&path, &nd);
@@ -3566,6 +3577,8 @@ asmlinkage long sys_linkat(int olddfd, c
goto out_dput;
error = vfs_link(old_nd.path.dentry, nd.path.dentry->d_inode,
path.dentry);
+ if (!error)
+ rdcache_invalidate(&nd.path);
mnt_drop_write(nd.path.mnt);
out_dput:
path_put_conditional(&path, &nd);
--- a/fs/union.c
+++ b/fs/union.c
@@ -39,6 +39,7 @@ static struct hlist_head *union_hashtabl
static unsigned int union_rhash_mask __read_mostly;
static unsigned int union_rhash_shift __read_mostly;
static struct hlist_head *union_rhashtable __read_mostly;
+static struct hlist_head *readdir_hashtable __read_mostly;
/*
* Locking Rules:
@@ -103,6 +104,18 @@ static int __init init_union(void)
for (loop = 0; loop < (1 << union_rhash_shift); loop++)
INIT_HLIST_HEAD(&union_rhashtable[loop]);
+ readdir_hashtable = alloc_large_system_hash("readdir-cache",
+ sizeof(struct hlist_head),
+ union_hash_entries,
+ 14,
+ 0,
+ &union_rhash_shift,
+ &union_rhash_mask,
+ 0);
+
+ for (loop = 0; loop < (1 << union_rhash_shift); loop++)
+ INIT_HLIST_HEAD(&readdir_hashtable[loop]);
+
readdir_cache = kmem_cache_create("readdir-cache",
sizeof(struct rdcache_entry), 0,
SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
@@ -126,6 +139,7 @@ struct union_mount *union_alloc(struct d
atomic_set(&um->u_count, 1);
INIT_LIST_HEAD(&um->u_unions);
INIT_LIST_HEAD(&um->u_list);
+ INIT_LIST_HEAD(&um->u_overlaid);
INIT_HLIST_NODE(&um->u_hash);
INIT_HLIST_NODE(&um->u_rhash);
@@ -290,6 +304,7 @@ int append_to_union(struct vfsmount *mnt
}
list_add(&this->u_list, &mnt->mnt_unions);
list_add(&this->u_unions, &dentry->d_unions);
+ list_add(&this->u_overlaid, &dest_dentry->d_overlaid);
dest_dentry->d_unionized++;
__union_hash(this);
spin_unlock(&union_lock);
@@ -367,6 +382,22 @@ int follow_union_mount(struct vfsmount *
return res;
}
+int follow_union_up(struct vfsmount **mnt, struct dentry **dentry)
+{
+ struct union_mount *um;
+
+ if (!IS_UNION(*dentry))
+ return 0;
+
+ um = union_rlookup(*dentry, *mnt);
+ if (um) {
+ *dentry = um->u_this.dentry;
+ *mnt = um->u_this.mnt;
+ return 1;
+ }
+ return 0;
+}
+
/*
* is_dir_unioned - check if the directory represented by @path has a union
* stack underneath.
@@ -418,6 +449,7 @@ repeat:
list_del(&this->u_list);
list_del(&this->u_unions);
this->u_next.dentry->d_unionized--;
+ list_del(&this->u_overlaid);
spin_unlock(&union_lock);
union_put(this);
goto repeat;
@@ -447,6 +479,7 @@ repeat:
list_del(&this->u_list);
list_del(&this->u_unions);
this->u_next.dentry->d_unionized--;
+ list_del(&this->u_overlaid);
spin_unlock(&union_lock);
if (__union_put(this)) {
__dput(n_dentry, list);
@@ -474,6 +507,7 @@ repeat:
list_del(&this->u_list);
list_del(&this->u_unions);
this->u_next.dentry->d_unionized--;
+ list_del(&this->u_overlaid);
spin_unlock(&union_lock);
union_put(this);
goto repeat;
@@ -505,6 +539,7 @@ void detach_mnt_union(struct vfsmount *m
list_del(&um->u_list);
list_del(&um->u_unions);
um->u_next.dentry->d_unionized--;
+ list_del(&um->u_overlaid);
spin_unlock(&union_lock);
union_put(um);
return;
@@ -657,6 +692,35 @@ static int filldir_union(void *buf, cons
}
/*
+ * Called when rdcache becomes stale due to modifications to the directory.
+ * Discard the existing rdcache, reread the entries and rebuild the cache
+ * till the current f_pos.
+ * rdcache is marked stale from places like mkdir(), creat() etc.
+ *
+ * CHECK: What if current f_pos can't be established after the change ?
+ */
+static int rebuild_rdcache(struct file *file)
+{
+ struct rdstate *r = file->f_rdstate;
+
+ rdcache_free(&r->dirent_cache);
+
+ path_put(&r->cur_path);
+ r->cur_path = file->f_path;
+ path_get(&r->cur_path);
+
+ /* Fill rdcache until the previously known offset */
+ r->fill_off = r->cur_off - 1;
+
+ r->cur_off = r->last_off = 0;
+ r->cur_dirent = NULL;
+ r->file_off = 0;
+ r->nr_dirents = 0;
+
+ return readdir_union(file, NULL, NULL);
+}
+
+/*
* 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.
@@ -704,6 +768,13 @@ static int readdir_rdcache(struct file *
struct rdcache_entry *tmp = r->cur_dirent;
int err = 0;
+ if (unlikely(r->flags & RDSTATE_STALE)) {
+ r->flags &= ~RDSTATE_STALE;
+ err = rebuild_rdcache(file);
+ if (err)
+ return err;
+ }
+
BUG_ON(r->cur_off > r->last_off);
/* If offsets already uptodate, just return */
@@ -734,6 +805,11 @@ void put_rdstate(struct rdstate *rdstate
return;
mutex_lock(&union_rdmutex);
+ spin_lock(&union_lock);
+ hlist_del(&rdstate->hash);
+ spin_unlock(&union_lock);
+
+ path_put(&rdstate->path);
path_put(&rdstate->cur_path);
rdcache_free(&rdstate->dirent_cache);
mutex_unlock(&union_rdmutex);
@@ -758,10 +834,16 @@ static struct rdstate *get_rdstate(struc
if (!r)
return ERR_PTR(-ENOMEM);
- r->cur_path = file->f_path;
+ r->path = r->cur_path = file->f_path;
+ path_get(&r->path);
path_get(&r->cur_path);
INIT_LIST_HEAD(&r->dirent_cache);
file->f_rdstate = r;
+ INIT_HLIST_NODE(&r->hash);
+ spin_lock(&union_lock);
+ hlist_add_head(&r->hash, readdir_hashtable +
+ hash(r->path.dentry, r->path.mnt));
+ spin_unlock(&union_lock);
return r;
}
@@ -916,6 +998,13 @@ loff_t llseek_union(struct file *file, l
if (!r)
return -EINVAL;
+ if (unlikely(r->flags & RDSTATE_STALE)) {
+ r->flags &= ~RDSTATE_STALE;
+ err = rebuild_rdcache(file);
+ if (err)
+ goto out;
+ }
+
switch (origin) {
case SEEK_END:
/*
@@ -965,6 +1054,93 @@ out:
return err;
}
+static struct rdstate *lookup_rdstate(struct path *path)
+{
+ struct hlist_head *head = readdir_hashtable +
+ hash(path->dentry, path->mnt);
+ struct hlist_node *node;
+ struct rdstate *r;
+
+ hlist_for_each_entry(r, node, head, hash) {
+ if ((r->path.dentry == path->dentry) &&
+ (r->path.mnt == path->mnt))
+ return r;
+ }
+ return NULL;
+}
+
+static inline void mark_rdstate_stale(struct path *path)
+{
+ struct rdstate *r = lookup_rdstate(path);
+
+ if (r)
+ r->flags |= RDSTATE_STALE;
+ return;
+}
+
+/*
+ * Mark rdstates associated with all the unions of this
+ * @path->dentry is part of as stale.
+ */
+static void rdcache_invalidate_union(struct path *path)
+{
+ struct union_mount *um;
+
+ spin_lock(&union_lock);
+ list_for_each_entry(um, &path->dentry->d_unions, u_unions)
+ mark_rdstate_stale(&um->u_this);
+ spin_unlock(&union_lock);
+}
+
+/*
+ * Mark rdstates associated with all the unions where
+ * @path->dentry has been overlaid as stale.
+ */
+static void rdcache_invalidate_overlaid(struct path *path)
+{
+ struct union_mount *um;
+ struct path cur;
+
+ spin_lock(&dcache_lock);
+ spin_lock(&union_lock);
+ list_for_each_entry(um, &path->dentry->d_overlaid, u_overlaid) {
+ /*
+ * CHECK: Here we don't get reference to toplevel dentry of any
+ * union with the understanding that no dentry can go away
+ * with us holding the dcache_lock.
+ */
+ cur = um->u_this;
+ mark_rdstate_stale(&cur);
+ while (follow_union_up(&cur.mnt, &cur.dentry))
+ mark_rdstate_stale(&cur);
+ }
+ spin_unlock(&union_lock);
+ spin_unlock(&dcache_lock);
+}
+
+void rdcache_invalidate(struct path *path)
+{
+ struct path save;
+
+ /* If this dentry is not part of any union, no rdstate to invalidate */
+ if (!IS_UNION(path->dentry))
+ return;
+
+ save = *path;
+ path_get(&save);
+
+ mutex_lock(&union_rdmutex);
+
+ if (!list_empty(&save.dentry->d_unions))
+ rdcache_invalidate_union(&save);
+
+ if (IS_DENTRY_OVERLAID(save.dentry))
+ rdcache_invalidate_overlaid(&save);
+
+ mutex_unlock(&union_rdmutex);
+ path_put(&save);
+}
+
/*
* Union mount copyup support
*/
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -97,9 +97,11 @@ struct dentry {
#ifdef CONFIG_UNION_MOUNT
/*
* The following fields are used by the VFS based union mount
- * implementation. Both are protected by union_lock!
+ * implementation. They are protected by union_lock!
*/
struct list_head d_unions; /* list of union_mount's */
+ struct list_head d_overlaid; /* list of union_mount's from where this
+ dentry is overlaid */
unsigned int d_unionized; /* unions referencing this dentry */
#endif
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -798,6 +798,8 @@ struct rdcache_entry {
struct rdstate {
struct path cur_path; /* Current directory on which readdir is
in progress */
+ struct hlist_node hash; /* used to link into readdir_hashtable */
+ struct path path; /* the path to which this rdstate belongs to */
loff_t file_off; /* File offset of underlying directory */
loff_t cur_off; /* Offset to current dirent in rdcache */
loff_t last_off; /* Offset to last dirent in rdcache */
@@ -815,9 +817,11 @@ struct rdstate {
#define RDSTATE_NEED_UPDATE 0x02
extern void put_rdstate(struct rdstate *rdstate);
+extern void rdcache_invalidate(struct path *path);
#else
#define put_rdstate(x) do { } while (0)
+#define rdcache_invalidate(x) do { } while (0)
#endif
#define FILE_MNT_WRITE_TAKEN 1
--- a/include/linux/union.h
+++ b/include/linux/union.h
@@ -31,6 +31,7 @@ struct union_mount {
struct mutex u_mutex;
struct list_head u_unions; /* list head for d_unions */
struct list_head u_list; /* list head for mnt_unions */
+ struct list_head u_overlaid; /* list head for d_overlaid */
struct hlist_node u_hash; /* list head for seaching */
struct hlist_node u_rhash; /* list head for reverse seaching */
@@ -42,6 +43,7 @@ struct union_mount {
#define IS_UNION(dentry) (!list_empty(&(dentry)->d_unions) || \
(dentry)->d_unionized)
#define IS_MNT_UNION(mnt) ((mnt)->mnt_flags & MNT_UNION)
+#define IS_DENTRY_OVERLAID(dentry) (dentry)->d_unionized
extern int is_unionized(struct dentry *, struct vfsmount *);
extern int append_to_union(struct vfsmount *, struct dentry *,
@@ -70,6 +72,7 @@ extern struct mutex union_rdmutex;
#define IS_UNION(x) (0)
#define IS_MNT_UNION(x) (0)
+#define IS_DENTRY_OVERLAID(x) (0)
#define is_unionized(x, y) (0)
#define append_to_union(x1, y1, x2, y2, z) ({ BUG(); (0); })
#define follow_union_down(x, y) ({ (0); })
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[Index of Archives]
[Kernel Newbies]
[Netfilter]
[Bugtraq]
[Photo]
[Stuff]
[Gimp]
[Yosemite News]
[MIPS Linux]
[ARM Linux]
[Linux Security]
[Linux RAID]
[Video 4 Linux]
[Linux for the blind]
[Linux Resources]