/* 
 * Copyright (C) 2002 Jeff Dike (jdike@karaya.com)
 * Licensed under the GPL
 */

#include "linux/init.h"
#include "linux/proc_fs.h"
#include "linux/proc_mm.h"
#include "linux/file.h"
#include "asm/uaccess.h"
#include "asm/mmu_context.h"

static struct file_operations proc_mm_fops;

struct mm_struct *proc_mm_get_mm(int fd)
{
	struct mm_struct *ret = ERR_PTR(-EBADF);
	struct file *file;

	file = fget(fd);
	if (!file)
		goto out;

	ret = ERR_PTR(-EINVAL);
	if(file->f_op != &proc_mm_fops)
		goto out_fput;

	ret = file->private_data;
 out_fput:
	fput(file);
 out:
	return(ret);
}

extern long __do_mmap2(struct mm_struct *mm, unsigned long addr, 
		     unsigned long len, unsigned long prot, 
		     unsigned long flags, unsigned long fd,
		     unsigned long pgoff);

static ssize_t write_proc_mm(struct file *file, const char *buffer,
			     size_t count, loff_t *ppos)
{
	struct mm_struct *mm = file->private_data;
	struct proc_mm_op req;
	int n, ret;

	if(count > sizeof(req))
		return(-EINVAL);

	n = copy_from_user(&req, buffer, count);
	if(n != 0)
		return(-EFAULT);

	ret = count;
	switch(req.op){
	case MM_MMAP: {
		struct mm_mmap *map = &req.u.mmap;

		ret = __do_mmap2(mm, map->addr, map->len, map->prot, 
			       map->flags, map->fd, map->offset >> PAGE_SHIFT);
		if((ret & ~PAGE_MASK) == 0)
			ret = count;
	
		break;
	}
	case MM_MUNMAP: {
		struct mm_munmap *unmap = &req.u.munmap;

		down_write(&mm->mmap_sem);
		ret = do_munmap(mm, unmap->addr, unmap->len);
		up_write(&mm->mmap_sem);

		if(ret == 0)
			ret = count;
		break;
	}
	case MM_MPROTECT: {
		struct mm_mprotect *protect = &req.u.mprotect;

		ret = do_mprotect(mm, protect->addr, protect->len, 
				  protect->prot);
		if(ret == 0)
			ret = count;
		break;
	}

	case MM_COPY_SEGMENTS: {
		struct mm_struct *from = proc_mm_get_mm(req.u.copy_segments);

		if(IS_ERR(from)){
			ret = PTR_ERR(from);
			break;
		}

		mm_copy_segments(from, mm);
		break;
	}
	default:
		ret = -EINVAL;
		break;
	}

	return(ret);
}

static int open_proc_mm(struct inode *inode, struct file *file)
{
	struct mm_struct *mm = mm_alloc();
	int ret;

	ret = -ENOMEM;
	if(mm == NULL)
		goto out_mem;

	ret = init_new_context(current, mm);
	if(ret)
		goto out_free;

	spin_lock(&mmlist_lock);
	list_add(&mm->mmlist, &current->mm->mmlist);
	mmlist_nr++;
	spin_unlock(&mmlist_lock);

	file->private_data = mm;

	return(0);

 out_free:
	mmput(mm);
 out_mem:
	return(ret);
}

static int release_proc_mm(struct inode *inode, struct file *file)
{
	struct mm_struct *mm = file->private_data;

	mmput(mm);
	return(0);
}

static struct file_operations proc_mm_fops = {
	.open		= open_proc_mm,
	.release	= release_proc_mm,
	.write		= write_proc_mm,
};

static int make_proc_mm(void)
{
	struct proc_dir_entry *ent;

	ent = create_proc_entry("mm", 0222, &proc_root);
	if(ent == NULL){
		printk("make_proc_mm : Failed to register /proc/mm\n");
		return(0);
	}
	ent->proc_fops = &proc_mm_fops;

	return(0);
}

__initcall(make_proc_mm);

/*
 * Overrides for Emacs so that we follow Linus's tabbing style.
 * Emacs will notice this stuff at the end of the file and automatically
 * adjust the settings for this buffer only.  This must remain at the end
 * of the file.
 * ---------------------------------------------------------------------------
 * Local variables:
 * c-file-style: "linux"
 * End:
 */
