OpenSolaris_b135/lib/brand/lx/lx_brand/common/sysv_ipc.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <errno.h>
#include <unistd.h>
#include <strings.h>
#include <rctl.h>
#include <alloca.h>
#include <values.h>
#include <sys/syscall.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/lx_debug.h>
#include <sys/lx_types.h>
#include <sys/lx_sysv_ipc.h>
#include <sys/lx_misc.h>
#include <sys/lx_syscall.h>

#define	SLOT_SEM	0
#define	SLOT_SHM	1
#define	SLOT_MSG	2

static int
get_rctlval(rctlblk_t *rblk, char *name)
{
	rctl_qty_t r;

	if (getrctl(name, NULL, rblk, RCTL_FIRST) == -1)
		return (-errno);

	r = rctlblk_get_value(rblk);
	if (r > MAXINT)
		return (-EOVERFLOW);
	return (r);
}

/*
 * Given a slot number and a maximum number of ids to extract from the
 * kernel, return the msgid in the provided slot.
 */
static int
slot_to_id(int type, int slot)
{
	uint_t nids, max;
	int *idbuf = NULL;
	int r;

	nids = 0;
	for (;;) {
		switch (type) {
		case SLOT_SEM:
			r = semids(idbuf, nids, &max);
			break;
		case SLOT_SHM:
			r = shmids(idbuf, nids, &max);
			break;
		case SLOT_MSG:
			r = msgids(idbuf, nids, &max);
			break;
		}

		if (r < 0)
			return (-errno);

		if (max == 0)
			return (-EINVAL);

		if (max <= nids)
			return (idbuf[slot]);

		nids = max;
		if ((idbuf = (int *)SAFE_ALLOCA(sizeof (int) * nids)) == NULL)
			return (-ENOMEM);
	}
}

/*
 * Semaphore operations.
 */
static int
lx_semget(key_t key, int nsems, int semflg)
{
	int sol_flag;
	int r;

	lx_debug("\nsemget(%d, %d, %d)\n", key, nsems, semflg);
	sol_flag = semflg & S_IAMB;
	if (semflg & LX_IPC_CREAT)
		sol_flag |= IPC_CREAT;
	if (semflg & LX_IPC_EXCL)
		sol_flag |= IPC_EXCL;

	r = semget(key, nsems, sol_flag);
	return ((r < 0) ? -errno : r);
}

static int
lx_semop(int semid, struct sembuf *sops, size_t nsops)
{
	int r;

	lx_debug("\nsemop(%d, 0x%p, %u)\n", semid, sops, nsops);
	if (nsops == 0)
		return (-EINVAL);

	r = semop(semid, sops, nsops);
	return ((r < 0) ? -errno : r);
}

static int
lx_semctl_ipcset(int semid, void *buf)
{
	struct lx_semid_ds semds;
	struct semid_ds sol_semds;
	int r;

	if (uucopy(buf, &semds, sizeof (semds)))
		return (-errno);

	bzero(&sol_semds, sizeof (sol_semds));
	sol_semds.sem_perm.uid = semds.sem_perm.uid;
	sol_semds.sem_perm.gid = semds.sem_perm.gid;
	sol_semds.sem_perm.mode = semds.sem_perm.mode;

	r = semctl(semid, 0, IPC_SET, &sol_semds);
	return ((r < 0) ? -errno : r);
}

static int
lx_semctl_ipcstat(int semid, void *buf)
{
	struct lx_semid_ds semds;
	struct semid_ds sol_semds;

	if (semctl(semid, 0, IPC_STAT, &sol_semds) != 0)
		return (-errno);

	bzero(&semds, sizeof (semds));
	semds.sem_perm.key = sol_semds.sem_perm.key;
	semds.sem_perm.seq = sol_semds.sem_perm.seq;
	semds.sem_perm.uid = sol_semds.sem_perm.uid;
	semds.sem_perm.gid = sol_semds.sem_perm.gid;
	semds.sem_perm.cuid = sol_semds.sem_perm.cuid;
	semds.sem_perm.cgid = sol_semds.sem_perm.cgid;

	/* Linux only uses the bottom 9 bits */
	semds.sem_perm.mode = sol_semds.sem_perm.mode & S_IAMB;
	semds.sem_otime = sol_semds.sem_otime;
	semds.sem_ctime = sol_semds.sem_ctime;
	semds.sem_nsems = sol_semds.sem_nsems;

	if (uucopy(&semds, buf, sizeof (semds)))
		return (-errno);

	return (0);
}

