/*
 * Find the given page, and call find_dirent() in order to try to
 * return the next entry.
 */
static inline
int find_dirent_page(nfs_readdir_descriptor_t *desc)
{
	struct inode	*inode = desc->file->f_dentry->d_inode;
	struct page	*page;
	int		status;

	dfprintk(VFS, "NFS: find_dirent_page() searching directory page %ld\n", desc->page_index);

	desc->plus = NFS_USE_READDIRPLUS(inode);
	page = read_cache_page(&inode->i_data, desc->page_index,
			       (filler_t *)nfs_readdir_filler, desc);
	if (IS_ERR(page)) {
		status = PTR_ERR(page);
		goto out;
	}
	if (!Page_Uptodate(page))
		goto read_error;

	/* NOTE: Someone else may have changed the READDIRPLUS flag */
	desc->page = page;
	desc->ptr = kmap(page);
	status = find_dirent(desc, page);
	if (status < 0)
		dir_page_release(desc);
 out:
	dfprintk(VFS, "NFS: find_dirent_page() returns %d\n", status);
	return status;
 read_error:
	page_cache_release(page);
	return -EIO;
}
/*
 * 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;
	int res;

	if (!NFS_USE_READDIRPLUS(dir))
		return -ENOENT;
	server = NFS_SERVER(dir);
	/* Don't use readdirplus unless the cache is stable */
	if ((server->flags & NFS_MOUNT_NOAC) != 0
			|| nfs_caches_unstable(dir)
			|| nfs_attribute_timeout(dir))
		return -ENOENT;
	if ((NFS_FLAGS(dir) & (NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA)) != 0)
		return -ENOENT;
	timestamp = NFS_I(dir)->readdir_timestamp;

	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_mapping, desc.page_index)); desc.page_index++) {

		res = -EIO;
		if (PageUptodate(page)) {
			void * kaddr = kmap_atomic(page, KM_USER0);
			desc.ptr = kaddr;
			res = find_dirent_name(&desc, page, dentry);
			kunmap_atomic(kaddr, KM_USER0);
		}
		page_cache_release(page);

		if (res == 0)
			goto out_found;
		if (res != -EAGAIN)
			break;
	}
	return -ENOENT;
 out_found:
	fattr->timestamp = timestamp;
	return 0;
}
/* The file offset position is now represented as a true offset into the
 * page cache as is the case in most of the other filesystems.
 */
static int nfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
{
	struct dentry	*dentry = filp->f_dentry;
	struct inode	*inode = dentry->d_inode;
	nfs_readdir_descriptor_t my_desc,
			*desc = &my_desc;
	struct nfs_entry my_entry;
	struct nfs_fh	 fh;
	struct nfs_fattr fattr;
	long		res;

	lock_kernel();

	res = nfs_revalidate_inode(NFS_SERVER(inode), inode);
	if (res < 0) {
		unlock_kernel();
		return res;
	}

	/*
	 * filp->f_pos points to the file offset in the page cache.
	 * but if the cache has meanwhile been zapped, we need to
	 * read from the last dirent to revalidate f_pos
	 * itself.
	 */
	memset(desc, 0, sizeof(*desc));

	desc->file = filp;
	desc->target = filp->f_pos;
	desc->decode = NFS_PROTO(inode)->decode_dirent;
	desc->plus = NFS_USE_READDIRPLUS(inode);

	my_entry.cookie = my_entry.prev_cookie = 0;
	my_entry.eof = 0;
	my_entry.fh = &fh;
	my_entry.fattr = &fattr;
	desc->entry = &my_entry;

	while(!desc->entry->eof) {
		res = readdir_search_pagecache(desc);
		if (res == -EBADCOOKIE) {
			/* This means either end of directory */
			if (desc->entry->cookie != desc->target) {
				/* Or that the server has 'lost' a cookie */
				res = uncached_readdir(desc, dirent, filldir);
				if (res >= 0)
					continue;
			}
			res = 0;
			break;
		}
		if (res == -ETOOSMALL && desc->plus) {
			NFS_FLAGS(inode) &= ~NFS_INO_ADVISE_RDPLUS;
			nfs_zap_caches(inode);
			desc->plus = 0;
			desc->entry->eof = 0;
			continue;
		}
		if (res < 0)
			break;

		res = nfs_do_filldir(desc, dirent, filldir);
		if (res < 0) {
			res = 0;
			break;
		}
	}
	unlock_kernel();
	if (desc->error < 0)
		return desc->error;
	if (res < 0)
		return res;
	return 0;
}