diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2010-10-25 13:48:29 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2010-10-25 13:48:29 -0700 |
commit | 74eb94b218d087798a52c0b4f1379b635287a4b8 (patch) | |
tree | 4e467c3014c2b1169f6f71d88cf5d1598f3ce28e /fs | |
parent | 7b6181e06841f5ad15c4ff708b967b4db65a64de (diff) | |
parent | 9a84d38031c258a17bb39beed1e500eadee67407 (diff) |
Merge branch 'nfs-for-2.6.37' of git://git.linux-nfs.org/projects/trondmy/nfs-2.6
* 'nfs-for-2.6.37' of git://git.linux-nfs.org/projects/trondmy/nfs-2.6: (67 commits)
SUNRPC: Cleanup duplicate assignment in rpcauth_refreshcred
nfs: fix unchecked value
Ask for time_delta during fsinfo probe
Revalidate caches on lock
SUNRPC: After calling xprt_release(), we must restart from call_reserve
NFSv4: Fix up the 'dircount' hint in encode_readdir
NFSv4: Clean up nfs4_decode_dirent
NFSv4: nfs4_decode_dirent must clear entry->fattr->valid
NFSv4: Fix a regression in decode_getfattr
NFSv4: Fix up decode_attr_filehandle() to handle the case of empty fh pointer
NFS: Ensure we check all allocation return values in new readdir code
NFS: Readdir plus in v4
NFS: introduce generic decode_getattr function
NFS: check xdr_decode for errors
NFS: nfs_readdir_filler catch all errors
NFS: readdir with vmapped pages
NFS: remove page size checking code
NFS: decode_dirent should use an xdr_stream
SUNRPC: Add a helper function xdr_inline_peek
NFS: remove readdir plus limit
...
Diffstat (limited to 'fs')
-rw-r--r-- | fs/lockd/clntlock.c | 15 | ||||
-rw-r--r-- | fs/lockd/clntproc.c | 13 | ||||
-rw-r--r-- | fs/nfs/Kconfig | 11 | ||||
-rw-r--r-- | fs/nfs/client.c | 17 | ||||
-rw-r--r-- | fs/nfs/dir.c | 1013 | ||||
-rw-r--r-- | fs/nfs/file.c | 81 | ||||
-rw-r--r-- | fs/nfs/idmap.c | 211 | ||||
-rw-r--r-- | fs/nfs/inode.c | 36 | ||||
-rw-r--r-- | fs/nfs/internal.h | 12 | ||||
-rw-r--r-- | fs/nfs/mount_clnt.c | 2 | ||||
-rw-r--r-- | fs/nfs/nfs2xdr.c | 107 | ||||
-rw-r--r-- | fs/nfs/nfs3proc.c | 62 | ||||
-rw-r--r-- | fs/nfs/nfs3xdr.c | 196 | ||||
-rw-r--r-- | fs/nfs/nfs4_fs.h | 4 | ||||
-rw-r--r-- | fs/nfs/nfs4proc.c | 279 | ||||
-rw-r--r-- | fs/nfs/nfs4state.c | 40 | ||||
-rw-r--r-- | fs/nfs/nfs4xdr.c | 340 | ||||
-rw-r--r-- | fs/nfs/nfsroot.c | 566 | ||||
-rw-r--r-- | fs/nfs/proc.c | 35 | ||||
-rw-r--r-- | fs/nfs/read.c | 1 | ||||
-rw-r--r-- | fs/nfs/super.c | 72 | ||||
-rw-r--r-- | fs/nfs/sysctl.c | 2 | ||||
-rw-r--r-- | fs/nfs/unlink.c | 259 | ||||
-rw-r--r-- | fs/nfs/write.c | 18 |
24 files changed, 2000 insertions, 1392 deletions
diff --git a/fs/lockd/clntlock.c b/fs/lockd/clntlock.c index 64fd427c993..d5bb86866e6 100644 --- a/fs/lockd/clntlock.c +++ b/fs/lockd/clntlock.c @@ -42,6 +42,7 @@ struct nlm_wait { }; static LIST_HEAD(nlm_blocked); +static DEFINE_SPINLOCK(nlm_blocked_lock); /** * nlmclnt_init - Set up per-NFS mount point lockd data structures @@ -97,7 +98,10 @@ struct nlm_wait *nlmclnt_prepare_block(struct nlm_host *host, struct file_lock * block->b_lock = fl; init_waitqueue_head(&block->b_wait); block->b_status = nlm_lck_blocked; + + spin_lock(&nlm_blocked_lock); list_add(&block->b_list, &nlm_blocked); + spin_unlock(&nlm_blocked_lock); } return block; } @@ -106,7 +110,9 @@ void nlmclnt_finish_block(struct nlm_wait *block) { if (block == NULL) return; + spin_lock(&nlm_blocked_lock); list_del(&block->b_list); + spin_unlock(&nlm_blocked_lock); kfree(block); } @@ -154,6 +160,7 @@ __be32 nlmclnt_grant(const struct sockaddr *addr, const struct nlm_lock *lock) * Look up blocked request based on arguments. * Warning: must not use cookie to match it! */ + spin_lock(&nlm_blocked_lock); list_for_each_entry(block, &nlm_blocked, b_list) { struct file_lock *fl_blocked = block->b_lock; @@ -178,6 +185,7 @@ __be32 nlmclnt_grant(const struct sockaddr *addr, const struct nlm_lock *lock) wake_up(&block->b_wait); res = nlm_granted; } + spin_unlock(&nlm_blocked_lock); return res; } @@ -216,10 +224,6 @@ reclaimer(void *ptr) allow_signal(SIGKILL); down_write(&host->h_rwsem); - - /* This one ensures that our parent doesn't terminate while the - * reclaim is in progress */ - lock_kernel(); lockd_up(); /* note: this cannot fail as lockd is already running */ dprintk("lockd: reclaiming locks for host %s\n", host->h_name); @@ -260,16 +264,17 @@ restart: dprintk("NLM: done reclaiming locks for host %s\n", host->h_name); /* Now, wake up all processes that sleep on a blocked lock */ + spin_lock(&nlm_blocked_lock); list_for_each_entry(block, &nlm_blocked, b_list) { if (block->b_host == host) { block->b_status = nlm_lck_denied_grace_period; wake_up(&block->b_wait); } } + spin_unlock(&nlm_blocked_lock); /* Release host handle after use */ nlm_release_host(host); lockd_down(); - unlock_kernel(); return 0; } diff --git a/fs/lockd/clntproc.c b/fs/lockd/clntproc.c index 7932c399fab..47ea1e1925b 100644 --- a/fs/lockd/clntproc.c +++ b/fs/lockd/clntproc.c @@ -166,7 +166,6 @@ int nlmclnt_proc(struct nlm_host *host, int cmd, struct file_lock *fl) /* Set up the argument struct */ nlmclnt_setlockargs(call, fl); - lock_kernel(); if (IS_SETLK(cmd) || IS_SETLKW(cmd)) { if (fl->fl_type != F_UNLCK) { call->a_args.block = IS_SETLKW(cmd) ? 1 : 0; @@ -177,10 +176,8 @@ int nlmclnt_proc(struct nlm_host *host, int cmd, struct file_lock *fl) status = nlmclnt_test(call, fl); else status = -EINVAL; - fl->fl_ops->fl_release_private(fl); fl->fl_ops = NULL; - unlock_kernel(); dprintk("lockd: clnt proc returns %d\n", status); return status; @@ -226,9 +223,7 @@ void nlm_release_call(struct nlm_rqst *call) static void nlmclnt_rpc_release(void *data) { - lock_kernel(); nlm_release_call(data); - unlock_kernel(); } static int nlm_wait_on_grace(wait_queue_head_t *queue) @@ -448,14 +443,18 @@ out: static void nlmclnt_locks_copy_lock(struct file_lock *new, struct file_lock *fl) { + spin_lock(&fl->fl_u.nfs_fl.owner->host->h_lock); new->fl_u.nfs_fl.state = fl->fl_u.nfs_fl.state; new->fl_u.nfs_fl.owner = nlm_get_lockowner(fl->fl_u.nfs_fl.owner); list_add_tail(&new->fl_u.nfs_fl.list, &fl->fl_u.nfs_fl.owner->host->h_granted); + spin_unlock(&fl->fl_u.nfs_fl.owner->host->h_lock); } static void nlmclnt_locks_release_private(struct file_lock *fl) { + spin_lock(&fl->fl_u.nfs_fl.owner->host->h_lock); list_del(&fl->fl_u.nfs_fl.list); + spin_unlock(&fl->fl_u.nfs_fl.owner->host->h_lock); nlm_put_lockowner(fl->fl_u.nfs_fl.owner); } @@ -721,9 +720,7 @@ static void nlmclnt_unlock_callback(struct rpc_task *task, void *data) die: return; retry_rebind: - lock_kernel(); nlm_rebind_host(req->a_host); - unlock_kernel(); retry_unlock: rpc_restart_call(task); } @@ -801,9 +798,7 @@ retry_cancel: /* Don't ever retry more than 3 times */ if (req->a_retries++ >= NLMCLNT_MAX_RETRIES) goto die; - lock_kernel(); nlm_rebind_host(req->a_host); - unlock_kernel(); rpc_restart_call(task); rpc_delay(task, 30 * HZ); } diff --git a/fs/nfs/Kconfig b/fs/nfs/Kconfig index b950415d7c4..5c55c26af16 100644 --- a/fs/nfs/Kconfig +++ b/fs/nfs/Kconfig @@ -118,3 +118,14 @@ config NFS_USE_KERNEL_DNS select DNS_RESOLVER select KEYS default y + +config NFS_USE_NEW_IDMAPPER + bool "Use the new idmapper upcall routine" + depends on NFS_V4 && KEYS + help + Say Y here if you want NFS to use the new idmapper upcall functions. + You will need /sbin/request-key (usually provided by the keyutils + package). For details, read + <file:Documentation/filesystems/nfs/idmapper.txt>. + + If you are unsure, say N. diff --git a/fs/nfs/client.c b/fs/nfs/client.c index e7340729af8..a882785eba4 100644 --- a/fs/nfs/client.c +++ b/fs/nfs/client.c @@ -635,7 +635,8 @@ static int nfs_create_rpc_client(struct nfs_client *clp, */ static void nfs_destroy_server(struct nfs_server *server) { - if (!(server->flags & NFS_MOUNT_NONLM)) + if (!(server->flags & NFS_MOUNT_LOCAL_FLOCK) || + !(server->flags & NFS_MOUNT_LOCAL_FCNTL)) nlmclnt_done(server->nlm_host); } @@ -657,7 +658,8 @@ static int nfs_start_lockd(struct nfs_server *server) if (nlm_init.nfs_version > 3) return 0; - if (server->flags & NFS_MOUNT_NONLM) + if ((server->flags & NFS_MOUNT_LOCAL_FLOCK) && + (server->flags & NFS_MOUNT_LOCAL_FCNTL)) return 0; switch (clp->cl_proto) { @@ -901,8 +903,8 @@ static void nfs_server_set_fsinfo(struct nfs_server *server, struct nfs_fsinfo * server->wtmult = nfs_block_bits(fsinfo->wtmult, NULL); server->dtsize = nfs_block_size(fsinfo->dtpref, NULL); - if (server->dtsize > PAGE_CACHE_SIZE) - server->dtsize = PAGE_CACHE_SIZE; + if (server->dtsize > PAGE_CACHE_SIZE * NFS_MAX_READDIR_PAGES) + server->dtsize = PAGE_CACHE_SIZE * NFS_MAX_READDIR_PAGES; if (server->dtsize > server->rsize) server->dtsize = server->rsize; @@ -913,6 +915,8 @@ static void nfs_server_set_fsinfo(struct nfs_server *server, struct nfs_fsinfo * server->maxfilesize = fsinfo->maxfilesize; + server->time_delta = fsinfo->time_delta; + /* We're airborne Set socket buffersize */ rpc_setbufsize(server->client, server->wsize + 100, server->rsize + 100); } @@ -1356,8 +1360,9 @@ static int nfs4_init_server(struct nfs_server *server, /* Initialise the client representation from the mount data */ server->flags = data->flags; - server->caps |= NFS_CAP_ATOMIC_OPEN|NFS_CAP_CHANGE_ATTR| - NFS_CAP_POSIX_LOCK; + server->caps |= NFS_CAP_ATOMIC_OPEN|NFS_CAP_CHANGE_ATTR|NFS_CAP_POSIX_LOCK; + if (!(data->flags & NFS_MOUNT_NORDIRPLUS)) + server->caps |= NFS_CAP_READDIRPLUS; server->options = data->options; /* Get a client record */ diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index e257172d438..257e4052492 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -33,11 +33,12 @@ #include <linux/namei.h> #include <linux/mount.h> #include <linux/sched.h> +#include <linux/vmalloc.h> -#include "nfs4_fs.h" #include "delegation.h" #include "iostat.h" #include "internal.h" +#include "fscache.h" /* #define NFS_DEBUG_VERBOSE 1 */ @@ -55,6 +56,7 @@ static int nfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *); static int nfs_fsync_dir(struct file *, int); static loff_t nfs_llseek_dir(struct file *, loff_t, int); +static int nfs_readdir_clear_array(struct page*, gfp_t); const struct file_operations nfs_dir_operations = { .llseek = nfs_llseek_dir, @@ -80,6 +82,10 @@ const struct inode_operations nfs_dir_inode_operations = { .setattr = nfs_setattr, }; +const struct address_space_operations nfs_dir_addr_space_ops = { + .releasepage = nfs_readdir_clear_array, +}; + #ifdef CONFIG_NFS_V3 const struct inode_operations nfs3_dir_inode_operations = { .create = nfs_create, @@ -104,8 +110,9 @@ const struct inode_operations nfs3_dir_inode_operations = { #ifdef CONFIG_NFS_V4 static struct dentry *nfs_atomic_lookup(struct inode *, struct dentry *, struct nameidata *); +static int nfs_open_create(struct inode *dir, struct dentry *dentry, int mode, struct nameidata *nd); const struct inode_operations nfs4_dir_inode_operations = { - .create = nfs_create, + .create = nfs_open_create, .lookup = nfs_atomic_lookup, .link = nfs_link, .unlink = nfs_unlink, @@ -150,51 +157,197 @@ nfs_opendir(struct inode *inode, struct file *filp) return res; } -typedef __be32 * (*decode_dirent_t)(__be32 *, struct nfs_entry *, int); +struct nfs_cache_array_entry { + u64 cookie; + u64 ino; + struct qstr string; +}; + +struct nfs_cache_array { + unsigned int size; + int eof_index; + u64 last_cookie; + struct nfs_cache_array_entry array[0]; +}; + +#define MAX_READDIR_ARRAY ((PAGE_SIZE - sizeof(struct nfs_cache_array)) / sizeof(struct nfs_cache_array_entry)) + +typedef __be32 * (*decode_dirent_t)(struct xdr_stream *, struct nfs_entry *, struct nfs_server *, int); typedef struct { struct file *file; struct page *page; unsigned long page_index; - __be32 *ptr; u64 *dir_cookie; loff_t current_index; - struct nfs_entry *entry; decode_dirent_t decode; - int plus; + unsigned long timestamp; unsigned long gencount; - int timestamp_valid; + unsigned int cache_entry_index; + unsigned int plus:1; + unsigned int eof:1; } nfs_readdir_descriptor_t; -/* Now we cache directories properly, by stuffing the dirent - * data directly in the page cache. - * - * Inode invalidation due to refresh etc. takes care of - * _everything_, no sloppy entry flushing logic, no extraneous - * copying, network direct to page cache, the way it was meant - * to be. - * - * NOTE: Dirent information verification is done always by the - * page-in of the RPC reply, nowhere else, this simplies - * things substantially. +/* + * The caller is responsible for calling nfs_readdir_release_array(page) */ static -int nfs_readdir_filler(nfs_readdir_descriptor_t *desc, struct page *page) +struct nfs_cache_array *nfs_readdir_get_array(struct page *page) +{ + if (page == NULL) + return ERR_PTR(-EIO); + return (struct nfs_cache_array *)kmap(page); +} + +static +void nfs_readdir_release_array(struct page *page) +{ + kunmap(page); +} + +/* + * we are freeing strings created by nfs_add_to_readdir_array() + */ +static +int nfs_readdir_clear_array(struct page *page, gfp_t mask) +{ + struct nfs_cache_array *array = nfs_readdir_get_array(page); + int i; + for (i = 0; i < array->size; i++) + kfree(array->array[i].string.name); + nfs_readdir_release_array(page); + return 0; +} + +/* + * the caller is responsible for freeing qstr.name + * when called by nfs_readdir_add_to_array, the strings will be freed in + * nfs_clear_readdir_array() + */ +static +int nfs_readdir_make_qstr(struct qstr *string, const char *name, unsigned int len) +{ + string->len = len; + string->name = kmemdup(name, len, GFP_KERNEL); + if (string->name == NULL) + return -ENOMEM; + string->hash = full_name_hash(name, len); + return 0; +} + +static +int nfs_readdir_add_to_array(struct nfs_entry *entry, struct page *page) +{ + struct nfs_cache_array *array = nfs_readdir_get_array(page); + struct nfs_cache_array_entry *cache_entry; + int ret; + + if (IS_ERR(array)) + return PTR_ERR(array); + ret = -EIO; + if (array->size >= MAX_READDIR_ARRAY) + goto out; + + cache_entry = &array->array[array->size]; + cache_entry->cookie = entry->prev_cookie; + cache_entry->ino = entry->ino; + ret = nfs_readdir_make_qstr(&cache_entry->string, entry->name, entry->len); + if (ret) + goto out; + array->last_cookie = entry->cookie; + if (entry->eof == 1) + array->eof_index = array->size; + array->size++; +out: + nfs_readdir_release_array(page); + return ret; +} + +static +int nfs_readdir_search_for_pos(struct nfs_cache_array *array, nfs_readdir_descriptor_t *desc) +{ + loff_t diff = desc->file->f_pos - desc->current_index; + unsigned int index; + + if (diff < 0) + goto out_eof; + if (diff >= array->size) { + if (array->eof_index > 0) + goto out_eof; + desc->current_index += array->size; + return -EAGAIN; + } + + index = (unsigned int)diff; + *desc->dir_cookie = array->array[index].cookie; + desc->cache_entry_index = index; + if (index == array->eof_index) + desc->eof = 1; + return 0; +out_eof: + desc->eof = 1; + return -EBADCOOKIE; +} + +static +int nfs_readdir_search_for_cookie(struct nfs_cache_array *array, nfs_readdir_descriptor_t *desc) +{ + int i; + int status = -EAGAIN; + + for (i = 0; i < array->size; i++) { + if (i == array->eof_index) { + desc->eof = 1; + status = -EBADCOOKIE; + } + if (array->array[i].cookie == *desc->dir_cookie) { + desc->cache_entry_index = i; + status = 0; + break; + } + } + + return status; +} + +static +int nfs_readdir_search_array(nfs_readdir_descriptor_t *desc) +{ + struct nfs_cache_array *array; + int status = -EBADCOOKIE; + + if (desc->dir_cookie == NULL) + goto out; + + array = nfs_readdir_get_array(desc->page); + if (IS_ERR(array)) { + status = PTR_ERR(array); + goto out; + } + + if (*desc->dir_cookie == 0) + status = nfs_readdir_search_for_pos(array, desc); + else + status = nfs_readdir_search_for_cookie(array, desc); + + nfs_readdir_release_array(desc->page); +out: + return status; +} + +/* Fill a page with xdr information before transferring to the cache page */ +static +int nfs_readdir_xdr_filler(struct page **pages, nfs_readdir_descriptor_t *desc, + struct nfs_entry *entry, struct file *file, struct inode *inode) { - struct file *file = desc->file; - struct inode *inode = file->f_path.dentry->d_inode; struct rpc_cred *cred = nfs_file_cred(file); unsigned long timestamp, gencount; int error; - dfprintk(DIRCACHE, "NFS: %s: reading cookie %Lu into page %lu\n", - __func__, (long long)desc->entry->cookie, - page->index); - again: timestamp = jiffies; gencount = nfs_inc_attr_generation_counter(); - error = NFS_PROTO(inode)->readdir(file->f_path.dentry, cred, desc->entry->cookie, page, + error = NFS_PROTO(inode)->readdir(file->f_path.dentry, cred, entry->cookie, pages, NFS_SERVER(inode)->dtsize, desc->plus); if (error < 0) { /* We requested READDIRPLUS, but the server doesn't grok it */ @@ -208,190 +361,292 @@ int nfs_readdir_filler(nfs_readdir_descriptor_t *desc, struct page *page) } desc->timestamp = timestamp; desc->gencount = gencount; - desc->timestamp_valid = 1; - SetPageUptodate(page); - /* Ensure consistent page alignment of the data. - * Note: assumes we have exclusive access to this mapping either - * through inode->i_mutex or some other mechanism. - */ - if (invalidate_inode_pages2_range(inode->i_mapping, page->index + 1, -1) < 0) { - /* Should never happen */ - nfs_zap_mapping(inode, inode->i_mapping); - } - unlock_page(page); - return 0; - error: - unlock_page(page); - return -EIO; +error: + return error; } -static inline -int dir_decode(nfs_readdir_descriptor_t *desc) +/* Fill in an entry based on the xdr code stored in desc->page */ +static +int xdr_decode(nfs_readdir_descriptor_t *desc, struct nfs_entry *entry, struct xdr_stream *stream) { - __be32 *p = desc->ptr; - p = desc->decode(p, desc->entry, desc->plus); + __be32 *p = desc->decode(stream, entry, NFS_SERVER(desc->file->f_path.dentry->d_inode), desc->plus); if (IS_ERR(p)) return PTR_ERR(p); - desc->ptr = p; - if (desc->timestamp_valid) { - desc->entry->fattr->time_start = desc->timestamp; - desc->entry->fattr->gencount = desc->gencount; - } else - desc->entry->fattr->valid &= ~NFS_ATTR_FATTR; + + entry->fattr->time_start = desc->timestamp; + entry->fattr->gencount = desc->gencount; return 0; } -static inline -void dir_page_release(nfs_readdir_descriptor_t *desc) +static +int nfs_same_file(struct dentry *dentry, struct nfs_entry *entry) { - kunmap(desc->page); - page_cache_release(desc->page); - desc->page = NULL; - desc->ptr = NULL; + struct nfs_inode *node; + if (dentry->d_inode == NULL) + goto different; + node = NFS_I(dentry->d_inode); + if (node->fh.size != entry->fh->size) + goto different; + if (strncmp(node->fh.data, entry->fh->data, node->fh.size) != 0) + goto different; + return 1; +different: + return 0; } -/* - * Given a pointer to a buffer that has already been filled by a call - * to readdir, find the next entry with cookie '*desc->dir_cookie'. - * - * If the end of the buffer has been reached, return -EAGAIN, if not, - * return the offset within the buffer of the next entry to be - * read. - */ -static inline -int find_dirent(nfs_readdir_descriptor_t *desc) +static +void nfs_prime_dcache(struct dentry *parent, struct nfs_entry *entry) { - struct nfs_entry *entry = desc->entry; - int loop_count = 0, - status; + struct qstr filename = { + .len = entry->len, + .name = entry->name, + }; + struct dentry *dentry; + struct dentry *alias; + struct inode *dir = parent->d_inode; + struct inode *inode; - while((status = dir_decode(desc)) == 0) { - dfprintk(DIRCACHE, "NFS: %s: examining cookie %Lu\n", - __func__, (unsigned long long)entry->cookie); - if (entry->prev_cookie == *desc->dir_cookie) - break; - if (loop_count++ > 200) { - loop_count = 0; - schedule(); + if (filename.name[0] == '.') { + if (filename.len == 1) + return; + if (filename.len == 2 && filename.name[1] == '.') + return; + } + filename.hash = full_name_hash(filename.name, filename.len); + + dentry = d_lookup(parent, &filename); + if (dentry != NULL) { + if (nfs_same_file(dentry, entry)) { + nfs_refresh_inode(dentry->d_inode, entry->fattr); + goto out; + } else { + d_drop(dentry); + dput(dentry); } } - return status; + + dentry = d_alloc(parent, &filename); + if (dentry == NULL) + return; + + dentry->d_op = NFS_PROTO(dir)->dentry_ops; + inode = nfs_fhget(dentry->d_sb, entry->fh, entry->fattr); + if (IS_ERR(inode)) + goto out; + + alias = d_materialise_unique(dentry, inode); + if (IS_ERR(alias)) + goto out; + else if (alias) { + nfs_set_verifier(alias, nfs_save_change_attribute(dir)); + dput(alias); + } else + nfs_set_verifier(dentry, nfs_save_change_attribute(dir)); + +out: + dput(dentry); +} + +/* Perform conversion from xdr to cache array */ +static +void nfs_readdir_page_filler(nfs_readdir_descriptor_t *desc, struct nfs_entry *entry, + void *xdr_page, struct page *page, unsigned int buflen) +{ + struct xdr_stream stream; + struct xdr_buf buf; + __be32 *ptr = xdr_page; + int status; + struct nfs_cache_array *array; + + buf.head->iov_base = xdr_page; + buf.head->iov_len = buflen; + buf.tail->iov_len = 0; + buf.page_base = 0; + buf.page_len = 0; + buf.buflen = buf.head->iov_len; + buf.len = buf.head->iov_len; + + xdr_init_decode(&stream, &buf, ptr); + + + do { + status = xdr_decode(desc, entry, &stream); + if (status != 0) + break; + + if (nfs_readdir_add_to_array(entry, page) == -1) + break; + if (desc->plus == 1) + nfs_prime_dcache(desc->file->f_path.dentry, entry); + } while (!entry->eof); + + if (status == -EBADCOOKIE && entry->eof) { + array = nfs_readdir_get_array(page); + array->eof_index = array->size - 1; + status = 0; + nfs_readdir_release_array(page); + } +} + +static +void nfs_readdir_free_pagearray(struct page **pages, unsigned int npages) +{ + unsigned int i; + for (i = 0; i < npages; i++) + put_page(pages[i]); +} + +static +void nfs_readdir_free_large_page(void *ptr, struct page **pages, + unsigned int npages) +{ + vm_unmap_ram(ptr, npages); + nfs_readdir_free_pagearray(pages, npages); } /* - * Given a pointer to a buffer that has already been filled by a call - * to readdir, find the entry at offset 'desc->file->f_pos'. - * - * If the end of the buffer has been reached, return -EAGAIN, if not, - * return the offset within the buffer of the next entry to be - * read. + * nfs_readdir_large_page will allocate pages that must be freed with a call + * to nfs_readdir_free_large_page */ -static inline -int find_dirent_index(nfs_readdir_descriptor_t *desc) +static +void *nfs_readdir_large_page(struct page **pages, unsigned int npages) { - struct nfs_entry *entry = desc->entry; - int loop_count = 0, - status; + void *ptr; + unsigned int i; + + for (i = 0; i < npages; i++) { + struct page *page = alloc_page(GFP_KERNEL); + if (page == NULL) + goto out_freepages; + pages[i] = page; + } - for(;;) { - status = dir_decode(desc); - if (status) - break; + ptr = vm_map_ram(pages, npages, 0, PAGE_KERNEL); + if (!IS_ERR_OR_NULL(ptr)) + return ptr; +out_freepages: + nfs_readdir_free_pagearray(pages, i); + return NULL; +} + +static +int nfs_readdir_xdr_to_array(nfs_readdir_descriptor_t *desc, struct page *page, struct inode *inode) +{ + struct page *pages[NFS_MAX_READDIR_PAGES]; + void *pages_ptr = NULL; + struct nfs_entry entry; + struct file *file = desc->file; + struct nfs_cache_array *array; + int status = 0; + unsigned int array_size = ARRAY_SIZE(pages); + + entry.prev_cookie = 0; + entry.cookie = *desc->dir_cookie; + entry.eof = 0; + entry.fh = nfs_alloc_fhandle(); + entry.fattr = nfs_alloc_fattr(); + if (entry.fh == NULL || entry.fattr == NULL) + goto out; - dfprintk(DIRCACHE, "NFS: found cookie %Lu at index %Ld\n", - (unsigned long long)entry->cookie, desc->current_index); + array = nfs_readdir_get_array(page); + memset(array, 0, sizeof(struct nfs_cache_array)); + array->eof_index = -1; - if (desc->file->f_pos == desc->current_index) { - *desc->dir_cookie = entry->cookie; + pages_ptr = nfs_readdir_large_page(pages, array_size); + if (!pages_ptr) + goto out_release_array; + do { + status = nfs_readdir_xdr_filler(pages, desc, &entry, file, inode); + + if (status < 0) break; - } - desc->current_index++; - if (loop_count++ > 200) { - loop_count = 0; - schedule(); - } - } + nfs_readdir_page_filler(desc, &entry, pages_ptr, page, array_size * PAGE_SIZE); + } while (array->eof_index < 0 && array->size < MAX_READDIR_ARRAY); + + nfs_readdir_free_large_page(pages_ptr, pages, array_size); +out_release_array: + nfs_readdir_release_array(page); +out: + nfs_free_fattr(entry.fattr); + nfs_free_fhandle(entry.fh); return status; } /* - * Find the given page, and call find_dirent() or find_dirent_index in - * order to try to return the next entry. + * Now we cache directories properly, by converting xdr information + * to an array that can be used for lookups later. This results in + * fewer cache pages, since we can store more information on each page. + * We only need to convert from xdr once so future lookups are much simpler */ -static inline -int find_dirent_page(nfs_readdir_descriptor_t *desc) +static +int nfs_readdir_filler(nfs_readdir_descriptor_t *desc, struct page* page) { struct inode *inode = desc->file->f_path.dentry->d_inode; - struct page *page; - int status; - dfprintk(DIRCACHE, "NFS: %s: searching page %ld for target %Lu\n", - __func__, desc->page_index, - (long long) *desc->dir_cookie); + if (nfs_readdir_xdr_to_array(desc, page, inode) < 0) + goto error; + SetPageUptodate(page); - /* If we find the page in the page_cache, we cannot be sure - * how fresh the data is, so we will ignore readdir_plus attributes. - */ - desc->timestamp_valid = 0; - page = read_cache_page(inode->i_mapping, desc->page_index, - (filler_t *)nfs_readdir_filler, desc); - if (IS_ERR(page)) { - status = PTR_ERR(page); - goto out; + if (invalidate_inode_pages2_range(inode->i_mapping, page->index + 1, -1) < 0) { + /* Should never happen */ + nfs_zap_mapping(inode, inode->i_mapping); } + unlock_page(page); + return 0; + error: + unlock_page(page); + return -EIO; +} - /* NOTE: Someone else may have changed the READDIRPLUS flag */ - desc->page = page; - desc->ptr = kmap(page); /* matching kunmap in nfs_do_filldir */ - if (*desc->dir_cookie != 0) - status = find_dirent(desc); - else - status = find_dirent_index(desc); - if (status < 0) - dir_page_release(desc); - out: - dfprintk(DIRCACHE, "NFS: %s: returns %d\n", __func__, status); - return status; +static +void cache_page_release(nfs_readdir_descriptor_t *desc) +{ + page_cache_release(desc->page); + desc->page = NULL; +} + +static +struct page *get_cache_page(nfs_readdir_descriptor_t *desc) +{ + struct page *page; + page = read_cache_page(desc->file->f_path.dentry->d_inode->i_mapping, + desc->page_index, (filler_t *)nfs_readdir_filler, desc); + if (IS_ERR(page)) + desc->eof = 1; + return page; } /* - * Recurse through the page cache pages, and return a - * filled nfs_entry structure of the next directory entry if possible. - * - * The target for the search is '*desc->dir_cookie' if non-0, - * 'desc->file->f_pos' otherwise + * Returns 0 if desc->dir_cookie was found on page desc->page_index */ +static +int find_cache_page(nfs_readdir_descriptor_t *desc) +{ + int res; + + desc->page = get_cache_page(desc); + if (IS_ERR(desc->page)) + return PTR_ERR(desc->page); + + res = nfs_readdir_search_array(desc); + if (res == 0) + return 0; + cache_page_release(desc); + return res; +} + +/* Search for desc->dir_cookie from the beginning of the page cache */ static inline int readdir_search_pagecache(nfs_readdir_descriptor_t *desc) { - int loop_count = 0; - int res; - - /* Always search-by-index from the beginning of the cache */ - if (*desc->dir_cookie == 0) { - dfprintk(DIRCACHE, "NFS: readdir_search_pagecache() searching for offset %Ld\n", - (long long)desc->file->f_pos); - desc->page_index = 0; - desc->entry->cookie = desc->entry->prev_cookie = 0; - desc->entry->eof = 0; - desc->current_index = 0; - } else - dfprintk(DIRCACHE, "NFS: readdir_search_pagecache() searching for cookie %Lu\n", - (unsigned long long)*desc->dir_cookie); + int res = -EAGAIN; - for (;;) { - res = find_dirent_page(desc); + while (1) { + res = find_cache_page(desc); if (res != -EAGAIN) break; - /* Align to beginning of next page */ - desc->page_index ++; - if (loop_count++ > 200) { - loop_count = 0; - schedule(); - } + desc->page_index++; } - - dfprintk(DIRCACHE, "NFS: %s: returns %d\n", __func__, res); return res; |