static int
lx_semctl_ipcinfo(void *buf)
{
	struct lx_seminfo i;
	rctlblk_t *rblk;
	int rblksz;
	uint_t nids;
	int idbuf;

	rblksz = rctlblk_size();
	if ((rblk = (rctlblk_t *)SAFE_ALLOCA(rblksz)) == NULL)
		return (-ENOMEM);

	bzero(&i, sizeof (i));
	if ((i.semmni = get_rctlval(rblk, "project.max-sem-ids")) < 0)
		return (i.semmni);
	if ((i.semmsl = get_rctlval(rblk, "process.max-sem-nsems")) < 0)
		return (i.semmsl);
	if ((i.semopm = get_rctlval(rblk, "process.max-sem-ops")) < 0)
		return (i.semopm);

	/*
	 * We don't have corresponding rctls for these fields.  The values
	 * are taken from the formulas used to derive the defaults listed
	 * in the Linux header file.  We're lying, but trying to be
	 * coherent about it.
	 */
	i.semmap = i.semmni;
	i.semmns = i.semmni * i.semmsl;
	i.semmnu = INT_MAX;
	i.semume = INT_MAX;
	i.semvmx = LX_SEMVMX;
	if (semids(&idbuf, 0, &nids) < 0)
		return (-errno);
	i.semusz = nids;
	i.semaem = INT_MAX;

	if (uucopy(&i, buf, sizeof (i)) != 0)
		return (-errno);

	return (nids);
}

static int
lx_semctl_semstat(int slot, void *buf)
{
	int r, semid;

	semid = slot_to_id(SLOT_SEM, slot);
	if (semid < 0)
		return (semid);

	r = lx_semctl_ipcstat(semid, buf);
	return (r < 0 ? r : semid);
}

/*
 * For the SETALL operation, we have to examine each of the semaphore
 * values to be sure it is legal.
 */
static int
lx_semctl_setall(int semid, union lx_semun *arg)
{
	struct semid_ds semds;
	ushort_t *vals;
	int i, sz, r;

	/*
	 * Find out how many semaphores are involved, reserve enough
	 * memory for an internal copy of the array, and then copy it in
	 * from the process.
	 */
	if (semctl(semid, 0, IPC_STAT, &semds) != 0)
		return (-errno);
	sz = semds.sem_nsems * sizeof (ushort_t);
	if ((vals = SAFE_ALLOCA(sz)) == NULL)
		return (-ENOMEM);
	if (uucopy(arg->sems, vals, sz))
		return (-errno);

	/* Validate each of the values. */
	for (i = 0; i < semds.sem_nsems; i++)
		if (vals[i] > LX_SEMVMX)
			return (-ERANGE);

	r = semctl(semid, 0, SETALL, arg->sems);

	return ((r < 0) ? -errno : r);
}

