/*
 *  linux/ibcs/ipc.c
 *
 *  Copyright (C) 1993,1994  Joe Portman (baron@hebron.connected.com)
 *	 First stab at ibcs shm, sem and msg handlers
 *
 *  NOTE:
 *  Please contact the author above before blindly making changes
 *  to this file. You will break things.
 *
 *  04-15-1994 JLP III
 *  Still no msgsys, but IPC_STAT now works for shm calls
 *  Corrected argument order for sys_ipc calls, to accomodate Mike's
 *  changes, so that we can just call sys_ipc instead of the internal
 *  sys_* calls for ipc functions.
 *  Cleaned up translation of perm structures
 *  tstshm for Oracle now works.
 *
 *  04-23-1994 JLP III
 *  Added in msgsys calls, Tested and working
 *  Added translation for IPC_SET portions of all xxxctl functions.
 *  Added SHM_LOCK and SHM_UNLOCK to shmsys
 *
 *  04-28-1994 JLP III
 *  Special thanks to Brad Pepers for adding in the GETALL and SETALL
 *  case of semaphores. (pepersb@cuug.ab.ca)
 *
 * $Id: ipc.c,v 1.6 1994/05/26 13:26:19 mike Exp $
 * $Source: /var/CVS/ibcs/ipc.c,v $
 */
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/stddef.h>
#include <linux/unistd.h>
#include <linux/segment.h>
#include <linux/ptrace.h>
#include <linux/config.h>

#include <asm/segment.h>
#include <asm/system.h>
#include <linux/fs.h>
#include <linux/sys.h>
#include <linux/ipc.h>
#include <linux/sem.h>
#include <linux/shm.h>
#include <linux/msg.h>
#include <linux/string.h>

#include <ibcs/ibcs.h>
#include <ibcs/trace.h>


/*
** I think this is what an IBCS semid_ds
** structure looks like
*/
struct ibcs_ipc_perm
  {
	 unsigned short uid;			  /* owner's user id */
	 unsigned short gid;			  /* owner's group id */
	 unsigned short cuid;		  /* creator's user id */
	 unsigned short cgid;		  /* creator's group id */
	 unsigned short mode;		  /* access modes */
	 unsigned short seq;			  /* slot usage sequence number */
	 long key;						  /* key */
  };

struct ibcs_semid_ds
  {
	 struct ibcs_ipc_perm sem_perm;
	 struct sem *sem_base;
	 unsigned short sem_nsems;
	 char __pad[2];
	 time_t sem_otime;
	 time_t sem_ctime;
  };


/*
** I think this is what an IBCS shmid_ds
** structure looks like
*/

struct ibcs_shmid_ds
  {
	 struct ibcs_ipc_perm shm_perm;	 /* operation permission struct */
	 int shm_segsz;				  /* size of segment in bytes */
	 struct region *__pad1;		  /* ptr to region structure */
	 char __pad2[4];				  /* for swap compatibility */
	 ushort shm_lpid;				  /* pid of last shmop */
	 ushort shm_cpid;				  /* pid of creator */
	 unsigned short shm_nattch;  /* used only for shminfo */
	 unsigned short __pad3;
	 time_t shm_atime;			  /* last shmat time */
	 time_t shm_dtime;			  /* last shmdt time */
	 time_t shm_ctime;			  /* last change time */
  };

/*
** Here is what an ibcs msqid_ds
** structure should look like
*/

struct ibcs_msqid_ds
  {
	 struct ibcs_ipc_perm msg_perm;
	 struct msg *msg_first;
	 struct msg *msg_last;
	 ushort msg_cbytes;
	 ushort msg_qnum;
	 ushort msg_qbytes;
	 ushort msg_lspid;
	 ushort msg_lrpid;
	 time_t msg_stime;
	 time_t msg_rtime;
	 time_t msg_ctime;
  };


