/*
 *  linux/fs/ifs/dir.c
 *
 *  Written 1992,1993 by Werner Almesberger
 *
 *  IFS directory handling functions
 */

#include <asm/segment.h>

#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/ifs_fs.h>
#include <linux/errno.h>
#include <linux/stat.h>
#include <linux/fcntl.h>


static int ifs_dir_lseek(struct inode *inode,struct file *filp,off_t offset,
    int whence);
static int ifs_readdir(struct inode *inode,struct file *filp,
    struct dirent *dirent,int count);
static int ifs_dir_ioctl(struct inode *inode,struct file *file,unsigned int op,
    unsigned int arg);
static int ifs_dir_open(struct inode *inode,struct file *filp);
static void ifs_dir_release(struct inode *inode,struct file *filp);


static struct file_operations ifs_dir_operations = {
	ifs_dir_lseek,		/* lseek */
	NULL,			/* read */
	NULL,			/* write - bad */
	ifs_readdir,		/* readdir */
	NULL,			/* select - default */
	ifs_dir_ioctl,		/* ioctl - default */
	NULL,			/* mmap */
	ifs_dir_open,		/* open */
	ifs_dir_release		/* release */
};

struct inode_operations ifs_dir_inode_operations = {
	&ifs_dir_operations,	/* default directory file-ops */
	ifs_create,		/* create */
	ifs_lookup,		/* lookup */
	ifs_link,		/* link */
	ifs_unlink,		/* unlink */
	ifs_symlink,		/* symlink */
	ifs_mkdir,		/* mkdir */
	ifs_rmdir,		/* rmdir */
	ifs_mknod,		/* mknod */
	ifs_rename,		/* rename */
	NULL,			/* readlink */
	NULL,			/* follow_link */
	NULL,			/* no bmap */
	NULL,			/* truncate */
	NULL			/* permission */
};


static int ifs_dir_lseek(struct inode *inode,struct file *filp,off_t offset,
    int whence)
{
	if (offset || whence)
		return -EINVAL;
	ifs_dir_release(inode,filp);
	IFS_F(filp)->layer = 0;
	IFS_F(filp)->open = NULL;
	return 0;
}


/*
 * f_pos is entirely ignored, so the BSD-isms telldir() and seekdir() won't
 * work. COUNT is not used. INODE may already be locked by the caller.
 */

static int ifs_readdir(struct inode *inode,struct file *filp,
    struct dirent *dirent,int count)
{
	struct inode *i;
	struct dirent de;
	int retval,old_layer,old_fs,length,n;

Dprintk("readdir\n");
	if (!inode || !S_ISDIR(inode->i_mode)) return -EBADF;
	old_fs = get_fs();
	set_fs(get_ds());
	while (1) {
		if (!IFS_F(filp)->open) {
			old_layer = IFS_F(filp)->layer;
			retval = 0;
			while (IFS_F(filp)->layer < IFS_MAX_LAYERS) {
				if (IFS_NTH(inode,IFS_F(filp)->layer))
					if (!(retval = ifs_open_file(IFS_NTH(
					    inode,IFS_F(filp)->layer),
					    &IFS_F(filp)->open,O_RDONLY)))
						break;
				IFS_F(filp)->layer++;
			}
			if (!IFS_F(filp)->open) {
				set_fs(old_fs);
				return old_layer ? 0 : retval;
			}
		}
		if (!IFS_F(filp)->open->f_op || !IFS_F(filp)->open->f_op->
		    readdir)
			length = -EINVAL;
		else length = IFS_F(filp)->open->f_op->readdir(IFS_F(filp)->
			    open->f_inode,IFS_F(filp)->open,&de,1);
		if (length < 0) {
Dprintk("readdir: error %d\n",length);
			break;
}
		if (!length) {
			ifs_close_file(IFS_F(filp)->open);
			IFS_F(filp)->open = NULL;
			IFS_F(filp)->layer++;
			continue;
		}
Dprintk("length=%d\n",length);
		if (length == 2 && de.d_name[0] == '.' && de.d_name[1] == '.') {
			for (n = 0; n < IFS_F(filp)->layer; n++)
				if (IFS_NTH(inode,n))
					break;
			if (n != IFS_F(filp)->layer) continue;
			break;
		}
		if (!inode->i_op || !inode->i_op->lookup)
			panic("no lookup");
		USE_INODE(inode);
		if (inode->i_op->lookup(inode,de.d_name,length,&i)) {
Dprintk("readdir: lookup failed\n");
			continue;
}
Dprintk("back from lookup\n");
		de.d_ino = i->i_ino;
		for (n = 0; n < IFS_F(filp)->layer; n++)
			if (IFS_NTH(i,n))
				break;
		iput(i);
		if (n < IFS_F(filp)->layer) {
Dprintk("%d (n) < %d (layer)\n",n,IFS_F(filp)->layer);
			continue;
}
Dprintk("readdir returning %d\n",length);
		break;
	}
	set_fs(old_fs);
	if (length > 0)
		memcpy_tofs(dirent,&de,sizeof(struct dirent));
	return length;
}


static int ifs_dir_open(struct inode *inode,struct file *filp)
{
	IFS_F(filp)->layer = 0;
	IFS_F(filp)->open = NULL;
	return 0;
}


static void ifs_dir_release(struct inode *inode,struct file *filp)
{
	if (IFS_F(filp)->open) {
		ifs_close_file(IFS_F(filp)->open);
		IFS_F(filp)->open = NULL;
	}
}


static int ifs_dir_ioctl(struct inode *inode,struct file *file,unsigned int op,
    unsigned int arg)
{
	char *page;
	int retval;

	if (op != IFS_UNWHITEOUT)
		return -EINVAL;
	retval = getname((char *) arg,&page);
	if (retval)
		return retval;
	page[PAGE_SIZE-1] = 0;
	ifs_lock(inode); /* not really necessary, but looks better */
	retval = ifs_user_unwhiteout(inode,page,strlen(page));
	ifs_unlock(inode);
	putname(page);
	return retval;
}
