/*      $NetBSD: netbsd32_compat_50_sysv.c,v 1.4 2021/01/19 03:20:13 simonb Exp $       */

/*-
* Copyright (c) 2008 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Christos Zoulas.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: netbsd32_compat_50_sysv.c,v 1.4 2021/01/19 03:20:13 simonb Exp $");

#if defined(_KERNEL_OPT)
#include "opt_sysv.h"
#include "opt_compat_netbsd.h"
#endif

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/msg.h>
#include <sys/sem.h>
#include <sys/shm.h>

#include <sys/syscallvar.h>

#include <compat/netbsd32/netbsd32.h>
#include <compat/netbsd32/netbsd32_syscall.h>
#include <compat/netbsd32/netbsd32_syscallargs.h>
#include <compat/netbsd32/netbsd32_conv.h>

#if defined(COMPAT_50)

#if defined(SYSVSEM)

int
compat_50_netbsd32___semctl14(struct lwp *l, const struct compat_50_netbsd32___semctl14_args *uap, register_t *retval)
{
       return do_netbsd32___semctl14(l, uap, retval, NULL);
}

int
do_netbsd32___semctl14(struct lwp *l, const struct compat_50_netbsd32___semctl14_args *uap, register_t *retval, void *vkarg)
{
       /* {
               syscallarg(int) semid;
               syscallarg(int) semnum;
               syscallarg(int) cmd;
               syscallarg(netbsd32_semun50p_t) arg;
       } */
       struct semid_ds sembuf;
       struct netbsd32_semid_ds50 sembuf32;
       int cmd, error;
       void *pass_arg;
       union __semun karg;
       union netbsd32_semun50 karg32;

       cmd = SCARG(uap, cmd);

       switch (cmd) {
       case IPC_SET:
       case IPC_STAT:
               pass_arg = &sembuf;
               break;

       case GETALL:
       case SETVAL:
       case SETALL:
               pass_arg = &karg;
               break;
       default:
               pass_arg = NULL;
               break;
       }

       if (pass_arg) {
               if (vkarg != NULL)
                       karg32 = *(union netbsd32_semun50 *)vkarg;
               else {
                       error = copyin(SCARG_P32(uap, arg), &karg32,
                                       sizeof(karg32));
                       if (error)
                               return error;
               }
               if (pass_arg == &karg) {
                       switch (cmd) {
                       case GETALL:
                       case SETALL:
                               karg.array = NETBSD32PTR64(karg32.array);
                               break;
                       case SETVAL:
                               karg.val = karg32.val;
                               break;
                       }
               }
               if (cmd == IPC_SET) {
                       error = copyin(NETBSD32PTR64(karg32.buf), &sembuf32,
                           sizeof(sembuf32));
                       if (error)
                               return error;
                       netbsd32_to_semid_ds50(&sembuf32, &sembuf);
               }
       }

       error = semctl1(l, SCARG(uap, semid), SCARG(uap, semnum), cmd,
           pass_arg, retval);

       if (error == 0 && cmd == IPC_STAT) {
               netbsd32_from_semid_ds50(&sembuf, &sembuf32);
               error = copyout(&sembuf32, NETBSD32PTR64(karg32.buf),
                   sizeof(sembuf32));
       }

       return error;
}
#endif

#if defined(SYSVMSG)

int
compat_50_netbsd32___msgctl13(struct lwp *l, const struct compat_50_netbsd32___msgctl13_args *uap, register_t *retval)
{
       /* {
               syscallarg(int) msqid;
               syscallarg(int) cmd;
               syscallarg(netbsd32_msqid_ds50p_t) buf;
       } */
       struct msqid_ds ds;
       struct netbsd32_msqid_ds50 ds32;
       int error, cmd;

       cmd = SCARG(uap, cmd);
       if (cmd == IPC_SET) {
               error = copyin(SCARG_P32(uap, buf), &ds32, sizeof(ds32));
               if (error)
                       return error;
               netbsd32_to_msqid_ds50(&ds32, &ds);
       }

       error = msgctl1(l, SCARG(uap, msqid), cmd,
           (cmd == IPC_SET || cmd == IPC_STAT) ? &ds : NULL);

       if (error == 0 && cmd == IPC_STAT) {
               netbsd32_from_msqid_ds50(&ds, &ds32);
               error = copyout(&ds32, SCARG_P32(uap, buf), sizeof(ds32));
       }

       return error;
}
#endif

#if defined(SYSVSHM)

int
compat_50_netbsd32___shmctl13(struct lwp *l, const struct compat_50_netbsd32___shmctl13_args *uap, register_t *retval)
{
       /* {
               syscallarg(int) shmid;
               syscallarg(int) cmd;
               syscallarg(netbsd32_shmid_ds50p_t) buf;
       } */
       struct shmid_ds ds;
       struct netbsd32_shmid_ds50 ds32;
       int error, cmd;

       cmd = SCARG(uap, cmd);
       if (cmd == IPC_SET) {
               error = copyin(SCARG_P32(uap, buf), &ds32, sizeof(ds32));
               if (error)
                       return error;
               netbsd32_to_shmid_ds50(&ds32, &ds);
       }

       error = shmctl1(l, SCARG(uap, shmid), cmd,
           (cmd == IPC_SET || cmd == IPC_STAT) ? &ds : NULL);

       if (error == 0 && cmd == IPC_STAT) {
               netbsd32_from_shmid_ds50(&ds, &ds32);
               error = copyout(&ds32, SCARG_P32(uap, buf), sizeof(ds32));
       }

       return error;
}
#endif

#define _PKG_ENTRY(name)        \
       { NETBSD32_SYS_ ## name, 0, (sy_call_t *)name }

static const struct syscall_package compat_sysvipc_50_syscalls[] = {
#if defined(SYSVSEM)
       _PKG_ENTRY(compat_50_netbsd32___semctl14),
#endif
#if defined(SYSVSHM)
       _PKG_ENTRY(compat_50_netbsd32___shmctl13),
#endif
#if defined(SYSVMSG)
       _PKG_ENTRY(compat_50_netbsd32___msgctl13),
#endif
       { 0, 0, NULL }
};

#define REQ1    "sysv_ipc,compat_sysv_50,"
#define REQ2    "compat_netbsd32,compat_netbsd32_sysvipc,"

MODULE(MODULE_CLASS_EXEC, compat_netbsd32_sysvipc_50, REQ1 REQ2);

static int
compat_netbsd32_sysvipc_50_modcmd(modcmd_t cmd, void *arg)
{

       switch (cmd) {
       case MODULE_CMD_INIT:
               return syscall_establish(&emul_netbsd32,
                   compat_sysvipc_50_syscalls);

       case MODULE_CMD_FINI:
               return syscall_disestablish(&emul_netbsd32,
                   compat_sysvipc_50_syscalls);

       default:
               return ENOTTY;
       }
}

#endif /* COMPAT_50 */