/*
 *  linux/fs/umsdos/emd.c
 *
 *  Written 1993 by Jacques Gelinas
 *
 *  Extended MS-DOS directory handling functions
 */

#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/msdos_fs.h>
#include <linux/umsdos_fs.h>
#include <linux/dcache.h>
#include <linux/pagemap.h>

#include <asm/delay.h>

static void copy_entry(struct umsdos_dirent *p, struct umsdos_dirent *q)
{
	p->name_len = q->name_len;
	p->name[p->name_len]='\0';
	p->flags = q->flags;
	p->nlink = le16_to_cpu (q->nlink);
	/* FIXME -- 32bit UID/GID issues */
	p->uid = le16_to_cpu (q->uid);
	p->gid = le16_to_cpu (q->gid);
	p->atime = le32_to_cpu (q->atime);
	p->mtime = le32_to_cpu (q->mtime);
	p->ctime = le32_to_cpu (q->ctime);
	p->rdev = le16_to_cpu (q->rdev);
	p->mode = le16_to_cpu (q->mode);
}

/*
 * Lookup the EMD dentry for a directory.
 *
 * Note: the caller must hold a lock on the parent directory.
 */
struct dentry *umsdos_get_emd_dentry(struct dentry *parent)
{
	struct dentry *demd;

	demd = umsdos_lookup_dentry(parent, UMSDOS_EMD_FILE, 
					UMSDOS_EMD_NAMELEN, 1);
	return demd;
}

/*
 * Check whether a directory has an EMD file.
 *
 * Note: the caller must hold a lock on the parent directory.
 */
int umsdos_have_emd(struct dentry *dir)
{
	struct dentry *demd = umsdos_get_emd_dentry (dir);
	int found = 0;

	if (!IS_ERR(demd)) {
		if (demd->d_inode)
			found = 1;
		dput(demd);
	}
	return found;
}

/*
 * Create the EMD file for a directory if it doesn't
 * already exist. Returns 0 or an error code.
 *
 * Note: the caller must hold a lock on the parent directory.
 */
int umsdos_make_emd(struct dentry *parent)
{
	struct dentry *demd = umsdos_get_emd_dentry(parent);
	int err = PTR_ERR(demd);

	if (IS_ERR(demd)) {
		printk("umsdos_make_emd: can't get dentry in %s, err=%d\n",
			parent->d_name.name, err);
		goto out;
	}

	/* already created? */
	err = 0;
	if (demd->d_inode)
		goto out_set;

Printk(("umsdos_make_emd: creating EMD %s/%s\n",
parent->d_name.name, demd->d_name.name));

	err = msdos_create(parent->d_inode, demd, S_IFREG | 0777);
	if (err) {
		printk (KERN_WARNING
			"umsdos_make_emd: create %s/%s failed, err=%d\n",
			parent->d_name.name, demd->d_name.name, err);
	}
out_set:
	dput(demd);
out:
	return err;
}


/*
 * Read an entry from the EMD file.
 * Support variable length record.
 * Return -EIO if error, 0 if OK.
 *
 * does not change {d,i}_count
 */

