/*
 * Some syscall stubs
 *
 * Copyright (C) 2008 Bahadir Balban
 */
#include <l4/api/errno.h>
#include <l4lib/types.h>
#include <l4lib/ipcdefs.h>
#include <l4lib/arch/syscalls.h>
#include <l4lib/arch/syslib.h>
#include <lib/pathstr.h>
#include <lib/malloc.h>
#include <string.h>
#include <stdio.h>
#include <task.h>
#include <stat.h>
#include <vfs.h>
#include <alloca.h>
#include <path.h>
#include <syscalls.h>

#define NILFD			-1

/*
 * This informs mm0 about an opened file descriptors.
 *
 * MM0 *also* keeps track of fd's because mm0 is a better candidate
 * for handling syscalls that access file content (i.e. read/write) since
 * it maintains the page cache. MM0 is not notified about opened files
 * but is rather informed when it asks to be. This avoids deadlocks by
 * keeping the request flow in one way.
 */

int pager_sys_open(struct tcb *pager, l4id_t opener, int fd)
{
	struct tcb *task;
	struct vnode *v;

	// printf("%s/%s\n", __TASKNAME__, __FUNCTION__);
	//
	if (pager->tid != PAGER_TID)
		return -EINVAL;

	/* Check if such task exists */
	if (!(task = find_task(opener)))
		return -ESRCH;

	/* Check if that fd has been opened */
	if (task->files->fd[fd] == NILFD)
		return -EBADF;

	/* Search the vnode by that vnum */
	if (IS_ERR(v = vfs_lookup_byvnum(vfs_root.pivot->sb,
					 task->files->fd[fd])))
		return (int)v;

	/*
	 * Write file information, they will
	 * be sent via the return reply.
	 */
	write_mr(L4SYS_ARG0, v->vnum);
	write_mr(L4SYS_ARG1, v->size);

	return 0;
}

/* This is called when the pager needs to open a vfs file via its path */
int pager_open_bypath(struct tcb *pager, char *pathname)
{
	struct pathdata *pdata;
	struct tcb *task;
	struct vnode *v;
	int retval;

	printf("%s/%s\n", __TASKNAME__, __FUNCTION__);
	if (pager->tid != PAGER_TID)
		return -EINVAL;

	// printf("%s/%s\n", __TASKNAME__, __FUNCTION__);

	/* Parse path data */
	if (IS_ERR(pdata = pathdata_parse(pathname,
					  alloca(strlen(pathname) + 1),
					  task)))
		return (int)pdata;

	/* Search the vnode by that path */
	if (IS_ERR(v = vfs_lookup_bypath(pdata))) {
		retval = (int)v;
		goto out;
	}

	/*
	 * Write file information, they will
	 * be sent via the return reply.
	 */
	write_mr(L4SYS_ARG0, v->vnum);
	write_mr(L4SYS_ARG1, v->size);

	return 0;

out:
	pathdata_destroy(pdata);
	return retval;

}


/* Directories only for now */
void print_vnode(struct vnode *v)
{
	struct dentry *d, *c;

	printf("Vnode names:\n");
	list_for_each_entry(d, &v->dentries, vref) {
		printf("%s\n", d->name);
		printf("Children dentries:\n");
		list_for_each_entry(c, &d->children, child)
			printf("%s\n", c->name);
	}
}

/* Creates a node under a directory, e.g. a file, directory. */
struct vnode *vfs_create(struct tcb *task, struct pathdata *pdata,
			 unsigned int mode)
{
	struct vnode *vparent, *newnode;
	const char *nodename;

	/* The last component is to be created */
	nodename = pathdata_last_component(pdata);

	/* Check that the parent directory exists. */
	if (IS_ERR(vparent = vfs_lookup_bypath(pdata)))
		return vparent;

	/* The parent vnode must be a directory. */
	if (!vfs_isdir(vparent))
		return PTR_ERR(-ENOENT);

	/* Create new directory under the parent */
	if (IS_ERR(newnode = vparent->ops.mknod(vparent, nodename, mode)))
		return newnode;

	// print_vnode(vparent);
	return newnode;
}

