/*
 * linux/ipc/dipc.c
 * 
 * Copyright (C) Kamran Karimi
 * For information about DIPC, contact linux-dipc@wallybox.cei.net
 * DIPC's Web pages are at http://wallybox.cei.ent/dipc
 */

#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/ipc.h>
#include <linux/shm.h>
#include <linux/msg.h>
#include <linux/sem.h>
#include <linux/stat.h>
#include <linux/malloc.h>

#include <asm/ipc.h>
#include <asm/pgtable.h>
#include <asm/uaccess.h>

#include <linux/dipc.h>
 
/* this structure holds the network address of the ipc owner, and if 
   applicable, the transfer mode of the shared memory */
static struct dipc_address {
	int type; /* SHM, SEM or MSG */
	int key;
	long address;
	int shm_mode; /* used to note the owner's shared memory transfer mode */ 
	struct dipc_address *next,*prev;
} dipc_address_first = { 0, 0, 0, -1, NULL, NULL };
static struct dipc_address *dipc_address_last = &dipc_address_first;

static struct dipc_request dipc_request_first = { {0,0,0,0,0,0,0,0,0,0,0},0,
                                                 NULL, NULL, NULL, NULL };
static struct dipc_request *dipc_request_last = &dipc_request_first;

static struct wait_queue *dipc_man_lock = NULL;
static struct task_struct *dipc_task = NULL; /* dipc manager */
static int My_Address;

int dipc_transfer_mode, dipc_inhibit_shm_signal, dipc_page_size;

static struct dipc_wait {
	struct wait_queue *queue;
	key_t key;
	int type;
	int wcnt;
	struct dipc_wait *next, *prev;
}dipc_first_wait = {NULL, 0, 0, 0, NULL, NULL};

/* for DIPC's memory mamanegment */
static struct dipc_protect_t {
	char *prot;
	key_t key;
	int size;
	struct dipc_protect_t *next, *prev;
}dipc_first_prot = {NULL, 0, 0, NULL, NULL};

static struct dipc_sem_proxy_t {
	key_t key;
	long owner;
	pid_t pid;
	struct dipc_sem_proxy_t *next, *prev;
}dipc_first_sem_proxy = {0, 0, -1, NULL, NULL};


void dipc_init (void)
{
	printk("DIPC: Distributed System V IPC. Version %s\n", DIPC_VERSION);
}

/* if it is the current task, the child or the grand child of the manager, 
   return 1 */
int dipc_manager(struct task_struct *task)
{
 	if((task == dipc_task) || (task->p_opptr == dipc_task) ||
 	  ((task->p_opptr)->p_opptr && ((task->p_opptr)->p_opptr == dipc_task))) 
 		return 1;
	return 0;
}

/* test to see if: the key is not private + dipcd is present + the input
   task is not spawned by dipcd
*/
int inline dipc_can_act(key_t key, struct task_struct *task)
{
	if((key != IPC_PRIVATE) && (dipc_task != NULL) && 
 			((task == NULL) ? 1 : !dipc_manager(task))) 
 		return 1;
	return 0;
} 
 
/* The following dipc_shm_x routines partially implement a software 
   Memory Management Unit :-)*/

/* Allocate and initialize the structure for an access mode (in val) */ 
void dipc_shm_init(key_t key, int val, int size)
{
	struct dipc_protect_t *dp;
	int count;

 	/**printk("dipc_shm_init(%d, %d, %d):\n", key, val, size);*/
	dp = dipc_first_prot.next;
	while(dp) {
 		if(dp->key == key)
  			break;
 		dp = dp->next;
	}
	if(dp == NULL) {
 		dp = kmalloc(sizeof(struct dipc_protect_t), GFP_KERNEL);
 		if(dp == NULL) {
  			printk("dipc_shm_init(%d, %d, %d): Can't allocate memory\n", 
	  						key, val, size);
  			return; /* fatal */
 		} 
 		dp->size = size / dipc_page_size + 1;
 		dp->prot = kmalloc(dp->size, GFP_KERNEL);
 		if(dp->prot == NULL) {
 	 		printk("dipc_shm_init(%d, %d, %d): Can't allocate memory\n",
 	 						key, val, size);
  			kfree(dp);
  			return; /* fatal */
 		}
 		for(count = 0; count <= size / dipc_page_size; count++)
	   		dp->prot[count] = (char)val;
 		dp->key = key;
 		dp->next = dipc_first_prot.next;
 		if(dipc_first_prot.next)
  			dipc_first_prot.next->prev = dp;
 		dipc_first_prot.next = dp;
 		dp->prev = &dipc_first_prot; 
	}
	else
 		printk("dipc_shm_init(%d, %d, %d): duplicate shm protection structure\n",
 							key, val, size); 
	return;
}