int umsdos_emd_dir_readentry (struct dentry *demd, loff_t *pos, struct umsdos_dirent *entry)
{
	struct address_space *mapping = demd->d_inode->i_mapping;
	struct page *page;
	struct umsdos_dirent *p;
	int offs = *pos & ~PAGE_CACHE_MASK;
	int recsize;
	int ret = 0;

	page = read_cache_page(mapping, *pos>>PAGE_CACHE_SHIFT,
			(filler_t*)mapping->a_ops->readpage, NULL);
	if (IS_ERR(page))
		goto sync_fail;
	wait_on_page(page);
	if (!Page_Uptodate(page))
		goto async_fail;
	p = (struct umsdos_dirent*)(kmap(page)+offs);

	/* if this is an invalid entry (invalid name length), ignore it */
	if( p->name_len > UMSDOS_MAXNAME )
	{
		printk (KERN_WARNING "Ignoring invalid EMD entry with size %d\n", entry->name_len);
		p->name_len = 0; 
		ret = -ENAMETOOLONG; /* notify umssync(8) code that something is wrong */
	}

	recsize = umsdos_evalrecsize(p->name_len);
	if (offs + recsize > PAGE_CACHE_SIZE) {
		struct page *page2;
		int part = (char *)(page_address(page) + PAGE_CACHE_SIZE) - p->spare;
		page2 = read_cache_page(mapping, 1+(*pos>>PAGE_CACHE_SHIFT),
				(filler_t*)mapping->a_ops->readpage, NULL);
		if (IS_ERR(page2)) {
			kunmap(page);
			page_cache_release(page);
			page = page2;
			goto sync_fail;
		}
		wait_on_page(page2);
		if (!Page_Uptodate(page2)) {
			kunmap(page);
			page_cache_release(page2);
			goto async_fail;
		}
		memcpy(entry->spare,p->spare,part);
		memcpy(entry->spare+part,kmap(page2),
				recsize+offs-PAGE_CACHE_SIZE);
		kunmap(page2);
		page_cache_release(page2);
	} else
		memcpy(entry->spare,p->spare,((char*)p+recsize)-p->spare);
	copy_entry(entry, p);
	kunmap(page);
	page_cache_release(page);
	*pos += recsize;
	return ret;
async_fail:
	page_cache_release(page);
	page = ERR_PTR(-EIO);
sync_fail:
	return PTR_ERR(page);
}


/*
 * Write an entry in the EMD file.
 * Return 0 if OK, -EIO if some error.
 *
 * Note: the caller must hold a lock on the parent directory.
 */