/*
 * Pager notifies vfs about a closed file descriptor.
 *
 * FIXME: fsync + close could be done under a single "close" ipc
 * from pager. Currently there are 2 ipcs: 1 fsync + 1 fd close.
 */
int pager_sys_close(struct tcb *sender, l4id_t closer, int fd)
{
	struct tcb *task;
	int err;

	// printf("%s/%s\n", __TASKNAME__, __FUNCTION__);

	BUG_ON(!(task = find_task(closer)));

	if ((err = id_del(task->files->fdpool, fd)) < 0) {
		printf("%s: Error releasing fd identifier.\n",
		       __FUNCTION__);
		return err;
	}
	task->files->fd[fd] = NILFD;

	return 0;
}

/* FIXME:
 * - Is it already open?
 * - Allocate a copy of path string since lookup destroys it
 * - Check flags and mode.
 */
int sys_open(struct tcb *task, const char *pathname, int flags, unsigned int mode)
{
	struct pathdata *pdata;
	struct vnode *v;
	int fd;
	int retval;

	// printf("%s/%s\n", __TASKNAME__, __FUNCTION__);

	/* Parse path data */
	if (IS_ERR(pdata = pathdata_parse(pathname,
					  alloca(strlen(pathname) + 1),
					  task)))
		return (int)pdata;

	/* Creating new file */
	if (flags & O_CREAT) {
		/* Make sure mode identifies a file */
		mode |= S_IFREG;
		if (IS_ERR(v = vfs_create(task, pdata, mode))) {
			retval = (int)v;
			goto out;
		}
	} else {
		/* Not creating, just opening, get the vnode */
		if (IS_ERR(v = vfs_lookup_bypath(pdata))) {
			retval = (int)v;
			goto out;
		}
	}

	/* Get a new fd */
	BUG_ON((fd = id_new(task->files->fdpool)) < 0);
	retval = fd;

	/* Assign the new fd with the vnode's number */
	task->files->fd[fd] = v->vnum;

out:
	pathdata_destroy(pdata);
	return retval;
}

int sys_mkdir(struct tcb *task, const char *pathname, unsigned int mode)
{
	struct pathdata *pdata;
	struct vnode *v;
	int ret = 0;

	/* Parse path data */
	if (IS_ERR(pdata = pathdata_parse(pathname,
					  alloca(strlen(pathname) + 1),
					  task)))
		return (int)pdata;

	/* Make sure we create a directory */
	mode |= S_IFDIR;

	/* Create the directory or fail */
	if (IS_ERR(v = vfs_create(task, pdata, mode)))
		ret = (int)v;

	/* Destroy extracted path data */
	pathdata_destroy(pdata);
	return ret;
}

int sys_chdir(struct tcb *task, const char *pathname)
{
	struct vnode *v;
	struct pathdata *pdata;
	int ret = 0;

	/* Parse path data */
	if (IS_ERR(pdata = pathdata_parse(pathname,
					  alloca(strlen(pathname) + 1),
					  task)))
		return (int)pdata;

	/* Get the vnode */
	if (IS_ERR(v = vfs_lookup_bypath(pdata))) {
		ret = (int)v;
		goto out;
	}

	/* Ensure it's a directory */
	if (!vfs_isdir(v)) {
		ret = -ENOTDIR;
		goto out;
	}

	/* Assign the current directory pointer */
	task->fs_data->curdir = v;

out:
	/* Destroy extracted path data */
	pathdata_destroy(pdata);
	return ret;
}