/* delete the shm protection structure */
void dipc_shm_del(key_t key)
{
	struct dipc_protect_t *dp;

	/**printk("dipc_shm_del(%d)\n", key);*/
	dp = dipc_first_prot.next;
	while(dp) {
 		if(dp->key == key)
  			break;
 		dp = dp->next;
	}
	if(dp) {
 		dp->prev->next = dp->next;
 		if(dp->next)
  			dp->next->prev = dp->prev;
 		kfree(dp->prot); 
 		kfree(dp);
	}
	else
 		printk("dipc_shm_del(%d): Can't find the structure\n", key);
	return;
}
 
/* The address can be read or written (depending on val) */
void dipc_shm_enable(key_t key, int val, int offset, int mode)
{
	struct dipc_protect_t *dp;
	int count, start, end;

	/**printk("dipc_shm_enable(%d, %d, %d, %d)\n", key, val, offset, mode);*/
	dp = dipc_first_prot.next;
	while(dp) {
 		if(dp->key == key)
  			break;
 		dp = dp->next;
	}
	if(dp) { 
 		start = end = offset / dipc_page_size;
 		if(mode == SEGMENT) {
  			start = 0;
  			end = dp->size - 1;
 		}
 		for(count = start; count <= end; count++)
  			dp->prot[count] |= val;
	} 
	else
 		printk("dipc_shm_enable(%d, %d, %d, %d): protection structure not found\n", 
 						key, val, offset, mode);
	return;
} 

/* The address can not be read or written */
void dipc_shm_disable(key_t key, int val, int offset, int mode)
{
	struct dipc_protect_t *dp;
	int count, start, end;

 	/**printk("dipc_shm_disable(%d, %d, %d, %d)\n", key, val, offset, mode);*/
	dp = dipc_first_prot.next;
	while(dp) {
 		if(dp->key == key)
  			break;
  		dp = dp->next;
	}
	if(dp) { 
 		start = end = offset / dipc_page_size;
 		if(mode == SEGMENT) {
  			start = 0;
  			end = dp->size - 1;
  		}
 		for(count = start; count <= end; count++) {
  			dp->prot[count] &= ~val;
 		} 
	} 
	else
		printk("dipc_shm_disable(%d, %d, %d, %d): protection structure not found\n",
						 key, val, offset, mode);
	return;
} 

/* check to see if the address is readable */
int dipc_shm_readable(key_t key, int offset)
{
	struct dipc_protect_t *dp;
	int addr = offset / dipc_page_size;

	/**printk("dipc_shm_readable(%d, %d)\n", key, offset);*/
	dp = dipc_first_prot.next;
	while(dp) {
 		if(dp->key == key)
  			break;
		dp = dp->next;
	}
	if(dp) {
  		if(dp->size <= addr) {
 			printk("dipc_shm_readable(%d, %d): out of bound reference\n", key, offset);
  			return 0;
 		}
 		return dp->prot[addr] & 1;
	}
	else
 		printk("dipc_shm_readable(%d, %d): Can't find the structure\n", key, offset);
	return 0;
}


