#FORMAT Plain
/*
 * tyler@agat.net
 *
 * Nano DevFS (intended to Linux Kernel 2.6(.12))
 *
 * This module implements the nano dev file system from Greg KH.
 * This code is included in the main tree but I've writtent this module
 * because it's a very simple file system.
 *
 * ndevfs is a simple ramfs based filesystem. It creates/deletes 
 * nodes in the /dev directory dynamically in the format seen in devfs. 
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/device.h>

#define	DRIVER_AUTHOR "tyler@agat.net"
#define	DRIVER_DESC	"Ram File System"

#define MAGIC 0x64756d62

struct entry {
	struct list_head node;
	struct dentry *dentry;
	char name[BUS_ID_SIZE];
};

static LIST_HEAD(entries);

static struct vfsmount *mount;
static int mount_count;

static struct file_operations file_ops = {
			read:		generic_file_read,
			write:		generic_file_write,
			mmap:		generic_file_mmap,
			fsync:		simple_sync_file,
			llseek:		generic_file_llseek,
};

static struct inode *get_inode(struct super_block *sb, int mode, dev_t dev)
{
	struct inode *inode = new_inode(sb);

	if (inode) {
		inode->i_mode = mode;
		inode->i_uid = 0;
		inode->i_gid = 0;
		inode->i_blksize = PAGE_CACHE_SIZE;
		inode->i_blocks = 0;
		inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
		switch (mode & S_IFMT) {
			case S_IFREG:
				inode->i_fop = &file_ops;
				break;
			case S_IFDIR:
				inode->i_op = &simple_dir_inode_operations;
				inode->i_fop = &simple_dir_operations;
				inode->i_nlink++;
				break;
		}
	}

	return inode;
}

static int mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
{
	struct inode *inode = get_inode(dir->i_sb, mode, dev);

	if (dentry->d_inode) {
		return -EEXIST;
	}

	if (! inode) {
		return -EPERM;
	}

	d_instantiate(dentry, inode);
	dget(dentry);

	return 0;
}

static inline int positive(struct dentry *dentry)
{
	return dentry->d_inode && !d_unhashed(dentry);
}

static void remove(struct dentry *dentry)
{
	struct dentry *parent;

	if (!dentry) {
		return;
	}

	parent = dentry->d_parent;
	if (!parent || !parent->d_inode) {
		return;
	}

	down(&parent->d_inode->i_sem);
	if (positive(dentry)) {
		if (dentry->d_inode) {
			if (S_ISDIR(dentry->d_inode->i_mode)) {
				simple_rmdir(parent->d_inode, dentry);
			}
			else {
				simple_unlink(parent->d_inode, dentry);
			}
		}
	}
	up(&parent->d_inode->i_sem);
	simple_release_fs(&mount, &mount_count);
}

void mfs_create(const char *name, dev_t dev)
{
	struct dentry *dentry;
	struct dentry *parent;
	struct entry *entry;
	int err;
	int mode = S_IRUSR | S_IWUSR;

	mode |= S_IFREG;

	err = simple_pin_fs("mfs", &mount, &mount_count);
	if (err) {
		return;
	}

	if (mount && mount->mnt_sb) {
		parent = mount->mnt_sb->s_root;
	}
	else {
		pr_debug("%s: no parent?\n", __FUNCTION__);
		goto error;
	}

	down(&parent->d_inode->i_sem);
	dentry = lookup_one_len(name, parent, strlen(name));
	if (!IS_ERR(dentry)) {
		err = mknod(parent->d_inode, dentry, mode, dev);
	}
	else {
		err = PTR_ERR(dentry);
	}
	up(&parent->d_inode->i_sem);

	if (err) {
		goto error;
	}

	entry = kmalloc(sizeof(struct entry), GFP_KERNEL);
	if (!entry) {
		remove(dentry);
		err = -ENOMEM;
		goto error;
	}

	entry->dentry = dentry;
	strcpy(&entry->name[0], name);
	list_add(&entry->node, &entries);

	return;

error:
	pr_debug("%s failed with error %d\n", __FUNCTION__, err);
	simple_release_fs(&mount, &mount_count);
}

EXPORT_SYMBOL_GPL(mfs_create);

void mfs_remove(const char *name)
{
	struct entry *entry;
	struct dentry *dentry = NULL;

	list_for_each_entry(entry, &entries, node) {
		if (strcmp(&entry->name[0], name) == 0) {
			dentry = entry->dentry;
			break;
		}
	}

	if (!dentry) {
		pr_debug("%s: can't find %s\n", __FUNCTION__, name);
		return;
	}

	remove(dentry);
}

EXPORT_SYMBOL_GPL(mfs_remove);

static int fill_super(struct super_block *sb, void *data, int silent)
{
	static struct tree_descr files[] = {{""}};

	return simple_fill_super(sb, MAGIC, files);
}

static struct super_block *get_sb(struct file_system_type *fs_type,
		int flags, const char *dev_name, void *data)
{
	return get_sb_single(fs_type, flags, data, fill_super);
}

static struct file_system_type mfs = {
	owner:		THIS_MODULE,
	name: 		"mfs",
	get_sb: 	get_sb,
	kill_sb: 	kill_litter_super,
};

int __init loading(void)
{
	return register_filesystem(&mfs);
}

void __exit unloading(void)
{
	simple_release_fs(&mount, &mount_count);
	unregister_filesystem(&mfs);
}

module_init(loading);
module_exit(unloading);

MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);