/*
 *  linux/fs/umsdos/rdir.c
 *
 *  Written 1994 by Jacques Gelinas
 *
 *  Extended MS-DOS directory pure MS-DOS handling functions
 *  (For directory without EMD file).
 */

#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/msdos_fs.h>
#include <linux/errno.h>
#include <linux/stat.h>
#include <linux/limits.h>
#include <linux/umsdos_fs.h>
#include <linux/malloc.h>

#include <asm/uaccess.h>

#define PRINTK(x)
#define Printk(x) printk x


extern struct inode *pseudo_root;

struct RDIR_FILLDIR {
	void *dirbuf;
	filldir_t filldir;
	int real_root;
};

static int rdir_filldir (
				void *buf,
				const char *name,
				int name_len,
				off_t offset,
				ino_t ino)
{
	int ret = 0;
	struct RDIR_FILLDIR *d = (struct RDIR_FILLDIR *) buf;

	PRINTK ((KERN_DEBUG "rdir_filldir /mn/: entering\n"));
	if (d->real_root) {
		PRINTK ((KERN_DEBUG "rdir_filldir /mn/: real root!\n"));
		/* real root of a pseudo_rooted partition */
		if (name_len != UMSDOS_PSDROOT_LEN
		    || memcmp (name, UMSDOS_PSDROOT_NAME, UMSDOS_PSDROOT_LEN) != 0) {
			/* So it is not the /linux directory */
			if (name_len == 2
			    && name[0] == '.'
			    && name[1] == '.') {
				/* Make sure the .. entry points back to the pseudo_root */
				ino = pseudo_root->i_ino;
			}
			ret = d->filldir (d->dirbuf, name, name_len, offset, ino);
		}
	} else {
		/* Any DOS directory */
		PRINTK ((KERN_DEBUG "rdir_filldir /mn/: calling d->filldir (%p) for %.*s (%lu)\n", d->filldir, name_len, name, ino));
		ret = d->filldir (d->dirbuf, name, name_len, offset, ino);
	}
	return ret;
}


static int UMSDOS_rreaddir (
				   struct file *filp,
				   void *dirbuf,
				   filldir_t filldir)
{
	struct RDIR_FILLDIR bufk;
	struct inode *dir = filp->f_dentry->d_inode;

	PRINTK ((KERN_DEBUG "UMSDOS_rreaddir /mn/: entering %p %p\n", filldir, dirbuf));


	bufk.filldir = filldir;
	bufk.dirbuf = dirbuf;
	bufk.real_root = pseudo_root
	    && dir == iget (dir->i_sb, UMSDOS_ROOT_INO)
	    && dir == iget (pseudo_root->i_sb, UMSDOS_ROOT_INO);
	PRINTK ((KERN_DEBUG "UMSDOS_rreaddir /mn/: calling fat_readdir with filldir=%p and exiting\n", filldir));
	return fat_readdir (filp, &bufk, rdir_filldir);
}


/*
 * Lookup into a non promoted directory.
 * If the result is a directory, make sure we find out if it is
 * a promoted one or not (calling umsdos_setup_dir_inode(inode)).
 */