int dipc_shm_writable(key_t key, int offset)
{
	struct dipc_protect_t *dp;
	int addr = offset / dipc_page_size;

	/**printk("dipc_shm_writable(%d, %d)\n", key, offset);*/
	dp = dipc_first_prot.next;
	while(dp) {
		if(dp->key == key)
  			break;
 		dp = dp->next;
	}
	if(dp) {
		if(dp->size <= addr) {
 			printk("dipc_shm_writable(%d, %d): out of bound reference. dp->size:%d, addr:%d\n", 
           			 key, offset, dp->size, addr);
 			return 0;
		}
 		return dp->prot[addr] & 2;
	}
	else
 		printk("dipc_shm_writable(%d, %d): Can't find the structure\n", key, offset);
	return 0;
}

/* create a queue for other tasks to wait on */
void dipc_excl(key_t key, int type)
{
	struct dipc_wait *dw;

	/** printk("dipc_excl(%d, %d), by task %d\n", key, type, current->pid);*/

	dw = dipc_first_wait.next;
	while(dw) {
 		if(dw->key == key && dw->type == type)
  			break;
 		dw = dw->next;
	}
	if(dw == NULL) {
 		dw = kmalloc(sizeof(struct dipc_wait), GFP_KERNEL);
 		if(dw == NULL) {
  			printk("dipc_excl(%d, %d): Can't allocate memory\n", key, type);
  			return; /* fatal */
 		}
 		dw->key = key;
 		dw->type = type;
 		dw->queue = NULL;
 		dw->wcnt = 0;
 		dw->next = dipc_first_wait.next;
 		if(dipc_first_wait.next)
  			dipc_first_wait.next->prev = dw;
 		dipc_first_wait.next = dw;
 		dw->prev = &dipc_first_wait; 
	}
	return;
}

/* see if a queue exists, and if yesm then wait on it */
int dipc_check(key_t key, int type)
{
	struct dipc_wait *dw;
	
	/* printk("dipc_check(%d, %d)\n", key, type);*/
	dw = dipc_first_wait.next;
	while(dw) {
 		if(dw->key == key && dw->type == type)
  			break;
 		dw = dw->next;
	}
	if(dw) {
 		dw->wcnt++;
 		interruptible_sleep_on(&dw->queue);
 		dw->wcnt--;
 		if (dw->wcnt == 0)
   			kfree(dw);
 		return 1;
	}
	return 0; 
}

/* wake up the tasks waiting on a queue */
void dipc_wake(key_t key, int type)
{
	struct dipc_wait *dw;

	/**printk("dipc_wake(%d, %d)\n", key, type);*/
	dw = dipc_first_wait.next;
	while(dw) {
 		if(dw->key == key && dw->type == type)
  			break;
 		dw = dw->next;
	}
	if(dw) {
 		wake_up(&dw->queue);
 		dw->prev->next = dw->next;
 		if(dw->next)
  			dw->next->prev = dw->prev;
 		if (dw->wcnt == 0)
   			kfree(dw);
	}
	else
 		printk("dipc_wake(%d, %d): Can't find the structure\n", key, type);
	return;
}

/* save the IPC structure's owner address, as returned by the referee */
static int dipc_add_owner_address(key_t key, int type, long net_address, 
                                                               int shm_mode)
{
	struct dipc_address *da;

	/**printk("Kernel: dipc_add_owner_address(%d, %d, %ld, %d)\n", key, type, 
                                          net_address, shm_mode);*/
	da = dipc_address_first.next;
	while(da) {
 		if((da->key == key) && (da->type == type))
 			break;
 		da = da->next;
	}
	if(da)
 		printk("dipc_add_owner_address(%d, %d): adding duplicate owner\n", key, type); 
	da = kmalloc(sizeof(struct dipc_address), GFP_KERNEL);
	if(da == NULL) {
 		printk("dipc_add_owner_address(%d, %d, %ld, %d): Can't allocate memory\n", 
	 				key, type, net_address, shm_mode); 
 		return -1;
	} 
	da->type = type;
	da->key = key;
	da->address = net_address;
	da->shm_mode = shm_mode; /* used only for shared memories */
	da->next = NULL;
	da->prev = dipc_address_last;
	dipc_address_last->next = da;
	dipc_address_last = da;
	return 0;
}


/* dipcd will need this returned address to handle the request. It is also 
   called from shm.c, msg.c and sem.c, where it runs "silent" (does not 
   print any error messgaes if no owner address could be found) */