static inline void
ip_to_lp (struct ibcs_ipc_perm *is, struct ipc_perm *os)
{
  os->uid = is->uid;
  os->gid = is->gid;
  os->cuid = is->cuid;
  os->cgid = is->cgid;
  os->mode = is->mode;
  os->seq = is->seq;
  os->key = is->key;
}

static inline void
lp_to_ip (struct ipc_perm *is, struct ibcs_ipc_perm *os)
{
  os->uid = is->uid;
  os->gid = is->gid;
  os->cuid = is->cuid;
  os->cgid = is->cgid;
  os->mode = is->mode;
  os->seq = is->seq;
  os->key = is->key;
}

/*
**	OK here we do some magic with shm and sem calls
**	SHM calls can have command + 1 or 3 args
**	SEM calls can have command + 3 or 4 args
*/
#define U_SEMCTL    (0)
#define U_SEMGET    (1)
#define U_SEMOP     (2)
#define U_SHMLOCK   (3)
#define U_SHMUNLOCK (4)

#define U_GETNCNT (3)
#define U_GETPID  (4)
#define U_GETVAL	(5)
#define U_GETALL	(6)
#define U_GETZCNT	(7)
#define U_SETVAL	(8)
#define U_SETALL	(9)

static int
ibcs_sem_trans (int arg)
{
  switch (arg)
	 {
	 case U_GETNCNT: return GETNCNT;
	 case U_GETPID: return GETPID;
	 case U_GETVAL: return GETVAL;
	 case U_GETALL: return GETALL;
	 case U_GETZCNT: return GETZCNT;
	 case U_SETVAL: return SETVAL;
	 case U_SETALL: return SETALL;
	 }
  return arg;
}

int
ibcs_semsys (struct pt_regs *regs)
{
  int command = get_fs_long (((unsigned long *) regs->esp) + (1));
  int arg1, arg2, arg3, arg5;
  int *arg4;
  int retval;
  arg1 = get_fs_long (((unsigned long *) regs->esp) + (2));
  arg2 = get_fs_long (((unsigned long *) regs->esp) + (3));
  arg3 = get_fs_long (((unsigned long *) regs->esp) + (4));
  switch (command)
	 {
		/* hard one first */
	 case U_SEMCTL:
		arg4 = (int *) regs->esp + (5);
		arg5 = get_fs_long (((int *) regs->esp) + (5));
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: ibcs_semctl: args: %d %d %d %lx(%d)\n",
					 arg1, arg2, arg3, (unsigned long) arg4, arg5);
#endif
		switch (ibcs_sem_trans (arg3))
		  {
		  case IPC_SET:
			 {
				struct ibcs_semid_ds is;
				struct semid_ds os;

				memcpy_fromfs (&is, (char *) arg4, sizeof (is));
				ip_to_lp (&is.sem_perm, &os.sem_perm);
				memcpy_tofs ((char *) arg4, &os, sizeof (os));

				retval = SYS (ipc) (SEMCTL, arg1, arg2, ibcs_sem_trans (arg3), arg4);

				memcpy_fromfs (&os, (char *) arg4, sizeof (os));
				lp_to_ip (&os.sem_perm, &is.sem_perm);
				memcpy_tofs ((char *) arg4, &is, sizeof (is));

				return retval;
			 }
		  case IPC_RMID:
		  case SETVAL:
		  case GETVAL:
			 return SYS (ipc) (SEMCTL, arg1, arg2, ibcs_sem_trans (arg3), arg4);
		  /*
	 		* Thanks to Brad Pepers
			* I still need to test and trouble shoot this code
		   * I cant remember if sems are the same between ibcs and linux
		   */
		  case SETALL:
		  case GETALL:
			 return SYS (ipc) (SEMCTL, arg1, 0, ibcs_sem_trans (arg3), arg4);
		  case IPC_STAT:
			 retval = SYS (ipc) (SEMCTL, arg1, arg2, ibcs_sem_trans (arg3), arg4);
			 if (retval < 0)
				return retval;
			 else
				{
				  struct ibcs_semid_ds os;
				  struct semid_ds is;
				  memcpy_fromfs (&is, arg4, sizeof (is));
				  memset (&os, 0, sizeof (os));
				  lp_to_ip (&is.sem_perm, &os.sem_perm);
				  os.sem_otime = is.sem_otime;
				  os.sem_ctime = is.sem_ctime;
				  os.sem_base = is.sem_base;
				  os.sem_nsems = is.sem_nsems;
				  memcpy_tofs (arg4, &os, sizeof (os));
				  return retval;
				}

		  default:
			 printk (KERN_ERR "ibcs_semctl: unsupported command %d\n", arg3);
		  }

	 case U_SEMGET:
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: ibcs_semget: args: %d %d %o \n",
					 arg1, arg2, arg3);