int umsdos_writeentry (struct dentry *parent, struct umsdos_info *info,
				int free_entry)
{
	struct inode *dir = parent->d_inode;
	struct umsdos_dirent *entry = &info->entry;
	struct dentry *emd_dentry;
	int ret;
	struct umsdos_dirent entry0,*p;
	struct address_space *mapping;
	struct page *page, *page2 = NULL;
	int offs;

	emd_dentry = umsdos_get_emd_dentry(parent);
	ret = PTR_ERR(emd_dentry);
	if (IS_ERR(emd_dentry))
		goto out;
	/* make sure there's an EMD file */
	ret = -EIO;
	if (!emd_dentry->d_inode) {
		printk(KERN_WARNING
			"umsdos_writeentry: no EMD file in %s/%s\n",
			parent->d_parent->d_name.name, parent->d_name.name);
		goto out_dput;
	}

	if (free_entry) {
		/* #Specification: EMD file / empty entries
		 * Unused entries in the EMD file are identified
		 * by the name_len field equal to 0. However to
		 * help future extension (or bug correction :-( ),
		 * empty entries are filled with 0.
		 */
		memset (&entry0, 0, sizeof (entry0));
		entry = &entry0;
	} else if (entry->name_len > 0) {
		memset (entry->name + entry->name_len, '\0', 
			sizeof (entry->name) - entry->name_len);
		/* #Specification: EMD file / spare bytes
		 * 10 bytes are unused in each record of the EMD. They
		 * are set to 0 all the time, so it will be possible
		 * to do new stuff and rely on the state of those
		 * bytes in old EMD files.
		 */
		memset (entry->spare, 0, sizeof (entry->spare));
	}

	/* write the entry and update the parent timestamps */
	mapping = emd_dentry->d_inode->i_mapping;
	offs = info->f_pos & ~PAGE_CACHE_MASK;
	ret = -ENOMEM;
	page = grab_cache_page(mapping, info->f_pos>>PAGE_CACHE_SHIFT);
	if (!page)
		goto out_dput;
	p = (struct umsdos_dirent *) (page_address(page) + offs);
	if (offs + info->recsize > PAGE_CACHE_SIZE) {
		ret = mapping->a_ops->prepare_write(NULL,page,offs,
					PAGE_CACHE_SIZE);
		if (ret)
			goto out_unlock;
		page2 = grab_cache_page(mapping,
					(info->f_pos>>PAGE_CACHE_SHIFT)+1);
		if (!page2)
			goto out_unlock2;
		ret = mapping->a_ops->prepare_write(NULL,page2,0,
					offs+info->recsize-PAGE_CACHE_SIZE);
		if (ret)
			goto out_unlock3;
		p->name_len = entry->name_len;
		p->flags = entry->flags;
		p->nlink = cpu_to_le16(entry->nlink);
		p->uid = cpu_to_le16(entry->uid);
		p->gid = cpu_to_le16(entry->gid);
		p->atime = cpu_to_le32(entry->atime);
		p->mtime = cpu_to_le32(entry->mtime);
		p->ctime = cpu_to_le32(entry->ctime);
		p->rdev = cpu_to_le16(entry->rdev);
		p->mode = cpu_to_le16(entry->mode);
		memcpy(p->name,entry->name,
			(char *)(page_address(page) + PAGE_CACHE_SIZE) - p->spare);
		memcpy(page_address(page2),
				entry->spare+PAGE_CACHE_SIZE-offs,
				offs+info->recsize-PAGE_CACHE_SIZE);
		ret = mapping->a_ops->commit_write(NULL,page2,0,
					offs+info->recsize-PAGE_CACHE_SIZE);
		if (ret)
			goto out_unlock3;
		ret = mapping->a_ops->commit_write(NULL,page,offs,
					PAGE_CACHE_SIZE);
		UnlockPage(page2);
		page_cache_release(page2);
		if (ret)
			goto out_unlock;
	} else {
		ret = mapping->a_ops->prepare_write(NULL,page,offs,
					offs + info->recsize);
		if (ret)
			goto out_unlock;
		p->name_len = entry->name_len;
		p->flags = entry->flags;
		p->nlink = cpu_to_le16(entry->nlink);
		p->uid = cpu_to_le16(entry->uid);
		p->gid = cpu_to_le16(entry->gid);
		p->atime = cpu_to_le32(entry->atime);
		p->mtime = cpu_to_le32(entry->mtime);
		p->ctime = cpu_to_le32(entry->ctime);
		p->rdev = cpu_to_le16(entry->rdev);
		p->mode = cpu_to_le16(entry->mode);
		memcpy(p->spare,entry->spare,((char*)p+info->recsize)-p->spare);
		ret = mapping->a_ops->commit_write(NULL,page,offs,
					offs + info->recsize);
		if (ret)
			goto out_unlock;
	}
	UnlockPage(page);
	page_cache_release(page);
		
	dir->i_ctime = dir->i_mtime = CURRENT_TIME;
	mark_inode_dirty(dir);

out_dput:
	dput(emd_dentry);
out:
	Printk (("umsdos_writeentry /mn/: returning %d...\n", ret));
	return ret;
out_unlock3:
	UnlockPage(page2);
	page_cache_release(page2);
out_unlock2:
	ClearPageUptodate(page);
	kunmap(page);
out_unlock:
	UnlockPage(page);
	page_cache_release(page);
	printk ("UMSDOS:  problem with EMD file:  can't write\n");
	goto out_dput;
}

/*
 * General search, locate a name in the EMD file or an empty slot to
 * store it. if info->entry.name_len == 0, search the first empty
 * slot (of the proper size).
 * 
 * Return 0 if found, -ENOENT if not found, another error code if
 * other problem.
 * 
 * So this routine is used to either find an existing entry or to
 * create a new one, while making sure it is a new one. After you
 * get -ENOENT, you make sure the entry is stuffed correctly and
 * call umsdos_writeentry().
 * 
 * To delete an entry, you find it, zero out the entry (memset)
 * and call umsdos_writeentry().
 * 
 * All this to say that umsdos_writeentry must be called after this
 * function since it relies on the f_pos field of info.
 *
 * Note: the caller must hold a lock on the parent directory.
 */
/* #Specification: EMD file structure
 * The EMD file uses a fairly simple layout.  It is made of records
 * (UMSDOS_REC_SIZE == 64).  When a name can't be written in a single
 * record, multiple contiguous records are allocated.
 */