long dipc_find_owner_address(key_t key, int type, int silent)
{
	struct dipc_address *da;

	/**printk("dipc_find_owner_address(%d, %d, %d)\n",key, type, silent);*/
	da = dipc_address_first.next;
	while(da) {
 		if((da->key == key) && (da->type == type)) 
 			break;
 		da = da->next;
	}
	if(da)
		return da->address;
	if(!silent)
		printk("dipc_find_owner_address(%d, %d, %d): Can't find the owner address\n", 
							key, type, silent);
	return -1;
}
 

static int dipc_del_owner_address(key_t key, int type)
{
	struct dipc_address *da;

	/**printk("dipc_del_owner_address(%d, %d)\n",key, type);*/
	da = dipc_address_first.next;
	while(da) {
 		if((da->key == key) && (da->type == type)) 
 			break;
 		da = da->next;
	}
	if(da) {
  		if(dipc_address_last == da) 
  			dipc_address_last = da->prev;
 		(da->prev)->next = da->next;
 		if(da->next)
  			(da->next)->prev = da->prev;
 		kfree(da);
 		return 0;
	}
	printk("dipc_del_owner_address(%d, %d): Can't find the address to delete\n", key, type);
	return -1;
}


static int dipc_find_owner_transfer_mode(key_t key)
{
	struct dipc_address *da;

	/**printk("dipc_find_owner_transfer_mode(%d)\n",key);*/
	da = dipc_address_first.next;
	while(da) {
 		if((da->key == key) && (da->type == SHM)) 
 			break;
  		da = da->next;
	}
	if(da)
  		return da->shm_mode;

 	printk("dipc_find_owner_transfer_mode(%d): Can't find the owner's shm transfer mode\n", key);
 	return -1;
}


/* return 1 if the shared memory manager works in a page transfer mode
   and the requests are for different pages. This allows different processes 
   to request different pages at the same time */
int dipc_shm_request_coexists(key_t key, int addr)
{
	struct dipc_request *dr;
	int tmode, at_least_one = 0;
	
	/**printk("dipc_shm_request_coexists(%d, %d)\n", key, addr);*/
	tmode = dipc_find_owner_transfer_mode(key);
	if(tmode == 0) { /* page mode */
 		dr = dipc_request_first.next;
 		while(dr) {
  			if((dr->info.key == key) && 
     			   ((dr->info.func == WANTWR) || 
     			   (dr->info.func == WANTRD))) {
   				if((dr->info.arg2 / dipc_page_size) == (addr / dipc_page_size))
    					return 0;
   				else
    					at_least_one = 1; 
  			}  
  			dr = dr->next;
 		}
 		if(at_least_one == 1)
  			return 1;
	}
	return 0;
}   


void dipc_add_sem_proxy(key_t key)
{
	struct dipc_sem_proxy_t *sp;

	/**printk("dipc_add_sem_proxy(%d)\n", key);*/
	sp = dipc_first_sem_proxy.next;
	while(sp) {
 		if(sp->key == key && sp->pid == current->pid)
  			break;
 		sp = sp->next;
	}
	if(sp == NULL) {
 		sp = kmalloc(sizeof(struct dipc_sem_proxy_t), GFP_KERNEL);
 		if(sp == NULL) 
 		{
  			printk("dipc_add_sem_proxy(%d): Can't allocate memory\n", key);
  			return;
 		}
 		sp->key = key;
 		sp->pid = current->pid;
 		sp->owner = dipc_find_owner_address(key, SEM, 0);
 		sp->next = dipc_first_sem_proxy.next;
 		sp->prev = &dipc_first_sem_proxy;
 		if(dipc_first_sem_proxy.next)
  			dipc_first_sem_proxy.next->prev = sp;
 		dipc_first_sem_proxy.next = sp;
	}
}


