#include <linux/fs.h>
#include <linux/module.h>
#include <linux/smp_lock.h>

/**
 * find_exported_dentry - helper routine to implement export_operations->decode_fh
 * @sb:		The &super_block identifying the filesystem
 * @obj:	An opaque identifier of the object to be found - passed to get_inode
 * @parent:	An optional opqaue identifier of the parent of the object.
 * @acceptable:	A function used to test possible &dentries to see of they are acceptable
 * @context:	A parameter to @acceptable so that it knows on what basis to judge.
 *
 * find_exported_dentry is the central helper routine to enable file systems to provide
 * the decode_fh() export_operation.  It's main task is to take an &inode, find or create an
 * appropriate &dentry structure, and possibly splice this into the dcache in the
 * correct place.
 *
 * The decode_fh() operation provided by the filesystem should call find_exported_dentry()
 * with the same parameters that it received except that instead of the file handle fragment,
 * pointers to opaque identifiers for the object and optionally its parent are passed.
 * The default decode_fh routine passes one pointer to the start of the filehandle fragment, and
 * one 8 bytes into the fragment.  It is expected that most filesystems will take this
 * approach, though the offset to the parent identifier may well be different.
 *
 * find_exported_dentry() will call get_dentry to get an dentry pointer from the file system.  If
 * any &dentry in the d_alias list is acceptable, it will be returned.  Otherwise
 * find_exported_dentry() will attempt to splice a new &dentry into the dcache using get_name() and
 * get_parent() to find the appropriate place.
 *
 */

struct export_operations export_op_default;

#define	CALL(ops,fun) ((ops->fun)?(ops->fun):export_op_default.fun)

#define dprintk(x, ...) do{}while(0)

