/*
 *  linux/kernel/vserver/context.c
 *
 *  Virtual Server: Context Support
 *
 *  Copyright (C) 2003-2004  Herbert P�tzl
 *
 *  V0.01  context helper
 *  V0.02  vx_ctx_kill syscall command
 *  V0.03  replaced context_info calls
 *  V0.04  redesign of struct (de)alloc
 *  V0.05  rlimit basic implementation
 *  V0.06  task_xid and info commands
 *
 */

#include <linux/config.h>
//#include <linux/linkage.h>
#include <linux/utsname.h>
#include <linux/slab.h>
#include <linux/vserver/context.h>
//#include <linux/vswitch.h>
#include <linux/vinline.h>
//#include <linux/sched.h>
#include <linux/kernel_stat.h>

#include <asm/errno.h>
//#include <asm/uaccess.h>



/*  system functions */


LIST_HEAD(vx_infos);

spinlock_t vxlist_lock
	__cacheline_aligned_in_smp = SPIN_LOCK_UNLOCKED;


/*
 *	struct vx_info allocation and deallocation
 */

static struct vx_info *alloc_vx_info(int id)
{
	struct vx_info *new = NULL;
	int lim;
	
	vxdprintk("alloc_vx_info(%d)\n", id);
	/* would this benefit from a slab cache? */
	new = kmalloc(sizeof(struct vx_info), GFP_KERNEL);
	if (!new)
		return 0;

	memset (new, 0, sizeof(struct vx_info));
	new->vx_id = id;
	INIT_LIST_HEAD(&new->vx_list);
	/* rest of init goes here */
	
	for (lim=0; lim<RLIM_NLIMITS; lim++)
		new->limit.rlim[lim] = RLIM_INFINITY;
	
	atomic_set(&new->limit.ticks, current->counter);
	new->virt.nr_threads = 1;
	new->virt.bias_cswtch = kstat.context_swtch;
	new->virt.bias_jiffies = jiffies;
	new->virt.bias_idle = init_tasks[0]->times.tms_utime +
		init_tasks[0]->times.tms_stime;
	down_read(&uts_sem);
	new->virt.utsname = system_utsname;
	up_read(&uts_sem);
	
	vxdprintk("alloc_vx_info(%d) = %p\n", id, new);
	return new;
}

void free_vx_info(struct vx_info *vxi)
{
	vxdprintk("free_vx_info(%p)\n", vxi);
	kfree(vxi);
}


/*
 *	struct vx_info search by id
 *	assumes vxlist_lock is held
 */

static __inline__ struct vx_info *__find_vx_info(int id)
{
	struct vx_info *vxi;

	list_for_each_entry(vxi, &vx_infos, vx_list)
		if (vxi->vx_id == id)
			return vxi;
	return 0;
}


/*
 *	struct vx_info ref stuff
 */

struct vx_info *find_vx_info(int id)
{
	struct vx_info *vxi;
	
	spin_lock(&vxlist_lock);
	if ((vxi = __find_vx_info(id)))
		get_vx_info(vxi);
	spin_unlock(&vxlist_lock);
	return vxi;
}


/*
 *	struct vx_info search by id
 *	assumes vxlist_lock is held
 */

static __inline__ xid_t __vx_dynamic_id(void)
{
	static xid_t seq = MAX_S_CONTEXT;
	xid_t barrier = seq;
	
	do {
		if (++seq > MAX_S_CONTEXT)
			seq = MIN_D_CONTEXT;
		if (!__find_vx_info(seq))
			return seq;
	} while (barrier != seq);
	return 0;
}


struct vx_info *find_or_create_vx_info(int id)
{
	struct vx_info *new, *vxi = NULL;
	
	vxdprintk("find_or_create_vx_info(%d)\n", id);
	if (!(new = alloc_vx_info(id)))
		return 0;

	spin_lock(&vxlist_lock);

	/* dynamic context requested */
	if (id == VX_DYNAMIC_ID) {
		id = __vx_dynamic_id();
		if (!id) {
			printk(KERN_ERR "no dynamic context available.\n");
			goto out_unlock;
		}
		new->vx_id = id;
	}
	/* existing context requested */
	else if ((vxi = __find_vx_info(id))) {
		vxdprintk("find_or_create_vx_info(%d) = %p (found)\n", id, vxi);
		get_vx_info(vxi);
		goto out_unlock;
	}