static int umsdos_find (struct dentry *demd, struct umsdos_info *info)
{
	struct umsdos_dirent *entry = &info->entry;
	int recsize = info->recsize;
	struct inode *emd_dir;
	int ret = -ENOENT;
	struct {
		off_t posok;	/* Position available to store the entry */
		off_t one;	/* One empty position -> maybe <- large enough */
	} empty;
	int found = 0;
	int empty_size = 0;
	struct address_space *mapping;
	filler_t *readpage;
	struct page *page = NULL;
	int index = -1;
	int offs = PAGE_CACHE_SIZE,max_offs = PAGE_CACHE_SIZE;
	char *p = NULL;
	loff_t pos = 0;

	/* make sure there's an EMD file ... */
	ret = -ENOENT;
	emd_dir = demd->d_inode;
	if (!emd_dir)
		goto out_dput;
	mapping = emd_dir->i_mapping;
	readpage = (filler_t*)mapping->a_ops->readpage;

	empty.posok = emd_dir->i_size;
	while (1) {
		struct umsdos_dirent *rentry;
		int entry_size;

		if (offs >= max_offs) {
			if (page) {
				kunmap(page);
				page_cache_release(page);
				page = NULL;
			}
			if (pos >= emd_dir->i_size) {
				info->f_pos = empty.posok;
				break;
			}
			if (++index == (emd_dir->i_size>>PAGE_CACHE_SHIFT))
				max_offs = emd_dir->i_size & ~PAGE_CACHE_MASK;
			offs -= PAGE_CACHE_SIZE;
			page = read_cache_page(mapping,index,readpage,NULL);
			if (IS_ERR(page))
				goto sync_fail;
			wait_on_page(page);
			if (!Page_Uptodate(page))
				goto async_fail;
			p = kmap(page);
		}

		rentry = (struct umsdos_dirent *)(p+offs);

		if (rentry->name_len == 0) {
			/* We are looking for an empty section at least */
			/* as large as recsize. */
			if (entry->name_len == 0) {
				info->f_pos = pos;
				ret = 0;
				break;
			}
			offs += UMSDOS_REC_SIZE;
			pos += UMSDOS_REC_SIZE;
			if (found)
				continue;
			if (!empty_size)
				empty.one = pos-UMSDOS_REC_SIZE;
			empty_size += UMSDOS_REC_SIZE;
			if (empty_size == recsize) {
				/* Here is a large enough section. */
				empty.posok = empty.one;
				found = 1;
			}
			continue;
		}

		entry_size = umsdos_evalrecsize(rentry->name_len);
		if (entry_size > PAGE_CACHE_SIZE)
			goto async_fail;
		empty_size = 0;
		if (entry->name_len != rentry->name_len)
			goto skip_it;

		if (entry_size + offs > PAGE_CACHE_SIZE) {
			/* Sucker spans the page boundary */
			int len = (p+PAGE_CACHE_SIZE)-rentry->name;
			struct page *next_page;
			char *q;
			next_page = read_cache_page(mapping,index+1,readpage,NULL);
			if (IS_ERR(next_page)) {
				page_cache_release(page);
				page = next_page;
				goto sync_fail;
			}
			wait_on_page(next_page);
			if (!Page_Uptodate(next_page)) {
				page_cache_release(page);
				page = next_page;
				goto async_fail;
			}
			q = kmap(next_page);
			if (memcmp(entry->name, rentry->name, len) ||
			    memcmp(entry->name+len, q, entry->name_len-len)) {
				kunmap(next_page);
				page_cache_release(next_page);
				goto skip_it;
			}
			kunmap(next_page);
			page_cache_release(next_page);
		} else if (memcmp (entry->name, rentry->name, entry->name_len))
			goto skip_it;

		info->f_pos = pos;
		copy_entry(entry, rentry);
		ret = 0;
		break;
skip_it:
		offs+=entry_size;
		pos+=entry_size;
	}
	if (page) {
		kunmap(page);
		page_cache_release(page);
	}
	umsdos_manglename (info);

out_dput:
	dput(demd);
	return ret;

async_fail:
	page_cache_release(page);
	page = ERR_PTR(-EIO);
sync_fail:
	return PTR_ERR(page);
}


/*
 * Add a new entry in the EMD file.
 * Return 0 if OK or a negative error code.
 * Return -EEXIST if the entry already exists.
 *
 * Complete the information missing in info.
 * 
 * N.B. What if the EMD file doesn't exist?
 */

