/*
 *  linux/fs/ifs/symlink.c
 *
 *  Written 1993 by Werner Almesberger
 *
 *  IFS symlink handling code
 */

#include <asm/segment.h>

#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/ifs_fs.h>
#include <linux/stat.h>


static int ifs_readlink(struct inode *inode,char *buffer,int buflen);

static int ifs_follow_link(struct inode *dir,struct inode *inode,int flag,
    int mode,struct inode **res_inode);


/*
 * symlinks can't do much...
 */

struct inode_operations ifs_symlink_inode_operations = {
	NULL,			/* no file-operations */
	NULL,			/* create */
	NULL,			/* lookup */
	NULL,			/* link */
	NULL,			/* unlink */
	NULL,			/* symlink */
	NULL,			/* mkdir */
	NULL,			/* rmdir */
	NULL,			/* mknod */
	NULL,			/* rename */
	ifs_readlink,		/* readlink */
	ifs_follow_link,	/* follow_link */
	NULL,			/* bmap */
	NULL,			/* truncate */
	NULL			/* permission */
};


static int ifs_follow_link(struct inode *dir,struct inode *inode,int flag,
    int mode,struct inode **res_inode)
{
	struct inode *use;
	unsigned long page;
	int retval,n,old_fs;

	*res_inode = NULL;
	if (!dir) {
		dir = current->root;
		dir->i_count++;
	}
	if (!inode) {
		iput(dir);
		return -ENOENT;
	}
	if (!S_ISLNK(inode->i_mode)) {
		iput(dir);
		*res_inode = inode;
		return 0;
	}
	retval = -ENOENT; /* bogus inode */
	use = NULL;
	for (n = 0; n < IFS_LAYERS(inode); n++)
		if (IFS_NTH(inode,n)) {
			use = IFS_NTH(inode,n);
			retval = use->i_op && use->i_op->readlink ? 0 : -EINVAL;
			break;
		}
	if (!retval) {
		ifs_lock(dir);
		page = get_free_page(GFP_KERNEL);
		old_fs = get_fs();
		set_fs(get_ds());
		USE_INODE(use);
		retval = use->i_op->readlink(use,(char *) page,PAGE_SIZE);
		if (retval >= 0) {
			((char *) page)[retval] = 0;
			USE_INODE(dir);
			retval = open_namei((char *) page,flag,mode,res_inode,
			    dir);
		}
		set_fs(old_fs);
		ifs_unlock(dir);
		free_page(page);
	}
	iput(inode);
	iput(dir);
	return retval;
}


static int ifs_readlink(struct inode *inode,char *buffer,int buflen)
{
	struct inode *use;
	int retval,n;

	if (!S_ISLNK(inode->i_mode)) {
		iput(inode);
		return -EINVAL;
	}
	retval = -ENOENT; /* bogus inode */
	use = NULL;
	for (n = 0; n < IFS_LAYERS(inode); n++)
		if (IFS_NTH(inode,n)) {
			use = IFS_NTH(inode,n);
			retval = use->i_op && use->i_op->readlink ? 0 : -EINVAL;
			break;
		}
	iput(inode);
	if (!retval) {
		USE_INODE(use);
		retval = use->i_op->readlink(use,buffer,buflen);
	}
	return retval;
}
