diff options
| author | Trond Myklebust <trond.myklebust@fys.uio.no> | 2002-07-28 02:46:16 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@home.transmeta.com> | 2002-07-28 02:46:16 -0700 |
| commit | 668609dcfcc4f951763151fd784c1ee439f0f70d (patch) | |
| tree | c196e33345878d2a76082d4bdd976a84b12ff659 | |
| parent | c385d3e11995b638e5e6d26b26125d372a0fbf4a (diff) | |
[PATCH] Support for cached lookups via readdirplus [4/6]
Add support for positive lookups using the READDIRPLUS cached
information. Both new lookups and lookup revalidation is supported.
Use READDIRPLUS instead of READDIR on NFSv3 directories with lengths
shorter than 8*PAGE_SIZE.
Note that inode attribute information is only updated if it is seen to
be more recent than any existing cached information.
| -rw-r--r-- | fs/nfs/dir.c | 100 | ||||
| -rw-r--r-- | fs/nfs/inode.c | 6 |
2 files changed, 104 insertions, 2 deletions
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 40f073a44dae..43315c8b8764 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -37,6 +37,8 @@ static int nfs_readdir(struct file *, void *, filldir_t); static struct dentry *nfs_lookup(struct inode *, struct dentry *); +static int nfs_cached_lookup(struct inode *, struct dentry *, + struct nfs_fh *, struct nfs_fattr *); static int nfs_create(struct inode *, struct dentry *, int); static int nfs_mkdir(struct inode *, struct dentry *, int); static int nfs_rmdir(struct inode *, struct dentry *); @@ -504,6 +506,15 @@ static int nfs_lookup_revalidate(struct dentry * dentry, int flags) goto out_valid; } + error = nfs_cached_lookup(dir, dentry, &fhandle, &fattr); + if (!error) { + if (memcmp(NFS_FH(inode), &fhandle, sizeof(struct nfs_fh))!= 0) + goto out_bad; + if (nfs_lookup_verify_inode(inode)) + goto out_bad; + goto out_valid_renew; + } + if (NFS_STALE(inode)) goto out_bad; @@ -515,6 +526,7 @@ static int nfs_lookup_revalidate(struct dentry * dentry, int flags) if ((error = nfs_refresh_inode(inode, &fattr)) != 0) goto out_bad; + out_valid_renew: nfs_renew_times(dentry); out_valid: unlock_kernel(); @@ -578,7 +590,7 @@ struct dentry_operations nfs_dentry_operations = { static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry) { - struct inode *inode; + struct inode *inode = NULL; int error; struct nfs_fh fhandle; struct nfs_fattr fattr; @@ -594,8 +606,19 @@ static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry) dentry->d_op = &nfs_dentry_operations; lock_kernel(); + error = nfs_cached_lookup(dir, dentry, &fhandle, &fattr); + if (!error) { + error = -EACCES; + inode = nfs_fhget(dentry, &fhandle, &fattr); + if (inode) { + d_add(dentry, inode); + nfs_renew_times(dentry); + error = 0; + } + goto out; + } + error = NFS_PROTO(dir)->lookup(dir, &dentry->d_name, &fhandle, &fattr); - inode = NULL; if (error == -ENOENT) goto no_entry; if (!error) { @@ -613,6 +636,79 @@ out: return ERR_PTR(error); } +static inline +int find_dirent_name(nfs_readdir_descriptor_t *desc, struct page *page, struct dentry *dentry) +{ + struct nfs_entry *entry = desc->entry; + int status; + + while((status = dir_decode(desc)) == 0) { + if (entry->len != dentry->d_name.len) + continue; + if (memcmp(entry->name, dentry->d_name.name, entry->len)) + continue; + if (!(entry->fattr->valid & NFS_ATTR_FATTR)) + continue; + break; + } + return status; +} + +/* + * Use the cached Readdirplus results in order to avoid a LOOKUP call + * whenever we believe that the parent directory has not changed. + * + * We assume that any file creation/rename changes the directory mtime. + * As this results in a page cache invalidation whenever it occurs, + * we don't require any other tests for cache coherency. + */ +static +int nfs_cached_lookup(struct inode *dir, struct dentry *dentry, + struct nfs_fh *fh, struct nfs_fattr *fattr) +{ + nfs_readdir_descriptor_t desc; + struct nfs_server *server; + struct nfs_entry entry; + struct page *page; + unsigned long timestamp = NFS_MTIME_UPDATE(dir); + int res; + + if (!NFS_USE_READDIRPLUS(dir)) + return -ENOENT; + server = NFS_SERVER(dir); + if (server->flags & NFS_MOUNT_NOAC) + return -ENOENT; + nfs_revalidate_inode(server, dir); + + entry.fh = fh; + entry.fattr = fattr; + + desc.decode = NFS_PROTO(dir)->decode_dirent; + desc.entry = &entry; + desc.page_index = 0; + desc.plus = 1; + + for(;(page = find_get_page(&dir->i_data, desc.page_index)); desc.page_index++) { + + res = -EIO; + if (PageUptodate(page)) { + desc.ptr = kmap(page); + res = find_dirent_name(&desc, page, dentry); + kunmap(page); + } + page_cache_release(page); + + if (res == 0) + goto out_found; + if (res != -EAGAIN) + break; + } + return -ENOENT; + out_found: + fattr->timestamp = timestamp; + return 0; +} + /* * Code common to create, mkdir, and mknod. */ diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index b39842050096..a547f76fc080 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -644,6 +644,9 @@ nfs_fhget(struct dentry *dentry, struct nfs_fh *fhandle, return __nfs_fhget(sb, fhandle, fattr); } +/* Don't use READDIRPLUS on directories that we believe are too large */ +#define NFS_LIMIT_READDIRPLUS (8*PAGE_SIZE) + /* * Look up the inode by super block and fattr->fileid. */ @@ -692,6 +695,9 @@ __nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr) } else if (S_ISDIR(inode->i_mode)) { inode->i_op = &nfs_dir_inode_operations; inode->i_fop = &nfs_dir_operations; + if (nfs_server_capable(inode, NFS_CAP_READDIRPLUS) + && fattr->size <= NFS_LIMIT_READDIRPLUS) + NFS_FLAGS(inode) |= NFS_INO_ADVISE_RDPLUS; } else if (S_ISLNK(inode->i_mode)) inode->i_op = &nfs_symlink_inode_operations; else |
