/*
 *  linux/fs/affs/dir.c
 *
 *  (C) 1993  Ray Burr - Modified for Amiga FFS filesystem.
 *
 *  (C) 1992  Eric Youngdale Modified for ISO9660 filesystem.
 *
 *  (C) 1991  Linus Torvalds - minix filesystem
 *
 *  affs directory handling functions
 */

#include <linux/errno.h>

#include <asm/segment.h>

#include <linux/fs.h>
#include <linux/affs_fs.h>
#include <linux/kernel.h>
#include <linux/affs_fs.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/sched.h>


static int affs_readdir(struct inode *, struct file *, struct dirent *, int);

struct file_operations affs_dir_operations = {
	NULL,			/* lseek - default */
	NULL,			/* read */
	NULL,			/* write - bad */
	affs_readdir,		/* readdir */
	NULL,			/* select - default */
	NULL,			/* ioctl - default */
	NULL,			/* no special open code */
	NULL			/* no special release code */
};

/*
 * directories can handle most operations...
 */
struct inode_operations affs_dir_inode_operations = {
	&affs_dir_operations,	/* default directory file-ops */
	NULL,			/* create */
	affs_lookup,		/* lookup */
	NULL,			/* link */
	NULL,       		/* unlink */
	NULL,       		/* symlink */
	NULL,       		/* mkdir */
	NULL,			/* rmdir */
	NULL,			/* mknod */
	NULL,			/* rename */
	NULL,			/* readlink */
	NULL,			/* follow_link */
	NULL,			/* bmap */
	NULL			/* truncate */
};

/* This is used to speed up lookup.  Without this we would need to
make a linear search of the directory to find the file that the
directory read just returned.  This is a single element cache. */

/* struct lookup_cache cache = {0,}; */

static int affs_readdir(struct inode * inode, struct file * filp,
	struct dirent * dirent, int count)
{
	int i, j, chain_pos, hash_pos, reclen;
	char *name;
	struct buffer_head *dir_bh;
	struct buffer_head *fh_bh;
	void *dir_data;
	void *fh_data;

#ifdef DEBUG
	printk ("AFFS: readdir: inode=%d f_pos=%d\n",
		inode->i_ino, filp->f_pos);
#endif
	
	if (!inode || !S_ISDIR(inode->i_mode))
		return -EBADF;

	reclen = 0;

	switch (filp->f_pos) {
	case 0:
		put_fs_long (inode->i_ino, &dirent->d_ino);
		reclen = 1;
		put_fs_byte ('.', &dirent->d_name[0]);
		put_fs_byte (0  , &dirent->d_name[1]);
		break;
	case 1:
		i = affs_parent_ino (inode);
		put_fs_long (i, &dirent->d_ino);
		reclen = 2;
		put_fs_byte ('.', &dirent->d_name[0]);
		put_fs_byte ('.', &dirent->d_name[1]);
		put_fs_byte (0  , &dirent->d_name[2]);
		break;
	default:
		break;
	}
	if (reclen) {
		put_fs_word (reclen, &dirent->d_reclen);
		++filp->f_pos;
		return reclen;
	}
	/* No caching here.  I've got 16 megs why should I care?  :-) */
	chain_pos = (filp->f_pos - 2) & 0xffff;
	if (chain_pos == 0xffff)
		return 0;
	hash_pos = (filp->f_pos - 2) >> 16;
#ifdef DEBUG
	printk ("AFFS: hash_pos=%d chain_pos=%d\n", hash_pos, chain_pos);
#endif
	if (!(dir_bh = affs_pread(inode, inode->i_ino, &dir_data)))
		return 0;
	/* HASH_POS should already be on a used entry unless it is
	   the first read of the directory.  Will this break the
	   dirtell thing somehow? */
	i = affs_find_next_hash_entry (AFFS_I2BSIZE (inode), dir_data,
				       &hash_pos);
	j = chain_pos;
	for (;;) {
		if (i < 0) {
#ifdef DEBUG
			printk ("AFFS: bad f_pos in readdir\n");
#endif
			brelse (dir_bh);
			return 0;
		}
		if (!(fh_bh = affs_pread (inode, i, &fh_data))) {
			brelse (dir_bh);
			return 0;
		}
		i = affs_get_fh_hash_link (AFFS_I2BSIZE (inode), fh_data);
		if (j == 0)
			break;
		brelse (fh_bh);
		j--;
	}
	if (i <= 0) {
		hash_pos++;
		if (affs_find_next_hash_entry (AFFS_I2BSIZE (inode),
					       dir_data, &hash_pos)
		    <= 0)
			chain_pos = 0xffff;
		else
			chain_pos = 0;
	}
	else
		chain_pos++;
	reclen = affs_get_file_name (AFFS_I2BSIZE (inode), fh_data, &name);
	for (i = 0; i < reclen; i++)
		put_fs_byte (name[i], &dirent->d_name[i]);
	put_fs_byte (0, &dirent->d_name[reclen]);
	brelse (fh_bh);
	brelse (dir_bh);
	put_fs_word (reclen, &dirent->d_reclen);
#ifdef DEBUG
	printk ("AFFS: reclen=%d\n", reclen);
#endif
	filp->f_pos = ((hash_pos << 16) | chain_pos) + 2;
	return reclen;
}
