#include <linux/module.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/debugfs.h>
#include <linux/vmalloc.h>
#include <linux/seq_file.h>
#include <linux/list.h>

#include <asm/pgtable.h>

#define PVIEW_VERSION "0.1"

MODULE_DESCRIPTION("Process Viewer");
MODULE_VERSION(PVIEW_VERSION);
MODULE_AUTHOR("Copyright (C) blah");
MODULE_LICENSE("GPL");

#define TASKLIST_EMPTY ((void*)-1)

static int pview_parameter;

static struct dentry *pview_debugfs_root, *pview_debugfs_file;

struct tasklist_list {
	struct list_head list;
	struct task_struct *task;
};

static void *pview_seq_start(struct seq_file *m, loff_t *pos)
{
	struct task_struct *p;

	struct list_head *new_list;

	printk(KERN_DEBUG "[pview_seq_start]\n");

	// we get called again at EOF; seq_stop sets this to say we're
	// done
	if (m->private == TASKLIST_EMPTY)
		return NULL;

	// start up a new list
	new_list = kmalloc(sizeof(struct list_head), GFP_KERNEL);
	INIT_LIST_HEAD(new_list);
	m->private = new_list;

	// add tasks to list
	read_lock(&tasklist_lock);

	p = next_task(&init_task);
	for ( ; p != &init_task; p = next_task(p)) {
		struct tasklist_list *new_entry = kmalloc(sizeof(struct tasklist_list), GFP_KERNEL);
		new_entry->task = p;
		list_add_tail(&new_entry->list, new_list);
	}

	read_unlock(&tasklist_lock);

	return new_list->next;
}

static void pview_seq_stop(struct seq_file *m, void *v)
{
	struct list_head *ptr, *next, *list = m->private;

	printk(KERN_DEBUG "[pview_seq_stop]\n");

	if (m->private == TASKLIST_EMPTY)
		return;

	list_for_each_safe(ptr, next, list) {
		struct tasklist_list *entry;
		list_del(ptr);
		entry = list_entry(ptr, struct tasklist_list, list);
		kfree(entry);
	}

	m->private = TASKLIST_EMPTY;
}

static void *pview_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
	struct list_head *incoming = v;
	struct list_head *list = m->private;

	if (incoming->next == list)
		return NULL;
	else
		return incoming->next;
}

static int pview_seq_show(struct seq_file *m, void *v)
{
	struct tasklist_list *entry = list_entry(v, struct tasklist_list, list);
	struct task_struct *p = entry->task;

	if (!pid_alive(p))
		return 0;

	// kernel threads have no mm, maybe you want them maybe not
	if (!p->mm)
		return 0;

	// do something here.
	// make sure you take the right locks, etc.
	// seq_printf whatever you need as output
	seq_printf(m, "%d pgd: %p %p\n", p->pid, p->mm, p->mm->pgd);

	return 0;
}

struct seq_operations pview_seq_ops = {
	.start = pview_seq_start,
	.stop =pview_seq_stop,
	.next = pview_seq_next,
	.show = pview_seq_show,
};

static int pview_open(struct inode *inode, struct file *file)
{
	int ret = -ENOMEM;
	printk(KERN_DEBUG "[pview_open]\n");

	ret = seq_open(file, &pview_seq_ops);

	return ret;
}

static struct file_operations pview_debugfs_fops = {
	.owner   = THIS_MODULE,
	.open    = pview_open,
	.read    = seq_read,
	.llseek  = seq_lseek,
	.release = seq_release,
};

static int __init pview_init(void)
{
	printk(KERN_DEBUG "[pview_init]\n");

	pview_debugfs_root = debugfs_create_dir("ppop", NULL);


	pview_debugfs_file = debugfs_create_file("pview_dump", S_IFREG|S_IRUGO|S_IWUSR,
						pview_debugfs_root, NULL, &pview_debugfs_fops);

	return 0;
}

static void __exit pview_exit(void)
{
	debugfs_remove(pview_debugfs_file);
	debugfs_remove(pview_debugfs_root);
	return;
}

module_param(pview_parameter, int, 0444);
MODULE_PARM_DESC(pview_parameter, "Some Parameter");

module_exit(pview_exit);
module_init(pview_init);