/* inform the proxies to exit */
void dipc_inform_sem_proxy(void)
{
	struct dipc_request *dr;
	struct dipc_sem_proxy_t *sp, *sp2;
  
	/**printk("dipc_inform_sem_proxy()\n");*/
	sp = dipc_first_sem_proxy.next;
	while(sp) {
 		sp2 = sp->next;
 		if(sp->pid == current->pid) {
  			dr = kmalloc(sizeof(struct dipc_request), GFP_KERNEL);
  			if(dr == NULL) {
   				printk("dipc_inform_sem_proxy(): Can't allocate memory\n");
   				return;
  			} 
  			dr->info.key = sp->key;
  			dr->info.type = SEM;
  			dr->info.pid = current->pid;
  			dr->info.uid = current->uid;
  			dr->info.func = SEM_EXIT;
  			dr->info.arg1 = sp->key;
  			dr->info.arg2 = 0;
  			dr->info.arg3 = 0;
  			dr->info.arg4 = 0;
  			dr->info.owner = sp->owner;
  			dr->info.dsize = 0;
  			dr->working = 0;
  			dr->next = dr->prev = NULL;
  			dr->data = NULL;
  			dr->lock = NULL;
   			dipc_add_request(dr); 
  			kfree(dr);
  
  			if(sp->next)
   				sp->next->prev = sp->prev;
  			sp->prev->next = sp->next;
  			kfree(sp); 
 		} 
 		sp = sp2;
	} 
} /* dipc_inform_sem_proxy */


/* Tasks use this routine to announce their need to search
   for a new key of type "type". An employer should later handle it. */
int dipc_search_key(key_t key, int type, int distrib, int *size)
{
	struct dipc_request *dr;
	int result;

	/**printk("dipc_search_key(%d, %d, %d, %d)\n",key, type, distrib, *size);*/
	dr = kmalloc(sizeof(struct dipc_request), GFP_KERNEL);
	if(dr == NULL) 
		return -ENOMEM; /* set errno */
	dr->info.key = key;
	dr->info.type = type;
	dr->info.pid = current->pid;
	dr->info.uid = current->uid;
	dr->info.func = SEARCH;
	dr->info.arg1 = key;
	dr->info.arg2 = distrib;
	dr->info.arg3 = dipc_transfer_mode; /* used only for shm */
	dr->info.arg4 = 0;
	dr->info.owner = -1;
	dr->working = 0;
 	dr->next = dr->prev = NULL;
	dr->lock = NULL;
	dr->info.dsize = 0;
	dr->data = NULL;

	dipc_add_request(dr);

 	if(dr->info.arg1 >= 0) {
 		result = dr->info.arg2;
 		*size = dr->info.arg3;
 		dipc_add_owner_address(key, type, dr->info.arg4, result & IS_SEGMENT);
 		result &= ~IS_SEGMENT;
	}
	else {
 		result = NOT_FOUND;
 		*size = 0;
	 	dipc_add_owner_address(key, type, My_Address, 
       	                 (dipc_transfer_mode == SEGMENT) ? IS_SEGMENT : 0);
	}
	kfree(dr); 
	return result;
}


/* this routine tells if an attempt to get a new key succeeded or not.
   in case of success, the result has some information about the key */ 
int dipc_commit_key(key_t key, int type, int size, int mode)
{
	struct dipc_request *dr;

	/**printk("dipc_commit_key(%d, %d, %d, %d)\n",key, type, size, mode);*/
	dr = kmalloc(sizeof(struct dipc_request), GFP_KERNEL);
	if(dr == NULL) return -ENOMEM; /* set errno */
	dr->info.key = key;
	dr->info.type = type;
	dr->info.pid = current->pid;
	dr->info.uid = current->uid;
	dr->info.func = COMMIT;
	dr->info.arg1 = key;
	dr->info.arg2 = size;
	dr->info.arg3 = mode;
	dr->info.arg4 = 0;
	dr->info.owner = -1; 
	dr->working = 0;
	dr->next = dr->prev = NULL;
	dr->lock = NULL;
	dr->info.dsize = 0;
	dr->data = NULL;

	dipc_add_request(dr); /* ignores errors */

	if(size <= 0) /* failed */
 		dipc_del_owner_address(key, type);
	kfree(dr); 
	return 0;
}

