/* *****************************************************************
 * (C) Copyright 2007 Benjamin Krill <ben@codiert.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
 * MA 02110-1301 USA
 * *****************************************************************/
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include <linux/slab.h>

#include "fpgafs.h"

static struct inode *fpgafs_alloc_inode(struct super_block *sb);
static void fpgafs_destroy_inode(struct inode *inode);
static void fpgafs_delete_inode(struct inode *inode);
static struct inode *fpgafs_new_inode(struct super_block *sb, int mode);

static void fpgafs_prune_dir(struct dentry *dir);
int fpgafs_mkdir(struct inode *dir, struct dentry *dentry, int mode);
static int fpgafs_rmdir(struct inode *dir, struct dentry *dentry);

static int fpgafs_new_file(struct super_block *sb, struct dentry *dentry,
                           const struct file_operations *fops, int mode,
                           struct fpga_context *ctx);
static int fpgafs_setattr(struct dentry *dentry, struct iattr *attr);

const struct file_operations fpgafs_context_foperations = {
	.open     = dcache_dir_open,
	//.release  = fpgafs_dir_close,
	.llseek   = dcache_dir_lseek,
	.read     = generic_read_dir,
	.readdir  = dcache_readdir,
	.fsync    = simple_sync_file,
};

const struct inode_operations fpgafs_simple_dir_inode_operations = {
	.lookup = simple_lookup
};

const struct inode_operations fpgafs_dir_inode_operations = {
	.lookup = simple_lookup,
	.mkdir  = fpgafs_mkdir,
	.rmdir  = fpgafs_rmdir
};

static struct kmem_cache *fpgafs_inode_cache;

/* basic inode operations */
static struct inode *fpgafs_alloc_inode(struct super_block *sb)
{
	struct fpgafs_inode_info *fsi;

	fsi = kmem_cache_alloc(fpgafs_inode_cache, GFP_KERNEL);
	if (!fsi)
		return NULL;

  fsi->i_ctx = NULL;

  return &fsi->vfs_inode;
}

static void fpgafs_destroy_inode(struct inode *inode)
{
	kmem_cache_free(fpgafs_inode_cache, FPGAFS_I(inode));
}

static void fpgafs_delete_inode(struct inode *inode)
{
	//struct fpgafs_inode_info *fsi = FPGAFS_I(inode);

//	XXX: do some fpga interface operations....
//	if (fsi->i_ctx)
//		put_fpga_context(ei->i_ctx);
	clear_inode(inode);
}

static struct inode *fpgafs_new_inode(struct super_block *sb, int mode)
{
	struct inode *inode;

	inode = new_inode(sb);
	if (!inode)
		return inode;

	inode->i_mode = mode;
	inode->i_uid = current->fsuid;
	inode->i_gid = current->fsgid;
	inode->i_blocks = 0;
	inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
  return inode;
}

/* FPGA FS specific functions */
static int fpgafs_fill_dir(struct dentry *dir, struct tree_descr *files,
		        int mode, struct fpga_context *ctx)
{
	struct dentry *dentry;
	int ret;

	while (files->name && files->name[0]) {
		ret = -ENOMEM;
		dentry = d_alloc_name(dir, files->name);
		if (!dentry)
			goto out;
		ret = fpgafs_new_file(dir->d_sb, dentry, files->ops,
		                     files->mode & mode, ctx);
		if (ret)
			goto out;
		files++;
	}
	return 0;
out:
  fpgafs_prune_dir(dir);
  return ret;
}


/* specific operations */
static int fpgafs_setattr(struct dentry *dentry, struct iattr *attr)
{
	struct inode *inode = dentry->d_inode;

	if ((attr->ia_valid & ATTR_SIZE) &&
	    (attr->ia_size != inode->i_size))
		return -EINVAL;
	return inode_setattr(inode, attr);
}

static int fpgafs_new_file(struct super_block *sb, struct dentry *dentry,
                           const struct file_operations *fops, int mode,
                           struct fpga_context *ctx)
{
	static struct inode_operations fpgafs_file_iops = {
		.setattr = fpgafs_setattr,
	};
	struct inode *inode;
	int ret;

	ret = -ENOSPC;
	inode = fpgafs_new_inode(sb, S_IFREG | mode);
	if (!inode)
		goto out;

	ret = 0;
	inode->i_op = &fpgafs_file_iops;
	inode->i_fop = fops;
	inode->i_private = FPGAFS_I(inode)->i_ctx = ctx;
	d_add(dentry, inode);
out:
	return ret;
}

