Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "primpl.h"
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
#include <string.h>
#if defined(AIX)
# include <dlfcn.h> /* For dlopen, dlsym, dlclose */
#endif
#if defined(DARWIN)
# if defined(HAVE_CRT_EXTERNS_H)
# include <crt_externs.h>
# endif
#else
PR_IMPORT_DATA(char**) environ;
#endif
/*
* HP-UX 9 doesn't have the SA_RESTART flag.
*/
#ifndef SA_RESTART
# define SA_RESTART 0
#endif
/*
**********************************************************************
*
* The Unix process routines
*
**********************************************************************
*/
#define _PR_SIGNALED_EXITSTATUS 256
typedef enum pr_PidState {
_PR_PID_DETACHED,
_PR_PID_REAPED,
_PR_PID_WAITING
} pr_PidState;
typedef struct pr_PidRecord {
pid_t pid;
int exitStatus;
pr_PidState state;
PRCondVar* reapedCV;
struct pr_PidRecord* next;
} pr_PidRecord;
/*
* LinuxThreads are actually a kind of processes
* that can share the virtual address space and file descriptors.
*/
#if ((defined(LINUX) || defined(__GNU__) || defined(__GLIBC__)) && \
defined(_PR_PTHREADS))
# define _PR_SHARE_CLONES
#endif
/*
* The macro _PR_NATIVE_THREADS indicates that we are
* using native threads only, so waitpid() blocks just the
* calling thread, not the process. In this case, the waitpid
* daemon thread can safely block in waitpid(). So we don't
* need to catch SIGCHLD, and the pipe to unblock PR_Poll() is
* also not necessary.
*/
#if defined(_PR_GLOBAL_THREADS_ONLY) || \
(defined(_PR_PTHREADS) && !defined(LINUX) && !defined(__GNU__) && \
!defined(__GLIBC__))
# define _PR_NATIVE_THREADS
#endif
/*
* All the static variables used by the Unix process routines are
* collected in this structure.
*/
static struct {
PRCallOnceType once;
PRThread* thread;
PRLock* ml;
#if defined(_PR_NATIVE_THREADS)
PRInt32 numProcs;
PRCondVar* cv;
#else
int pipefd[2];
#endif
pr_PidRecord** pidTable;
#ifdef _PR_SHARE_CLONES
struct pr_CreateProcOp *opHead, *opTail;
#endif
#ifdef AIX
pid_t (*forkptr)(void); /* Newer versions of AIX (starting in 4.3.2)
* have f_fork, which is faster than the
* regular fork in a multithreaded process
* because it skips calling the fork handlers.
* So we look up the f_fork symbol to see if
* it's available and fall back on fork.
*/
#endif /* AIX */
} pr_wp;
#ifdef _PR_SHARE_CLONES
static int pr_waitpid_daemon_exit;
void _MD_unix_terminate_waitpid_daemon(void) {
if (pr_wp.thread) {
pr_waitpid_daemon_exit = 1;
write(pr_wp.pipefd[1], "", 1);
PR_JoinThread(pr_wp.thread);
}
}
#endif
static PRStatus _MD_InitProcesses(void);
#if !defined(_PR_NATIVE_THREADS)
static void pr_InstallSigchldHandler(void);
#endif
static PRProcess* ForkAndExec(const char* path, char* const* argv,
char* const* envp, const PRProcessAttr* attr) {
PRProcess* process;
int nEnv, idx;
char* const* childEnvp;
char** newEnvp = NULL;
int flags;
process = PR_NEW(PRProcess);
if (!process) {
PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
return NULL;
}
childEnvp = envp;
if (attr && attr->fdInheritBuffer) {
PRBool found = PR_FALSE;
if (NULL == childEnvp) {
#ifdef DARWIN
# ifdef HAVE_CRT_EXTERNS_H
childEnvp = *(_NSGetEnviron());
# else
/* _NSGetEnviron() is not available on iOS. */
PR_DELETE(process);
PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
return NULL;
# endif
#else
childEnvp = environ;
#endif
}
for (nEnv = 0; childEnvp[nEnv]; nEnv++) {
}
newEnvp = (char**)PR_MALLOC((nEnv + 2) * sizeof(char*));
if (NULL == newEnvp) {
PR_DELETE(process);
PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
return NULL;
}
for (idx = 0; idx < nEnv; idx++) {
newEnvp[idx] = childEnvp[idx];
if (!found && !strncmp(newEnvp[idx], "NSPR_INHERIT_FDS=", 17)) {
newEnvp[idx] = attr->fdInheritBuffer;
found = PR_TRUE;
}
}
if (!found) {
newEnvp[idx++] = attr->fdInheritBuffer;
}
newEnvp[idx] = NULL;
childEnvp = newEnvp;
}
#ifdef AIX
process->md.pid = (*pr_wp.forkptr)();
#elif defined(NTO)
/*
* fork() & exec() does not work in a multithreaded process.
* Use spawn() instead.
*/
{
int fd_map[3] = {0, 1, 2};
if (attr) {
if (attr->stdinFd && attr->stdinFd->secret->md.osfd != 0) {
fd_map[0] = dup(attr->stdinFd->secret->md.osfd);
flags = fcntl(fd_map[0], F_GETFL, 0);
if (flags & O_NONBLOCK) {
fcntl(fd_map[0], F_SETFL, flags & ~O_NONBLOCK);
}
}
if (attr->stdoutFd && attr->stdoutFd->secret->md.osfd != 1) {
fd_map[1] = dup(attr->stdoutFd->secret->md.osfd);
flags = fcntl(fd_map[1], F_GETFL, 0);
if (flags & O_NONBLOCK) {
fcntl(fd_map[1], F_SETFL, flags & ~O_NONBLOCK);
}
}
if (attr->stderrFd && attr->stderrFd->secret->md.osfd != 2) {
fd_map[2] = dup(attr->stderrFd->secret->md.osfd);
flags = fcntl(fd_map[2], F_GETFL, 0);
if (flags & O_NONBLOCK) {
fcntl(fd_map[2], F_SETFL, flags & ~O_NONBLOCK);
}
}
PR_ASSERT(attr->currentDirectory == NULL); /* not implemented */
}
process->md.pid = spawn(path, 3, fd_map, NULL, argv, childEnvp);
if (fd_map[0] != 0) {
close(fd_map[0]);
}
if (fd_map[1] != 1) {
close(fd_map[1]);
}
if (fd_map[2] != 2) {
close(fd_map[2]);
}
}
#else
process->md.pid = fork();
#endif
if ((pid_t)-1 == process->md.pid) {
PR_SetError(PR_INSUFFICIENT_RESOURCES_ERROR, errno);
PR_DELETE(process);
if (newEnvp) {
PR_DELETE(newEnvp);
}
return NULL;
}
if (0 == process->md.pid) { /* the child process */
/*
* If the child process needs to exit, it must call _exit().
* Do not call exit(), because exit() will flush and close
* the standard I/O file descriptors, and hence corrupt
* the parent process's standard I/O data structures.
*/
#if !defined(NTO)
if (attr) {
/* the osfd's to redirect stdin, stdout, and stderr to */
int in_osfd = -1, out_osfd = -1, err_osfd = -1;
if (attr->stdinFd && attr->stdinFd->secret->md.osfd != 0) {
in_osfd = attr->stdinFd->secret->md.osfd;
if (dup2(in_osfd, 0) != 0) {
_exit(1); /* failed */
}
flags = fcntl(0, F_GETFL, 0);
if (flags & O_NONBLOCK) {
fcntl(0, F_SETFL, flags & ~O_NONBLOCK);
}
}
if (attr->stdoutFd && attr->stdoutFd->secret->md.osfd != 1) {
out_osfd = attr->stdoutFd->secret->md.osfd;
if (dup2(out_osfd, 1) != 1) {
_exit(1); /* failed */
}
flags = fcntl(1, F_GETFL, 0);
if (flags & O_NONBLOCK) {
fcntl(1, F_SETFL, flags & ~O_NONBLOCK);
}
}
if (attr->stderrFd && attr->stderrFd->secret->md.osfd != 2) {
err_osfd = attr->stderrFd->secret->md.osfd;
if (dup2(err_osfd, 2) != 2) {
_exit(1); /* failed */
}
flags = fcntl(2, F_GETFL, 0);
if (flags & O_NONBLOCK) {
fcntl(2, F_SETFL, flags & ~O_NONBLOCK);
}
}
if (in_osfd != -1) {
close(in_osfd);
}
if (out_osfd != -1 && out_osfd != in_osfd) {
close(out_osfd);
}
if (err_osfd != -1 && err_osfd != in_osfd && err_osfd != out_osfd) {
close(err_osfd);
}
if (attr->currentDirectory) {
if (chdir(attr->currentDirectory) < 0) {
_exit(1); /* failed */
}
}
}
if (childEnvp) {
(void)execve(path, argv, childEnvp);
} else {
/* Inherit the environment of the parent. */
(void)execv(path, argv);
}
/* Whoops! It returned. That's a bad sign. */
_exit(1);
#endif /* !NTO */
}
if (newEnvp) {
PR_DELETE(newEnvp);
}
#if defined(_PR_NATIVE_THREADS)
PR_Lock(pr_wp.ml);
if (0 == pr_wp.numProcs++) {
PR_NotifyCondVar(pr_wp.cv);
}
PR_Unlock(pr_wp.ml);
#endif
return process;
}
#ifdef _PR_SHARE_CLONES
struct pr_CreateProcOp {
const char* path;
char* const* argv;
char* const* envp;
const PRProcessAttr* attr;
PRProcess* process;
PRErrorCode prerror;
PRInt32 oserror;
PRBool done;
PRCondVar* doneCV;
struct pr_CreateProcOp* next;
};
PRProcess* _MD_CreateUnixProcess(const char* path, char* const* argv,
char* const* envp, const PRProcessAttr* attr) {
struct pr_CreateProcOp* op;
PRProcess* proc;
int rv;
if (PR_CallOnce(&pr_wp.once, _MD_InitProcesses) == PR_FAILURE) {
return NULL;
}
op = PR_NEW(struct pr_CreateProcOp);
if (NULL == op) {
PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
return NULL;
}
op->path = path;
op->argv = argv;
op->envp = envp;
op->attr = attr;
op->done = PR_FALSE;
op->doneCV = PR_NewCondVar(pr_wp.ml);
if (NULL == op->doneCV) {
PR_DELETE(op);
return NULL;
}
PR_Lock(pr_wp.ml);
/* add to the tail of op queue */
op->next = NULL;
if (pr_wp.opTail) {
pr_wp.opTail->next = op;
pr_wp.opTail = op;
} else {
PR_ASSERT(NULL == pr_wp.opHead);
pr_wp.opHead = pr_wp.opTail = op;
}
/* wake up the daemon thread */
do {
rv = write(pr_wp.pipefd[1], "", 1);
} while (-1 == rv && EINTR == errno);
while (op->done == PR_FALSE) {
PR_WaitCondVar(op->doneCV, PR_INTERVAL_NO_TIMEOUT);
}
PR_Unlock(pr_wp.ml);
PR_DestroyCondVar(op->doneCV);
proc = op->process;
if (!proc) {
PR_SetError(op->prerror, op->oserror);
}
PR_DELETE(op);
return proc;
}
#else /* ! _PR_SHARE_CLONES */
PRProcess* _MD_CreateUnixProcess(const char* path, char* const* argv,
char* const* envp, const PRProcessAttr* attr) {
if (PR_CallOnce(&pr_wp.once, _MD_InitProcesses) == PR_FAILURE) {
return NULL;
}
return ForkAndExec(path, argv, envp, attr);
} /* _MD_CreateUnixProcess */
#endif /* _PR_SHARE_CLONES */
/*
* The pid table is a hashtable.
*
* The number of buckets in the hashtable (NBUCKETS) must be a power of 2.
*/
#define NBUCKETS_LOG2 6
#define NBUCKETS (1 << NBUCKETS_LOG2)
#define PID_HASH_MASK ((pid_t)(NBUCKETS - 1))
static pr_PidRecord* FindPidTable(pid_t pid) {
pr_PidRecord* pRec;
int keyHash = (int)(pid & PID_HASH_MASK);
pRec = pr_wp.pidTable[keyHash];
while (pRec) {
if (pRec->pid == pid) {
break;
}
pRec = pRec->next;
}
return pRec;
}
static void InsertPidTable(pr_PidRecord* pRec) {
int keyHash = (int)(pRec->pid & PID_HASH_MASK);
pRec->next = pr_wp.pidTable[keyHash];
pr_wp.pidTable[keyHash] = pRec;
}
static void DeletePidTable(pr_PidRecord* pRec) {
int keyHash = (int)(pRec->pid & PID_HASH_MASK);
if (pr_wp.pidTable[keyHash] == pRec) {
pr_wp.pidTable[keyHash] = pRec->next;
} else {
pr_PidRecord *pred, *cur; /* predecessor and current */
pred = pr_wp.pidTable[keyHash];
cur = pred->next;
while (cur) {
if (cur == pRec) {
pred->next = cur->next;
break;
}
pred = cur;
cur = cur->next;
}
PR_ASSERT(cur != NULL);
}
}
static int ExtractExitStatus(int rawExitStatus) {
/*
* We did not specify the WCONTINUED and WUNTRACED options
* for waitpid, so these two events should not be reported.
*/
PR_ASSERT(!WIFSTOPPED(rawExitStatus));
#ifdef WIFCONTINUED
PR_ASSERT(!WIFCONTINUED(rawExitStatus));
#endif
if (WIFEXITED(rawExitStatus)) {
return WEXITSTATUS(rawExitStatus);
}
PR_ASSERT(WIFSIGNALED(rawExitStatus));
return _PR_SIGNALED_EXITSTATUS;
}
static void ProcessReapedChildInternal(pid_t pid, int status) {
pr_PidRecord* pRec;
pRec = FindPidTable(pid);
if (NULL == pRec) {
pRec = PR_NEW(pr_PidRecord);
pRec->pid = pid;
pRec->state = _PR_PID_REAPED;
pRec->exitStatus = ExtractExitStatus(status);
pRec->reapedCV = NULL;
InsertPidTable(pRec);
} else {
PR_ASSERT(pRec->state != _PR_PID_REAPED);
if (_PR_PID_DETACHED == pRec->state) {
PR_ASSERT(NULL == pRec->reapedCV);
DeletePidTable(pRec);
PR_DELETE(pRec);
} else {
PR_ASSERT(_PR_PID_WAITING == pRec->state);
PR_ASSERT(NULL != pRec->reapedCV);
pRec->exitStatus = ExtractExitStatus(status);
pRec->state = _PR_PID_REAPED;
PR_NotifyCondVar(pRec->reapedCV);
}
}
}
#if defined(_PR_NATIVE_THREADS)
/*
* If all the threads are native threads, the daemon thread is
* simpler. We don't need to catch the SIGCHLD signal. We can
* just have the daemon thread block in waitpid().
*/
static void WaitPidDaemonThread(void* unused) {
pid_t pid;
int status;
while (1) {
PR_Lock(pr_wp.ml);
while (0 == pr_wp.numProcs) {
PR_WaitCondVar(pr_wp.cv, PR_INTERVAL_NO_TIMEOUT);
}
PR_Unlock(pr_wp.ml);
while (1) {
do {
pid = waitpid((pid_t)-1, &status, 0);
} while ((pid_t)-1 == pid && EINTR == errno);
/*
* waitpid() cannot return 0 because we did not invoke it
* with the WNOHANG option.
*/
PR_ASSERT(0 != pid);
/*
* The only possible error code is ECHILD. But if we do
* our accounting correctly, we should only call waitpid()
* when there is a child process to wait for.
*/
PR_ASSERT((pid_t)-1 != pid);
if ((pid_t)-1 == pid) {
break;
}
PR_Lock(pr_wp.ml);
ProcessReapedChildInternal(pid, status);
pr_wp.numProcs--;
while (0 == pr_wp.numProcs) {
PR_WaitCondVar(pr_wp.cv, PR_INTERVAL_NO_TIMEOUT);
}
PR_Unlock(pr_wp.ml);
}
}
}
#else /* _PR_NATIVE_THREADS */
static void WaitPidDaemonThread(void* unused) {
PRPollDesc pd;
PRFileDesc* fd;
int rv;
char buf[128];
pid_t pid;
int status;
# ifdef _PR_SHARE_CLONES
struct pr_CreateProcOp* op;
# endif
# ifdef _PR_SHARE_CLONES
pr_InstallSigchldHandler();
# endif
fd = PR_ImportFile(pr_wp.pipefd[0]);
PR_ASSERT(NULL != fd);
pd.fd = fd;
pd.in_flags = PR_POLL_READ;
while (1) {
rv = PR_Poll(&pd, 1, PR_INTERVAL_NO_TIMEOUT);
PR_ASSERT(1 == rv);
# ifdef _PR_SHARE_CLONES
if (pr_waitpid_daemon_exit) {
return;
}
PR_Lock(pr_wp.ml);
# endif
do {
rv = read(pr_wp.pipefd[0], buf, sizeof(buf));
} while (sizeof(buf) == rv || (-1 == rv && EINTR == errno));
# ifdef _PR_SHARE_CLONES
while ((op = pr_wp.opHead) != NULL) {
PR_Unlock(pr_wp.ml);
op->process = ForkAndExec(op->path, op->argv, op->envp, op->attr);
if (NULL == op->process) {
op->prerror = PR_GetError();
op->oserror = PR_GetOSError();
}
PR_Lock(pr_wp.ml);
pr_wp.opHead = op->next;
if (NULL == pr_wp.opHead) {
pr_wp.opTail = NULL;
}
op->done = PR_TRUE;
PR_NotifyCondVar(op->doneCV);
}
PR_Unlock(pr_wp.ml);
# endif
while (1) {
do {
pid = waitpid((pid_t)-1, &status, WNOHANG);
} while ((pid_t)-1 == pid && EINTR == errno);
if (0 == pid) {
break;
}
if ((pid_t)-1 == pid) {
/* must be because we have no child processes */
PR_ASSERT(ECHILD == errno);
break;
}
PR_Lock(pr_wp.ml);
ProcessReapedChildInternal(pid, status);
PR_Unlock(pr_wp.ml);
}
}
}
static void pr_SigchldHandler(int sig) {
int errnoCopy;
int rv;
errnoCopy = errno;
do {
rv = write(pr_wp.pipefd[1], "", 1);
} while (-1 == rv && EINTR == errno);
# ifdef DEBUG
if (-1 == rv && EAGAIN != errno && EWOULDBLOCK != errno) {
char* msg = "cannot write to pipe\n";
write(2, msg, strlen(msg) + 1);
_exit(1);
}
# endif
errno = errnoCopy;
}
static void pr_InstallSigchldHandler() {
struct sigaction act, oact;
int rv;
act.sa_handler = pr_SigchldHandler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NOCLDSTOP | SA_RESTART;
rv = sigaction(SIGCHLD, &act, &oact);
PR_ASSERT(0 == rv);
/* Make sure we are not overriding someone else's SIGCHLD handler */
# ifndef _PR_SHARE_CLONES
PR_ASSERT(oact.sa_handler == SIG_DFL);
# endif
}
#endif /* !defined(_PR_NATIVE_THREADS) */
static PRStatus _MD_InitProcesses(void) {
#if !defined(_PR_NATIVE_THREADS)
int rv;
int flags;
#endif
#ifdef AIX
{
void* handle = dlopen(NULL, RTLD_NOW | RTLD_GLOBAL);
pr_wp.forkptr = (pid_t(*)(void))dlsym(handle, "f_fork");
if (!pr_wp.forkptr) {
pr_wp.forkptr = fork;
}
dlclose(handle);
}
#endif /* AIX */
pr_wp.ml = PR_NewLock();
PR_ASSERT(NULL != pr_wp.ml);
#if defined(_PR_NATIVE_THREADS)
pr_wp.numProcs = 0;
pr_wp.cv = PR_NewCondVar(pr_wp.ml);
PR_ASSERT(NULL != pr_wp.cv);
#else
rv = pipe(pr_wp.pipefd);
PR_ASSERT(0 == rv);
flags = fcntl(pr_wp.pipefd[0], F_GETFL, 0);
fcntl(pr_wp.pipefd[0], F_SETFL, flags | O_NONBLOCK);
flags = fcntl(pr_wp.pipefd[1], F_GETFL, 0);
fcntl(pr_wp.pipefd[1], F_SETFL, flags | O_NONBLOCK);
# ifndef _PR_SHARE_CLONES
pr_InstallSigchldHandler();
# endif
#endif /* !_PR_NATIVE_THREADS */
pr_wp.thread = PR_CreateThread(PR_SYSTEM_THREAD, WaitPidDaemonThread, NULL,
PR_PRIORITY_NORMAL,
#ifdef _PR_SHARE_CLONES
PR_GLOBAL_THREAD,
#else
PR_LOCAL_THREAD,
#endif
PR_JOINABLE_THREAD, 0);
PR_ASSERT(NULL != pr_wp.thread);
pr_wp.pidTable = (pr_PidRecord**)PR_CALLOC(NBUCKETS * sizeof(pr_PidRecord*));
PR_ASSERT(NULL != pr_wp.pidTable);
return PR_SUCCESS;
}
PRStatus _MD_DetachUnixProcess(PRProcess* process) {
PRStatus retVal = PR_SUCCESS;
pr_PidRecord* pRec;
PR_Lock(pr_wp.ml);
pRec = FindPidTable(process->md.pid);
if (NULL == pRec) {
pRec = PR_NEW(pr_PidRecord);
if (NULL == pRec) {
PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
retVal = PR_FAILURE;
goto done;
}
pRec->pid = process->md.pid;
pRec->state = _PR_PID_DETACHED;
pRec->reapedCV = NULL;
InsertPidTable(pRec);
} else {
PR_ASSERT(_PR_PID_REAPED == pRec->state);
if (_PR_PID_REAPED != pRec->state) {
PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
retVal = PR_FAILURE;
} else {
DeletePidTable(pRec);
PR_ASSERT(NULL == pRec->reapedCV);
PR_DELETE(pRec);
}
}
PR_DELETE(process);
done:
PR_Unlock(pr_wp.ml);
return retVal;
}
PRStatus _MD_WaitUnixProcess(PRProcess* process, PRInt32* exitCode) {
pr_PidRecord* pRec;
PRStatus retVal = PR_SUCCESS;
PRBool interrupted = PR_FALSE;
PR_Lock(pr_wp.ml);
pRec = FindPidTable(process->md.pid);
if (NULL == pRec) {
pRec = PR_NEW(pr_PidRecord);
if (NULL == pRec) {
PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
retVal = PR_FAILURE;
goto done;
}
pRec->pid = process->md.pid;
pRec->state = _PR_PID_WAITING;
pRec->reapedCV = PR_NewCondVar(pr_wp.ml);
if (NULL == pRec->reapedCV) {
PR_DELETE(pRec);
retVal = PR_FAILURE;
goto done;
}
InsertPidTable(pRec);
while (!interrupted && _PR_PID_REAPED != pRec->state) {
if (PR_WaitCondVar(pRec->reapedCV, PR_INTERVAL_NO_TIMEOUT) ==
PR_FAILURE &&
PR_GetError() == PR_PENDING_INTERRUPT_ERROR) {
interrupted = PR_TRUE;
}
}
if (_PR_PID_REAPED == pRec->state) {
if (exitCode) {
*exitCode = pRec->exitStatus;
}
} else {
PR_ASSERT(interrupted);
retVal = PR_FAILURE;
}
DeletePidTable(pRec);
PR_DestroyCondVar(pRec->reapedCV);
PR_DELETE(pRec);
} else {
PR_ASSERT(_PR_PID_REAPED == pRec->state);
PR_ASSERT(NULL == pRec->reapedCV);
DeletePidTable(pRec);
if (exitCode) {
*exitCode = pRec->exitStatus;
}
PR_DELETE(pRec);
}
PR_DELETE(process);
done:
PR_Unlock(pr_wp.ml);
return retVal;
} /* _MD_WaitUnixProcess */
PRStatus _MD_KillUnixProcess(PRProcess* process) {
PRErrorCode prerror;
PRInt32 oserror;
if (kill(process->md.pid, SIGKILL) == 0) {
return PR_SUCCESS;
}
oserror = errno;
switch (oserror) {
case EPERM:
prerror = PR_NO_ACCESS_RIGHTS_ERROR;
break;
case ESRCH:
prerror = PR_INVALID_ARGUMENT_ERROR;
break;
default:
prerror = PR_UNKNOWN_ERROR;
break;
}
PR_SetError(prerror, oserror);
return PR_FAILURE;
} /* _MD_KillUnixProcess */