/* this routine tests to see if the struture can be created in the 
   owner machine. This is for access checking. result is the id of the
   remote key, or an error code */ 
int dipc_test_key(key_t key, int type, int size, int flag)
{
	struct dipc_request *dr;
	int result;

	/**printk("dipc_test_key(%d, %d, %d, %d)\n",key, type, size, flag);*/
 	dr = kmalloc(sizeof(struct dipc_request), GFP_KERNEL);
	if(dr == NULL) 
		return -ENOMEM; /* set errno */
	dr->info.key = key;
	dr->info.type = type;
	dr->info.pid = current->pid;
	dr->info.uid = current->uid;
	dr->info.func = TSTGET;
	dr->info.arg1 = key;
	dr->info.arg2 = size;
	dr->info.arg3 = flag;
	dr->info.arg4 = 0;
	dr->info.owner = -1; 
	dr->working = 0;
	dr->next = dr->prev = NULL;
	dr->lock = NULL;
	dr->info.dsize = 0;
	dr->data = NULL;

	dipc_add_request(dr);

	result = dr->info.arg1; /* any kind of error (network, test error...) */
	kfree(dr); 
	return result;
}

int dipc_rmid_key(key_t key, int type)
{
	struct dipc_request *dr;

	/**printk("dipc_rmid_key(%d, %d)\n", key, type);*/
	dr = kmalloc(sizeof(struct dipc_request), GFP_KERNEL);
	if(dr == NULL) 
		return -ENOMEM; /* set errno */
	dr->info.key = key;
	dr->info.type = type;
 	dr->info.pid = current->pid;
	dr->info.uid = current->uid;
 	dr->info.func = RMID;
	dr->info.arg1 = key;
	dr->info.arg2 = 0;
	dr->info.arg3 = 0;
	dr->info.arg4 = 0;
	dr->info.owner = -1;
	dr->working = 0;
	dr->next = dr->prev = NULL;
	dr->lock = NULL;
	dr->info.dsize = 0;
 	dr->data = NULL;

	dipc_add_request(dr); /* ignores errors */

 	kfree(dr);
	return 0;
}

/* Tasks use this routine to announce that they are removing 
   the specified key. This should be reflected in the referee tables */
int dipc_del_key(key_t key, int type)
{
	struct dipc_request *dr;

	/**printk("dipc_del_key(%d, %d)\n", key, type);*/
	dr = kmalloc(sizeof(struct dipc_request), GFP_KERNEL);
	if(dr == NULL) 
		return -ENOMEM; /* set errno */
	dr->info.key = key;
	dr->info.type = type;
	dr->info.pid = current->pid;
	dr->info.uid = current->uid;
	dr->info.func = DELKEY;
	dr->info.arg1 = key;
	dr->info.arg2 = 0;
	dr->info.arg3 = 0;
	dr->info.arg4 = 0;
	dr->info.owner = -1; 
	dr->working = 0;
	dr->next = dr->prev = NULL;
	dr->lock = NULL;
	dr->info.dsize = 0;
	dr->data = NULL;

	dipc_add_request(dr); /* ignores error */
	
	dipc_del_owner_address(key, type);
	kfree(dr); 
	return 0;
}



/* tasks call this to provide work for the back_end process */
void dipc_add_request(struct dipc_request *dr)
{
	long owner;

	/**printk("dipc_add_request(function: %d,  pid: %d)\n", dr->info.func, dr->info.pid);*/
	if(dipc_task == NULL)  {
 		dr->info.arg1 = -EIO;
 		return;
	}
	if(dr->info.func != RMID && dr->info.func != DELKEY &&
   	   dr->info.func != SEARCH && dr->info.func != COMMIT &&
   	   dr->info.func != SEM_EXIT) {
 		owner = dipc_find_owner_address(dr->info.arg1, dr->info.type, 0);
 		if(owner == -1) { 
  			printk("dipc_add_request: Can't find the owner of key %d, type %d\n",
                                      dr->info.arg1, dr->info.type);
  			dr->info.arg1 = -EIO;
  			return;
 		}
 		dr->info.owner = owner;
	} 
	dr->next = NULL;
	dr->prev = dipc_request_last;
	dipc_request_last->next = dr;
	dipc_request_last = dr;
	wake_up(&dipc_man_lock);

 	interruptible_sleep_on(&(dr->lock));
 
	/* see if we were awakened by an interrupt */
	if(signal_pending(current))
 		dr->info.arg1 = -EINTR;
	if(dipc_request_last == dr)
 		dipc_request_last = dr->prev;
	(dr->prev)->next = dr->next;
	if(dr->next) 
 		(dr->next)->prev = dr->prev;
	return;
}