int fpgafs_mkdir( struct inode *dir, struct dentry *dentry, int mode)
{
	int ret;
	struct inode *inode;
	struct fpga_context *ctx = NULL;

	ret = -ENOSPC;
	inode = fpgafs_new_inode(dir->i_sb, mode | S_IFDIR);
	if (!inode)
		return ret;

	mutex_lock(&inode->i_mutex);

	if (dir->i_mode & S_ISGID) {
		inode->i_gid = dir->i_gid;
		inode->i_mode &= S_ISGID;
	}

	ctx = alloc_fpga_context();
	FPGAFS_I(inode)->i_ctx = ctx;
	if (!ctx)
		goto unmutex;

	inode->i_op = &fpgafs_simple_dir_inode_operations;
	inode->i_fop = &simple_dir_operations;

  ret = fpgafs_fill_dir(dentry, fpgafs_dir_contents, mode, ctx);
	ret = 0;
  //if (ret) {
	//	FREEE CONTENT
	//	goto unmutex;
	//}

	d_instantiate(dentry, inode);
	dget(dentry);
	dir->i_nlink++;
	dentry->d_inode->i_nlink++;

unmutex:
	mutex_unlock(&inode->i_mutex);
	return ret;
}

static void fpgafs_prune_dir(struct dentry *dir)
{
	struct dentry *dentry, *tmp;
	//mutex_lock(&dir->d_inode->i_mutex);
	list_for_each_entry_safe(dentry, tmp, &dir->d_subdirs, d_u.d_child) {
		spin_lock(&dcache_lock);
		spin_lock(&dentry->d_lock);
		if (!(d_unhashed(dentry)) && dentry->d_inode) {
			dget_locked(dentry);
			__d_drop(dentry);
			spin_unlock(&dentry->d_lock);
			simple_unlink(dir->d_inode, dentry);
			spin_unlock(&dcache_lock);
			dput(dentry);
    } else {
			spin_unlock(&dentry->d_lock);
			spin_unlock(&dcache_lock);
    }
  }
  shrink_dcache_parent(dir);
  //mutex_unlock(&dir->d_inode->i_mutex);
}

static int fpgafs_rmdir(struct inode *dir, struct dentry *dentry)
{
	/* remove all entries */
	free_fpga_context(FPGAFS_I(dentry->d_inode)->i_ctx);
	fpgafs_prune_dir(dentry);
	//fpgafs_remove_inode

	return simple_rmdir(dir, dentry);
}

/* SUPERBLOCK operations */
int fpgafs_fill_sb(struct super_block *sb, void *data, int silent)
{
  struct inode *inode;
  int ret = -ENOMEM;

	static struct super_operations fpgafs_ops = {
		//.read_inode = fpgafs_read_inode,
		//.put_super  = fpgafs_put_super,
		.alloc_inode = fpgafs_alloc_inode,
    .destroy_inode = fpgafs_destroy_inode,
    .statfs = simple_statfs,
    .delete_inode = fpgafs_delete_inode,
    .drop_inode = generic_delete_inode,
	};

  sb->s_maxbytes = MAX_LFS_FILESIZE;
  sb->s_blocksize = PAGE_CACHE_SIZE;
  sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
  sb->s_magic = FPGAFS_MAGIC;
  sb->s_op = &fpgafs_ops;

	inode = fpgafs_new_inode(sb, S_IFDIR | 0775);
	if (!inode)
		return ret;

  inode->i_op = &fpgafs_dir_inode_operations;
  inode->i_fop = &simple_dir_operations;
  FPGAFS_I(inode)->i_ctx = NULL;

  sb->s_root = d_alloc_root(inode);
  if (!sb->s_root)
		return ret;

  return 0;
}

static int fpgafs_get_sb(struct file_system_type *fs_type,
     int flags, const char *dev_name, void *data, struct vfsmount *mnt)
{
	return get_sb_nodev(fs_type, flags, data, fpgafs_fill_sb, mnt);
}

static struct file_system_type fpgafs_type = {
	.owner   = THIS_MODULE,
	.name    = "fpgafs",
	.get_sb  = fpgafs_get_sb,
	.kill_sb = kill_anon_super
};

static void fpgafs_init_once(void *p, struct kmem_cache * cachep,
                            unsigned long flags)
{
  struct fpgafs_inode_info *fsi = p;
	inode_init_once(&fsi->vfs_inode);
}

/* init exit functions ... */
int __init fpgafs_init(void)
{
	fpgafs_inode_cache = kmem_cache_create("fpgafs_inode_cache",
	               sizeof(struct fpgafs_inode_info), 0,
	               SLAB_HWCACHE_ALIGN, fpgafs_init_once, NULL);
	if(!fpgafs_inode_cache)
		return -ENOMEM;

	register_filesystem(&fpgafs_type);
	printk("fpgafs: Benjamin Krill <ben@codiert.org>\n");
	printk("fpgafs: successfull loaded ...\n");
	return 0;
}

void __exit fpgafs_exit(void)
{
	kmem_cache_destroy(fpgafs_inode_cache);
	unregister_filesystem(&fpgafs_type);
}

module_init(fpgafs_init);
module_exit(fpgafs_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Benjamin Krill <ben@codiert.org>");