struct dentry *
find_exported_dentry(struct super_block *sb, void *obj, void *parent,
		     int (*acceptable)(void *context, struct dentry *de),
		     void *context)
{
	struct dentry *result = NULL;
	struct dentry *target_dir;
	int err;
	struct export_operations *nops = sb->s_export_op;
	struct list_head *le, *head;
	struct dentry *toput = NULL;
	int noprogress;


	/*
	 * Attempt to find the inode.
	 */
	result = CALL(sb->s_export_op,get_dentry)(sb,obj);
	err = -ESTALE;
	if (result == NULL)
		goto err_out;
	if (IS_ERR(result)) {
		err = PTR_ERR(result);
		goto err_out;
	}
	if (S_ISDIR(result->d_inode->i_mode) &&
	    (result->d_flags & DCACHE_DISCONNECTED)) {
		/* it is an unconnected directory, we must connect it */
		;
	} else {
		if (acceptable(context, result))
			return result;
		if (S_ISDIR(result->d_inode->i_mode)) {
			/* there is no other dentry, so fail */
			goto err_result;
		}
		/* try any other aliases */
		spin_lock(&dcache_lock);
		head = &result->d_inode->i_dentry;
		list_for_each(le, head) {
			struct dentry *dentry = list_entry(le, struct dentry, d_alias);
			dget_locked(dentry);
			spin_unlock(&dcache_lock);
			if (toput)
				dput(toput);
			toput = NULL;
			if (dentry != result &&
			    acceptable(context, dentry)) {
				dput(result);
				dentry->d_vfs_flags |= DCACHE_REFERENCED;
				return dentry;
			}
			spin_lock(&dcache_lock);
			toput = dentry;
		}
		spin_unlock(&dcache_lock);
		if (toput)
			dput(toput);
	}			

	/* It's a directory, or we are required to confirm the file's
	 * location in the tree based on the parent information
 	 */
	dprintk("find_exported_dentry: need to look harder for %d/%d\n",kdev_t_to_nr(sb->s_dev),*(int*)obj);
	if (S_ISDIR(result->d_inode->i_mode))
		target_dir = dget(result);
	else {
		if (parent == NULL)
			goto err_result;

		target_dir = CALL(sb->s_export_op,get_dentry)(sb,parent);
		if (IS_ERR(target_dir))
			err = PTR_ERR(target_dir);
		if (target_dir == NULL || IS_ERR(target_dir))
			goto err_result;
	}
	/*
	 * Now we need to make sure that target_dir is properly connected.
	 * It may already be, as the flag isn't always updated when connection
	 * happens.
	 * So, we walk up parent links until we find a connected directory,
	 * or we run out of directories.  Then we find the parent, find
	 * the name of the child in that parent, and do a lookup.
	 * This should connect the child into the parent
	 * We then repeat.
	 */

	/* it is possible that a confused file system might not let us complete the
	 * path to the root.  For example, if get_parent returns a directory
	 * in which we cannot find a name for the child.  While this implies a very
	 * sick filesystem we don't want it to cause knfsd to spin.  Hence the noprogress
	 * counter.  If we go through the loop 10 times (2 is probably enough) without
	 * getting anywhere, we just give up
	 */
	lock_kernel();
	noprogress= 0;
	while (target_dir->d_flags & DCACHE_DISCONNECTED && noprogress++ < 10) {
		struct dentry *pd = target_dir;
		read_lock(&dparent_lock);
		while (!IS_ROOT(pd) &&
		       (pd->d_parent->d_flags & DCACHE_DISCONNECTED))
			pd = pd->d_parent;

		dget(pd);
		read_unlock(&dparent_lock);

		if (!IS_ROOT(pd)) {
			/* must have found a connected parent - great */
			pd->d_flags &= ~DCACHE_DISCONNECTED;
			noprogress = 0;
		} else if (pd == sb->s_root) {
			printk(KERN_ERR "export: Eeek filesystem root is not connected, impossible\n");
			pd->d_flags &= ~DCACHE_DISCONNECTED;
			noprogress = 0;
		} else {
			/* we have hit the top of a disconnected path.  Try
			 * to find parent and connect
			 * note: racing with some other process renaming a
			 * directory isn't much of a problem here.  If someone
			 * renames the directory, it will end up properly connected,
			 * which is what we want
			 */
			struct dentry *ppd;
			struct dentry *npd;
			char nbuf[NAME_MAX+1];

			down(&pd->d_inode->i_sem);
			ppd = CALL(nops,get_parent)(pd);
			up(&pd->d_inode->i_sem);

			if (IS_ERR(ppd)) {
				err = PTR_ERR(ppd);
				dprintk("find_exported_dentry: get_parent of %ld failed, err %d\n",
					pd->d_inode->i_ino, err);
				dput(pd);
				break;
			}
			dprintk("find_exported_dentry: find name of %lu in %lu\n", pd->d_inode->i_ino, ppd->d_inode->i_ino);
			err = CALL(nops,get_name)(ppd, nbuf, pd);
			if (err) {
				dput(ppd);
				if (err == -ENOENT)
					/* some race between get_parent and get_name?
					 * just try again
					 */
					continue;
				dput(pd);
				break;
			}
			dprintk("find_exported_dentry: found name: %s\n", nbuf);
			down(&ppd->d_inode->i_sem);
			npd = lookup_one_len(nbuf, ppd, strlen(nbuf));
			up(&ppd->d_inode->i_sem);
			if (IS_ERR(npd)) {
				err = PTR_ERR(npd);
				dprintk("find_exported_dentry: lookup failed: %d\n", err);
				dput(ppd);
				dput(pd);
				break;
			}
			/* we didn't really want npd, we really wanted
			 * a side-effect of the lookup.
			 * hopefully, npd == pd, though it isn't really
			 * a problem if it isn't
			 */
			if (npd == pd)
				noprogress = 0;
			else
				printk("find_exported_dentry: npd != pd\n");
			dput(npd);
			dput(ppd);
			if (IS_ROOT(pd)) {
				/* something went wrong, we will have to give up */
				dput(pd);
				break;
			}
		}
		dput(pd);
	}

	if (target_dir->d_flags & DCACHE_DISCONNECTED) {
		/* something went wrong - oh-well */
		if (!err)
			err = -ESTALE;
		unlock_kernel();
		goto err_target;
	}
	/* if we weren't after a directory, have one more step to go */
	if (result != target_dir) {
		struct dentry *nresult;
		char nbuf[NAME_MAX+1];
		err = CALL(nops,get_name)(target_dir, nbuf, result);
		if (!err) {
			down(&target_dir->d_inode->i_sem);
			nresult = lookup_one_len(nbuf, target_dir, strlen(nbuf));
			up(&target_dir->d_inode->i_sem);
			if (!IS_ERR(nresult)) {
				if (nresult->d_inode) {
					dput(result);
					result = nresult;
				} else
					dput(nresult);
			}
		}
	}
	dput(target_dir);
	unlock_kernel();
	/* now result is properly connected, it is our best bet */
	if (acceptable(context, result))
		return result;
	/* one last try of the aliases.. */
	spin_lock(&dcache_lock);
	head = &result->d_inode->i_dentry;
	list_for_each(le, head) {
		struct dentry *dentry = list_entry(le, struct dentry, d_alias);
		dget_locked(dentry);
		spin_unlock(&dcache_lock);
		if (toput) dput(toput);
		if (dentry != result &&
		    acceptable(context, dentry)) {
			dput(result);
			dentry->d_vfs_flags |= DCACHE_REFERENCED;
			return dentry;
		}
		spin_lock(&dcache_lock);
		toput = dentry;
	}
	spin_unlock(&dcache_lock);
	if (toput)
		dput(toput);

	/* drat - I just cannot find anything acceptable */
	dput(result);
	return ERR_PTR(-ESTALE);

 err_target:
	dput(target_dir);
 err_result:
	dput(result);
 err_out:
	return ERR_PTR(err);
}