/* back_end calls this function to collect work */
static int dipc_get_new_request(int type, void *ptr)
{
	struct dipc_request *dr;
	int err;
	void *pt[2];
	struct dipc_info *info;
	char *data;

	/** printk("dipc_get_new_request(%d)\n", type); */
	err = verify_area(VERIFY_READ, ptr, 2 * sizeof(void *));
	if(err) 
		return err;
	copy_from_user (pt, ptr, 2 * sizeof(void *));
	info = pt[0];
	data = pt[1];

	dr = dipc_request_first.next;
	while(dr) {
 		if(dr->working == 0) {
  			/* if type == 0, return any request type */
  			if(type == 0)
  				break;
  			else if(type == dr->info.type) 
  				break;
 		}
 		dr = dr->next;
	}
	if(dr) {
 		dr->working = 1;
 		err = verify_area(VERIFY_WRITE, info, sizeof(struct dipc_info));
 		if(err) 
 			return err;
 		copy_to_user (info, &dr->info, sizeof(struct dipc_info));
 		if(dr->info.dsize) {
  			err = verify_area(VERIFY_WRITE, data, dr->info.dsize);
  			if(err) 
  				return err;
  			copy_to_user (data, dr->data, dr->info.dsize);
 		}
 		return DIPC_SUCCESS; 
	}
	/* nothing suitable found */
	return DIPC_SUCCESS + 1;
} 

/* this routine returns the result of a request, hopefully after
   successful execution at the remote owner */
static int dipc_put_result_of_request(int pid, void *ptr)
{
	struct dipc_request *dr;
	int err;
	void *pt[2];
	struct dipc_info *info;
	char *data;

	/**printk("dipc_put_result_of_request(%d)\n", pid);*/
	err = verify_area(VERIFY_READ, ptr, 2 * sizeof(void *));
	if(err) 
		return err;
	copy_from_user (pt, ptr, 2 * sizeof(void *));
	info = pt[0];
 	data = pt[1];
	dr = dipc_request_first.next;
	while(dr) {
 		if(dr->info.pid == pid)
 			break;
 		dr = dr->next;
	}
	if(dr) {
 		/* save the pointers before over write */
 		err = verify_area(VERIFY_READ, info, sizeof(struct dipc_info));
 		if(err) 
  			return err;
 		copy_from_user (&(dr->info), info, sizeof(struct dipc_info));
 		if((dr->info.arg1 >= 0) && dr->info.dsize) {
  			err = verify_area(VERIFY_READ, data, dr->info.dsize);
  			if(err) 
  				return err;
  			copy_from_user (dr->data, data, dr->info.dsize);
 		}
 		/* Now wake it up */
 		wake_up(&(dr->lock));
 		return DIPC_SUCCESS;
	} /* if dr */  
	printk("dipc_put_result_of_request(%d) Can't find the waiting task (exited?)\n", pid);
	return DIPC_FAIL;
}


static int dipc_wait_for_work(void)
{
	struct dipc_request *dr;
 
 	/**printk("dipc_wait_for_work()\n");*/
	dr = dipc_request_first.next;
	while(dr) {
 		if(dr->working == 0)
 			break;
 		dr = dr->next;
	}
	if(dr != NULL)
 		return DIPC_SUCCESS;

	interruptible_sleep_on(&dipc_man_lock);

	return DIPC_SUCCESS;
}