static int
lx_semctl(int semid, int semnum, int cmd, void *ptr)
{
	union lx_semun arg;
	int rval;
	int opt = cmd & ~LX_IPC_64;
	int use_errno = 0;

	lx_debug("\nsemctl(%d, %d, %d, 0x%p)\n", semid, semnum, cmd, ptr);

	/*
	 * The final arg to semctl() is a pointer to a union.  For some
	 * commands we can hand that pointer directly to the kernel.  For
	 * these commands, we need to extract an argument from the union
	 * before calling into the kernel.
	 */
	if (opt == LX_SETVAL || opt == LX_SETALL || opt == LX_GETALL ||
	    opt == LX_IPC_SET || opt == LX_IPC_STAT || opt == LX_SEM_STAT ||
	    opt == LX_IPC_INFO || opt == LX_SEM_INFO)
		if (uucopy(ptr, &arg, sizeof (arg)))
			return (-errno);

	switch (opt) {
	case LX_GETVAL:
		use_errno = 1;
		rval = semctl(semid, semnum, GETVAL, NULL);
		break;
	case LX_SETVAL:
		if (arg.val > LX_SEMVMX) {
			rval = -ERANGE;
			break;
		}
		use_errno = 1;
		rval = semctl(semid, semnum, SETVAL, arg.val);
		break;
	case LX_GETPID:
		use_errno = 1;
		rval = semctl(semid, semnum, GETPID, NULL);
		break;
	case LX_GETNCNT:
		use_errno = 1;
		rval = semctl(semid, semnum, GETNCNT, NULL);
		break;
	case LX_GETZCNT:
		use_errno = 1;
		rval = semctl(semid, semnum, GETZCNT, NULL);
		break;
	case LX_GETALL:
		use_errno = 1;
		rval = semctl(semid, semnum, GETALL, arg.sems);
		break;
	case LX_SETALL:
		rval = lx_semctl_setall(semid, &arg);
		break;
	case LX_IPC_RMID:
		use_errno = 1;
		rval = semctl(semid, semnum, IPC_RMID, NULL);
		break;
	case LX_SEM_STAT:
		rval = lx_semctl_semstat(semid, arg.semds);
		break;
	case LX_IPC_STAT:
		rval = lx_semctl_ipcstat(semid, arg.semds);
		break;

	case LX_IPC_SET:
		rval = lx_semctl_ipcset(semid, arg.semds);
		break;

	case LX_IPC_INFO:
	case LX_SEM_INFO:
		rval = lx_semctl_ipcinfo(arg.semds);
		break;

	default:
		rval = -EINVAL;
	}

	if (use_errno == 1 && rval < 0)
		return (-errno);
	return (rval);
}

/*
 * msg operations.
 */
static int
lx_msgget(key_t key, int flag)
{
	int sol_flag;
	int r;

	lx_debug("\tlx_msgget(%d, %d)\n", key, flag);

	sol_flag = flag & S_IAMB;
	if (flag & LX_IPC_CREAT)
		sol_flag |= IPC_CREAT;
	if (flag & LX_IPC_EXCL)
		sol_flag |= IPC_EXCL;

	r = msgget(key, sol_flag);
	return (r < 0 ? -errno : r);
}

static int
lx_msgsnd(int id, struct msgbuf *buf, size_t sz, int flag)
{
	int sol_flag = 0;
	int r;

	lx_debug("\tlx_msgsnd(%d, 0x%p, %d, %d)\n", id, buf, sz, flag);

	if (flag & LX_IPC_NOWAIT)
		sol_flag |= IPC_NOWAIT;

	if (((ssize_t)sz < 0) || (sz > LX_MSGMAX))
		return (-EINVAL);

	r = msgsnd(id, buf, sz, sol_flag);
	return (r < 0 ? -errno : r);
}

static int
lx_msgrcv(int id, struct msgbuf *buf, size_t sz, int flag)
{
	int sol_flag = 0;
	struct {
		void *msgp;
		long msgtype;
	} args;
	int r;

	/*
	 * Rather than passing 5 args into ipc(2) directly, glibc passes 4
	 * args and uses the buf argument to point to a structure
	 * containing two args: a pointer to the message and the message
	 * type.
	 */
	if (uucopy(buf, &args, sizeof (args)))
		return (-errno);

	lx_debug("\tlx_msgrcv(%d, 0x%p, %d, %d, %ld, %d)\n",
	    id, args.msgp, sz, args.msgtype, flag);

	/*
	 * Check for a negative sz parameter.
	 *
	 * Unlike msgsnd(2), the Linux man page does not specify that
	 * msgrcv(2) should return EINVAL if (sz > MSGMAX), only if (sz < 0).
	 */
	if ((ssize_t)sz < 0)
		return (-EINVAL);

	if (flag & LX_MSG_NOERROR)
		sol_flag |= MSG_NOERROR;
	if (flag & LX_IPC_NOWAIT)
		sol_flag |= IPC_NOWAIT;

	r = msgrcv(id, args.msgp, sz, args.msgtype, sol_flag);
	return (r < 0 ? -errno : r);
}