#endif
		return SYS (ipc) (SEMGET, arg1, arg2, arg3, 0);
	 case U_SEMOP:
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  {
			 printk (KERN_DEBUG "iBCS: ibcs_semop: args: %d %d %o \n",
						arg1, arg2, arg3);
			 {
				int x;
				struct sembuf tmp;
				struct sembuf *tp = (struct sembuf *) arg2;
				for (x = 0; x < arg3; x++)
				  {
					 memcpy_fromfs (&tmp, tp, sizeof (tmp));
					 printk (KERN_DEBUG "iBCS: ibcs_semop args: %d %d %o \n",
								tmp.sem_num, tmp.sem_op, tmp.sem_flg);
					 tp++;
				  }
			 }
		  }
#endif
		return SYS (ipc) (SEMOP, arg1, arg3, 0, (struct msgbuf *) arg2);
	 }
  return -EINVAL;
}

/*
**	start with the easy stuff
*/
#define U_SHMAT  (0)
#define U_SHMCTL (1)
#define U_SHMDT  (2)
#define U_SHMGET (3)
/*
** modified to set the flags directly
** does not return to emulate
*/
int
ibcs_shmsys (struct pt_regs *regs)
{
  int command = get_fs_long (((unsigned long *) regs->esp) + (1));
  int arg1, arg2, arg3;
  long retval = 0;
  char *addr = 0;
  struct shmid_ds is;
  arg1 = arg2 = arg3 = 0;
  switch (command)
	 {
	 case U_SHMAT:
	 case U_SHMCTL:
	 case U_SHMGET:
		arg1 = get_fs_long (((unsigned long *) regs->esp) + (2));
		arg2 = get_fs_long (((unsigned long *) regs->esp) + (3));
		arg3 = get_fs_long (((unsigned long *) regs->esp) + (4));
		break;
	 case U_SHMDT:
		addr = (char *) get_fs_long (((unsigned long *) regs->esp) + (2));
		break;
	 default:
		printk (KERN_ERR "iBCS: bad SHM command %d\n", command);
		retval = -EINVAL;
		goto test_exit;
	 }

  switch (command)
	 {

	 case U_SHMAT:
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: ibcs_shmat: args: %d %x %o \n",
					 arg1, arg2, arg3);
#endif
		/*
		** raddr = 0 tells sys_shmat to limit to 2G
		**	and we are IBCS, no raddr value to return
		*/
		retval = SYS (ipc) (SHMAT, arg1, arg3, 0, (char *) arg2);

#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: ibcs_shmat: return val is %lx\n",
					 retval);
#endif
		goto test_exit;

	 case U_SHMGET:
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: ibcs_shmget: args: %d %x %o \n",
					 arg1, arg2, arg3);
#endif
		retval = SYS (ipc) (SHMGET, arg1, arg2, arg3, 0);
		goto test_exit;

	 case U_SHMDT:
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: ibcs_shmdt: arg: %lx\n",
					 (unsigned long) addr);
