Arquivos
hhvm/hphp/runtime/ext/ext_process.cpp
T
Edwin Smith 1664473ecb Use uppercase for RuntimeOption static methods.
Main style for static functions is UpperCase.
2013-05-28 10:30:26 -07:00

920 linhas
26 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/ext/ext_process.h"
#include "hphp/runtime/ext/ext_file.h"
#include "hphp/runtime/ext/ext_function.h"
#include "hphp/runtime/base/util/string_buffer.h"
#include "hphp/runtime/base/zend/zend_string.h"
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include "hphp/util/lock.h"
#include "hphp/runtime/base/file/plain_file.h"
#include "hphp/util/light_process.h"
#include "hphp/util/logger.h"
#include "hphp/runtime/base/util/request_local.h"
#include "hphp/runtime/vm/repo.h"
#if !defined(_NSIG) && defined(NSIG)
# define _NSIG NSIG
#endif
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
// build environment pair list
static char **build_envp(CArrRef envs, std::vector<String> &senvs) {
char **envp = NULL;
int size = envs.size();
if (size) {
envp = (char **)malloc((size + 1) * sizeof(char *));
int i = 0;
for (ArrayIter iter(envs); iter; ++iter, ++i) {
StringBuffer nvpair;
nvpair += iter.first().toString();
nvpair += '=';
nvpair += iter.second().toString();
String env = nvpair.detach();
senvs.push_back(env);
*(envp + i) = (char *)env.data();
}
*(envp + i) = NULL;
}
return envp;
}
// check whitelist
static bool check_cmd(const char *cmd) {
const char *cmd_tmp = cmd;
while (true) {
bool allow = false;
while (isblank(*cmd_tmp)) cmd_tmp++;
const char *space = strchr(cmd_tmp, ' ');
unsigned int cmd_len = strlen(cmd_tmp);
if (space) {
cmd_len = space - cmd_tmp;
}
for (unsigned int i = 0; i < RuntimeOption::AllowedExecCmds.size(); i++) {
std::string &allowedCmd = RuntimeOption::AllowedExecCmds[i];
if (allowedCmd.size() != cmd_len) {
continue;
}
if (strncmp(allowedCmd.c_str(), cmd_tmp, allowedCmd.size()) == 0) {
allow = true;
break;
}
}
if (!allow) {
String file = g_vmContext->getContainingFileName();
int line = g_vmContext->getLine();
Logger::Warning("Command %s is not in the whitelist, called at %s:%d",
cmd_tmp, file.data(), line);
if (!RuntimeOption::WhitelistExecWarningOnly) {
return false;
}
}
const char *bar = strchr(cmd_tmp, '|');
if (!bar) { // no pipe, we are done
return true;
}
cmd_tmp = bar + 1;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
// pcntl
IMPLEMENT_DEFAULT_EXTENSION(pcntl);
int64_t f_pcntl_alarm(int seconds) {
return alarm(seconds);
}
void f_pcntl_exec(CStrRef path, CArrRef args /* = null_array */,
CArrRef envs /* = null_array */) {
if (RuntimeOption::WhitelistExec && !check_cmd(path.data())) {
return;
}
if (Repo::prefork()) {
raise_error("execing is disallowed in multi-threaded mode");
return;
}
// build argumnent list
std::vector<String> sargs; // holding those char *
int size = args.size();
char **argv = (char **)malloc((size + 2) * sizeof(char *));
*argv = (char *)path.data();
int i = 1;
if (size) {
sargs.reserve(size);
for (ArrayIter iter(args); iter; ++iter, ++i) {
String arg = iter.second().toString();
sargs.push_back(arg);
*(argv + i) = (char *)arg.data();
}
}
*(argv + i) = NULL;
// build environment pair list
std::vector<String> senvs; // holding those char *
char **envp = build_envp(envs, senvs);
if (execve(path.c_str(), argv, envp) == -1) {
raise_warning("Error has occured: (errno %d) %s",
errno, Util::safe_strerror(errno).c_str());
}
free(envp);
free(argv);
}
int64_t f_pcntl_fork() {
if (RuntimeOption::ServerExecutionMode()) {
raise_error("forking is disallowed in server mode");
return -1;
}
if (Repo::prefork()) {
raise_error("forking is disallowed in multi-threaded mode");
return -1;
}
std::cout.flush();
std::cerr.flush();
pid_t pid = fork();
Repo::postfork(pid);
return pid;
}
Variant f_pcntl_getpriority(int pid /* = 0 */,
int process_identifier /* = 0 */) {
if (pid == 0) {
pid = getpid();
}
if (process_identifier == 0) {
process_identifier = PRIO_PROCESS;
}
// this isn't thread-safe, but probably not a huge deal
errno = 0;
int pri = getpriority(process_identifier, pid);
if (errno) {
switch (errno) {
case ESRCH:
raise_warning("Error %d: No process was located using the given "
"parameters", errno);
break;
case EINVAL:
raise_warning("Error %d: Invalid identifier flag", errno);
break;
default:
raise_warning("Unknown error %d has occured", errno);
break;
}
return false;
}
return pri;
}
bool f_pcntl_setpriority(int priority, int pid /* = 0 */,
int process_identifier /* = 0 */) {
if (pid == 0) {
pid = getpid();
}
if (process_identifier == 0) {
process_identifier = PRIO_PROCESS;
}
if (setpriority(process_identifier, pid, priority)) {
switch (errno) {
case ESRCH:
raise_warning("Error %d: No process was located using the given "
"parameters", errno);
break;
case EINVAL:
raise_warning("Error %d: Invalid identifier flag", errno);
break;
case EPERM:
raise_warning("Error %d: A process was located, but neither its "
"effective nor real user ID matched the effective "
"user ID of the caller", errno);
break;
case EACCES:
raise_warning("Error %d: Only a super user may attempt to increase "
"the process priority", errno);
break;
default:
raise_warning("Unknown error %d has occured", errno);
break;
}
return false;
}
return true;
}
/* php_signal using sigaction is derrived from Advanced Programing
* in the Unix Environment by W. Richard Stevens p 298. */
typedef void Sigfunc(int);
static Sigfunc *php_signal(int signo, Sigfunc *func, bool restart) {
struct sigaction act,oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM || (!restart)) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS */
#endif
} else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 4.3+BSD */
#endif
}
if (sigaction(signo, &act, &oact) < 0)
return SIG_ERR;
return oact.sa_handler;
}
/* Our custom signal handler that calls the appropriate php_function */
class SignalHandlers : public RequestEventHandler {
public:
SignalHandlers() {
memset(signaled, 0, sizeof(signaled));
pthread_sigmask(SIG_SETMASK, NULL, &oldSet);
}
virtual void requestInit() {
handlers.reset();
// restore the old signal mask, thus unblock those that should be
pthread_sigmask(SIG_SETMASK, &oldSet, NULL);
}
virtual void requestShutdown() {
// block all signals
sigset_t set;
sigfillset(&set);
pthread_sigmask(SIG_BLOCK, &set, NULL);
handlers.reset();
}
Array handlers;
int signaled[_NSIG];
sigset_t oldSet;
};
IMPLEMENT_STATIC_REQUEST_LOCAL(SignalHandlers, s_signal_handlers);
static void pcntl_signal_handler(int signo) {
if (signo > 0 && signo < _NSIG && !g_context.isNull()) {
s_signal_handlers->signaled[signo] = 1;
RequestInjectionData &data = ThreadInfo::s_threadInfo.getNoCheck()->
m_reqInjectionData;
data.setSignaledFlag();
}
}
class SignalHandlersStaticInitializer {
public:
SignalHandlersStaticInitializer() {
signal(SIGALRM, pcntl_signal_handler);
signal(SIGUSR1, pcntl_signal_handler);
signal(SIGUSR2, pcntl_signal_handler);
}
};
static SignalHandlersStaticInitializer s_signal_handlers_initializer;
bool f_pcntl_signal_dispatch() {
int *signaled = s_signal_handlers->signaled;
for (int i = 0; i < _NSIG; i++) {
if (signaled[i]) {
signaled[i] = 0;
if (s_signal_handlers->handlers.exists(i)) {
vm_call_user_func(s_signal_handlers->handlers[i],
CREATE_VECTOR1(i));
}
}
}
return true;
}
bool f_pcntl_signal(int signo, CVarRef handler,
bool restart_syscalls /* = true */) {
/* Special long value case for SIG_DFL and SIG_IGN */
if (handler.isInteger()) {
int64_t handle = handler.toInt64();
if (handle != (long)SIG_DFL && handle != (long)SIG_IGN) {
raise_warning("Invalid value for handle argument specified");
}
if (php_signal(signo, (Sigfunc *)handle, restart_syscalls) == SIG_ERR) {
raise_warning("Error assigning signal");
return false;
}
return true;
}
if (!f_is_callable(handler)) {
raise_warning("%s is not a callable function name error",
handler.toString().data());
return false;
}
s_signal_handlers->handlers.set(signo, handler);
if (php_signal(signo, pcntl_signal_handler, restart_syscalls) == SIG_ERR) {
raise_warning("Error assigning signal");
return false;
}
return true;
}
int64_t f_pcntl_wait(VRefParam status, int options /* = 0 */) {
int child_id;
int nstatus = 0;
child_id = LightProcess::pcntl_waitpid(-1, &nstatus, options);
/* if (options) {
child_id = wait3(&nstatus, options, NULL);
} else {
child_id = wait(&nstatus);
}*/
status = nstatus;
return child_id;
}
int64_t f_pcntl_waitpid(int pid, VRefParam status, int options /* = 0 */) {
int nstatus = status;
pid_t child_id = LightProcess::pcntl_waitpid((pid_t)pid, &nstatus, options);
status = nstatus;
return child_id;
}
int64_t f_pcntl_wexitstatus(int status) {
return WEXITSTATUS(status);
}
bool f_pcntl_wifexited(int status) { return WIFEXITED(status);}
bool f_pcntl_wifsignaled(int status) { return WIFSIGNALED(status);}
bool f_pcntl_wifstopped(int status) { return WIFSTOPPED(status);}
int64_t f_pcntl_wstopsig(int status) { return WSTOPSIG(status);}
int64_t f_pcntl_wtermsig(int status) { return WTERMSIG(status);}
///////////////////////////////////////////////////////////////////////////////
// popen
#define EXEC_INPUT_BUF 4096
class ShellExecContext {
public:
ShellExecContext() : m_proc(NULL) {
m_sig_handler = signal(SIGCHLD, SIG_DFL);
}
~ShellExecContext() {
if (m_proc) {
LightProcess::pclose(m_proc);
}
if (m_sig_handler) {
signal(SIGCHLD, m_sig_handler);
}
}
FILE *exec(const char *cmd) {
assert(m_proc == NULL);
if (RuntimeOption::WhitelistExec && !check_cmd(cmd)) {
return NULL;
}
m_proc = LightProcess::popen(cmd, "r", g_context->getCwd().data());
if (m_proc == NULL) {
raise_warning("Unable to execute '%s'", cmd);
}
return m_proc;
}
int exit() {
int status = LightProcess::pclose(m_proc);
m_proc = NULL;
return status;
}
private:
void (*m_sig_handler)(int);
FILE *m_proc;
};
String f_shell_exec(CStrRef cmd) {
ShellExecContext ctx;
FILE *fp = ctx.exec(cmd.c_str());
if (!fp) return "";
StringBuffer sbuf;
sbuf.read(fp);
return sbuf.detach();
}
String f_exec(CStrRef command, VRefParam output /* = null */,
VRefParam return_var /* = null */) {
ShellExecContext ctx;
FILE *fp = ctx.exec(command.c_str());
if (!fp) return "";
StringBuffer sbuf;
sbuf.read(fp);
Array lines = StringUtil::Explode(sbuf.detach(), "\n");
int ret = ctx.exit();
if (WIFEXITED(ret)) ret = WEXITSTATUS(ret);
return_var = ret;
int count = lines.size();
if (count > 0 && lines[count - 1].toString().empty()) {
count--; // remove explode()'s last empty line
}
if (!output.is(KindOfArray)) {
output = Array(ArrayData::Create());
}
for (int i = 0; i < count; i++) {
output.append(lines[i]);
}
if (!count || lines.empty()) {
return String();
}
return StringUtil::Trim(lines[count - 1], StringUtil::TrimRight);
}
void f_passthru(CStrRef command, VRefParam return_var /* = null */) {
ShellExecContext ctx;
FILE *fp = ctx.exec(command.c_str());
if (!fp) return;
char buffer[1024];
while (true) {
int len = read(fileno(fp), buffer, sizeof(buffer) - 1);
if (len == -1 && errno == EINTR) continue;
if (len <= 0) break; // break on error or EOF
buffer[len] = '\0';
echo(String(buffer, len, AttachLiteral));
}
int ret = ctx.exit();
if (WIFEXITED(ret)) ret = WEXITSTATUS(ret);
return_var = ret;
}
String f_system(CStrRef command, VRefParam return_var /* = null */) {
ShellExecContext ctx;
FILE *fp = ctx.exec(command.c_str());
if (!fp) return "";
StringBuffer sbuf;
if (fp) {
sbuf.read(fp);
}
Array lines = StringUtil::Explode(sbuf.detach(), "\n");
int ret = ctx.exit();
if (WIFEXITED(ret)) ret = WEXITSTATUS(ret);
return_var = ret;
int count = lines.size();
if (count > 0 && lines[count - 1].toString().empty()) {
count--; // remove explode()'s last empty line
}
for (int i = 0; i < count; i++) {
echo(lines[i]);
echo("\n");
}
if (!count || lines.empty()) {
return String();
}
return StringUtil::Trim(lines[count - 1], StringUtil::TrimRight);
}
///////////////////////////////////////////////////////////////////////////////
// proc
class ChildProcess : public SweepableResourceData {
public:
DECLARE_OBJECT_ALLOCATION(ChildProcess)
pid_t child;
Array pipes;
String command;
Variant env;
static StaticString s_class_name;
// overriding ResourceData
virtual CStrRef o_getClassNameHook() const { return s_class_name; }
int close() {
// Although the PHP doc about proc_close() says that the pipes need to be
// explicitly pclose()'ed, it seems that Zend is implicitly closing the
// pipes when proc_close() is called.
for (ArrayIter iter(pipes); iter; ++iter) {
iter.second().toObject().getTyped<PlainFile>()->close();
}
pipes.clear();
pid_t wait_pid;
int wstatus;
do {
wait_pid = LightProcess::waitpid(child, &wstatus, 0,
RuntimeOption::RequestTimeoutSeconds);
} while (wait_pid == -1 && errno == EINTR);
if (wait_pid == -1) {
return -1;
}
if (WIFEXITED(wstatus)) {
wstatus = WEXITSTATUS(wstatus);
}
return wstatus;
}
};
IMPLEMENT_OBJECT_ALLOCATION_NO_DEFAULT_SWEEP(ChildProcess);
void ChildProcess::sweep() {
// do nothing here, as everything will be collected by SmartAllocator
}
StaticString ChildProcess::s_class_name("Process");
#define DESC_PIPE 1
#define DESC_FILE 2
#define DESC_PARENT_MODE_WRITE 8
class DescriptorItem {
public:
DescriptorItem() :
index(-1), parentend(-1), childend(-1), mode(-1), mode_flags(-1) {
}
~DescriptorItem() {
}
void cleanup() {
if (childend >= 0) close(childend);
if (parentend >= 0) close(parentend);
}
int index; // desired fd number in child process
int parentend, childend; // fds for pipes in parent/child
int mode; // mode for proc_open code
int mode_flags; // mode flags for opening fds
static Mutex s_mutex; // Prevents another thread from forking at the
// same time, before FD_CLOEXEC is set on the fds.
// NOTE: no need to lock with light processes.
bool readFile(File *file) {
mode = DESC_FILE;
childend = dup(file->fd());
if (childend < 0) {
raise_warning("unable to dup File-Handle for descriptor %d - %s",
index, Util::safe_strerror(errno).c_str());
return false;
}
return true;
}
bool readPipe(CStrRef zmode) {
mode = DESC_PIPE;
int newpipe[2];
if (0 != pipe(newpipe)) {
raise_warning("unable to create pipe %s",
Util::safe_strerror(errno).c_str());
return false;
}
if (zmode != "w") {
parentend = newpipe[1];
childend = newpipe[0];
mode |= DESC_PARENT_MODE_WRITE;
} else {
parentend = newpipe[0];
childend = newpipe[1];
}
mode_flags = mode & DESC_PARENT_MODE_WRITE ? O_WRONLY : O_RDONLY;
return true;
}
bool openFile(CStrRef zfile, CStrRef zmode) {
mode = DESC_FILE;
/* try a wrapper */
FILE *file = fopen(zfile.c_str(), zmode.c_str());
if (!file) {
raise_warning("Unable to open specified file: %s (mode %s)",
zfile.data(), zmode.data());
return false;
}
childend = fileno(file);
return true;
}
void dupChild() {
if ((mode & ~DESC_PARENT_MODE_WRITE) == DESC_PIPE) {
close(parentend); parentend = -1;
}
if (dup2(childend, index) < 0) {
perror("dup2");
}
if (childend != index) {
close(childend); childend = -1;
}
}
/* clean up all the child ends and then open streams on the parent
* ends, where appropriate */
Object dupParent() {
close(childend); childend = -1;
if ((mode & ~DESC_PARENT_MODE_WRITE) == DESC_PIPE) {
/* mark the descriptor close-on-exec, so that it won't be inherited
by potential other children */
fcntl(parentend, F_SETFD, FD_CLOEXEC);
return Object(NEWOBJ(PlainFile)(parentend, true));
}
return Object();
}
};
/**
* This mutex must be non-reentrant so when the child process tries to unlock
* it after a fork(), the call to pthread_mutex_unlock() will succeed.
*/
Mutex DescriptorItem::s_mutex(false);
static bool pre_proc_open(CArrRef descriptorspec,
vector<DescriptorItem> &items) {
/* walk the descriptor spec and set up files/pipes */
items.resize(descriptorspec.size());
int i = 0;
for (ArrayIter iter(descriptorspec); iter; ++iter, ++i) {
DescriptorItem &item = items[i];
String index = iter.first();
if (!index.isNumeric()) {
raise_warning("descriptor spec must be an integer indexed array");
break;
}
item.index = index.toInt32();
Variant descitem = iter.second();
if (descitem.isResource()) {
File *file = descitem.toObject().getTyped<File>();
if (!item.readFile(file)) break;
} else if (!descitem.is(KindOfArray)) {
raise_warning("Descriptor must be either an array or a File-Handle");
break;
} else {
Array descarr = descitem.toArray();
if (!descarr.exists(int64_t(0))) {
raise_warning("Missing handle qualifier in array");
break;
}
String ztype = descarr[int64_t(0)].toString();
if (ztype == "pipe") {
if (!descarr.exists(int64_t(1))) {
raise_warning("Missing mode parameter for 'pipe'");
break;
}
if (!item.readPipe(descarr[int64_t(1)].toString())) break;
} else if (ztype == "file") {
if (!descarr.exists(int64_t(1))) {
raise_warning("Missing file name parameter for 'file'");
break;
}
if (!descarr.exists(int64_t(2))) {
raise_warning("Missing mode parameter for 'file'");
break;
}
if (!item.openFile(descarr[int64_t(1)].toString(), descarr[int64_t(2)].toString())) {
break;
}
} else {
raise_warning("%s is not a valid descriptor spec", ztype.data());
break;
}
}
}
if (i >= descriptorspec.size()) return true;
for (int j = 0; j < i; j++) {
items[j].cleanup();
}
return false;
}
static Variant post_proc_open(CStrRef cmd, Variant &pipes,
CVarRef env, vector<DescriptorItem> &items,
pid_t child) {
if (child < 0) {
/* failed to fork() */
for (int i = 0; i < (int)items.size(); i++) {
items[i].cleanup();
}
raise_warning("fork failed - %s", Util::safe_strerror(errno).c_str());
return false;
}
/* we forked/spawned and this is the parent */
ChildProcess *proc = NEWOBJ(ChildProcess)();
proc->command = cmd;
proc->child = child;
proc->env = env;
for (int i = 0; i < (int)items.size(); i++) {
Object f = items[i].dupParent();
if (!f.isNull()) {
proc->pipes.append(f);
}
pipes.set(items[i].index, f);
}
return Object(proc);
}
Variant f_proc_open(CStrRef cmd, CArrRef descriptorspec, VRefParam pipes,
CStrRef cwd /* = null_string */,
CVarRef env /* = null_variant */,
CVarRef other_options /* = null_variant */) {
if (RuntimeOption::WhitelistExec && !check_cmd(cmd.data())) {
return false;
}
std::vector<DescriptorItem> items;
string scwd = "";
if (!cwd.empty()) {
scwd = cwd.c_str();
} else if (!g_context->getCwd().empty()) {
scwd = g_context->getCwd().c_str();
}
Array enva;
if (env.isNull()) {
// Default to the current environment from the execution context
// rather than leaving it unspecified so that we pick up local
// changes made via putenv()
enva = g_context->getEnvs();
} else {
enva = env.toArray();
}
pid_t child;
if (LightProcess::Available()) {
// light process available
// there is no need to do any locking, because the forking is delegated
// to the light process
if (!pre_proc_open(descriptorspec, items)) return false;
std::vector<int> created;
std::vector<int> intended;
for (int i = 0; i < (int)items.size(); i++) {
created.push_back(items[i].childend);
intended.push_back(items[i].index);
}
std::vector<std::string> envs;
for (ArrayIter iter(enva); iter; ++iter) {
StringBuffer nvpair;
nvpair += iter.first().toString();
nvpair += '=';
nvpair += iter.second().toString();
envs.push_back(nvpair.detach().c_str());
}
child = LightProcess::proc_open(cmd.c_str(), created, intended,
scwd.c_str(), envs);
assert(child);
return post_proc_open(cmd, pipes, enva, items, child);
} else {
/* the unix way */
Lock lock(DescriptorItem::s_mutex);
if (!pre_proc_open(descriptorspec, items)) return false;
child = fork();
if (child) {
// the parent process
return post_proc_open(cmd, pipes, enva, items, child);
}
}
assert(child == 0);
/* this is the child process */
/* close those descriptors that we just opened for the parent stuff,
* dup new descriptors into required descriptors and close the original
* cruft */
for (int i = 0; i < (int)items.size(); i++) {
items[i].dupChild();
}
if (scwd.length() > 0 && chdir(scwd.c_str())) {
// chdir failed, the working directory remains unchanged
}
vector<String> senvs; // holding those char *
char **envp = build_envp(enva, senvs);
execle("/bin/sh", "sh", "-c", cmd.data(), NULL, envp);
free(envp);
_exit(127);
}
bool f_proc_terminate(CObjRef process, int signal /* = 0 */) {
ChildProcess *proc = process.getTyped<ChildProcess>();
return kill(proc->child, signal <= 0 ? SIGTERM : signal) == 0;
}
int64_t f_proc_close(CObjRef process) {
return process.getTyped<ChildProcess>()->close();
}
static const StaticString s_command("command");
static const StaticString s_pid("pid");
static const StaticString s_running("running");
static const StaticString s_signaled("signaled");
static const StaticString s_stopped("stopped");
static const StaticString s_exitcode("exitcode");
static const StaticString s_termsig("termsig");
static const StaticString s_stopsig("stopsig");
Array f_proc_get_status(CObjRef process) {
ChildProcess *proc = process.getTyped<ChildProcess>();
errno = 0;
int wstatus;
pid_t wait_pid =
LightProcess::waitpid(proc->child, &wstatus, WNOHANG|WUNTRACED);
bool running = true, signaled = false, stopped = false;
int exitcode = -1, termsig = 0, stopsig = 0;
if (wait_pid == proc->child) {
if (WIFEXITED(wstatus)) {
running = false;
exitcode = WEXITSTATUS(wstatus);
}
if (WIFSIGNALED(wstatus)) {
running = false;
signaled = true;
termsig = WTERMSIG(wstatus);
}
if (WIFSTOPPED(wstatus)) {
stopped = true;
stopsig = WSTOPSIG(wstatus);
}
} else if (wait_pid == -1) {
running = false;
}
ArrayInit ret(8);
ret.set(s_command, proc->command);
ret.set(s_pid, proc->child);
ret.set(s_running, running);
ret.set(s_signaled, signaled);
ret.set(s_stopped, stopped);
ret.set(s_exitcode, exitcode);
ret.set(s_termsig, termsig);
ret.set(s_stopsig, stopsig);
return ret.create();
}
bool f_proc_nice(int increment) {
if (nice(increment) < 0 && errno) {
raise_warning("Only a super user may attempt to increase the "
"priority of a process");
return false;
}
return true;
}
///////////////////////////////////////////////////////////////////////////////
// string functions
String f_escapeshellarg(CStrRef arg) {
if (!arg.empty()) {
char *ret = string_escape_shell_arg(arg.c_str());
return String(ret, AttachString);
}
return arg;
}
String f_escapeshellcmd(CStrRef command) {
if (!command.empty()) {
char *ret = string_escape_shell_cmd(command.c_str());
return String(ret, AttachString);
}
return command;
}
///////////////////////////////////////////////////////////////////////////////
}