int umsdos_newentry (struct dentry *parent, struct umsdos_info *info)
{
	int err, ret = -EEXIST;
	struct dentry *demd = umsdos_get_emd_dentry(parent);

	ret = PTR_ERR(demd);
	if (IS_ERR(demd))
		goto out;
	err = umsdos_find (demd, info);
	if (err && err == -ENOENT) {
		ret = umsdos_writeentry (parent, info, 0);
		Printk (("umsdos_writeentry EMD ret = %d\n", ret));
	}
out:
	return ret;
}


/*
 * Create a new hidden link.
 * Return 0 if OK, an error code if not.
 */

/* #Specification: hard link / hidden name
 * When a hard link is created, the original file is renamed
 * to a hidden name. The name is "..LINKNNN" where NNN is a
 * number define from the entry offset in the EMD file.
 */
int umsdos_newhidden (struct dentry *parent, struct umsdos_info *info)
{
	int ret;
	struct dentry *demd = umsdos_get_emd_dentry(parent);
	ret = PTR_ERR(demd);
	if (IS_ERR(demd))
		goto out;

	umsdos_parse ("..LINK", 6, info);
	info->entry.name_len = 0;
	ret = umsdos_find (demd, info);
	if (ret == -ENOENT || ret == 0) {
		info->entry.name_len = sprintf (info->entry.name,
						"..LINK%ld", info->f_pos);
		ret = 0;
	}
out:
	return ret;
}


/*
 * Remove an entry from the EMD file.
 * Return 0 if OK, a negative error code otherwise.
 * 
 * Complete the information missing in info.
 */

int umsdos_delentry (struct dentry *parent, struct umsdos_info *info, int isdir)
{
	int ret;
	struct dentry *demd = umsdos_get_emd_dentry(parent);

	ret = PTR_ERR(demd);
	if (IS_ERR(demd))
		goto out;
	ret = umsdos_find (demd, info);
	if (ret)
		goto out;
	if (info->entry.name_len == 0)
		goto out;

	if ((isdir != 0) != (S_ISDIR (info->entry.mode) != 0)) {
		if (S_ISDIR (info->entry.mode)) {
			ret = -EISDIR;
		} else {
			ret = -ENOTDIR;
		}
		goto out;
	}
	ret = umsdos_writeentry (parent, info, 1);

out:
	return ret;
}


/*
 * Verify that an EMD directory is empty.
 * Return: 
 * 0 if not empty,
 * 1 if empty (except for EMD file),
 * 2 if empty or no EMD file.
 */

int umsdos_isempty (struct dentry *dentry)
{
	struct dentry *demd;
	int ret = 2;
	loff_t pos = 0;

	demd = umsdos_get_emd_dentry(dentry);
	if (IS_ERR(demd))
		goto out;
	/* If the EMD file does not exist, it is certainly empty. :-) */
	if (!demd->d_inode)
		goto out_dput;

	ret = 1;
	while (pos < demd->d_inode->i_size) {
		struct umsdos_dirent entry;

		if (umsdos_emd_dir_readentry (demd, &pos, &entry) != 0) {
			ret = 0;
			break;
		}
		if (entry.name_len != 0) {
			ret = 0;
			break;
		}
	}

out_dput:
	dput(demd);
out:
	return ret;
}

/*
 * Locate an entry in a EMD directory.
 * Return 0 if OK, error code if not, generally -ENOENT.
 *
 * expect argument:
 * 	0: anything
 * 	1: file
 * 	2: directory
 */

int umsdos_findentry (struct dentry *parent, struct umsdos_info *info,
			int expect)
{		
	int ret;
	struct dentry *demd = umsdos_get_emd_dentry(parent);

	ret = PTR_ERR(demd);
	if (IS_ERR(demd))
		goto out;
	ret = umsdos_find (demd, info);
	if (ret)
		goto out;

	switch (expect) {
	case 1:
		if (S_ISDIR (info->entry.mode))
			ret = -EISDIR;
		break;
	case 2:
		if (!S_ISDIR (info->entry.mode))
			ret = -ENOTDIR;
	}

out:
	Printk (("umsdos_findentry: returning %d\n", ret));
	return ret;
}