#endif
		retval = SYS (ipc) (SHMDT, 0, 0, 0, addr);
		goto test_exit;

	 case U_SHMCTL:
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: ibcs_shmctl: args: %d %x %o %d %x\n",
					 arg1, arg2, arg3, arg3, arg3);
#endif
		switch (arg2)
		  {
		  case U_SHMLOCK:
			 retval = SYS (ipc) (SHMCTL, arg1, SHM_LOCK, 0, arg3);
			 goto test_exit;
		  case U_SHMUNLOCK:
			 retval = SYS (ipc) (SHMCTL, arg1, SHM_UNLOCK, 0, arg3);
			 goto test_exit;
		  case IPC_SET:
			 {
				struct ibcs_shmid_ds is;
				struct shmid_ds os;

				memcpy_fromfs (&is, (char *) arg3, sizeof (is));
				ip_to_lp (&is.shm_perm, &os.shm_perm);
				memcpy_tofs ((char *) arg3, &os, sizeof (os));

				retval = SYS (ipc) (SHMCTL, arg1, arg2, 0, arg3);

				memcpy_fromfs (&os, (char *) arg3, sizeof (os));
				lp_to_ip (&os.shm_perm, &is.shm_perm);
				memcpy_tofs ((char *) arg3, &is, sizeof (is));

				goto test_exit;
			 }
		  case IPC_RMID:
			 retval = (SYS (ipc) (SHMCTL, arg1, arg2, arg3));
			 goto test_exit;

		  case IPC_STAT:
			 /* retval = SYS(ipc) (SHMCTL, arg1, arg2, arg3); */
			 retval = SYS (ipc) (SHMCTL, arg1, arg2, 0, arg3);
			 if (retval < 0)
				goto test_exit;
			 else
				{
				  struct ibcs_shmid_ds os;
				  memset (&os, 0, sizeof (os));
				  memcpy_fromfs (&is, (char *) arg3, sizeof (is));
				  lp_to_ip (&is.shm_perm, &os.shm_perm);
				  os.shm_segsz = is.shm_segsz;
				  os.shm_lpid = is.shm_lpid;
				  os.shm_cpid = is.shm_cpid;
				  os.shm_nattch = is.shm_nattch;
				  os.shm_atime = is.shm_atime;
				  os.shm_dtime = is.shm_dtime;
				  os.shm_ctime = is.shm_ctime;
				  memcpy_tofs ((char *) arg3, &os, sizeof (os));
				  goto test_exit;
				}
		  default:
			 printk (KERN_ERR "iBCS: ibcs_shmctl: unsupported command %d\n", arg2);
		  }
		retval = -EINVAL;
		goto test_exit;

	 default:
#ifdef IBCS_TRACE
		printk (KERN_DEBUG "iBCS: ibcs_shm: command: %x\n", command);
#endif
		retval = -EINVAL;
		goto test_exit;
	 }

test_exit:;

  if ((retval < 0) && (retval > -255))
	 {
		regs->eflags |= 1;		  /* set carry flag */
		regs->eax = iABI_errors (-retval);
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: Error %ld\n", regs->eax);
#endif
	 }
  else
	 {
		regs->eflags &= ~1;		  /* Clear carry flag */
		regs->eax = retval;
	 }

  return 0;
}

/*
**	start with the easy stuff
*/
#define U_MSGGET  (0)
#define U_MSGCTL  (1)
#define U_MSGRCV  (2)
#define U_MSGSND  (3)