static int
lx_msgctl_ipcstat(int msgid, void *buf)
{
	struct lx_msqid_ds msgids;
	struct msqid_ds sol_msgids;
	int r;

	r = msgctl(msgid, IPC_STAT, &sol_msgids);
	if (r < 0)
		return (-errno);

	bzero(&msgids, sizeof (msgids));
	msgids.msg_perm.key = sol_msgids.msg_perm.key;
	msgids.msg_perm.seq = sol_msgids.msg_perm.seq;
	msgids.msg_perm.uid = sol_msgids.msg_perm.uid;
	msgids.msg_perm.gid = sol_msgids.msg_perm.gid;
	msgids.msg_perm.cuid = sol_msgids.msg_perm.cuid;
	msgids.msg_perm.cgid = sol_msgids.msg_perm.cgid;

	/* Linux only uses the bottom 9 bits */
	msgids.msg_perm.mode = sol_msgids.msg_perm.mode & S_IAMB;

	msgids.msg_stime = sol_msgids.msg_stime;
	msgids.msg_rtime = sol_msgids.msg_rtime;
	msgids.msg_ctime = sol_msgids.msg_ctime;
	msgids.msg_qbytes = sol_msgids.msg_qbytes;
	msgids.msg_cbytes = sol_msgids.msg_cbytes;
	msgids.msg_qnum = sol_msgids.msg_qnum;
	msgids.msg_lspid = sol_msgids.msg_lspid;
	msgids.msg_lrpid = sol_msgids.msg_lrpid;

	if (uucopy(&msgids, buf, sizeof (msgids)))
		return (-errno);

	return (0);
}

static int
lx_msgctl_ipcinfo(int cmd, void *buf)
{
	struct lx_msginfo m;
	rctlblk_t *rblk;
	int idbuf, rblksz, msgseg, maxmsgs;
	uint_t nids;
	int rval;

	rblksz = rctlblk_size();
	if ((rblk = (rctlblk_t *)SAFE_ALLOCA(rblksz)) == NULL)
		return (-ENOMEM);

	bzero(&m, sizeof (m));
	if ((m.msgmni = get_rctlval(rblk, "project.max-msg-ids")) < 0)
		return (m.msgmni);
	if ((m.msgmnb = get_rctlval(rblk, "process.max-msg-qbytes")) < 0)
		return (m.msgmnb);

	if (cmd == LX_IPC_INFO) {
		if ((maxmsgs = get_rctlval(rblk,
		    "process.max-msg-messages")) < 0)
			return (maxmsgs);
		m.msgtql = maxmsgs * m.msgmni;
		m.msgmap = m.msgmnb;
		m.msgpool = m.msgmax * m.msgmnb;
		rval = 0;
	} else {
		if (msgids(&idbuf, 0, &nids) < 0)
			return (-errno);
		m.msgpool = nids;

		/*
		 * For these fields, we can't even come up with a good fake
		 * approximation.  These are listed as 'obsolete' or
		 * 'unused' in the header files, so hopefully nobody is
		 * relying on them anyway.
		 */
		m.msgtql = INT_MAX;
		m.msgmap = INT_MAX;
		rval = nids;
	}

	/*
	 * We don't have corresponding rctls for these fields.  The values
	 * are taken from the formulas used to derive the defaults listed
	 * in the Linux header file.  We're lying, but trying to be
	 * coherent about it.
	 */
	m.msgmax = m.msgmnb;
	m.msgssz = 16;
	msgseg = (m.msgpool * 1024) / m.msgssz;
	m.msgseg = (msgseg > 0xffff) ? 0xffff : msgseg;

	if (uucopy(&m, buf, sizeof (m)))
		return (-errno);
	return (rval);
}