static struct dentry *get_parent(struct dentry *child)
{
	/* get_parent cannot be supported generically, the locking
	 * is too icky.
	 * instead, we just return EACCES.  If server reboots or inodes
	 * get flushed, you lose
	 */
	return ERR_PTR(-EACCES);
}


struct getdents_callback {
	char *name;		/* name that was found. It already points to a buffer NAME_MAX+1 is size */
	unsigned long ino;	/* the inum we are looking for */
	int found;		/* inode matched? */
	int sequence;		/* sequence counter */
};

/*
 * A rather strange filldir function to capture
 * the name matching the specified inode number.
 */
static int filldir_one(void * __buf, const char * name, int len,
			loff_t pos, ino_t ino, unsigned int d_type)
{
	struct getdents_callback *buf = __buf;
	int result = 0;

	buf->sequence++;
	if (buf->ino == ino) {
		memcpy(buf->name, name, len);
		buf->name[len] = '\0';
		buf->found = 1;
		result = -1;
	}
	return result;
}

/**
 * get_name - default export_operations->get_name function
 * @dentry: the directory in which to find a name
 * @name:   a pointer to a %NAME_MAX+1 char buffer to store the name
 * @child:  the dentry for the child directory.
 *
 * calls readdir on the parent until it finds an entry with
 * the same inode number as the child, and returns that.
 */
static int get_name(struct dentry *dentry, char *name,
			struct dentry *child)
{
	struct inode *dir = dentry->d_inode;
	int error;
	struct file file;
	struct getdents_callback buffer;

	error = -ENOTDIR;
	if (!dir || !S_ISDIR(dir->i_mode))
		goto out;
	error = -EINVAL;
	if (!dir->i_fop)
		goto out;
	/*
	 * Open the directory ...
	 */
	error = init_private_file(&file, dentry, FMODE_READ);
	if (error)
		goto out;
	error = -EINVAL;
	if (!file.f_op->readdir)
		goto out_close;

	buffer.name = name;
	buffer.ino = child->d_inode->i_ino;
	buffer.found = 0;
	buffer.sequence = 0;
	while (1) {
		int old_seq = buffer.sequence;

		error = vfs_readdir(&file, filldir_one, &buffer);

		if (error < 0)
			break;

		error = 0;
		if (buffer.found)
			break;
		error = -ENOENT;
		if (old_seq == buffer.sequence)
			break;
	}

out_close:
	if (file.f_op->release)
		file.f_op->release(dir, &file);
out:
	return error;
}


static struct dentry *export_iget(struct super_block *sb, unsigned long ino, __u32 generation)
{