int
ibcs_msgsys (struct pt_regs *regs)
{
  int command = get_fs_long (((unsigned long *) regs->esp) + (1));
  int arg1, arg2, arg4, arg5;
  char *arg3;
  int retval;
  arg1 = get_fs_long (((unsigned long *) regs->esp) + (2));
  arg2 = get_fs_long (((unsigned long *) regs->esp) + (3));
  arg3 = (char *) get_fs_long (((unsigned long *) regs->esp) + (4));
  switch (command)
	 {
		/* hard one first */
	 case U_MSGCTL:
		switch ((arg2))
		  {
		  case IPC_SET:
			 {
				struct ibcs_msqid_ds is;
				struct msqid_ds os;

				memcpy_fromfs (&is, (char *) arg3, sizeof (is));
				ip_to_lp (&is.msg_perm, &os.msg_perm);
				memcpy_tofs ((char *) arg3, &os, sizeof (os));

				retval = SYS (ipc) (MSGCTL, arg1, arg2, 0, arg3);

				memcpy_fromfs (&os, (char *) arg3, sizeof (os));
				lp_to_ip (&os.msg_perm, &is.msg_perm);
				memcpy_tofs ((char *) arg3, &is, sizeof (is));

				return retval;
			 }


		  case IPC_RMID:
			 return SYS (ipc) (MSGCTL, arg1, arg2, 0, arg3);

		  case IPC_STAT:
			 retval = SYS (ipc) (MSGCTL, arg1, arg2, 0, arg3);
			 if (retval < 0)
				return retval;
			 else
				{
				  struct ibcs_msqid_ds os;
				  struct msqid_ds is;
				  memset (&os, 0, sizeof (os));
				  memcpy_fromfs (&is, (char *) arg3, sizeof (is));
				  os.msg_perm.uid = is.msg_perm.uid;
				  os.msg_perm.gid = is.msg_perm.gid;
				  os.msg_perm.cuid = is.msg_perm.cuid;
				  os.msg_perm.cgid = is.msg_perm.cgid;
				  os.msg_perm.mode = is.msg_perm.mode;
				  os.msg_perm.seq = is.msg_perm.seq;
				  os.msg_perm.key = is.msg_perm.key;

				  os.msg_cbytes = is.msg_cbytes;
				  os.msg_qnum = is.msg_qnum;
				  os.msg_qbytes = is.msg_qbytes;
				  os.msg_lspid = is.msg_lspid;
				  os.msg_lrpid = is.msg_lrpid;
				  os.msg_stime = is.msg_stime;
				  os.msg_rtime = is.msg_rtime;
				  os.msg_ctime = is.msg_ctime;
				  memcpy_tofs ((char *) arg3, &os, sizeof (os));
				  return retval;
				}

		  default:
			 printk (KERN_ERR "ibcs_msgctl: unsupported command %d\n", arg2);
		  }

	 case U_MSGGET:
		return SYS (ipc) (MSGGET, arg1, arg2, 0, 0);
	 case U_MSGSND:
		{
		/*
		 * special thanks to Brad Pepers (pepersb@cuug.ab.ca)
		 * for pointing this out. IE, the return value for msgsnd
		 * is zero for succes on ibcs.
		 */
		  arg4 = get_fs_long (((unsigned long *) regs->esp) + (5));
		  retval = SYS (ipc) (MSGSND, arg1, arg3, arg4, (char *) arg2);
		  return ((retval > 0) ? 0 : retval);
		}
	 case U_MSGRCV:
		{
		  struct ipc_kludge tmp;
		  /* major kludge stuff here */
		  /* god what a hack !!!!    */
		  /* and all in the name of keeping the kernel symbol */
		  /* table small													 */
		  /* arg 1 = key */
		  /* arg 2 = buffer */
		  /* arg 3 = size */
		  /* arg 4 = type */
		  /* arg 5 = flag */
		  arg4 = get_fs_long (((unsigned long *) regs->esp) + (5));
		  arg5 = get_fs_long (((unsigned long *) regs->esp) + (6));
		  tmp.msgp = (struct msgbuf *) arg2;
		  tmp.msgtyp = arg4;
		  return SYS (ipc) (MSGRCV, arg1, arg3, arg5, &tmp);
		}

	 default:
		printk (KERN_ERR "ibcs_msgctl: unsupported command %d\n", command);
	 }
  return -EINVAL;
}