	/* new context requested */
	vxdprintk("find_or_create_vx_info(%d) = %p (new)\n", id, vxi);
	atomic_set(&new->vx_refcount, 1);
	list_add(&new->vx_list, &vx_infos);
	vxi = new, new = NULL;

out_unlock:
	spin_unlock(&vxlist_lock);
	if (new)
		free_vx_info(new);
	return vxi;
}


#include <asm/uaccess.h>


int vc_task_xid(uint32_t id, void *data)
{
        xid_t xid;

        if (id) {
                struct task_struct *tsk;

                if (!vx_check(0, VX_ADMIN|VX_WATCH))
                        return -EPERM;

                read_lock(&tasklist_lock);
                tsk = find_task_by_pid(id);
                xid = (tsk) ? tsk->xid : -ESRCH;
                read_unlock(&tasklist_lock);
        }
        else
                xid = current->xid;
        return xid;
}


int vc_vx_info(uint32_t id, void *data)
{
	struct vx_info *vxi;
	struct vcmd_vx_info_v0 vc_data;

	if (!vx_check(0, VX_ADMIN))
		return -ENOSYS;
	if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SYS_RESOURCE))
		return -EPERM;

	vxi = find_vx_info(id);
	if (!vxi)
		return -ESRCH;

	if (copy_to_user (data, &vc_data, sizeof(vc_data)))
		return -EFAULT;
	return 0;
}


/* virtual host info names */

static char * vx_vhi_name(struct vx_info *vxi, int id)
{
	switch (id) {
		case VHIN_CONTEXT:
			return vxi->vx_name;
		case VHIN_SYSNAME:
			return vxi->virt.utsname.sysname;
		case VHIN_NODENAME:
			return vxi->virt.utsname.nodename;
		case VHIN_RELEASE:
			return vxi->virt.utsname.release;
		case VHIN_VERSION:
			return vxi->virt.utsname.version;
		case VHIN_MACHINE:
			return vxi->virt.utsname.machine;
		case VHIN_DOMAINNAME:
			return vxi->virt.utsname.domainname;
		default:
	}
	return NULL;
}

int vc_set_vhi_name(uint32_t id, void *data)
{
	struct vx_info *vxi;
	struct vcmd_vx_vhi_name_v0 vc_data;
	char *name;

	if (!vx_check(0, VX_ADMIN))
		return -ENOSYS;
	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;
	if (copy_from_user (&vc_data, data, sizeof(vc_data)))
		return -EFAULT;
	
	vxi = find_vx_info(id);
	if (!vxi)
		return -ESRCH;
	
	name = vx_vhi_name(vxi, vc_data.field);
	if (name)
		memcpy(name, vc_data.name, 65);
	put_vx_info(vxi);
	return (name ? 0 : -EFAULT);
}

int vc_get_vhi_name(uint32_t id, void *data)
{
	struct vx_info *vxi;
	struct vcmd_vx_vhi_name_v0 vc_data;
	char *name;

	if (!vx_check(0, VX_ADMIN))
		return -ENOSYS;
	if (copy_from_user (&vc_data, data, sizeof(vc_data)))
		return -EFAULT;

	vxi = find_vx_info(id);
	if (!vxi)
		return -ESRCH;

	name = vx_vhi_name(vxi, vc_data.field);
	if (!name)
		goto out_put;
			
	memcpy(vc_data.name, name, 65);
	if (copy_to_user (data, &vc_data, sizeof(vc_data)))
		return -EFAULT;
out_put:
	put_vx_info(vxi);
	return (name ? 0 : -EFAULT);
}


#include <linux/namespace.h>

int vc_enter_namespace(uint32_t id, void *data)
{
	struct vx_info *vxi;
	struct namespace *old_ns;

	if (!vx_check(0, VX_ADMIN))
		return -ENOSYS;

	vxi = find_vx_info(id);
	if (!vxi)
		return -ESRCH;

	if (!vxi->vx_namespace)
		goto out_put;
	
	old_ns = current->namespace;
	get_namespace(vxi->vx_namespace);
	current->namespace = vxi->vx_namespace;	
	put_namespace(old_ns);

out_put:
	put_vx_info(vxi);
	return 0;
}