/* called when dipcd is going to die */
static int dipc_quit_kernel(void)
{
	struct dipc_request *dr;
	struct dipc_wait *dw;

	/**printk("dipc_quit_kernel()\n");*/
	dipc_task = NULL;

	/* wake up processes waiting for remote actions.
   	   This should lead to all other structures getting freed */
	dr = dipc_request_first.next;
	while(dr) {
 		dr->info.arg1 = -EIO;
 		wake_up(&dr->lock);
 		dr = dr->next;
	}

	/* exclusion mechanism */
	dw = dipc_first_wait.next;
	while(dw) {
 		wake_up(&dw->queue);
 		dw = dw->next;
	}
	return DIPC_SUCCESS;
}


/* DIPC cleans the kernel data structure when dipcd starts runnung, not
   when dipcd stops. This is because deleting the structures that could have
   some tasks waiting on them can lead to problems. 
   
   The user/admin can make sure no tasks are waiting on DIPC's kernel 
   structure before deleting them */
static int dipc_set_manager(int first, void *ptr)
{
	struct dipc_request *dr, *dr2;
	struct dipc_wait *dw, *dw2;
	struct dipc_address *da, *da2;
	struct dipc_protect_t *dp, *dp2;
	struct dipc_sem_proxy_t *ds, *ds2;
	int err;
	int pt[3];

	/**printk("dipc_set_manager()\n");*/ 	
 	err = verify_area(VERIFY_READ, ptr, 3 * sizeof(int));
	if(err) 
		return err;
	copy_from_user (pt, ptr, 3 * sizeof(void *));
	dipc_transfer_mode = pt[0];
	dipc_inhibit_shm_signal = pt[1];
	dipc_page_size = pt[2];
	dipc_task = current;
	My_Address = first; /* ints are 32 bits */
	dipc_man_lock = NULL;

	/* now make sure evrything from previous runs is thrown away */
	dr = dipc_request_first.next;
	while(dr) {
 		dr2 = dr->next;
 		kfree(dr);
  		dr = dr2;
	}
	dipc_request_first.next = dipc_request_first.prev = NULL;
	dipc_request_last = &dipc_request_first;

	/* exclusion mechanism */
	dw = dipc_first_wait.next;
	while(dw) {
 		dw2 = dw->next;
 		kfree(dw);  
 		dw = dw2;
	} 
	
	/* owner addresses */ 
	da = dipc_address_first.next;
	while(da) {
 		da2 = da->next;
 		kfree(da);
 		da = da2;
	}
	dipc_address_first.next = dipc_address_first.prev = NULL;
	dipc_address_last = &dipc_address_first;
 	
 	/* MM data structures */
	dp = dipc_first_prot.next;
	while(dp) {
 		dp2 = dp->next;
		kfree(dp->prot);
 		kfree(dp);
 		dp = dp2;
	}
	dipc_first_prot.next = dipc_first_prot.prev = NULL;

 	/* semaphore proxies */
	ds = dipc_first_sem_proxy.next;
	while(ds) {
 		ds2 = ds->next;
 		kfree(ds);
 		ds = ds2;
	}
	dipc_first_sem_proxy.next = dipc_first_sem_proxy.prev = NULL;

	return DIPC_SUCCESS;
}


/* in shm.c */
extern int dipc_disable_prot(key_t key, int cmd, int *mask);
 
asmlinkage int sys_dipc(int first, int cmd, void *ptr)
{
	if(!suser())
 		return DIPC_FAIL;

	switch(cmd) {
 		case DIPC_GETWORK:
  			return dipc_get_new_request(first, ptr);
  			break;
 
 		case DIPC_RETWORK:
  			return dipc_put_result_of_request(first, ptr);
  			break;

 		case DIPC_SLEEP:
  			return dipc_wait_for_work();
  			break;
  
 		case DIPC_SETMNG:
  			return dipc_set_manager(first, ptr);
  			break;
  
 		case DIPC_REMMNG:
  			return dipc_quit_kernel();
  			break;

  		case DIPC_REMPROT:
  			return dipc_disable_prot(first, cmd, ptr);
  			break;

  		default:
  			printk("sys_dipc(%d, %d): Unknown request\n", first, cmd);
  			break;
	}   
	return DIPC_FAIL;
}