void fill_kstat(struct vnode *v, struct kstat *ks)
{
	ks->vnum = (u64)v->vnum;
	ks->mode = v->mode;
	ks->links = v->links;
	ks->uid = v->owner & 0xFFFF;
	ks->gid = (v->owner >> 16) & 0xFFFF;
	ks->size = v->size;
	ks->blksize = v->sb->blocksize;
	ks->atime = v->atime;
	ks->mtime = v->mtime;
	ks->ctime = v->ctime;
}

int sys_fstat(struct tcb *task, int fd, void *statbuf)
{
	struct vnode *v;
	unsigned long vnum;

	/* Get the vnum */
	if (fd < 0 || fd > TASK_FILES_MAX || task->files->fd[fd] == NILFD)
		return -EBADF;

	vnum = task->files->fd[fd];

	/* Lookup vnode */
	if (!(v = vfs_lookup_byvnum(vfs_root.pivot->sb, vnum)))
		return -EINVAL;

	/* Fill in the c0-style stat structure */
	fill_kstat(v, statbuf);

	return 0;
}

/*
 * Returns codezero-style stat structure which in turn is
 * converted to posix style stat structure via the libposix
 * library in userspace.
 */
int sys_stat(struct tcb *task, const char *pathname, void *statbuf)
{
	struct vnode *v;
	struct pathdata *pdata;
	int ret = 0;

	/* Parse path data */
	if (IS_ERR(pdata = pathdata_parse(pathname,
					  alloca(strlen(pathname) + 1),
					  task)))
		return (int)pdata;

	/* Get the vnode */
	if (IS_ERR(v = vfs_lookup_bypath(pdata))) {
		ret = (int)v;
		goto out;
	}

	/* Fill in the c0-style stat structure */
	fill_kstat(v, statbuf);

out:
	/* Destroy extracted path data */
	pathdata_destroy(pdata);
	return ret;
}

/*
 * Note this can be solely called by the pager and is not the posix read call.
 * That call is in the pager. This merely supplies the pages the pager needs
 * if they're not in the page cache.
 */
int pager_sys_read(struct tcb *pager, unsigned long vnum, unsigned long f_offset,
		   unsigned long npages, void *pagebuf)
{
	struct vnode *v;

	// printf("%s/%s\n", __TASKNAME__, __FUNCTION__);

	if (pager->tid != PAGER_TID)
		return -EINVAL;

	/* Lookup vnode */
	if (!(v = vfs_lookup_byvnum(vfs_root.pivot->sb, vnum)))
		return -EINVAL;

	/* Ensure vnode is not a directory */
	if (vfs_isdir(v))
		return -EISDIR;

	return v->fops.read(v, f_offset, npages, pagebuf);
}

int pager_update_stats(struct tcb *pager, unsigned long vnum,
		       unsigned long newsize)
{
	struct vnode *v;

	// printf("%s/%s\n", __TASKNAME__, __FUNCTION__);

	if (pager->tid != PAGER_TID)
		return -EINVAL;

	/* Lookup vnode */
	if (!(v = vfs_lookup_byvnum(vfs_root.pivot->sb, vnum)))
		return -EINVAL;

	v->size = newsize;
	v->sb->ops->write_vnode(v->sb, v);

	return 0;
}

/*
 * This can be solely called by the pager and is not the posix write call.
 * That call is in the pager. This writes the dirty pages of a file
 * back to disk via vfs.
 *
 * The buffer must be contiguous by page, if npages > 1.
 */