static int
lx_msgctl_ipcset(int msgid, void *buf)
{
	struct lx_msqid_ds msgids;
	struct msqid_ds sol_msgids;
	int r;

	if (uucopy(buf, &msgids, sizeof (msgids)))
		return (-errno);

	bzero(&sol_msgids, sizeof (sol_msgids));
	sol_msgids.msg_perm.uid = LX_UID16_TO_UID32(msgids.msg_perm.uid);
	sol_msgids.msg_perm.gid = LX_UID16_TO_UID32(msgids.msg_perm.gid);

	/* Linux only uses the bottom 9 bits */
	sol_msgids.msg_perm.mode = msgids.msg_perm.mode & S_IAMB;
	sol_msgids.msg_qbytes = msgids.msg_qbytes;

	r = msgctl(msgid, IPC_SET, &sol_msgids);
	return (r < 0 ? -errno : r);
}

static int
lx_msgctl_msgstat(int slot, void *buf)
{
	int r, msgid;

	lx_debug("msgstat(%d, 0x%p)\n", slot, buf);

	msgid = slot_to_id(SLOT_MSG, slot);

	if (msgid < 0)
		return (msgid);

	r = lx_msgctl_ipcstat(msgid, buf);
	return (r < 0 ? r : msgid);
}

/*
 * Split off the various msgctl's here
 */
static int
lx_msgctl(int msgid, int cmd, void *buf)
{
	int r;

	lx_debug("\tlx_msgctl(%d, %d, 0x%p)\n", msgid, cmd, buf);
	switch (cmd & ~LX_IPC_64) {
	case LX_IPC_RMID:
		r = msgctl(msgid, IPC_RMID, NULL);
		if (r < 0)
			r = -errno;
		break;
	case LX_IPC_SET:
		r = lx_msgctl_ipcset(msgid, buf);
		break;
	case LX_IPC_STAT:
		r = lx_msgctl_ipcstat(msgid, buf);
		break;
	case LX_MSG_STAT:
		r = lx_msgctl_msgstat(msgid, buf);
		break;

	case LX_IPC_INFO:
	case LX_MSG_INFO:
		r = lx_msgctl_ipcinfo(cmd, buf);
		break;

	default:
		r = -EINVAL;
		break;
	}

	return (r);
}

/*
 * shm-related operations.
 */
static int
lx_shmget(key_t key, size_t size, int flag)
{
	int sol_flag;
	int r;

	lx_debug("\tlx_shmget(%d, %d, %d)\n", key, size, flag);

	sol_flag = flag & S_IAMB;
	if (flag & LX_IPC_CREAT)
		sol_flag |= IPC_CREAT;
	if (flag & LX_IPC_EXCL)
		sol_flag |= IPC_EXCL;

	r = shmget(key, size, sol_flag);
	return (r < 0 ? -errno : r);
}

static int
lx_shmat(int shmid, void *addr, int flags, void **rval)
{
	int sol_flags;
	void *ptr;

	lx_debug("\tlx_shmat(%d, 0x%p, %d, 0%o)\n", shmid, addr, flags);

	sol_flags = 0;
	if (flags & LX_SHM_RDONLY)
		sol_flags |= SHM_RDONLY;
	if (flags & LX_SHM_RND)
		sol_flags |= SHM_RND;
	if ((flags & LX_SHM_REMAP) && (addr == NULL))
		return (-EINVAL);

	ptr = shmat(shmid, addr, sol_flags);
	if (ptr == (void *)-1)
		return (-errno);
	if (uucopy(&ptr, rval, sizeof (ptr)) != 0)
		return (-errno);

	return (0);
}