	/* iget isn't really right if the inode is currently unallocated!!
	 * This should really all be done inside each filesystem
	 *
	 * ext2fs' read_inode has been strengthed to return a bad_inode if the inode
	 *   had been deleted.
	 *
	 * Currently we don't know the generation for parent directory, so a generation
	 * of 0 means "accept any"
	 */
	struct inode *inode;
	struct dentry *result;
	if (ino == 0)
		return ERR_PTR(-ESTALE);
	inode = iget(sb, ino);
	if (inode == NULL)
		return ERR_PTR(-ENOMEM);
	if (is_bad_inode(inode)
	    || (generation && inode->i_generation != generation)
		) {
		/* we didn't find the right inode.. */
		dprintk("fh_verify: Inode %lu, Bad count: %d %d or version  %u %u\n",
			inode->i_ino,
			inode->i_nlink, atomic_read(&inode->i_count),
			inode->i_generation,
			generation);

		iput(inode);
		return ERR_PTR(-ESTALE);
	}
	/* now to find a dentry.
	 * If possible, get a well-connected one
	 */
	result = d_alloc_anon(inode);
	if (!result) {
		iput(inode);
		return ERR_PTR(-ENOMEM);
	}
	result->d_vfs_flags |= DCACHE_REFERENCED;
	return result;
}


static struct dentry *get_object(struct super_block *sb, void *vobjp)
{
	__u32 *objp = vobjp;
	unsigned long ino = objp[0];
	__u32 generation = objp[1];

	return export_iget(sb, ino, generation);
}


/**
 * export_encode_fh - default export_operations->encode_fh function
 * dentry:  the dentry to encode
 * fh:      where to store the file handle fragment
 * max_len: maximum length to store there
 * connectable: whether to store parent infomation
 *
 * This default encode_fh function assumes that the 32 inode number
 * is suitable for locating an inode, and that the generation number
 * can be used to check that it is still valid.  It places them in the
 * filehandle fragment where export_decode_fh expects to find them.
 */
static int export_encode_fh(struct dentry *dentry, __u32 *fh, int *max_len,
		   int connectable)
{
	struct inode * inode = dentry->d_inode;
	struct inode *parent = dentry->d_parent->d_inode;
	int len = *max_len;
	int type = 1;
	
	if (len < 2 || (connectable && len < 4))
		return 255;

	len = 2;
	fh[0] = inode->i_ino;
	fh[1] = inode->i_generation;
	if (connectable && !S_ISDIR(inode->i_mode)) {
		fh[2] = parent->i_ino;
		fh[3] = parent->i_generation;
		len = 4;
		type = 2;
	}
	*max_len = len;
	return type;
}


/**
 * export_decode_fh - default export_operations->decode_fh function
 * sb:  The superblock
 * fh:  pointer to the file handle fragment
 * fh_len: length of file handle fragment
 * acceptable: function for testing acceptability of dentrys
 * context:   context for @acceptable
 *
 * This is the default decode_fh() function.
 * a fileid_type of 1 indicates that the filehandlefragment
 * just contains an object identifier understood by  get_dentry.
 * a fileid_type of 2 says that there is also a directory
 * identifier 8 bytes in to the filehandlefragement.
 */
static struct dentry *export_decode_fh(struct super_block *sb, __u32 *fh, int fh_len,
			      int fileid_type,
			 int (*acceptable)(void *context, struct dentry *de),
			 void *context)
{
	__u32 parent[2];
	parent[0] = parent[1] = 0;
	if (fh_len < 2 || fileid_type > 2)
		return NULL;
	if (fileid_type == 2) {
		if (fh_len > 2) parent[0] = fh[2];
		if (fh_len > 3) parent[1] = fh[3];
	}
	return find_exported_dentry(sb, fh, parent,
				   acceptable, context);
}

struct export_operations export_op_default = {
	decode_fh:	export_decode_fh,
	encode_fh:	export_encode_fh,

	get_name:	get_name,
	get_parent:	get_parent,
	get_dentry:	get_object,
};

EXPORT_SYMBOL(export_op_default);
EXPORT_SYMBOL(find_exported_dentry);