NetBSD/sys/compat/irix/irix_prctl.c
manu 022bd85d32 When sproc is used to create a share group with shared memory, there is a
private area called PRDA that remains unshared. We implement this by using
different vmspace for each share group member, and keeping the memory
appings in sync on each mmap/munmap/mprotect/break...

We use irix_saddr_sync_vmcmd and irix_saddr_sync_syscall to apply a
vmcmd or a syscall to all share group member, this makes the job a bit
easier.

Also implements {get|set}rlimit{64}.
2002-06-12 20:33:20 +00:00

742 lines
18 KiB
C

/* $NetBSD: irix_prctl.c,v 1.13 2002/06/12 20:33:20 manu Exp $ */
/*-
* Copyright (c) 2001-2002 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Emmanuel Dreyfus.
*
* 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the NetBSD
* Foundation, Inc. and its contributors.
* 4. Neither the name of The NetBSD Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 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: irix_prctl.c,v 1.13 2002/06/12 20:33:20 manu Exp $");
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/signal.h>
#include <sys/signalvar.h>
#include <sys/systm.h>
#include <sys/exec.h>
#include <sys/pool.h>
#include <sys/filedesc.h>
#include <sys/vnode.h>
#include <sys/resourcevar.h>
#include <uvm/uvm_extern.h>
#include <uvm/uvm_map.h>
#include <machine/regnum.h>
#include <machine/vmparam.h>
#include <compat/svr4/svr4_types.h>
#include <compat/irix/irix_types.h>
#include <compat/irix/irix_exec.h>
#include <compat/irix/irix_prctl.h>
#include <compat/irix/irix_signal.h>
#include <compat/irix/irix_syscallargs.h>
struct irix_sproc_child_args {
struct proc **isc_proc;
void *isc_entry;
void *isc_arg;
size_t isc_len;
int isc_inh;
struct proc *isc_parent;
};
static void irix_sproc_child __P((struct irix_sproc_child_args *));
static int irix_sproc __P((void *, unsigned int, void *, caddr_t, size_t,
pid_t, struct proc *, register_t *));
int
irix_sys_prctl(p, v, retval)
struct proc *p;
void *v;
register_t *retval;
{
struct irix_sys_prctl_args /* {
syscallarg(int) option;
syscallarg(void *) arg1;
} */ *uap = v;
int option = SCARG(uap, option);
#ifdef DEBUG_IRIX
printf("irix_sys_prctl(): option = %d\n", option);
#endif
switch(option) {
case IRIX_PR_GETSHMASK: { /* Get shared resources */
struct proc *p2;
int shmask = 0;
struct irix_emuldata *ied;
p2 = pfind((pid_t)SCARG(uap, arg1));
if (p2 == p || SCARG(uap, arg1) == 0) {
/* XXX return our own shmask */
return 0;
}
if (p2 == NULL)
return EINVAL;
ied = (struct irix_emuldata *)p->p_emuldata;
if (ied->ied_shareaddr)
shmask |= IRIX_PR_SADDR;
if (p->p_fd == p2->p_fd)
shmask |= IRIX_PR_SFDS;
if (p->p_cwdi == p2->p_cwdi);
shmask |= (IRIX_PR_SDIR|IRIX_PR_SUMASK);
*retval = (register_t)shmask;
return 0;
break;
}
case IRIX_PR_LASTSHEXIT: /* "Last sproc exit" */
/* We no nothing */
break;
case IRIX_PR_GETNSHARE: { /* Number of sproc share group memb.*/
struct irix_emuldata *ied;
struct proc *pp;
struct proc *shareparent;
int count;
ied = (struct irix_emuldata *)p->p_emuldata;
shareparent = ied->ied_shareparent;
if (shareparent == NULL) {
*retval = 0;
return 0;
}
count = 0;
LIST_FOREACH(pp, &allproc, p_list) {
if (irix_check_exec(pp)) {
ied = (struct irix_emuldata *)pp->p_emuldata;
if (ied->ied_shareparent == shareparent)
count++;
}
}
*retval = count;
return 0;
break;
}
case IRIX_PR_TERMCHILD: { /* Send SIGHUP to children on exit */
struct irix_emuldata *ied;
ied = (struct irix_emuldata *)(p->p_emuldata);
ied->ied_pptr = p->p_pptr;
break;
}
case IRIX_PR_ISBLOCKED: { /* Is process blocked? */
pid_t pid = (pid_t)SCARG(uap, arg1);
struct irix_emuldata *ied;
struct proc *target;
struct pcred *pc;
if (pid == 0)
pid = p->p_pid;
if ((target = pfind(pid)) == NULL)
return ESRCH;
if (irix_check_exec(target) == 0)
return 0;
pc = p->p_cred;
if (!(pc->pc_ucred->cr_uid == 0 || \
pc->p_ruid == target->p_cred->p_ruid || \
pc->pc_ucred->cr_uid == target->p_cred->p_ruid || \
pc->p_ruid == target->p_ucred->cr_uid || \
pc->pc_ucred->cr_uid == target->p_ucred->cr_uid))
return EPERM;
ied = (struct irix_emuldata *)(target->p_emuldata);
*retval = (ied->ied_procblk_count < 0);
return 0;
break;
}
default:
printf("Warning: call to unimplemented prctl() command %d\n",
option);
return EINVAL;
break;
}
return 0;
}
int
irix_sys_pidsprocsp(p, v, retval)
struct proc *p;
void *v;
register_t *retval;
{
struct irix_sys_pidsprocsp_args /* {
syscallarg(void *) entry;
syscallarg(unsigned) inh;
syscallarg(void *) arg;
syscallarg(caddr_t) sp;
syscallarg(irix_size_t) len;
syscallarg(irix_pid_t) pid;
} */ *uap = v;
/* pid is ignored for now */
printf("Warning: unsupported pid argument to IRIX sproc\n");
return irix_sproc(SCARG(uap, entry), SCARG(uap, inh), SCARG(uap, arg),
SCARG(uap, sp), SCARG(uap, len), SCARG(uap, pid), p, retval);
}
int
irix_sys_sprocsp(p, v, retval)
struct proc *p;
void *v;
register_t *retval;
{
struct irix_sys_sprocsp_args /* {
syscallarg(void *) entry;
syscallarg(unsigned) inh;
syscallarg(void *) arg;
syscallarg(caddr_t) sp;
syscallarg(irix_size_t) len;
} */ *uap = v;
return irix_sproc(SCARG(uap, entry), SCARG(uap, inh), SCARG(uap, arg),
SCARG(uap, sp), SCARG(uap, len), 0, p, retval);
}
int
irix_sys_sproc(p, v, retval)
struct proc *p;
void *v;
register_t *retval;
{
struct irix_sys_sproc_args /* {
syscallarg(void *) entry;
syscallarg(unsigned) inh;
syscallarg(void *) arg;
} */ *uap = v;
return irix_sproc(SCARG(uap, entry), SCARG(uap, inh), SCARG(uap, arg),
NULL, p->p_rlimit[RLIMIT_STACK].rlim_cur, 0, p, retval);
}
static int
irix_sproc(entry, inh, arg, sp, len, pid, p, retval)
void *entry;
unsigned int inh;
void *arg;
caddr_t sp;
size_t len;
pid_t pid;
struct proc *p;
register_t *retval;
{
int bsd_flags = 0;
struct exec_vmcmd vmc;
int error;
struct proc *p2;
struct proc *pp;
struct irix_sproc_child_args isc;
struct irix_emuldata *ied;
struct irix_emuldata *iedp;
segsz_t stacksize;
#ifdef DEBUG_IRIX
printf("irix_sproc(): entry = %p, inh = %x, arg = %p, sp = 0x%08lx, len = 0x%08lx, pid = %d\n", entry, inh, arg, (u_long)sp, (u_long)len, pid);
#endif
if (len == 0)
return EINVAL;
if (inh & IRIX_PR_SFDS)
bsd_flags |= FORK_SHAREFILES;
if (inh & (IRIX_PR_SUMASK|IRIX_PR_SDIR)) {
bsd_flags |= FORK_SHARECWD;
/* Forget them so that we don't get warning below */
inh &= ~(IRIX_PR_SUMASK|IRIX_PR_SDIR);
}
/* We know how to do PR_SUMASK and PR_SDIR together only */
if (inh & IRIX_PR_SUMASK)
printf("Warning: unimplemented IRIX sproc flag PR_SUMASK\n");
if (inh & IRIX_PR_SDIR)
printf("Warning: unimplemented IRIX sproc flag PR_SDIR\n");
/*
* Setting up child stack
*/
if (inh & IRIX_PR_SADDR) {
if (sp == NULL) {
/*
* All share group members have vm_maxsaddr set
* to the bottom of the lowest stack in address space,
* therefore we map the new stack there.
*/
sp = p->p_vmspace->vm_maxsaddr;
/* Compute new stacks's bottom address */
sp = (caddr_t)trunc_page((u_long)sp - len);
}
/* Now map the new stack */
bzero(&vmc, sizeof(vmc));
vmc.ev_addr = trunc_page((u_long)sp);
vmc.ev_len = round_page(len);
vmc.ev_prot = UVM_PROT_RWX;
vmc.ev_flags = UVM_FLAG_COPYONW|UVM_FLAG_FIXED|UVM_FLAG_OVERLAY;
vmc.ev_proc = vmcmd_map_zero;
#ifdef DEBUG_IRIX
printf("irix_sproc(): new stack addr=0x%08lx, len=0x%08lx\n",
(u_long)sp, (u_long)len);
#endif
/* Eventually do map for a whole share group */
if ((error = irix_sync_saddr_vmcmd(p, &vmc)) != 0)
return error;
/* Update stack parameters for the share group members */
ied = (struct irix_emuldata *)p->p_emuldata;
stacksize = (p->p_vmspace->vm_minsaddr - sp) / PAGE_SIZE;
LIST_FOREACH(pp, &allproc, p_list) {
if (irix_check_exec(pp) == 0)
continue;
iedp = (struct irix_emuldata *)pp->p_emuldata;
if (iedp->ied_shareparent != ied->ied_shareparent)
continue;
pp->p_vmspace->vm_maxsaddr = (caddr_t)sp;
pp->p_vmspace->vm_ssize = stacksize;
}
}
/*
* Arguments for irix_sproc_child()
*/
isc.isc_proc = &p2;
isc.isc_entry = entry;
isc.isc_arg = arg;
isc.isc_len = len;
isc.isc_inh = inh;
isc.isc_parent = p;
/*
* If revelant, initialize as the parent of the share group
*/
ied = (struct irix_emuldata *)(p->p_emuldata);
if (ied->ied_shareparent == NULL)
ied->ied_shareparent = p;
if (inh & IRIX_PR_SADDR)
ied->ied_shareaddr = 1;
if ((error = fork1(p, bsd_flags, SIGCHLD, (void *)sp, len,
(void *)irix_sproc_child, (void *)&isc, retval, &p2)) != 0)
return error;
/*
* Some local variables are referenced in irix_sproc_child()
* through isc. We need to ensure the child does not use them
* anymore before leaving.
*/
(void)ltsleep((void *)&isc, 0, "sproc", 0, NULL);
retval[0] = (register_t)p2->p_pid;
retval[1] = 0;
return 0;
}
static void
irix_sproc_child(isc)
struct irix_sproc_child_args *isc;
{
struct proc *p2 = *isc->isc_proc;
int inh = isc->isc_inh;
struct proc *parent = isc->isc_parent;
struct frame *tf = (struct frame *)p2->p_md.md_regs;
struct frame *ptf = (struct frame *)parent->p_md.md_regs;
struct pcred *pc;
struct plimit *pl;
struct irix_emuldata *ied;
struct irix_emuldata *parent_ied;
/*
* Handle shared VM space. The process private arena is not shared
*/
if (inh & IRIX_PR_SADDR) {
int error;
vaddr_t dstaddrp;
vaddr_t vm_min;
vsize_t vm_len;
vm_min = vm_map_min(&parent->p_vmspace->vm_map);
vm_len = vm_map_max(&parent->p_vmspace->vm_map) - vm_min;
/* Drop the current VM space */
uvm_unmap(&p2->p_vmspace->vm_map, vm_min, vm_min + vm_len);
/* Clone the mapping from the parent */
error = uvm_map_extract(&parent->p_vmspace->vm_map,
vm_min, vm_len, &p2->p_vmspace->vm_map, &dstaddrp, 0);
if (error != 0) {
printf("sproc: uvm_map_extract failed ");
printf("error = %d, pid = %d\n", error, p2->p_pid);
sigexit(p2, SIGSEGV);
}
/* Unmap the process private arena (shared) */
uvm_unmap(&p2->p_vmspace->vm_map, (vaddr_t)IRIX_PRDA,
(vaddr_t)((u_long)IRIX_PRDA + sizeof(struct irix_prda)));
/* Remap the process private arena (unshared) */
error = irix_prda_init(p2);
if (error != 0) {
printf("sproc: uvm_map arena failed ");
printf("error = %d, pid = %d\n", error, p2->p_pid);
sigexit(p2, SIGSEGV);
}
}
/*
* Handle shared process UID/GID
*/
if (inh & IRIX_PR_SID) {
pc = p2->p_cred;
parent->p_cred->p_refcnt++;
p2->p_cred = parent->p_cred;
if (--pc->p_refcnt == 0) {
crfree(pc->pc_ucred);
pool_put(&pcred_pool, pc);
}
}
/*
* Handle shared process limits
*/
if (inh & IRIX_PR_SULIMIT) {
pl = p2->p_limit;
parent->p_limit->p_refcnt++;
p2->p_limit = parent->p_limit;
if(--pl->p_refcnt == 0)
limfree(pl);
}
/*
* Setup PC to return to the child entry point
*/
tf->f_regs[PC] = (unsigned long)isc->isc_entry;
tf->f_regs[RA] = 0;
/*
* Setup child arguments
*/
tf->f_regs[A0] = (unsigned long)isc->isc_arg;
tf->f_regs[A1] = 0;
tf->f_regs[A2] = 0;
tf->f_regs[A3] = 0;
if (ptf->f_regs[S3] == (unsigned long)isc->isc_len) {
tf->f_regs[S0] = ptf->f_regs[S0];
tf->f_regs[S1] = ptf->f_regs[S1];
tf->f_regs[S2] = ptf->f_regs[S2];
tf->f_regs[S3] = ptf->f_regs[S3];
}
/*
* Join the share group
*/
ied = (struct irix_emuldata *)(p2->p_emuldata);
parent_ied = (struct irix_emuldata *)(parent->p_emuldata);
ied->ied_shareparent = parent_ied->ied_shareparent;
if (inh & IRIX_PR_SADDR)
ied->ied_shareaddr = 1;
/*
* We do not need isc anymore, we can wakeup our parent
*/
wakeup((void *)isc);
/*
* Return to userland for a newly created process
*/
child_return((void *)p2);
return;
}
int
irix_sys_procblk(p, v, retval)
struct proc *p;
void *v;
register_t *retval;
{
struct irix_sys_procblk_args /* {
syscallarg(int) cmd;
syscallarg(pid_t) pid;
syscallarg(int) count;
} */ *uap = v;
int cmd = SCARG(uap, cmd);
struct irix_emuldata *ied;
struct proc *target;
struct pcred *pc;
int oldcount;
int error, last_error;
struct proc *pp;
struct irix_emuldata *pp_ied;
struct irix_sys_procblk_args cup;
/* Find the process */
if ((target = pfind(SCARG(uap, pid))) == NULL)
return ESRCH;
/* May we stop it? */
pc = p->p_cred;
if (!(pc->pc_ucred->cr_uid == 0 || \
pc->p_ruid == target->p_cred->p_ruid || \
pc->pc_ucred->cr_uid == target->p_cred->p_ruid || \
pc->p_ruid == target->p_ucred->cr_uid || \
pc->pc_ucred->cr_uid == target->p_ucred->cr_uid))
return EPERM;
/* Is it an IRIX process? */
if (irix_check_exec(target) == 0)
return EPERM;
ied = (struct irix_emuldata *)(target->p_emuldata);
oldcount = ied->ied_procblk_count;
switch (cmd) {
case IRIX_PROCBLK_BLOCK:
ied->ied_procblk_count--;
break;
case IRIX_PROCBLK_UNBLOCK:
ied->ied_procblk_count++;
break;
case IRIX_PROCBLK_COUNT:
if (SCARG(uap, count) > IRIX_PR_MAXBLOCKCNT ||
SCARG(uap, count) < IRIX_PR_MINBLOCKCNT)
return EINVAL;
ied->ied_procblk_count = SCARG(uap, count);
break;
case IRIX_PROCBLK_BLOCKALL:
case IRIX_PROCBLK_UNBLOCKALL:
case IRIX_PROCBLK_COUNTALL:
SCARG(&cup, cmd) = cmd -IRIX_PROCBLK_ONLYONE;
SCARG(&cup, count) = SCARG(uap, count);
last_error = 0;
LIST_FOREACH(pp, &allproc, p_list) {
/* Select IRIX processes */
if (irix_check_exec(target) == 0)
continue;
/* Is this process in the target share group? */
pp_ied = (struct irix_emuldata *)pp->p_emuldata;
if (pp_ied->ied_shareparent != ied->ied_shareparent)
continue;
/* Recall procblk for this process */
SCARG(&cup, pid) = pp->p_pid;
if ((error = irix_sys_procblk(p, &cup, retval)) != 0)
last_error = error;
}
return last_error;
break;
default:
printf("Warning: unimplemented IRIX procblk command %d\n", cmd);
return EINVAL;
break;
}
/*
* We emulate the process block/unblock using SIGSTOP and SIGCONT
* signals. This is not very accurate, since on IRIX theses way
* of blocking a process are completely separated.
*/
if (oldcount >= 0 && ied->ied_procblk_count < 0) /* blocked */
psignal(target, SIGSTOP);
if (oldcount < 0 && ied->ied_procblk_count >= 0) /* unblocked */
psignal(target, SIGCONT);
return 0;
}
int
irix_prda_init(p)
struct proc *p;
{
int error;
struct exec_vmcmd evc;
bzero(&evc, sizeof(evc));
evc.ev_addr = (u_long)IRIX_PRDA;
evc.ev_len = sizeof(struct irix_prda);
evc.ev_prot = UVM_PROT_RW;
evc.ev_proc = *vmcmd_map_zero;
error = (*evc.ev_proc)(p, &evc);
return error;
}
int
irix_sync_saddr_syscall(p, v, retval, syscall)
struct proc *p;
void *v;
register_t *retval;
int (*syscall) __P((struct proc *, void *, register_t *));
{
struct irix_emuldata *ied;
struct irix_emuldata *iedp;
struct proc *pp;
int error;
/*
* First run the system call on the original process
*/
if ((error = (*syscall)(p, v, retval)) != 0)
return error;
/*
* Is memory shared with other members of the share group ?
*/
ied = (struct irix_emuldata *)p->p_emuldata;
if (ied->ied_shareaddr == 0 || ied->ied_shareparent == NULL)
return 0;
/*
* Do the syscall for all ather process in the share group
*/
LIST_FOREACH(pp, &allproc, p_list) {
if (pp != p && irix_check_exec(pp)) {
iedp = (struct irix_emuldata *)pp->p_emuldata;
if (iedp->ied_shareparent == ied->ied_shareparent &&
iedp->ied_shareaddr == 1)
if ((error = (*syscall)(pp, v, retval)) != 0)
break;
}
}
/* Full success */
if (pp == NULL)
return 0;
/*
* In case of failure, destroy the whole share group
*/
LIST_FOREACH(pp, &allproc, p_list) {
if (irix_check_exec(pp)) {
iedp = (struct irix_emuldata *)pp->p_emuldata;
if (iedp->ied_shareparent == ied->ied_shareparent)
sigexit(pp, SIGSEGV);
}
}
#ifdef DEBUG_IRIX
printf("irix_sync_saddr_syscall: killed IRIX share group (pid %d)\n",
p->p_pid);
#endif
return error;
}
int
irix_sync_saddr_vmcmd(p, evc)
struct proc *p;
struct exec_vmcmd *evc;
{
struct irix_emuldata *ied;
struct irix_emuldata *iedp;
struct proc *pp;
int error;
void *addr;
int len;
/*
* First, do the command on the original process
*/
if ((error = (*evc->ev_proc)(p, evc)) != 0)
return error;
/*
* Check that this vmcmd does not operate on the private arena
*/
addr = (void *)evc->ev_addr;
len = evc->ev_len;
if ((u_long)addr >= (u_long)IRIX_PRDA &&
(u_long)addr + len < (u_long)IRIX_PRDA + sizeof(struct irix_prda))
printf("Warning: shared vmcmd on process private arena\n");
/*
* If the process shares memory within a share group, apply
* the vmcmd to all other members of the share group
*/
ied = (struct irix_emuldata *)p->p_emuldata;
if (ied->ied_shareaddr == 0 || ied->ied_shareparent == NULL)
return 0;
LIST_FOREACH(pp, &allproc, p_list) {
if (pp != p && irix_check_exec(pp)) {
iedp = (struct irix_emuldata *)pp->p_emuldata;
if (iedp->ied_shareparent == ied->ied_shareparent &&
iedp->ied_shareaddr == 1)
if ((error = (*evc->ev_proc)(pp, evc)) != 0)
break;
}
}
/* Full success */
if (pp == NULL)
return 0;
/*
* In case of failure, destroy the whole share group
*/
LIST_FOREACH(pp, &allproc, p_list) {
if (irix_check_exec(pp)) {
iedp = (struct irix_emuldata *)pp->p_emuldata;
if (iedp->ied_shareparent == ied->ied_shareparent)
sigexit(pp, SIGSEGV);
}
}
#ifdef DEBUG_IRIX
printf("irix_sync_saddr_vmcmd: killed IRIX share group (pid %d)\n",
p->p_pid);
#endif
return EFAULT;
}