int umsdos_rlookup_x (
			     struct inode *dir,
			     struct dentry *dentry,
			     int nopseudo)
{				/* Don't care about pseudo root mode */
	/* so locating "linux" will work */
	int len = dentry->d_name.len;
	const char *name = dentry->d_name.name;
	struct inode *inode;
	int ret;

	if (pseudo_root
	    && len == 2
	    && name[0] == '.'
	    && name[1] == '.'
	    && dir == iget (dir->i_sb, UMSDOS_ROOT_INO)
	    && dir == iget (pseudo_root->i_sb, UMSDOS_ROOT_INO)) {
		/*    *result = pseudo_root; */
		Printk ((KERN_WARNING "umsdos_rlookup_x: we are at pseudo-root thingy?\n"));
		inc_count (pseudo_root);
		ret = 0;
		/* #Specification: pseudo root / DOS/..
		 * In the real root directory (c:\), the directory ..
		 * is the pseudo root (c:\linux).
		 */
	} else {
		ret = umsdos_real_lookup (dir, dentry);
		inode = dentry->d_inode;

#if 0
		Printk ((KERN_DEBUG "umsdos_rlookup_x: umsdos_real_lookup for %.*s in %lu returned %d\n", len, name, dir->i_ino, ret));
		Printk ((KERN_DEBUG "umsdos_rlookup_x: umsdos_real_lookup: inode is %p resolving to ", inode));
		if (inode) {	/* /mn/ FIXME: DEL_ME */
			Printk ((KERN_DEBUG "i_ino=%lu\n", inode->i_ino));
		} else {
			Printk ((KERN_DEBUG "NONE!\n"));
		}
#endif

		if ((ret == 0) && inode) {

			if (pseudo_root && inode == pseudo_root && !nopseudo) {
				/* #Specification: pseudo root / DOS/linux
				 * Even in the real root directory (c:\), the directory
				 * /linux won't show
				 */
				Printk ((KERN_WARNING "umsdos_rlookup_x: do the pseudo-thingy...\n"));
				ret = -ENOENT;
				iput (pseudo_root); /* FIXME? */

			} else if (S_ISDIR (inode->i_mode)) {
				/* We must place the proper function table */
				/* depending if this is a MsDOS directory or an UMSDOS directory */
				Printk ((KERN_DEBUG "umsdos_rlookup_x: setting up setup_dir_inode %lu...\n", inode->i_ino));
				umsdos_setup_dir_inode (inode);
			}
		}
	}
	iput (dir); /* FIXME? */
	PRINTK ((KERN_DEBUG "umsdos_rlookup_x: returning %d\n", ret));
	return ret;
}


int UMSDOS_rlookup (
			   struct inode *dir,
			   struct dentry *dentry
)
{
	PRINTK ((KERN_DEBUG "UMSDOS_rlookup /mn/: executing umsdos_rlookup_x for ino=%lu in %.*s\n", dir->i_ino, (int) dentry->d_name.len, dentry->d_name.name));
	return umsdos_rlookup_x (dir, dentry, 0);
}


static int UMSDOS_rrmdir (
				 struct inode *dir,
				 struct dentry *dentry)
{
	/* #Specification: dual mode / rmdir in a DOS directory
	 * In a DOS (not EMD in it) directory, we use a reverse strategy
	 * compared with an Umsdos directory. We assume that a subdirectory
	 * of a DOS directory is also a DOS directory. This is not always
	 * true (umssync may be used anywhere), but make sense.
	 * 
	 * So we call msdos_rmdir() directly. If it failed with a -ENOTEMPTY
	 * then we check if it is a Umsdos directory. We check if it is
	 * really empty (only . .. and --linux-.--- in it). If it is true
	 * we remove the EMD and do a msdos_rmdir() again.
	 * 
	 * In a Umsdos directory, we assume all subdirectory are also
	 * Umsdos directory, so we check the EMD file first.
	 */
	int ret;

