#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/vmalloc.h>
#include <linux/lzo1x.h>
#include <asm/uaccess.h>

#include "compress-test.h"

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Testing for de/compression algorithms");
MODULE_AUTHOR("Nitin Gupta <nitingupta910@gmail.com>");

struct stats stats;

ssize_t compress_write(struct file *file, const char __user *buf,
			size_t size, loff_t *ppos)
{
	if (!*ppos)
		stats.orig_size = 0;

	if (*ppos + size > MAX_BUFF_SIZE) {
		stats.orig_size = 0;
		return -ENOSPC;
	}

	if (copy_from_user(user_buffer + *ppos, buf, size))
		return -ENOMEM;

	*ppos += size;
	stats.orig_size += size;

	return size;
}

int compress_release(struct inode *inode, struct file *file)
{
	int ret;

	if (!stats.orig_size) {
		printk(KERN_DEBUG "No data to compress!\n");
		return 0;
	}

	ret = lzo1x_compress(user_buffer, stats.orig_size, lzo_comp_buffer,
				&stats.comp_size, lzo_workmem); 
	if (ret) {
		printk(KERN_DEBUG "LZO compress failed: ret=%d\n", ret);
		stats.comp_size = 0;
	} else
		printk(KERN_DEBUG "LZO compress successful: orig_size=%d, "
				"comp_size=%d\n", stats.orig_size, stats.comp_size);

	return 0;
}

ssize_t decompress_read(struct file *file, char __user *buf,
			size_t size, loff_t *ppos)
{
	int ret;
	unsigned long left;
	
	if (*ppos)
		goto to_user;
		
	if (!stats.comp_size) {
		printk(KERN_DEBUG "No data to decompress!\n");
		return 0;
	}

	stats.decomp_size = stats.orig_size;
	ret = lzo1x_decompress_safe(lzo_comp_buffer, stats.comp_size,
				decomp_buffer, &stats.decomp_size);
	if (ret) {
		printk(KERN_DEBUG "LZO decompress failed: ret=%d\n", ret);
		stats.decomp_size = 0;
	} else
		printk(KERN_DEBUG "LZO decompress successful: decomp_size=%d\n",
						stats.decomp_size);
	stats.comp_size = 0;

to_user:
	if (*ppos == stats.decomp_size) {
		stats.comp_size = 0;
		return 0;
	}
	left = stats.decomp_size - *ppos;
	size = (size > left ? left : size);

	if(copy_to_user(buf, decomp_buffer + *ppos, size))
		return -ENOMEM;

	*ppos += size; 
	return size;
}

static struct file_operations compress_fops = {
	.write	= compress_write,
	.release = compress_release,
};

static struct file_operations decompress_fops = {
	.read	= decompress_read,
};

int init_compress_test(void)
{
	int ret = -ENOMEM;

	if (!(user_buffer = vmalloc(MAX_BUFF_SIZE)))
		goto out;
	if (!(decomp_buffer = vmalloc(MAX_BUFF_SIZE)))
		goto out;
	if (!(lzo_workmem = vmalloc(LZO1X_WORKMEM_SIZE)))
        	goto out;
	if (!(lzo_comp_buffer = vmalloc(MAX_BUFF_SIZE +
				(MAX_BUFF_SIZE / 1024) * 16)))
		goto out;

	if (!(compress_test_dir = debugfs_create_dir("compress_test", NULL)))
		goto out;
	if (!(compress_file = debugfs_create_file("compress", S_IRUGO | S_IWUGO,
			compress_test_dir, NULL, &compress_fops)))
		goto error1;
	if (!(decompress_file = debugfs_create_file("decompress", S_IRUGO | S_IWUGO,
			compress_test_dir, NULL, &decompress_fops)))
		goto error2;
	if (!(algo_idx_file = debugfs_create_u8("algo_idx", S_IRUGO | S_IWUGO,
			compress_test_dir, &algo_idx)))
		goto error3;

	ret = 0;
	goto out;

error3:
	debugfs_remove(decompress_file);
error2:
	debugfs_remove(compress_file);
error1:
	debugfs_remove(compress_test_dir);
out:
	return ret;
}


void exit_compress_test(void)
{
	debugfs_remove(algo_idx_file);
	debugfs_remove(compress_file);
	debugfs_remove(decompress_file);
	debugfs_remove(compress_test_dir);

	vfree(user_buffer);
	vfree(decomp_buffer);
	vfree(lzo_workmem);
	vfree(lzo_comp_buffer);
}

module_init(init_compress_test);
module_exit(exit_compress_test);

