/*
 * This is called every time the dcache has a lookup hit,
 * and we should check whether we can really trust that
 * lookup.
 *
 * NOTE! The hit can be a negative hit too, don't assume
 * we have an inode!
 *
 * If the parent directory is seen to have changed, we throw out the
 * cached dentry and do a new lookup.
 */
static int nfs_lookup_revalidate(struct dentry * dentry, int flags)
{
	struct inode *dir;
	struct inode *inode;
	int error;
	struct nfs_fh fhandle;
	struct nfs_fattr fattr;

	lock_kernel();
	dir = dentry->d_parent->d_inode;
	inode = dentry->d_inode;

	if (!inode) {
		if (nfs_neg_need_reval(dir, dentry))
			goto out_bad;
		goto out_valid;
	}

	if (is_bad_inode(inode)) {
		dfprintk(VFS, "nfs_lookup_validate: %s/%s has dud inode\n",
			dentry->d_parent->d_name.name, dentry->d_name.name);
		goto out_bad;
	}

	/* Force a full look up iff the parent directory has changed */
	if (nfs_check_verifier(dir, dentry)) {
		if (nfs_lookup_verify_inode(inode, flags))
			goto out_bad;
		goto out_valid;
	}

	if (NFS_STALE(inode))
		goto out_bad;

	error = NFS_PROTO(dir)->lookup(dir, &dentry->d_name, &fhandle, &fattr);
	if (error)
		goto out_bad;
	if (memcmp(NFS_FH(inode), &fhandle, sizeof(struct nfs_fh))!= 0)
		goto out_bad;
	if ((error = nfs_refresh_inode(inode, &fattr)) != 0)
		goto out_bad;

	nfs_renew_times(dentry);
 out_valid:
	unlock_kernel();
	return 1;
 out_bad:
	NFS_CACHEINV(dir);
	if (inode && S_ISDIR(inode->i_mode)) {
		/* Purge readdir caches. */
		nfs_zap_caches(inode);
		/* If we have submounts, don't unhash ! */
		if (have_submounts(dentry))
			goto out_valid;
		shrink_dcache_parent(dentry);
	}
	d_drop(dentry);
	unlock_kernel();
	return 0;
}
/*
 * We judge how long we want to trust negative
 * dentries by looking at the parent inode mtime.
 *
 * If parent mtime has changed, we revalidate, else we wait for a
 * period corresponding to the parent's attribute cache timeout value.
 */
static inline
int nfs_neg_need_reval(struct inode *dir, struct dentry *dentry,
		       struct nameidata *nd)
{
	int ndflags = 0;

	if (nd)
		ndflags = nd->flags;
	/* Don't revalidate a negative dentry if we're creating a new file */
	if ((ndflags & LOOKUP_CREATE) && !(ndflags & LOOKUP_CONTINUE))
		return 0;
	return !nfs_check_verifier(dir, dentry);
}
/*
 * We judge how long we want to trust negative
 * dentries by looking at the parent inode mtime.
 *
 * If parent mtime has changed, we revalidate, else we wait for a
 * period corresponding to the parent's attribute cache timeout value.
 */
static inline int nfs_neg_need_reval(struct inode *dir, struct dentry *dentry)
{
	if (!nfs_check_verifier(dir, dentry))
		return 1;
	return time_after(jiffies, dentry->d_time + NFS_ATTRTIMEO(dir));
}
/*
 * This is called every time the dcache has a lookup hit,
 * and we should check whether we can really trust that
 * lookup.
 *
 * NOTE! The hit can be a negative hit too, don't assume
 * we have an inode!
 *
 * If the parent directory is seen to have changed, we throw out the
 * cached dentry and do a new lookup.
 */
static int nfs_lookup_revalidate(struct dentry * dentry, struct nameidata *nd)
{
	struct inode *dir;
	struct inode *inode;
	struct dentry *parent;
	int error;
	struct nfs_fh fhandle;
	struct nfs_fattr fattr;
	unsigned long verifier;
	int isopen = 0;

	parent = dget_parent(dentry);
	lock_kernel();
	dir = parent->d_inode;
	inode = dentry->d_inode;

	if (nd && !(nd->flags & LOOKUP_CONTINUE) && (nd->flags & LOOKUP_OPEN))
		isopen = 1;

	if (!inode) {
		if (nfs_neg_need_reval(dir, dentry, nd))
			goto out_bad;
		goto out_valid;
	}

	if (is_bad_inode(inode)) {
		dfprintk(VFS, "nfs_lookup_validate: %s/%s has dud inode\n",
			dentry->d_parent->d_name.name, dentry->d_name.name);
		goto out_bad;
	}

	/* Revalidate parent directory attribute cache */
	nfs_revalidate_inode(NFS_SERVER(dir), dir);

	/* Force a full look up iff the parent directory has changed */
	if (nfs_check_verifier(dir, dentry)) {
		if (nfs_lookup_verify_inode(inode, isopen))
			goto out_zap_parent;
		goto out_valid;
	}

	/*
	 * Note: we're not holding inode->i_sem and so may be racing with
	 * operations that change the directory. We therefore save the
	 * change attribute *before* we do the RPC call.
	 */
	verifier = nfs_save_change_attribute(dir);
	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, isopen))
			goto out_zap_parent;
		goto out_valid_renew;
	}

	if (NFS_STALE(inode))
		goto out_bad;

	error = NFS_PROTO(dir)->lookup(dir, &dentry->d_name, &fhandle, &fattr);
	if (error)
		goto out_bad;
	if (memcmp(NFS_FH(inode), &fhandle, sizeof(struct nfs_fh))!= 0)
		goto out_bad;
	if ((error = nfs_refresh_inode(inode, &fattr)) != 0)
		goto out_bad;

 out_valid_renew:
	nfs_renew_times(dentry);
	nfs_set_verifier(dentry, verifier);
 out_valid:
	unlock_kernel();
	dput(parent);
	return 1;
out_zap_parent:
	nfs_zap_caches(dir);
 out_bad:
	NFS_CACHEINV(dir);
	if (inode && S_ISDIR(inode->i_mode)) {
		/* Purge readdir caches. */
		nfs_zap_caches(inode);
		/* If we have submounts, don't unhash ! */
		if (have_submounts(dentry))
			goto out_valid;
		shrink_dcache_parent(dentry);
	}
	d_drop(dentry);
	unlock_kernel();
	dput(parent);
	return 0;
}