int pager_sys_write(struct tcb *pager, unsigned long vnum, unsigned long f_offset,
		    unsigned long npages, void *pagebuf)
{
	struct vnode *v;
	int ret;
	int fwrite_end;

	// printf("%s/%s\n", __TASKNAME__, __FUNCTION__);

	if (pager->tid != PAGER_TID)
		return -EINVAL;

	/* Lookup vnode */
	if (!(v = vfs_lookup_byvnum(vfs_root.pivot->sb, vnum)))
		return -EINVAL;

	/* Ensure vnode is not a directory */
	if (vfs_isdir(v))
		return -EISDIR;

	//printf("%s/%s: Writing to vnode %lu, at pgoff 0x%x, %d pages, buf at 0x%x\n",
	//	__TASKNAME__, __FUNCTION__, vnum, f_offset, npages, pagebuf);

	if ((ret = v->fops.write(v, f_offset, npages, pagebuf)) < 0)
		return ret;

	/*
	 * If the file is extended, write silently extends it.
	 * We update the extended size here. Otherwise subsequent write's
	 * may fail by relying on wrong file size.
	 */
	fwrite_end = __pfn_to_addr(f_offset) + ret;
	if (v->size < fwrite_end) {
		v->size = fwrite_end;
		v->sb->ops->write_vnode(v->sb, v);
	}

	return ret;
}

/*
 * FIXME: Here's how this should have been:
 * v->ops.readdir() -> Reads fs-specific directory contents. i.e. reads
 * the directory buffer, doesn't care however contained vnode details are
 * stored.
 *
 * After reading, it converts the fs-spceific contents into generic vfs
 * dentries and populates the dentries of those vnodes.
 *
 * If vfs_readdir() is issued, those generic dentries are converted into
 * the posix-defined directory record structure. During this on-the-fly
 * generation, pseudo-entries such as . and .. are also added.
 *
 * If this layering is not done, i.e. the low-level dentry buffer already
 * keeps this record structure and we try to return that, then we wont
 * have a chance to add the pseudo-entries . and .. These record entries
 * are essentially created from parent vnode and current vnode but using
 * the names . and ..
 */

int fill_dirent(void *buf, unsigned long vnum, int offset, char *name)
{
	struct dirent *d = buf;

	d->inum = (unsigned int)vnum;
	d->offset = offset;
	d->rlength = sizeof(struct dirent);
	strncpy((char *)d->name, name, DIRENT_NAME_MAX);

	return d->rlength;
}

/*
 * Reads @count bytes of posix struct dirents into @buf. This implements
 * the raw dirent read syscall upon which readdir() etc. posix calls
 * can be built in userspace.
 *
 * FIXME: Ensure buf is in shared utcb, and count does not exceed it.
 */
int sys_readdir(struct tcb *t, int fd, void *buf, int count)
{
	int dirent_size = sizeof(struct dirent);
	int total = 0, nbytes = 0;
	unsigned long vnum;
	struct vnode *v;
	struct dentry *d;

	// printf("%s/%s\n", __TASKNAME__, __FUNCTION__);

	/* Check address is in task's utcb */
	if ((unsigned long)buf < t->shpage_address ||
	    (unsigned long)buf > t->shpage_address + PAGE_SIZE)
		return -EINVAL;

	if (fd < 0 || fd > TASK_FILES_MAX || t->files->fd[fd] == NILFD)
		return -EBADF;

	vnum = t->files->fd[fd];

	/* Lookup vnode */
	if (!(v = vfs_lookup_byvnum(vfs_root.pivot->sb, vnum)))
		return -EINVAL;

	d = list_entry(v->dentries.next, struct dentry, vref);

	/* Ensure vnode is a directory */
	if (!vfs_isdir(v))
		return -ENOTDIR;

	/* Write pseudo-entries . and .. to user buffer */
	if (count < dirent_size)
		return 0;

	fill_dirent(buf, v->vnum, nbytes, VFS_STR_CURDIR);
	nbytes += dirent_size;
	buf += dirent_size;
	count -= dirent_size;

	if (count < dirent_size)
		return 0;

	fill_dirent(buf, d->parent->vnode->vnum, nbytes, VFS_STR_PARDIR);
	nbytes += dirent_size;
	buf += dirent_size;
	count -= dirent_size;

	/* Copy fs-specific dir to buf in struct dirent format */
	if ((total = v->ops.filldir(buf, v, count)) < 0)
		return total;

	return nbytes + total;
}

