freebsd-dev/sys/compat/linux/linux_misc.c
Marcel Moolenaar 03567510a8 Add bounds checking to stackgap_alloc. Previously it was possible
to construct a path that was long enough (ie longer than
SPARE_USRSPACE bytes) and trash the stack.

Note that SPARE_USRSPACE is much smaller than MAXPATHLEN so that
the Linuxulator will now return ENAMETOOLONG even if the path
is smaller than MAXPATHLEN.

PR: 12749
2000-07-23 16:54:18 +00:00

1471 lines
34 KiB
C

/*-
* Copyright (c) 1994-1995 Søren Schmidt
* All rights reserved.
*
* 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
* in this position and unchanged.
* 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. The name of the author may not be used to endorse or promote products
* derived from this software withough specific prior written permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
*
* $FreeBSD$
*/
#include "opt_compat.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/sysproto.h>
#include <sys/kernel.h>
#include <sys/mman.h>
#include <sys/proc.h>
#include <sys/fcntl.h>
#include <sys/imgact_aout.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/resourcevar.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/unistd.h>
#include <sys/vnode.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/signalvar.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <vm/vm_kern.h>
#include <vm/vm_map.h>
#include <vm/vm_extern.h>
#include <machine/frame.h>
#include <machine/psl.h>
#include <machine/sysarch.h>
#include <machine/segments.h>
#include <i386/linux/linux.h>
#include <i386/linux/linux_proto.h>
#include <i386/linux/linux_util.h>
#include <i386/linux/linux_mib.h>
#include <posix4/sched.h>
#define BSD_TO_LINUX_SIGNAL(sig) \
(((sig) <= LINUX_SIGTBLSZ) ? bsd_to_linux_signal[_SIG_IDX(sig)] : sig)
static unsigned int linux_to_bsd_resource[LINUX_RLIM_NLIMITS] =
{ RLIMIT_CPU, RLIMIT_FSIZE, RLIMIT_DATA, RLIMIT_STACK,
RLIMIT_CORE, RLIMIT_RSS, RLIMIT_NPROC, RLIMIT_NOFILE,
RLIMIT_MEMLOCK, -1
};
int
linux_alarm(struct proc *p, struct linux_alarm_args *args)
{
struct itimerval it, old_it;
struct timeval tv;
int s;
#ifdef DEBUG
printf("Linux-emul(%ld): alarm(%u)\n", (long)p->p_pid, args->secs);
#endif
if (args->secs > 100000000)
return EINVAL;
it.it_value.tv_sec = (long)args->secs;
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = 0;
s = splsoftclock();
old_it = p->p_realtimer;
getmicrouptime(&tv);
if (timevalisset(&old_it.it_value))
untimeout(realitexpire, (caddr_t)p, p->p_ithandle);
if (it.it_value.tv_sec != 0) {
p->p_ithandle = timeout(realitexpire, (caddr_t)p, tvtohz(&it.it_value));
timevaladd(&it.it_value, &tv);
}
p->p_realtimer = it;
splx(s);
if (timevalcmp(&old_it.it_value, &tv, >)) {
timevalsub(&old_it.it_value, &tv);
if (old_it.it_value.tv_usec != 0)
old_it.it_value.tv_sec++;
p->p_retval[0] = old_it.it_value.tv_sec;
}
return 0;
}
int
linux_brk(struct proc *p, struct linux_brk_args *args)
{
#if 0
struct vmspace *vm = p->p_vmspace;
vm_offset_t new, old;
int error;
if ((vm_offset_t)args->dsend < (vm_offset_t)vm->vm_daddr)
return EINVAL;
if (((caddr_t)args->dsend - (caddr_t)vm->vm_daddr)
> p->p_rlimit[RLIMIT_DATA].rlim_cur)
return ENOMEM;
old = round_page((vm_offset_t)vm->vm_daddr) + ctob(vm->vm_dsize);
new = round_page((vm_offset_t)args->dsend);
p->p_retval[0] = old;
if ((new-old) > 0) {
if (swap_pager_full)
return ENOMEM;
error = vm_map_find(&vm->vm_map, NULL, 0, &old, (new-old), FALSE,
VM_PROT_ALL, VM_PROT_ALL, 0);
if (error)
return error;
vm->vm_dsize += btoc((new-old));
p->p_retval[0] = (int)(vm->vm_daddr + ctob(vm->vm_dsize));
}
return 0;
#else
struct vmspace *vm = p->p_vmspace;
vm_offset_t new, old;
struct obreak_args /* {
char * nsize;
} */ tmp;
#ifdef DEBUG
printf("Linux-emul(%ld): brk(%p)\n", (long)p->p_pid, (void *)args->dsend);
#endif
old = (vm_offset_t)vm->vm_daddr + ctob(vm->vm_dsize);
new = (vm_offset_t)args->dsend;
tmp.nsize = (char *) new;
if (((caddr_t)new > vm->vm_daddr) && !obreak(p, &tmp))
p->p_retval[0] = (int)new;
else
p->p_retval[0] = (int)old;
return 0;
#endif
}
int
linux_uselib(struct proc *p, struct linux_uselib_args *args)
{
struct nameidata ni;
struct vnode *vp;
struct exec *a_out;
struct vattr attr;
vm_offset_t vmaddr;
unsigned long file_offset;
vm_offset_t buffer;
unsigned long bss_size;
int error;
caddr_t sg;
int locked;
sg = stackgap_init();
CHECKALTEXIST(p, &sg, args->library);
#ifdef DEBUG
printf("Linux-emul(%ld): uselib(%s)\n", (long)p->p_pid, args->library);
#endif
a_out = NULL;
locked = 0;
vp = NULL;
NDINIT(&ni, LOOKUP, FOLLOW | LOCKLEAF, UIO_USERSPACE, args->library, p);
error = namei(&ni);
if (error)
goto cleanup;
vp = ni.ni_vp;
/*
* XXX This looks like a bogus check - a LOCKLEAF namei should not succeed
* without returning a vnode.
*/
if (vp == NULL) {
error = ENOEXEC; /* ?? */
goto cleanup;
}
NDFREE(&ni, NDF_ONLY_PNBUF);
/*
* From here on down, we have a locked vnode that must be unlocked.
*/
locked++;
/*
* Writable?
*/
if (vp->v_writecount) {
error = ETXTBSY;
goto cleanup;
}
/*
* Executable?
*/
error = VOP_GETATTR(vp, &attr, p->p_ucred, p);
if (error)
goto cleanup;
if ((vp->v_mount->mnt_flag & MNT_NOEXEC) ||
((attr.va_mode & 0111) == 0) ||
(attr.va_type != VREG)) {
error = ENOEXEC;
goto cleanup;
}
/*
* Sensible size?
*/
if (attr.va_size == 0) {
error = ENOEXEC;
goto cleanup;
}
/*
* Can we access it?
*/
error = VOP_ACCESS(vp, VEXEC, p->p_ucred, p);
if (error)
goto cleanup;
error = VOP_OPEN(vp, FREAD, p->p_ucred, p);
if (error)
goto cleanup;
/*
* Lock no longer needed
*/
VOP_UNLOCK(vp, 0, p);
locked = 0;
/*
* Pull in executable header into kernel_map
*/
error = vm_mmap(kernel_map, (vm_offset_t *)&a_out, PAGE_SIZE,
VM_PROT_READ, VM_PROT_READ, 0, (caddr_t)vp, 0);
if (error)
goto cleanup;
/*
* Is it a Linux binary ?
*/
if (((a_out->a_magic >> 16) & 0xff) != 0x64) {
error = ENOEXEC;
goto cleanup;
}
/* While we are here, we should REALLY do some more checks */
/*
* Set file/virtual offset based on a.out variant.
*/
switch ((int)(a_out->a_magic & 0xffff)) {
case 0413: /* ZMAGIC */
file_offset = 1024;
break;
case 0314: /* QMAGIC */
file_offset = 0;
break;
default:
error = ENOEXEC;
goto cleanup;
}
bss_size = round_page(a_out->a_bss);
/*
* Check various fields in header for validity/bounds.
*/
if (a_out->a_text & PAGE_MASK || a_out->a_data & PAGE_MASK) {
error = ENOEXEC;
goto cleanup;
}
/* text + data can't exceed file size */
if (a_out->a_data + a_out->a_text > attr.va_size) {
error = EFAULT;
goto cleanup;
}
/*
* text/data/bss must not exceed limits
* XXX: this is not complete. it should check current usage PLUS
* the resources needed by this library.
*/
if (a_out->a_text > MAXTSIZ ||
a_out->a_data + bss_size > p->p_rlimit[RLIMIT_DATA].rlim_cur) {
error = ENOMEM;
goto cleanup;
}
/*
* prevent more writers
*/
vp->v_flag |= VTEXT;
/*
* Check if file_offset page aligned,.
* Currently we cannot handle misalinged file offsets,
* and so we read in the entire image (what a waste).
*/
if (file_offset & PAGE_MASK) {
#ifdef DEBUG
printf("uselib: Non page aligned binary %lu\n", file_offset);
#endif
/*
* Map text+data read/write/execute
*/
/* a_entry is the load address and is page aligned */
vmaddr = trunc_page(a_out->a_entry);
/* get anon user mapping, read+write+execute */
error = vm_map_find(&p->p_vmspace->vm_map, NULL, 0, &vmaddr,
a_out->a_text + a_out->a_data, FALSE,
VM_PROT_ALL, VM_PROT_ALL, 0);
if (error)
goto cleanup;
/* map file into kernel_map */
error = vm_mmap(kernel_map, &buffer,
round_page(a_out->a_text + a_out->a_data + file_offset),
VM_PROT_READ, VM_PROT_READ, 0,
(caddr_t)vp, trunc_page(file_offset));
if (error)
goto cleanup;
/* copy from kernel VM space to user space */
error = copyout((caddr_t)(void *)(uintptr_t)(buffer + file_offset),
(caddr_t)vmaddr, a_out->a_text + a_out->a_data);
/* release temporary kernel space */
vm_map_remove(kernel_map, buffer,
buffer + round_page(a_out->a_text + a_out->a_data + file_offset));
if (error)
goto cleanup;
}
else {
#ifdef DEBUG
printf("uselib: Page aligned binary %lu\n", file_offset);
#endif
/*
* for QMAGIC, a_entry is 20 bytes beyond the load address
* to skip the executable header
*/
vmaddr = trunc_page(a_out->a_entry);
/*
* Map it all into the process's space as a single copy-on-write
* "data" segment.
*/
error = vm_mmap(&p->p_vmspace->vm_map, &vmaddr,
a_out->a_text + a_out->a_data,
VM_PROT_ALL, VM_PROT_ALL, MAP_PRIVATE | MAP_FIXED,
(caddr_t)vp, file_offset);
if (error)
goto cleanup;
}
#ifdef DEBUG
printf("mem=%08x = %08x %08x\n", vmaddr, ((int*)vmaddr)[0], ((int*)vmaddr)[1]);
#endif
if (bss_size != 0) {
/*
* Calculate BSS start address
*/
vmaddr = trunc_page(a_out->a_entry) + a_out->a_text + a_out->a_data;
/*
* allocate some 'anon' space
*/
error = vm_map_find(&p->p_vmspace->vm_map, NULL, 0, &vmaddr,
bss_size, FALSE,
VM_PROT_ALL, VM_PROT_ALL, 0);
if (error)
goto cleanup;
}
cleanup:
/*
* Unlock vnode if needed
*/
if (locked)
VOP_UNLOCK(vp, 0, p);
/*
* Release the kernel mapping.
*/
if (a_out)
vm_map_remove(kernel_map, (vm_offset_t)a_out, (vm_offset_t)a_out + PAGE_SIZE);
return error;
}
/* XXX move */
struct linux_select_argv {
int nfds;
fd_set *readfds;
fd_set *writefds;
fd_set *exceptfds;
struct timeval *timeout;
};
int
linux_select(struct proc *p, struct linux_select_args *args)
{
struct linux_select_argv linux_args;
struct linux_newselect_args newsel;
int error;
#ifdef SELECT_DEBUG
printf("Linux-emul(%ld): select(%x)\n", (long)p->p_pid, args->ptr);
#endif
if ((error = copyin((caddr_t)args->ptr, (caddr_t)&linux_args,
sizeof(linux_args))))
return error;
newsel.nfds = linux_args.nfds;
newsel.readfds = linux_args.readfds;
newsel.writefds = linux_args.writefds;
newsel.exceptfds = linux_args.exceptfds;
newsel.timeout = linux_args.timeout;
return linux_newselect(p, &newsel);
}
int
linux_newselect(struct proc *p, struct linux_newselect_args *args)
{
struct select_args bsa;
struct timeval tv0, tv1, utv, *tvp;
caddr_t sg;
int error;
#ifdef DEBUG
printf("Linux-emul(%ld): newselect(%d, %p, %p, %p, %p)\n",
(long)p->p_pid, args->nfds, (void *)args->readfds,
(void *)args->writefds, (void *)args->exceptfds,
(void *)args->timeout);
#endif
error = 0;
bsa.nd = args->nfds;
bsa.in = args->readfds;
bsa.ou = args->writefds;
bsa.ex = args->exceptfds;
bsa.tv = args->timeout;
/*
* Store current time for computation of the amount of
* time left.
*/
if (args->timeout) {
if ((error = copyin(args->timeout, &utv, sizeof(utv))))
goto select_out;
#ifdef DEBUG
printf("Linux-emul(%ld): incoming timeout (%ld/%ld)\n",
(long)p->p_pid, utv.tv_sec, utv.tv_usec);
#endif
if (itimerfix(&utv)) {
/*
* The timeval was invalid. Convert it to something
* valid that will act as it does under Linux.
*/
sg = stackgap_init();
tvp = stackgap_alloc(&sg, sizeof(utv));
utv.tv_sec += utv.tv_usec / 1000000;
utv.tv_usec %= 1000000;
if (utv.tv_usec < 0) {
utv.tv_sec -= 1;
utv.tv_usec += 1000000;
}
if (utv.tv_sec < 0)
timevalclear(&utv);
if ((error = copyout(&utv, tvp, sizeof(utv))))
goto select_out;
bsa.tv = tvp;
}
microtime(&tv0);
}
error = select(p, &bsa);
#ifdef DEBUG
printf("Linux-emul(%ld): real select returns %d\n", (long)p->p_pid, error);
#endif
if (error) {
/*
* See fs/select.c in the Linux kernel. Without this,
* Maelstrom doesn't work.
*/
if (error == ERESTART)
error = EINTR;
goto select_out;
}
if (args->timeout) {
if (p->p_retval[0]) {
/*
* Compute how much time was left of the timeout,
* by subtracting the current time and the time
* before we started the call, and subtracting
* that result from the user-supplied value.
*/
microtime(&tv1);
timevalsub(&tv1, &tv0);
timevalsub(&utv, &tv1);
if (utv.tv_sec < 0)
timevalclear(&utv);
} else
timevalclear(&utv);
#ifdef DEBUG
printf("Linux-emul(%ld): outgoing timeout (%ld/%ld)\n",
(long)p->p_pid, utv.tv_sec, utv.tv_usec);
#endif
if ((error = copyout(&utv, args->timeout, sizeof(utv))))
goto select_out;
}
select_out:
#ifdef DEBUG
printf("Linux-emul(%ld): newselect_out -> %d\n", (long)p->p_pid, error);
#endif
return error;
}
int
linux_getpgid(struct proc *p, struct linux_getpgid_args *args)
{
struct proc *curp;
#ifdef DEBUG
printf("Linux-emul(%ld): getpgid(%d)\n", (long)p->p_pid, args->pid);
#endif
if (args->pid != p->p_pid) {
if (!(curp = pfind(args->pid)))
return ESRCH;
}
else
curp = p;
p->p_retval[0] = curp->p_pgid;
return 0;
}
int
linux_fork(struct proc *p, struct linux_fork_args *args)
{
int error;
#ifdef DEBUG
printf("Linux-emul(%ld): fork()\n", (long)p->p_pid);
#endif
if ((error = fork(p, (struct fork_args *)args)) != 0)
return error;
if (p->p_retval[1] == 1)
p->p_retval[0] = 0;
return 0;
}
int
linux_vfork(struct proc *p, struct linux_vfork_args *args)
{
int error;
#ifdef DEBUG
printf("Linux-emul(%ld): vfork()\n", (long)p->p_pid);
#endif
if ((error = vfork(p, (struct vfork_args *)args)) != 0)
return error;
/* Are we the child? */
if (p->p_retval[1] == 1)
p->p_retval[0] = 0;
return 0;
}
#define CLONE_VM 0x100
#define CLONE_FS 0x200
#define CLONE_FILES 0x400
#define CLONE_SIGHAND 0x800
#define CLONE_PID 0x1000
int
linux_clone(struct proc *p, struct linux_clone_args *args)
{
int error, ff = RFPROC;
struct proc *p2;
int exit_signal;
vm_offset_t start;
struct rfork_args rf_args;
#ifdef DEBUG
if (args->flags & CLONE_PID)
printf("linux_clone(%ld): CLONE_PID not yet supported\n",
(long)p->p_pid);
printf("linux_clone(%ld): invoked with flags %x and stack %x\n",
(long)p->p_pid, (unsigned int)args->flags,
(unsigned int)args->stack);
#endif
if (!args->stack)
return (EINVAL);
exit_signal = args->flags & 0x000000ff;
if (exit_signal >= LINUX_NSIG)
return EINVAL;
if (exit_signal <= LINUX_SIGTBLSZ)
exit_signal = linux_to_bsd_signal[_SIG_IDX(exit_signal)];
/* RFTHREAD probably not necessary here, but it shouldn't hurt either */
ff |= RFTHREAD;
if (args->flags & CLONE_VM)
ff |= RFMEM;
if (args->flags & CLONE_SIGHAND)
ff |= RFSIGSHARE;
if (!(args->flags & CLONE_FILES))
ff |= RFFDG;
error = 0;
start = 0;
rf_args.flags = ff;
if ((error = rfork(p, &rf_args)) != 0)
return error;
p2 = pfind(p->p_retval[0]);
if (p2 == 0)
return ESRCH;
p2->p_sigparent = exit_signal;
p2->p_md.md_regs->tf_esp = (unsigned int)args->stack;
#ifdef DEBUG
printf ("linux_clone(%ld): successful rfork to %ld\n",
(long)p->p_pid, (long)p2->p_pid);
#endif
return 0;
}
/* XXX move */
struct linux_mmap_argv {
linux_caddr_t addr;
int len;
int prot;
int flags;
int fd;
int pos;
};
#define STACK_SIZE (2 * 1024 * 1024)
#define GUARD_SIZE (4 * PAGE_SIZE)
int
linux_mmap(struct proc *p, struct linux_mmap_args *args)
{
struct mmap_args /* {
caddr_t addr;
size_t len;
int prot;
int flags;
int fd;
long pad;
off_t pos;
} */ bsd_args;
int error;
struct linux_mmap_argv linux_args;
if ((error = copyin((caddr_t)args->ptr, (caddr_t)&linux_args,
sizeof(linux_args))))
return error;
#ifdef DEBUG
printf("Linux-emul(%ld): mmap(%p, %d, %d, 0x%08x, %d, %d)",
(long)p->p_pid, (void *)linux_args.addr, linux_args.len,
linux_args.prot, linux_args.flags, linux_args.fd, linux_args.pos);
#endif
bsd_args.flags = 0;
if (linux_args.flags & LINUX_MAP_SHARED)
bsd_args.flags |= MAP_SHARED;
if (linux_args.flags & LINUX_MAP_PRIVATE)
bsd_args.flags |= MAP_PRIVATE;
if (linux_args.flags & LINUX_MAP_FIXED)
bsd_args.flags |= MAP_FIXED;
if (linux_args.flags & LINUX_MAP_ANON)
bsd_args.flags |= MAP_ANON;
if (linux_args.flags & LINUX_MAP_GROWSDOWN) {
bsd_args.flags |= MAP_STACK;
/* The linux MAP_GROWSDOWN option does not limit auto
* growth of the region. Linux mmap with this option
* takes as addr the inital BOS, and as len, the initial
* region size. It can then grow down from addr without
* limit. However, linux threads has an implicit internal
* limit to stack size of STACK_SIZE. Its just not
* enforced explicitly in linux. But, here we impose
* a limit of (STACK_SIZE - GUARD_SIZE) on the stack
* region, since we can do this with our mmap.
*
* Our mmap with MAP_STACK takes addr as the maximum
* downsize limit on BOS, and as len the max size of
* the region. It them maps the top SGROWSIZ bytes,
* and autgrows the region down, up to the limit
* in addr.
*
* If we don't use the MAP_STACK option, the effect
* of this code is to allocate a stack region of a
* fixed size of (STACK_SIZE - GUARD_SIZE).
*/
/* This gives us TOS */
bsd_args.addr = linux_args.addr + linux_args.len;
/* This gives us our maximum stack size */
if (linux_args.len > STACK_SIZE - GUARD_SIZE)
bsd_args.len = linux_args.len;
else
bsd_args.len = STACK_SIZE - GUARD_SIZE;
/* This gives us a new BOS. If we're using VM_STACK, then
* mmap will just map the top SGROWSIZ bytes, and let
* the stack grow down to the limit at BOS. If we're
* not using VM_STACK we map the full stack, since we
* don't have a way to autogrow it.
*/
bsd_args.addr -= bsd_args.len;
} else {
bsd_args.addr = linux_args.addr;
bsd_args.len = linux_args.len;
}
bsd_args.prot = linux_args.prot | PROT_READ; /* always required */
if (linux_args.flags & LINUX_MAP_ANON)
bsd_args.fd = -1;
else
bsd_args.fd = linux_args.fd;
bsd_args.pos = linux_args.pos;
bsd_args.pad = 0;
#ifdef DEBUG
printf("-> (%p, %d, %d, 0x%08x, %d, %d)\n",
(void *)bsd_args.addr, bsd_args.len,
bsd_args.prot, bsd_args.flags, bsd_args.fd, (int)bsd_args.pos);
#endif
return mmap(p, &bsd_args);
}
int
linux_mremap(struct proc *p, struct linux_mremap_args *args)
{
struct munmap_args /* {
void *addr;
size_t len;
} */ bsd_args;
int error = 0;
#ifdef DEBUG
printf("Linux-emul(%ld): mremap(%p, %08x, %08x, %08x)\n",
(long)p->p_pid, (void *)args->addr, args->old_len, args->new_len,
args->flags);
#endif
args->new_len = round_page(args->new_len);
args->old_len = round_page(args->old_len);
if (args->new_len > args->old_len) {
p->p_retval[0] = 0;
return ENOMEM;
}
if (args->new_len < args->old_len) {
bsd_args.addr = args->addr + args->new_len;
bsd_args.len = args->old_len - args->new_len;
error = munmap(p, &bsd_args);
}
p->p_retval[0] = error ? 0 : (int)args->addr;
return error;
}
int
linux_msync(struct proc *p, struct linux_msync_args *args)
{
struct msync_args bsd_args;
bsd_args.addr = args->addr;
bsd_args.len = args->len;
bsd_args.flags = 0; /* XXX ignore */
return msync(p, &bsd_args);
}
int
linux_pipe(struct proc *p, struct linux_pipe_args *args)
{
int error;
int reg_edx;
#ifdef DEBUG
printf("Linux-emul(%ld): pipe(*)\n", (long)p->p_pid);
#endif
reg_edx = p->p_retval[1];
error = pipe(p, 0);
if (error) {
p->p_retval[1] = reg_edx;
return error;
}
error = copyout(p->p_retval, args->pipefds, 2*sizeof(int));
if (error) {
p->p_retval[1] = reg_edx;
return error;
}
p->p_retval[1] = reg_edx;
p->p_retval[0] = 0;
return 0;
}
int
linux_time(struct proc *p, struct linux_time_args *args)
{
struct timeval tv;
linux_time_t tm;
int error;
#ifdef DEBUG
printf("Linux-emul(%ld): time(*)\n", (long)p->p_pid);
#endif
microtime(&tv);
tm = tv.tv_sec;
if (args->tm && (error = copyout(&tm, args->tm, sizeof(linux_time_t))))
return error;
p->p_retval[0] = tm;
return 0;
}
struct linux_times_argv {
long tms_utime;
long tms_stime;
long tms_cutime;
long tms_cstime;
};
#define CLK_TCK 100 /* Linux uses 100 */
#define CONVTCK(r) (r.tv_sec * CLK_TCK + r.tv_usec / (1000000 / CLK_TCK))
int
linux_times(struct proc *p, struct linux_times_args *args)
{
struct timeval tv;
struct linux_times_argv tms;
struct rusage ru;
int error;
#ifdef DEBUG
printf("Linux-emul(%ld): times(*)\n", (long)p->p_pid);
#endif
calcru(p, &ru.ru_utime, &ru.ru_stime, NULL);
tms.tms_utime = CONVTCK(ru.ru_utime);
tms.tms_stime = CONVTCK(ru.ru_stime);
tms.tms_cutime = CONVTCK(p->p_stats->p_cru.ru_utime);
tms.tms_cstime = CONVTCK(p->p_stats->p_cru.ru_stime);
if ((error = copyout((caddr_t)&tms, (caddr_t)args->buf,
sizeof(struct linux_times_argv))))
return error;
microuptime(&tv);
p->p_retval[0] = (int)CONVTCK(tv);
return 0;
}
int
linux_newuname(struct proc *p, struct linux_newuname_args *args)
{
struct linux_new_utsname utsname;
char *osrelease, *osname;
#ifdef DEBUG
printf("Linux-emul(%ld): newuname(*)\n", (long)p->p_pid);
#endif
osname = linux_get_osname(p);
osrelease = linux_get_osrelease(p);
bzero(&utsname, sizeof(struct linux_new_utsname));
strncpy(utsname.sysname, osname, LINUX_MAX_UTSNAME-1);
strncpy(utsname.nodename, hostname, LINUX_MAX_UTSNAME-1);
strncpy(utsname.release, osrelease, LINUX_MAX_UTSNAME-1);
strncpy(utsname.version, version, LINUX_MAX_UTSNAME-1);
strncpy(utsname.machine, machine, LINUX_MAX_UTSNAME-1);
strncpy(utsname.domainname, domainname, LINUX_MAX_UTSNAME-1);
return (copyout((caddr_t)&utsname, (caddr_t)args->buf,
sizeof(struct linux_new_utsname)));
}
struct linux_utimbuf {
linux_time_t l_actime;
linux_time_t l_modtime;
};
int
linux_utime(struct proc *p, struct linux_utime_args *args)
{
struct utimes_args /* {
char *path;
struct timeval *tptr;
} */ bsdutimes;
struct timeval tv[2], *tvp;
struct linux_utimbuf lut;
int error;
caddr_t sg;
sg = stackgap_init();
CHECKALTEXIST(p, &sg, args->fname);
#ifdef DEBUG
printf("Linux-emul(%ld): utime(%s, *)\n", (long)p->p_pid, args->fname);
#endif
if (args->times) {
if ((error = copyin(args->times, &lut, sizeof lut)))
return error;
tv[0].tv_sec = lut.l_actime;
tv[0].tv_usec = 0;
tv[1].tv_sec = lut.l_modtime;
tv[1].tv_usec = 0;
/* so that utimes can copyin */
tvp = (struct timeval *)stackgap_alloc(&sg, sizeof(tv));
if (tvp == NULL)
return (ENAMETOOLONG);
if ((error = copyout(tv, tvp, sizeof(tv))))
return error;
bsdutimes.tptr = tvp;
} else
bsdutimes.tptr = NULL;
bsdutimes.path = args->fname;
return utimes(p, &bsdutimes);
}
#define __WCLONE 0x80000000
int
linux_waitpid(struct proc *p, struct linux_waitpid_args *args)
{
struct wait_args /* {
int pid;
int *status;
int options;
struct rusage *rusage;
} */ tmp;
int error, tmpstat;
#ifdef DEBUG
printf("Linux-emul(%ld): waitpid(%d, %p, %d)\n",
(long)p->p_pid, args->pid, (void *)args->status, args->options);
#endif
tmp.pid = args->pid;
tmp.status = args->status;
tmp.options = (args->options & (WNOHANG | WUNTRACED));
/* WLINUXCLONE should be equal to __WCLONE, but we make sure */
if (args->options & __WCLONE)
tmp.options |= WLINUXCLONE;
tmp.rusage = NULL;
if ((error = wait4(p, &tmp)) != 0)
return error;
if (args->status) {
if ((error = copyin(args->status, &tmpstat, sizeof(int))) != 0)
return error;
tmpstat &= 0xffff;
if (WIFSIGNALED(tmpstat))
tmpstat = (tmpstat & 0xffffff80) |
BSD_TO_LINUX_SIGNAL(WTERMSIG(tmpstat));
else if (WIFSTOPPED(tmpstat))
tmpstat = (tmpstat & 0xffff00ff) |
(BSD_TO_LINUX_SIGNAL(WSTOPSIG(tmpstat)) << 8);
return copyout(&tmpstat, args->status, sizeof(int));
} else
return 0;
}
int
linux_wait4(struct proc *p, struct linux_wait4_args *args)
{
struct wait_args /* {
int pid;
int *status;
int options;
struct rusage *rusage;
} */ tmp;
int error, tmpstat;
#ifdef DEBUG
printf("Linux-emul(%ld): wait4(%d, %p, %d, %p)\n",
(long)p->p_pid, args->pid, (void *)args->status, args->options,
(void *)args->rusage);
#endif
tmp.pid = args->pid;
tmp.status = args->status;
tmp.options = (args->options & (WNOHANG | WUNTRACED));
/* WLINUXCLONE should be equal to __WCLONE, but we make sure */
if (args->options & __WCLONE)
tmp.options |= WLINUXCLONE;
tmp.rusage = args->rusage;
if ((error = wait4(p, &tmp)) != 0)
return error;
SIGDELSET(p->p_siglist, SIGCHLD);
if (args->status) {
if ((error = copyin(args->status, &tmpstat, sizeof(int))) != 0)
return error;
tmpstat &= 0xffff;
if (WIFSIGNALED(tmpstat))
tmpstat = (tmpstat & 0xffffff80) |
BSD_TO_LINUX_SIGNAL(WTERMSIG(tmpstat));
else if (WIFSTOPPED(tmpstat))
tmpstat = (tmpstat & 0xffff00ff) |
(BSD_TO_LINUX_SIGNAL(WSTOPSIG(tmpstat)) << 8);
return copyout(&tmpstat, args->status, sizeof(int));
} else
return 0;
}
int
linux_mknod(struct proc *p, struct linux_mknod_args *args)
{
caddr_t sg;
struct mknod_args bsd_mknod;
struct mkfifo_args bsd_mkfifo;
sg = stackgap_init();
CHECKALTCREAT(p, &sg, args->path);
#ifdef DEBUG
printf("Linux-emul(%ld): mknod(%s, %d, %d)\n",
(long)p->p_pid, args->path, args->mode, args->dev);
#endif
if (args->mode & S_IFIFO) {
bsd_mkfifo.path = args->path;
bsd_mkfifo.mode = args->mode;
return mkfifo(p, &bsd_mkfifo);
} else {
bsd_mknod.path = args->path;
bsd_mknod.mode = args->mode;
bsd_mknod.dev = args->dev;
return mknod(p, &bsd_mknod);
}
}
/*
* UGH! This is just about the dumbest idea I've ever heard!!
*/
int
linux_personality(struct proc *p, struct linux_personality_args *args)
{
#ifdef DEBUG
printf("Linux-emul(%ld): personality(%d)\n",
(long)p->p_pid, args->per);
#endif
if (args->per != 0)
return EINVAL;
/* Yes Jim, it's still a Linux... */
p->p_retval[0] = 0;
return 0;
}
/*
* Wrappers for get/setitimer for debugging..
*/
int
linux_setitimer(struct proc *p, struct linux_setitimer_args *args)
{
struct setitimer_args bsa;
struct itimerval foo;
int error;
#ifdef DEBUG
printf("Linux-emul(%ld): setitimer(%p, %p)\n",
(long)p->p_pid, (void *)args->itv, (void *)args->oitv);
#endif
bsa.which = args->which;
bsa.itv = args->itv;
bsa.oitv = args->oitv;
if (args->itv) {
if ((error = copyin((caddr_t)args->itv, (caddr_t)&foo,
sizeof(foo))))
return error;
#ifdef DEBUG
printf("setitimer: value: sec: %ld, usec: %ld\n",
foo.it_value.tv_sec, foo.it_value.tv_usec);
printf("setitimer: interval: sec: %ld, usec: %ld\n",
foo.it_interval.tv_sec, foo.it_interval.tv_usec);
#endif
}
return setitimer(p, &bsa);
}
int
linux_getitimer(struct proc *p, struct linux_getitimer_args *args)
{
struct getitimer_args bsa;
#ifdef DEBUG
printf("Linux-emul(%ld): getitimer(%p)\n",
(long)p->p_pid, (void *)args->itv);
#endif
bsa.which = args->which;
bsa.itv = args->itv;
return getitimer(p, &bsa);
}
int
linux_ioperm(struct proc *p, struct linux_ioperm_args *args)
{
struct sysarch_args sa;
struct i386_ioperm_args *iia;
caddr_t sg;
sg = stackgap_init();
iia = stackgap_alloc(&sg, sizeof(struct i386_ioperm_args));
iia->start = args->start;
iia->length = args->length;
iia->enable = args->enable;
sa.op = I386_SET_IOPERM;
sa.parms = (char *)iia;
return sysarch(p, &sa);
}
int
linux_iopl(struct proc *p, struct linux_iopl_args *args)
{
int error;
if (args->level < 0 || args->level > 3)
return (EINVAL);
if ((error = suser(p)) != 0)
return (error);
if (securelevel > 0)
return (EPERM);
p->p_md.md_regs->tf_eflags = (p->p_md.md_regs->tf_eflags & ~PSL_IOPL) |
(args->level * (PSL_IOPL / 3));
return (0);
}
int
linux_nice(struct proc *p, struct linux_nice_args *args)
{
struct setpriority_args bsd_args;
bsd_args.which = PRIO_PROCESS;
bsd_args.who = 0; /* current process */
bsd_args.prio = args->inc;
return setpriority(p, &bsd_args);
}
int
linux_setgroups(p, uap)
struct proc *p;
struct linux_setgroups_args *uap;
{
struct pcred *pc;
linux_gid_t linux_gidset[NGROUPS];
gid_t *bsd_gidset;
int ngrp, error;
pc = p->p_cred;
ngrp = uap->gidsetsize;
/*
* cr_groups[0] holds egid. Setting the whole set from
* the supplied set will cause egid to be changed too.
* Keep cr_groups[0] unchanged to prevent that.
*/
if ((error = suser(p)) != 0)
return (error);
if (ngrp >= NGROUPS)
return (EINVAL);
pc->pc_ucred = crcopy(pc->pc_ucred);
if (ngrp > 0) {
error = copyin((caddr_t)uap->gidset, (caddr_t)linux_gidset,
ngrp * sizeof(linux_gid_t));
if (error)
return (error);
pc->pc_ucred->cr_ngroups = ngrp + 1;
bsd_gidset = pc->pc_ucred->cr_groups;
ngrp--;
while (ngrp >= 0) {
bsd_gidset[ngrp + 1] = linux_gidset[ngrp];
ngrp--;
}
}
else
pc->pc_ucred->cr_ngroups = 1;
setsugid(p);
return (0);
}
int
linux_getgroups(p, uap)
struct proc *p;
struct linux_getgroups_args *uap;
{
struct pcred *pc;
linux_gid_t linux_gidset[NGROUPS];
gid_t *bsd_gidset;
int bsd_gidsetsz, ngrp, error;
pc = p->p_cred;
bsd_gidset = pc->pc_ucred->cr_groups;
bsd_gidsetsz = pc->pc_ucred->cr_ngroups - 1;
/*
* cr_groups[0] holds egid. Returning the whole set
* here will cause a duplicate. Exclude cr_groups[0]
* to prevent that.
*/
if ((ngrp = uap->gidsetsize) == 0) {
p->p_retval[0] = bsd_gidsetsz;
return (0);
}
if (ngrp < bsd_gidsetsz)
return (EINVAL);
ngrp = 0;
while (ngrp < bsd_gidsetsz) {
linux_gidset[ngrp] = bsd_gidset[ngrp + 1];
ngrp++;
}
if ((error = copyout((caddr_t)linux_gidset, (caddr_t)uap->gidset,
ngrp * sizeof(linux_gid_t))))
return (error);
p->p_retval[0] = ngrp;
return (0);
}
int
linux_setrlimit(p, uap)
struct proc *p;
struct linux_setrlimit_args *uap;
{
struct osetrlimit_args bsd;
#ifdef DEBUG
printf("Linux-emul(%ld): setrlimit(%d, %p)\n",
(long)p->p_pid, uap->resource, (void *)uap->rlim);
#endif
if (uap->resource >= LINUX_RLIM_NLIMITS)
return EINVAL;
bsd.which = linux_to_bsd_resource[uap->resource];
if (bsd.which == -1)
return EINVAL;
bsd.rlp = uap->rlim;
return osetrlimit(p, &bsd);
}
int
linux_getrlimit(p, uap)
struct proc *p;
struct linux_getrlimit_args *uap;
{
struct ogetrlimit_args bsd;
#ifdef DEBUG
printf("Linux-emul(%ld): getrlimit(%d, %p)\n",
(long)p->p_pid, uap->resource, (void *)uap->rlim);
#endif
if (uap->resource >= LINUX_RLIM_NLIMITS)
return EINVAL;
bsd.which = linux_to_bsd_resource[uap->resource];
if (bsd.which == -1)
return EINVAL;
bsd.rlp = uap->rlim;
return ogetrlimit(p, &bsd);
}
int
linux_sched_setscheduler(p, uap)
struct proc *p;
struct linux_sched_setscheduler_args *uap;
{
struct sched_setscheduler_args bsd;
#ifdef DEBUG
printf("Linux-emul(%ld): sched_setscheduler(%d, %d, %p)\n",
(long)p->p_pid, uap->pid, uap->policy, (const void *)uap->param);
#endif
switch (uap->policy) {
case LINUX_SCHED_OTHER:
bsd.policy = SCHED_OTHER;
break;
case LINUX_SCHED_FIFO:
bsd.policy = SCHED_FIFO;
break;
case LINUX_SCHED_RR:
bsd.policy = SCHED_RR;
break;
default:
return EINVAL;
}
bsd.pid = uap->pid;
bsd.param = uap->param;
return sched_setscheduler(p, &bsd);
}
int
linux_sched_getscheduler(p, uap)
struct proc *p;
struct linux_sched_getscheduler_args *uap;
{
struct sched_getscheduler_args bsd;
int error;
#ifdef DEBUG
printf("Linux-emul(%ld): sched_getscheduler(%d)\n",
(long)p->p_pid, uap->pid);
#endif
bsd.pid = uap->pid;
error = sched_getscheduler(p, &bsd);
switch (p->p_retval[0]) {
case SCHED_OTHER:
p->p_retval[0] = LINUX_SCHED_OTHER;
break;
case SCHED_FIFO:
p->p_retval[0] = LINUX_SCHED_FIFO;
break;
case SCHED_RR:
p->p_retval[0] = LINUX_SCHED_RR;
break;
}
return error;
}
struct linux_descriptor {
unsigned int entry_number;
unsigned long base_addr;
unsigned int limit;
unsigned int seg_32bit:1;
unsigned int contents:2;
unsigned int read_exec_only:1;
unsigned int limit_in_pages:1;
unsigned int seg_not_present:1;
unsigned int useable:1;
};
int
linux_modify_ldt(p, uap)
struct proc *p;
struct linux_modify_ldt_args *uap;
{
int error;
caddr_t sg;
struct sysarch_args args;
struct i386_ldt_args *ldt;
struct linux_descriptor ld;
union descriptor *desc;
sg = stackgap_init();
if (uap->ptr == NULL)
return (EINVAL);
switch (uap->func) {
case 0x00: /* read_ldt */
ldt = stackgap_alloc(&sg, sizeof(*ldt));
ldt->start = 0;
ldt->descs = uap->ptr;
ldt->num = uap->bytecount / sizeof(union descriptor);
args.op = I386_GET_LDT;
args.parms = (char*)ldt;
error = sysarch(p, &args);
p->p_retval[0] *= sizeof(union descriptor);
break;
case 0x01: /* write_ldt */
case 0x11: /* write_ldt */
if (uap->bytecount != sizeof(ld))
return (EINVAL);
error = copyin(uap->ptr, &ld, sizeof(ld));
if (error)
return (error);
ldt = stackgap_alloc(&sg, sizeof(*ldt));
desc = stackgap_alloc(&sg, sizeof(*desc));
ldt->start = ld.entry_number;
ldt->descs = desc;
ldt->num = 1;
desc->sd.sd_lolimit = (ld.limit & 0x0000ffff);
desc->sd.sd_hilimit = (ld.limit & 0x000f0000) >> 16;
desc->sd.sd_lobase = (ld.base_addr & 0x00ffffff);
desc->sd.sd_hibase = (ld.base_addr & 0xff000000) >> 24;
desc->sd.sd_type = SDT_MEMRO | ((ld.read_exec_only ^ 1) << 1) |
(ld.contents << 2);
desc->sd.sd_dpl = 3;
desc->sd.sd_p = (ld.seg_not_present ^ 1);
desc->sd.sd_xx = 0;
desc->sd.sd_def32 = ld.seg_32bit;
desc->sd.sd_gran = ld.limit_in_pages;
args.op = I386_SET_LDT;
args.parms = (char*)ldt;
error = sysarch(p, &args);
break;
default:
error = EINVAL;
break;
}
if (error == EOPNOTSUPP) {
printf("linux: modify_ldt needs kernel option USER_LDT\n");
error = ENOSYS;
}
return (error);
}