static int
lx_shmctl_ipcinfo(void *buf)
{
	struct lx_shminfo s;
	rctlblk_t *rblk;
	int rblksz;

	rblksz = rctlblk_size();
	if ((rblk = (rctlblk_t *)SAFE_ALLOCA(rblksz)) == NULL)
		return (-ENOMEM);

	bzero(&s, sizeof (s));
	if ((s.shmmni = get_rctlval(rblk, "project.max-shm-ids")) < 0)
		return (s.shmmni);
	if ((s.shmmax = get_rctlval(rblk, "project.max-shm-memory")) < 0)
		return (s.shmmax);

	/*
	 * We don't have corresponding rctls for these fields.  The values
	 * are taken from the formulas used to derive the defaults listed
	 * in the Linux header file.  We're lying, but trying to be
	 * coherent about it.
	 */
	s.shmmin = 1;
	s.shmseg = INT_MAX;
	s.shmall = s.shmmax / getpagesize();

	if (uucopy(&s, buf, sizeof (s)))
		return (-errno);

	return (0);
}

static int
lx_shmctl_ipcstat(int shmid, void *buf)
{
	struct lx_shmid_ds shmds;
	struct shmid_ds sol_shmds;

	if (shmctl(shmid, IPC_STAT, &sol_shmds) != 0)
		return (-errno);

	bzero(&shmds, sizeof (shmds));
	shmds.shm_perm.key = sol_shmds.shm_perm.key;
	shmds.shm_perm.seq = sol_shmds.shm_perm.seq;
	shmds.shm_perm.uid = sol_shmds.shm_perm.uid;
	shmds.shm_perm.gid = sol_shmds.shm_perm.gid;
	shmds.shm_perm.cuid = sol_shmds.shm_perm.cuid;
	shmds.shm_perm.cgid = sol_shmds.shm_perm.cgid;
	shmds.shm_perm.mode = sol_shmds.shm_perm.mode & S_IAMB;
	if (sol_shmds.shm_lkcnt > 0)
		shmds.shm_perm.mode |= LX_SHM_LOCKED;
	shmds.shm_segsz = sol_shmds.shm_segsz;
	shmds.shm_atime	 = sol_shmds.shm_atime;
	shmds.shm_dtime = sol_shmds.shm_dtime;
	shmds.shm_ctime = sol_shmds.shm_ctime;
	shmds.shm_cpid = sol_shmds.shm_cpid;
	shmds.shm_lpid = sol_shmds.shm_lpid;
	shmds.shm_nattch = (ushort_t)sol_shmds.shm_nattch;

	if (uucopy(&shmds, buf, sizeof (shmds)))
		return (-errno);

	return (0);
}

static int
lx_shmctl_ipcset(int shmid, void *buf)
{
	struct lx_shmid_ds shmds;
	struct shmid_ds sol_shmds;
	int r;

	if (uucopy(buf, &shmds, sizeof (shmds)))
		return (-errno);

	bzero(&sol_shmds, sizeof (sol_shmds));
	sol_shmds.shm_perm.uid = shmds.shm_perm.uid;
	sol_shmds.shm_perm.gid = shmds.shm_perm.gid;
	sol_shmds.shm_perm.mode = shmds.shm_perm.mode & S_IAMB;

	r = shmctl(shmid, IPC_SET, &sol_shmds);
	return (r < 0 ? -errno : r);
}

/*
 * Build and return a shm_info structure. We only return the bare
 * essentials required by ipcs. The rest of the info is not readily
 * available.
 */
static int
lx_shmctl_shminfo(void *buf)
{
	struct lx_shm_info shminfo;
	uint_t nids;
	int idbuf;

	bzero(&shminfo, sizeof (shminfo));

	if (shmids(&idbuf, 0, &nids) < 0)
		return (-errno);

	shminfo.used_ids = nids;
	if (uucopy(&shminfo, buf, sizeof (shminfo)) != 0)
		return (-errno);

	return (nids);
}