	if (umsdos_is_pseudodos (dir, dentry)) {
		/* #Specification: pseudo root / rmdir /DOS
		 * The pseudo sub-directory /DOS can't be removed!
		 * This is done even if the pseudo root is not a Umsdos
		 * directory anymore (very unlikely), but an accident (under
		 * MsDOS) is always possible.
		 * 
		 * EPERM is returned.
		 */
		ret = -EPERM;
	} else {
		umsdos_lockcreate (dir);
		inc_count (dir);
		ret = msdos_rmdir (dir, dentry);
		if (ret == -ENOTEMPTY) {
			struct inode *sdir;

			inc_count (dir);

			ret = UMSDOS_rlookup (dir, dentry);
			sdir = dentry->d_inode;
			PRINTK (("rrmdir lookup %d ", ret));
			if (ret == 0) {
				int empty;

				if ((empty = umsdos_isempty (sdir)) != 0) {
					PRINTK (("isempty %d i_count %d ", empty,
					  atomic_read (&sdir->i_count)));
					if (empty == 2) {
						/*
						 * Not a Umsdos directory, so the previous msdos_rmdir
						 * was not lying :-)
						 */
						ret = -ENOTEMPTY;
					} else if (empty == 1) {
						/* We have to removed the EMD file */
						struct dentry *temp;

						Printk ((KERN_WARNING "UMSDOS_rmdir: hmmm... whatabout inode ? FIXME\n"));
						temp = creat_dentry (UMSDOS_EMD_FILE, UMSDOS_EMD_NAMELEN, NULL, NULL);	/* FIXME: prolly should fill inode part ? */
						ret = msdos_unlink (sdir, temp);
						sdir = NULL;
						if (ret == 0) {
							inc_count (dir);
							ret = msdos_rmdir (dir, dentry);
						}
					}
				} else {
					ret = -ENOTEMPTY;
				}
				/* iput (sdir); FIXME */
			}
		}
		umsdos_unlockcreate (dir);
	}
	/* iput (dir); FIXME */
	return ret;
}

/* #Specification: dual mode / introduction
 * One goal of UMSDOS is to allow a practical and simple coexistence
 * between MsDOS and Linux in a single partition. Using the EMD file
 * in each directory, UMSDOS add Unix semantics and capabilities to
 * normal DOS file system. To help and simplify coexistence, here is
 * the logic related to the EMD file.
 * 
 * If it is missing, then the directory is managed by the MsDOS driver.
 * The names are limited to DOS limits (8.3). No links, no device special
 * and pipe and so on.
 * 
 * If it is there, it is the directory. If it is there but empty, then
 * the directory looks empty. The utility umssync allows synchronisation
 * of the real DOS directory and the EMD.
 * 
 * Whenever umssync is applied to a directory without EMD, one is
 * created on the fly. The directory is promoted to full unix semantic.
 * Of course, the ls command will show exactly the same content as before
 * the umssync session.
 * 
 * It is believed that the user/admin will promote directories to unix
 * semantic as needed.
 * 
 * The strategy to implement this is to use two function table (struct
 * inode_operations). One for true UMSDOS directory and one for directory
 * with missing EMD.
 * 
 * Functions related to the DOS semantic (but aware of UMSDOS) generally
 * have a "r" prefix (r for real) such as UMSDOS_rlookup, to differentiate
 * from the one with full UMSDOS semantic.
 */
static struct file_operations umsdos_rdir_operations =
{
	NULL,			/* lseek - default */
	UMSDOS_dir_read,	/* read */
	NULL,			/* write - bad */
	UMSDOS_rreaddir,	/* readdir */
	NULL,			/* poll - default */
	UMSDOS_ioctl_dir,	/* ioctl - default */
	NULL,			/* mmap */
	NULL,			/* no special open code */
	NULL,			/* no special release code */
	NULL			/* fsync */
};

struct inode_operations umsdos_rdir_inode_operations =
{
	&umsdos_rdir_operations,	/* default directory file-ops */
	msdos_create,		/* create */
	UMSDOS_rlookup,		/* lookup */
	NULL,			/* link */
	msdos_unlink,		/* unlink */
	NULL,			/* symlink */
	msdos_mkdir,		/* mkdir */
	UMSDOS_rrmdir,		/* rmdir */
	NULL,			/* mknod */
	msdos_rename,		/* rename */
	NULL,			/* readlink */
	NULL,			/* followlink */
	NULL,			/* readpage */
	NULL,			/* writepage */
	NULL,			/* bmap */
	NULL,			/* truncate */
	NULL,			/* permission */
	NULL,			/* smap */
	NULL,			/* updatepage */
	NULL,			/* revalidate */
};
