wait.c revision 9acbbeaf2a1ffe5c14b244867d427714fab43c5c
/*
* 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"
/*
* wait() family of functions.
*
* The first minor difference between the Linux and Solaris family of wait()
* calls is that the values for WNOHANG and WUNTRACED are different. Solaris
* also has additional options (WCONTINUED, WNOWAIT) which should be flagged as
* invalid on Linux. Thankfully, the exit status values are identical between
* the two implementations.
*
* Things get very different and very complicated when we introduce the Linux
* threading model. Under linux, both threads and child processes are
* represented as processes. However, the behavior of wait() with respect to
* each child varies according to the flags given to clone()
*
* SIGCHLD The SIGCHLD signal should be sent on termination
* CLONE_THREAD The child shares the same thread group as the parent
* CLONE_DETACHED The parent receives no notification when the child exits
*
* The following flags control the Linux behavior w.r.t. the above attributes:
*
* __WALL Wait on all children, regardless of type
* __WCLONE Wait only on non-SIGCHLD children
* __WNOTHREAD Don't wait on children of other threads in this group
*
* The following chart shows whether wait() returns when the child exits:
*
* default __WCLONE __WALL
* no SIGCHLD - X X
* SIGCHLD X - X
*
* The following chart shows whether wait() returns when the grandchild exits:
*
* default __WNOTHREAD
* no CLONE_THREAD - -
* CLONE_THREAD X -
*
* The CLONE_DETACHED flag is universal - when the child exits, no state is
* stored and wait() has no effect.
*
* XXX Support the above combination of options, or some reasonable subset that
* covers at least fork() and pthread_create().
*/
#include <errno.h>
#include <sys/wait.h>
#include <sys/lx_types.h>
#include <sys/lx_signal.h>
#include <sys/lx_misc.h>
#include <sys/lx_syscall.h>
#include <sys/times.h>
#include <strings.h>
#include <unistd.h>
#include <assert.h>
/*
* Convert between Linux options and Solaris options, returning -1 if any
* invalid flags are found.
*/
#define LX_WNOHANG 0x1
#define LX_WUNTRACED 0x2
#define LX_WNOTHREAD 0x20000000
#define LX_WALL 0x40000000
#define LX_WCLONE 0x80000000
static int
ltos_options(uintptr_t options)
{
int newoptions = 0;
if (((options) & ~(LX_WNOHANG | LX_WUNTRACED | LX_WNOTHREAD |
LX_WALL | LX_WCLONE)) != 0) {
return (-1);
}
/* XXX implement LX_WNOTHREAD, LX_WALL, LX_WCLONE */
if (options & LX_WNOHANG)
newoptions |= WNOHANG;
if (options & LX_WUNTRACED)
newoptions |= WUNTRACED;
return (newoptions);
}
static int
lx_wstat(int code, int status)
{
int stat = 0;
switch (code) {
case CLD_EXITED:
stat = status << 8;
break;
case CLD_DUMPED:
stat = stol_signo[status];
assert(stat != -1);
stat |= WCOREFLG;
break;
case CLD_KILLED:
stat = stol_signo[status];
assert(stat != -1);
break;
case CLD_TRAPPED:
case CLD_STOPPED:
stat = stol_signo[status];
assert(stat != -1);
stat <<= 8;
stat |= WSTOPFLG;
break;
case CLD_CONTINUED:
stat = WCONTFLG;
break;
}
return (stat);
}
int
lx_wait4(uintptr_t p1, uintptr_t p2, uintptr_t p3, uintptr_t p4)
{
siginfo_t info = { 0 };
struct rusage ru = { 0 };
idtype_t idtype;
id_t id;
int options, status = 0;
pid_t pid = (pid_t)p1;
int rval;
if ((options = ltos_options(p3)) == -1)
return (-EINVAL);
/*
* While not listed as a valid return code, Linux's wait4(2) does,
* in fact, get an EFAULT if either the status pointer or rusage
* pointer is invalid. Since a failed waitpid should leave child
* process in a state where a future wait4(2) will succeed, we
* check them by copying out the values their buffers originally
* contained. (We need to do this as a failed system call should
* never affect the contents of a passed buffer.)
*
* This will fail if the buffers in question are write-only.
*/
if ((void *)p2 != NULL &&
((uucopy((void *)p2, &status, sizeof (status)) != 0) ||
(uucopy(&status, (void *)p2, sizeof (status)) != 0)))
return (-EFAULT);
if ((void *)p4 != NULL) {
if ((uucopy((void *)p4, &ru, sizeof (ru)) != 0) ||
(uucopy(&ru, (void *)p4, sizeof (ru)) != 0))
return (-EFAULT);
}
if (pid < -1) {
idtype = P_PGID;
id = -pid;
} else if (pid == -1) {
idtype = P_ALL;
id = 0;
} else if (pid == 0) {
idtype = P_PGID;
id = getpgrp();
} else {
idtype = P_PID;
id = pid;
}
options |= WEXITED | WTRAPPED;
again:
/*
* It's possible that we return EINVAL here if the idtype is P_PID or
* P_PGID and id is out of bounds for a valid pid or pgid, but Linux
* expects to see ECHILD. No good way occurs to handle this so we'll
* punt for now.
*/
if (waitid(idtype, id, &info, options) < 0)
return (-errno);
/*
* If the WNOHANG flag was specified and no child was found return 0.
*/
if ((options & WNOHANG) && info.si_pid == 0)
return (0);
/*
* It's possible that we may have a spurious return for one of the
* child processes created by the ptrace subsystem. In that's the case,
* we simply try again.
*/
if (lx_ptrace_wait(&info) == -1)
goto again;
status = lx_wstat(info.si_code, info.si_status);
/*
* Unfortunately if this attempt to copy out either the status or the
* rusage fails, the process will be in an inconsistent state as
* subsequent calls to wait for the same child will fail where they
* should succeed on a Linux system. This, however, is rather
* unlikely since we tested the validity of both above.
*/
if (p2 != NULL && uucopy(&status, (void *)p2, sizeof (status)) != 0)
return (-EFAULT);
if (p4 != NULL && (rval = lx_getrusage(LX_RUSAGE_CHILDREN, p4)) != 0)
return (rval);
return (info.si_pid);
}
int
lx_waitpid(uintptr_t p1, uintptr_t p2, uintptr_t p3)
{
return (lx_wait4(p1, p2, p3, NULL));
}