static int
lx_shmctl_shmstat(int slot, void *buf)
{
	int r, shmid;

	lx_debug("shmctl_shmstat(%d, 0x%p)\n", slot, buf);
	shmid = slot_to_id(SLOT_SHM, slot);
	if (shmid < 0)
		return (shmid);

	r = lx_shmctl_ipcstat(shmid, buf);
	return (r < 0 ? r : shmid);
}

static int
lx_shmctl(int shmid, int cmd, void *buf)
{
	int r;
	int use_errno = 0;

	lx_debug("\tlx_shmctl(%d, %d, 0x%p)\n", shmid, cmd, buf);
	switch (cmd & ~LX_IPC_64) {
	case LX_IPC_RMID:
		use_errno = 1;
		r = shmctl(shmid, IPC_RMID, NULL);
		break;

	case LX_IPC_SET:
		r = lx_shmctl_ipcset(shmid, buf);
		break;

	case LX_IPC_STAT:
		r = lx_shmctl_ipcstat(shmid, buf);
		break;

	case LX_IPC_INFO:
		r = lx_shmctl_ipcinfo(buf);
		break;

	case LX_SHM_LOCK:
		use_errno = 1;
		r = shmctl(shmid, SHM_LOCK, NULL);
		break;

	case LX_SHM_UNLOCK:
		use_errno = 1;
		r = shmctl(shmid, SHM_UNLOCK, NULL);
		break;

	case LX_SHM_INFO:
		r = lx_shmctl_shminfo(buf);
		break;

	case LX_SHM_STAT:
		r = lx_shmctl_shmstat(shmid, buf);
		break;
	default:
		r = -EINVAL;
		break;
	}

	if (use_errno == 1 && r < 0)
		return (-errno);

	return (r);
}

/*
 * Under Linux, glibc funnels all of the sysv IPC operations into this
 * single ipc(2) system call.  We need to blow that up and filter the
 * remnants into the proper Solaris system calls.
 */
int
lx_ipc(uintptr_t cmd, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3,
    uintptr_t arg4)
{
	int r;
	void *bufptr = (void *)arg4;

	lx_debug("lx_ipc(%d, %d, %d, %d, 0x%p, %d)\n",
		cmd, arg1, arg2, arg3, bufptr, arg4);

	switch (cmd) {
	case LX_MSGGET:
		r = lx_msgget((key_t)arg1, (int)arg2);
		break;
	case LX_MSGSND:
		r = lx_msgsnd((int)arg1, bufptr, (size_t)arg2, (int)arg3);
		break;
	case LX_MSGRCV:
		r = lx_msgrcv((int)arg1, bufptr, (size_t)arg2, (int)arg3);
		break;
	case LX_MSGCTL:
		r = lx_msgctl((int)arg1, (int)arg2, bufptr);
		break;
	case LX_SEMCTL:
		r = lx_semctl((int)arg1, (size_t)arg2, (int)arg3, bufptr);
		break;
	case LX_SEMOP:
		/*
		 * 'struct sembuf' is the same on Linux and Solaris, so we
		 * pass bufptr straight through.
		 */
		r = lx_semop((int)arg1, bufptr, (size_t)arg2);
		break;
	case LX_SEMGET:
		r = lx_semget((int)arg1, (size_t)arg2, (int)arg3);
		break;
	case LX_SHMAT:
		r = lx_shmat((int)arg1, bufptr, (size_t)arg2, (void *)arg3);
		break;
	case LX_SHMDT:
		r = shmdt(bufptr);
		if (r < 0)
			r = -errno;
		break;
	case LX_SHMGET:
		r = lx_shmget((int)arg1, (size_t)arg2, (int)arg3);
		break;
	case LX_SHMCTL:
		r = lx_shmctl((int)arg1, (int)arg2, bufptr);
		break;

	default:
		r = -EINVAL;
	}

	return (r);
}