2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 #include "hphp/runtime/ext/ext_process.h"
19 #include "hphp/runtime/ext/ext_file.h"
20 #include "hphp/runtime/ext/ext_function.h"
21 #include "hphp/runtime/base/string_buffer.h"
22 #include "hphp/runtime/base/zend-string.h"
23 #include "hphp/runtime/base/thread_init_fini.h"
27 #include "hphp/util/lock.h"
28 #include "hphp/runtime/base/plain_file.h"
29 #include "hphp/util/light_process.h"
30 #include "hphp/util/logger.h"
31 #include "hphp/runtime/base/request_local.h"
32 #include "hphp/runtime/vm/repo.h"
34 #if !defined(_NSIG) && defined(NSIG)
38 extern char **environ
;
42 ///////////////////////////////////////////////////////////////////////////////
44 // build environment pair list
45 static char **build_envp(CArrRef envs
, std::vector
<String
> &senvs
) {
47 int size
= envs
.size();
49 envp
= (char **)malloc((size
+ 1) * sizeof(char *));
51 for (ArrayIter
iter(envs
); iter
; ++iter
, ++i
) {
53 nvpair
+= iter
.first().toString();
55 nvpair
+= iter
.second().toString();
57 String env
= nvpair
.detach();
59 *(envp
+ i
) = (char *)env
.data();
67 static bool check_cmd(const char *cmd
) {
68 const char *cmd_tmp
= cmd
;
71 while (isblank(*cmd_tmp
)) cmd_tmp
++;
72 const char *space
= strchr(cmd_tmp
, ' ');
73 unsigned int cmd_len
= strlen(cmd_tmp
);
75 cmd_len
= space
- cmd_tmp
;
77 for (unsigned int i
= 0; i
< RuntimeOption::AllowedExecCmds
.size(); i
++) {
78 std::string
&allowedCmd
= RuntimeOption::AllowedExecCmds
[i
];
79 if (allowedCmd
.size() != cmd_len
) {
82 if (strncmp(allowedCmd
.c_str(), cmd_tmp
, allowedCmd
.size()) == 0) {
88 String file
= g_vmContext
->getContainingFileName();
89 int line
= g_vmContext
->getLine();
90 Logger::Warning("Command %s is not in the whitelist, called at %s:%d",
91 cmd_tmp
, file
.data(), line
);
92 if (!RuntimeOption::WhitelistExecWarningOnly
) {
96 const char *bar
= strchr(cmd_tmp
, '|');
97 if (!bar
) { // no pipe, we are done
105 ///////////////////////////////////////////////////////////////////////////////
108 IMPLEMENT_DEFAULT_EXTENSION(pcntl
);
110 int64_t f_pcntl_alarm(int seconds
) {
111 return alarm(seconds
);
114 void f_pcntl_exec(CStrRef path
, CArrRef args
/* = null_array */,
115 CArrRef envs
/* = null_array */) {
116 if (RuntimeOption::WhitelistExec
&& !check_cmd(path
.data())) {
119 if (Repo::prefork()) {
120 raise_error("execing is disallowed in multi-threaded mode");
124 // build argumnent list
125 std::vector
<String
> sargs
; // holding those char *
126 int size
= args
.size();
127 char **argv
= (char **)malloc((size
+ 2) * sizeof(char *));
128 *argv
= (char *)path
.data();
132 for (ArrayIter
iter(args
); iter
; ++iter
, ++i
) {
133 String arg
= iter
.second().toString();
134 sargs
.push_back(arg
);
135 *(argv
+ i
) = (char *)arg
.data();
140 // build environment pair list
141 std::vector
<String
> senvs
; // holding those char *
142 char **envp
= build_envp(envs
, senvs
);
143 if (execve(path
.c_str(), argv
, envp
) == -1) {
144 raise_warning("Error has occured: (errno %d) %s",
145 errno
, Util::safe_strerror(errno
).c_str());
152 int64_t f_pcntl_fork() {
153 if (RuntimeOption::ServerExecutionMode()) {
154 raise_error("forking is disallowed in server mode");
157 if (Repo::prefork()) {
158 raise_error("forking is disallowed in multi-threaded mode");
169 Variant
f_pcntl_getpriority(int pid
/* = 0 */,
170 int process_identifier
/* = 0 */) {
174 if (process_identifier
== 0) {
175 process_identifier
= PRIO_PROCESS
;
178 // this isn't thread-safe, but probably not a huge deal
180 int pri
= getpriority(process_identifier
, pid
);
184 raise_warning("Error %d: No process was located using the given "
185 "parameters", errno
);
188 raise_warning("Error %d: Invalid identifier flag", errno
);
191 raise_warning("Unknown error %d has occured", errno
);
199 bool f_pcntl_setpriority(int priority
, int pid
/* = 0 */,
200 int process_identifier
/* = 0 */) {
204 if (process_identifier
== 0) {
205 process_identifier
= PRIO_PROCESS
;
208 if (setpriority(process_identifier
, pid
, priority
)) {
211 raise_warning("Error %d: No process was located using the given "
212 "parameters", errno
);
215 raise_warning("Error %d: Invalid identifier flag", errno
);
218 raise_warning("Error %d: A process was located, but neither its "
219 "effective nor real user ID matched the effective "
220 "user ID of the caller", errno
);
223 raise_warning("Error %d: Only a super user may attempt to increase "
224 "the process priority", errno
);
227 raise_warning("Unknown error %d has occured", errno
);
235 /* php_signal using sigaction is derrived from Advanced Programing
236 * in the Unix Environment by W. Richard Stevens p 298. */
237 typedef void Sigfunc(int);
238 static Sigfunc
*php_signal(int signo
, Sigfunc
*func
, bool restart
) {
239 struct sigaction act
,oact
;
240 act
.sa_handler
= func
;
241 sigemptyset(&act
.sa_mask
);
243 if (signo
== SIGALRM
|| (!restart
)) {
245 act
.sa_flags
|= SA_INTERRUPT
; /* SunOS */
249 act
.sa_flags
|= SA_RESTART
; /* SVR4, 4.3+BSD */
252 if (sigaction(signo
, &act
, &oact
) < 0)
255 return oact
.sa_handler
;
258 /* Our custom signal handler that calls the appropriate php_function */
259 class SignalHandlers
: public RequestEventHandler
{
262 memset(signaled
, 0, sizeof(signaled
));
263 pthread_sigmask(SIG_SETMASK
, NULL
, &oldSet
);
265 virtual void requestInit() {
267 // restore the old signal mask, thus unblock those that should be
268 pthread_sigmask(SIG_SETMASK
, &oldSet
, NULL
);
270 virtual void requestShutdown() {
274 pthread_sigmask(SIG_BLOCK
, &set
, NULL
);
283 IMPLEMENT_STATIC_REQUEST_LOCAL(SignalHandlers
, s_signal_handlers
);
285 // We must register the s_signal_handlers RequestEventHandler
286 // immediately: otherwise, pcntl_signal_handler might try to register
287 // it while processing a signal, which means calling malloc to insert
288 // it into various vectors and sets, which is not ok from a signal
290 static InitFiniNode
initSignalHandler(
291 [] { s_signal_handlers
.get(); },
292 InitFiniNode::When::ThreadInit
295 static void pcntl_signal_handler(int signo
) {
296 if (signo
> 0 && signo
< _NSIG
&& !g_context
.isNull()) {
297 s_signal_handlers
->signaled
[signo
] = 1;
298 RequestInjectionData
&data
= ThreadInfo::s_threadInfo
.getNoCheck()->
300 data
.setSignaledFlag();
304 class SignalHandlersStaticInitializer
{
306 SignalHandlersStaticInitializer() {
307 signal(SIGALRM
, pcntl_signal_handler
);
308 signal(SIGUSR1
, pcntl_signal_handler
);
309 signal(SIGUSR2
, pcntl_signal_handler
);
312 static SignalHandlersStaticInitializer s_signal_handlers_initializer
;
314 bool f_pcntl_signal_dispatch() {
315 int *signaled
= s_signal_handlers
->signaled
;
316 for (int i
= 0; i
< _NSIG
; i
++) {
319 if (s_signal_handlers
->handlers
.exists(i
)) {
320 vm_call_user_func(s_signal_handlers
->handlers
[i
],
328 bool f_pcntl_signal(int signo
, CVarRef handler
,
329 bool restart_syscalls
/* = true */) {
330 /* Special long value case for SIG_DFL and SIG_IGN */
331 if (handler
.isInteger()) {
332 int64_t handle
= handler
.toInt64();
333 if (handle
!= (long)SIG_DFL
&& handle
!= (long)SIG_IGN
) {
334 raise_warning("Invalid value for handle argument specified");
336 if (php_signal(signo
, (Sigfunc
*)handle
, restart_syscalls
) == SIG_ERR
) {
337 raise_warning("Error assigning signal");
343 if (!f_is_callable(handler
)) {
344 raise_warning("%s is not a callable function name error",
345 handler
.toString().data());
349 s_signal_handlers
->handlers
.set(signo
, handler
);
351 if (php_signal(signo
, pcntl_signal_handler
, restart_syscalls
) == SIG_ERR
) {
352 raise_warning("Error assigning signal");
358 int64_t f_pcntl_wait(VRefParam status
, int options
/* = 0 */) {
361 child_id
= LightProcess::pcntl_waitpid(-1, &nstatus
, options
);
363 child_id = wait3(&nstatus, options, NULL);
365 child_id = wait(&nstatus);
371 int64_t f_pcntl_waitpid(int pid
, VRefParam status
, int options
/* = 0 */) {
372 int nstatus
= status
;
373 pid_t child_id
= LightProcess::pcntl_waitpid((pid_t
)pid
, &nstatus
, options
);
378 int64_t f_pcntl_wexitstatus(int status
) {
379 return WEXITSTATUS(status
);
382 bool f_pcntl_wifexited(int status
) { return WIFEXITED(status
);}
383 bool f_pcntl_wifsignaled(int status
) { return WIFSIGNALED(status
);}
384 bool f_pcntl_wifstopped(int status
) { return WIFSTOPPED(status
);}
385 int64_t f_pcntl_wstopsig(int status
) { return WSTOPSIG(status
);}
386 int64_t f_pcntl_wtermsig(int status
) { return WTERMSIG(status
);}
388 ///////////////////////////////////////////////////////////////////////////////
391 #define EXEC_INPUT_BUF 4096
393 class ShellExecContext
{
395 ShellExecContext() : m_proc(NULL
) {
396 m_sig_handler
= signal(SIGCHLD
, SIG_DFL
);
399 ~ShellExecContext() {
401 LightProcess::pclose(m_proc
);
404 signal(SIGCHLD
, m_sig_handler
);
408 FILE *exec(const char *cmd
) {
409 assert(m_proc
== NULL
);
410 if (RuntimeOption::WhitelistExec
&& !check_cmd(cmd
)) {
413 m_proc
= LightProcess::popen(cmd
, "r", g_context
->getCwd().data());
414 if (m_proc
== NULL
) {
415 raise_warning("Unable to execute '%s'", cmd
);
421 int status
= LightProcess::pclose(m_proc
);
427 void (*m_sig_handler
)(int);
431 String
f_shell_exec(CStrRef cmd
) {
432 ShellExecContext ctx
;
433 FILE *fp
= ctx
.exec(cmd
.c_str());
437 return sbuf
.detach();
440 String
f_exec(CStrRef command
, VRefParam output
/* = null */,
441 VRefParam return_var
/* = null */) {
442 ShellExecContext ctx
;
443 FILE *fp
= ctx
.exec(command
.c_str());
448 Array lines
= StringUtil::Explode(sbuf
.detach(), "\n").toArray();
449 int ret
= ctx
.exit();
450 if (WIFEXITED(ret
)) ret
= WEXITSTATUS(ret
);
452 int count
= lines
.size();
453 if (count
> 0 && lines
[count
- 1].toString().empty()) {
454 count
--; // remove explode()'s last empty line
456 if (!output
.is(KindOfArray
)) {
457 output
= Array(ArrayData::Create());
460 for (int i
= 0; i
< count
; i
++) {
461 output
.append(lines
[i
]);
464 if (!count
|| lines
.empty()) {
468 return StringUtil::Trim(lines
[count
- 1].toString(),
469 StringUtil::TrimType::Right
);
472 void f_passthru(CStrRef command
, VRefParam return_var
/* = null */) {
473 ShellExecContext ctx
;
474 FILE *fp
= ctx
.exec(command
.c_str());
479 int len
= read(fileno(fp
), buffer
, sizeof(buffer
) - 1);
480 if (len
== -1 && errno
== EINTR
) continue;
481 if (len
<= 0) break; // break on error or EOF
483 echo(String(buffer
, len
, CopyString
));
485 int ret
= ctx
.exit();
486 if (WIFEXITED(ret
)) ret
= WEXITSTATUS(ret
);
490 String
f_system(CStrRef command
, VRefParam return_var
/* = null */) {
491 ShellExecContext ctx
;
492 FILE *fp
= ctx
.exec(command
.c_str());
499 Array lines
= StringUtil::Explode(sbuf
.detach(), "\n").toArray();
500 int ret
= ctx
.exit();
501 if (WIFEXITED(ret
)) ret
= WEXITSTATUS(ret
);
503 int count
= lines
.size();
504 if (count
> 0 && lines
[count
- 1].toString().empty()) {
505 count
--; // remove explode()'s last empty line
508 for (int i
= 0; i
< count
; i
++) {
509 echo(lines
[i
].toString());
512 if (!count
|| lines
.empty()) {
516 return StringUtil::Trim(lines
[count
- 1].toString(),
517 StringUtil::TrimType::Right
);
520 ///////////////////////////////////////////////////////////////////////////////
523 class ChildProcess
: public SweepableResourceData
{
525 DECLARE_OBJECT_ALLOCATION(ChildProcess
)
532 static StaticString s_class_name
;
533 // overriding ResourceData
534 virtual CStrRef
o_getClassNameHook() const { return s_class_name
; }
537 // Although the PHP doc about proc_close() says that the pipes need to be
538 // explicitly pclose()'ed, it seems that Zend is implicitly closing the
539 // pipes when proc_close() is called.
540 for (ArrayIter
iter(pipes
); iter
; ++iter
) {
541 iter
.second().toResource().getTyped
<PlainFile
>()->close();
548 wait_pid
= LightProcess::waitpid(child
, &wstatus
, 0,
549 RuntimeOption::RequestTimeoutSeconds
);
550 } while (wait_pid
== -1 && errno
== EINTR
);
552 if (wait_pid
== -1) {
556 if (WIFEXITED(wstatus
)) {
557 wstatus
= WEXITSTATUS(wstatus
);
562 IMPLEMENT_OBJECT_ALLOCATION_NO_DEFAULT_SWEEP(ChildProcess
);
563 void ChildProcess::sweep() {
564 // do nothing here, as everything will be collected by SmartAllocator
567 StaticString
ChildProcess::s_class_name("Process");
571 #define DESC_PARENT_MODE_WRITE 8
573 class DescriptorItem
{
576 index(-1), parentend(-1), childend(-1), mode(-1), mode_flags(-1) {
583 if (childend
>= 0) close(childend
);
584 if (parentend
>= 0) close(parentend
);
587 int index
; // desired fd number in child process
588 int parentend
, childend
; // fds for pipes in parent/child
589 int mode
; // mode for proc_open code
590 int mode_flags
; // mode flags for opening fds
592 static Mutex s_mutex
; // Prevents another thread from forking at the
593 // same time, before FD_CLOEXEC is set on the fds.
594 // NOTE: no need to lock with light processes.
596 bool readFile(File
*file
) {
598 childend
= dup(file
->fd());
600 raise_warning("unable to dup File-Handle for descriptor %d - %s",
601 index
, Util::safe_strerror(errno
).c_str());
607 bool readPipe(CStrRef zmode
) {
610 if (0 != pipe(newpipe
)) {
611 raise_warning("unable to create pipe %s",
612 Util::safe_strerror(errno
).c_str());
617 parentend
= newpipe
[1];
618 childend
= newpipe
[0];
619 mode
|= DESC_PARENT_MODE_WRITE
;
621 parentend
= newpipe
[0];
622 childend
= newpipe
[1];
624 mode_flags
= mode
& DESC_PARENT_MODE_WRITE
? O_WRONLY
: O_RDONLY
;
628 bool openFile(CStrRef zfile
, CStrRef zmode
) {
631 FILE *file
= fopen(zfile
.c_str(), zmode
.c_str());
633 raise_warning("Unable to open specified file: %s (mode %s)",
634 zfile
.data(), zmode
.data());
637 childend
= fileno(file
);
642 if ((mode
& ~DESC_PARENT_MODE_WRITE
) == DESC_PIPE
) {
643 close(parentend
); parentend
= -1;
645 if (dup2(childend
, index
) < 0) {
648 if (childend
!= index
) {
649 close(childend
); childend
= -1;
653 /* clean up all the child ends and then open streams on the parent
654 * ends, where appropriate */
655 Resource
dupParent() {
656 close(childend
); childend
= -1;
658 if ((mode
& ~DESC_PARENT_MODE_WRITE
) == DESC_PIPE
) {
659 /* mark the descriptor close-on-exec, so that it won't be inherited
660 by potential other children */
661 fcntl(parentend
, F_SETFD
, FD_CLOEXEC
);
662 return Resource(NEWOBJ(PlainFile
)(parentend
, true));
670 * This mutex must be non-reentrant so when the child process tries to unlock
671 * it after a fork(), the call to pthread_mutex_unlock() will succeed.
673 Mutex
DescriptorItem::s_mutex(false);
675 static bool pre_proc_open(CArrRef descriptorspec
,
676 vector
<DescriptorItem
> &items
) {
677 /* walk the descriptor spec and set up files/pipes */
678 items
.resize(descriptorspec
.size());
680 for (ArrayIter
iter(descriptorspec
); iter
; ++iter
, ++i
) {
681 DescriptorItem
&item
= items
[i
];
683 String index
= iter
.first();
684 if (!index
.isNumeric()) {
685 raise_warning("descriptor spec must be an integer indexed array");
688 item
.index
= index
.toInt32();
690 Variant descitem
= iter
.second();
691 if (descitem
.isResource()) {
692 File
*file
= descitem
.toResource().getTyped
<File
>();
693 if (!item
.readFile(file
)) break;
694 } else if (!descitem
.is(KindOfArray
)) {
695 raise_warning("Descriptor must be either an array or a File-Handle");
698 Array descarr
= descitem
.toArray();
699 if (!descarr
.exists(int64_t(0))) {
700 raise_warning("Missing handle qualifier in array");
703 String ztype
= descarr
[int64_t(0)].toString();
704 if (ztype
== "pipe") {
705 if (!descarr
.exists(int64_t(1))) {
706 raise_warning("Missing mode parameter for 'pipe'");
709 if (!item
.readPipe(descarr
[int64_t(1)].toString())) break;
710 } else if (ztype
== "file") {
711 if (!descarr
.exists(int64_t(1))) {
712 raise_warning("Missing file name parameter for 'file'");
715 if (!descarr
.exists(int64_t(2))) {
716 raise_warning("Missing mode parameter for 'file'");
719 if (!item
.openFile(descarr
[int64_t(1)].toString(), descarr
[int64_t(2)].toString())) {
723 raise_warning("%s is not a valid descriptor spec", ztype
.data());
729 if (i
>= descriptorspec
.size()) return true;
730 for (int j
= 0; j
< i
; j
++) {
736 static Variant
post_proc_open(CStrRef cmd
, Variant
&pipes
,
737 CVarRef env
, vector
<DescriptorItem
> &items
,
740 /* failed to fork() */
741 for (int i
= 0; i
< (int)items
.size(); i
++) {
744 raise_warning("fork failed - %s", Util::safe_strerror(errno
).c_str());
748 /* we forked/spawned and this is the parent */
749 ChildProcess
*proc
= NEWOBJ(ChildProcess
)();
753 for (int i
= 0; i
< (int)items
.size(); i
++) {
754 Resource f
= items
[i
].dupParent();
756 proc
->pipes
.append(f
);
758 pipes
.set(items
[i
].index
, f
);
760 return Resource(proc
);
763 Variant
f_proc_open(CStrRef cmd
, CArrRef descriptorspec
, VRefParam pipes
,
764 CStrRef cwd
/* = null_string */,
765 CVarRef env
/* = null_variant */,
766 CVarRef other_options
/* = null_variant */) {
767 if (RuntimeOption::WhitelistExec
&& !check_cmd(cmd
.data())) {
771 std::vector
<DescriptorItem
> items
;
776 } else if (!g_context
->getCwd().empty()) {
777 scwd
= g_context
->getCwd().c_str();
783 // Build out an environment that conceptually matches what we'd
784 // see if we were to iterate the environment and call getenv()
787 // Env vars defined in the hdf file go in first
788 for (std::map
<string
, string
>::const_iterator iter
=
789 RuntimeOption::EnvVariables
.begin();
790 iter
!= RuntimeOption::EnvVariables
.end(); ++iter
) {
791 enva
.set(String(iter
->first
), String(iter
->second
));
794 // global environment overrides the hdf
795 for (char **env
= environ
; env
&& *env
; env
++) {
796 char *p
= strchr(*env
, '=');
798 String
name(*env
, p
- *env
, CopyString
);
799 String
val(p
+ 1, CopyString
);
804 // and then any putenv() changes take precedence
805 for (ArrayIter
iter(g_context
->getEnvs()); iter
; ++iter
) {
806 enva
.set(iter
.first(), iter
.second());
809 enva
= env
.toArray();
814 if (LightProcess::Available()) {
815 // light process available
816 // there is no need to do any locking, because the forking is delegated
817 // to the light process
818 if (!pre_proc_open(descriptorspec
, items
)) return false;
819 std::vector
<int> created
;
820 std::vector
<int> intended
;
821 for (int i
= 0; i
< (int)items
.size(); i
++) {
822 created
.push_back(items
[i
].childend
);
823 intended
.push_back(items
[i
].index
);
826 std::vector
<std::string
> envs
;
827 for (ArrayIter
iter(enva
); iter
; ++iter
) {
829 nvpair
+= iter
.first().toString();
831 nvpair
+= iter
.second().toString();
832 string tmp
= nvpair
.detach().c_str();
833 if (tmp
.find('\n') == string::npos
) {
838 child
= LightProcess::proc_open(cmd
.c_str(), created
, intended
,
841 return post_proc_open(cmd
, pipes
, enva
, items
, child
);
844 Lock
lock(DescriptorItem::s_mutex
);
845 if (!pre_proc_open(descriptorspec
, items
)) return false;
848 // the parent process
849 return post_proc_open(cmd
, pipes
, enva
, items
, child
);
854 /* this is the child process */
856 /* close those descriptors that we just opened for the parent stuff,
857 * dup new descriptors into required descriptors and close the original
859 for (int i
= 0; i
< (int)items
.size(); i
++) {
862 if (scwd
.length() > 0 && chdir(scwd
.c_str())) {
863 // chdir failed, the working directory remains unchanged
865 vector
<String
> senvs
; // holding those char *
866 char **envp
= build_envp(enva
, senvs
);
867 execle("/bin/sh", "sh", "-c", cmd
.data(), NULL
, envp
);
872 bool f_proc_terminate(CResRef process
, int signal
/* = 0 */) {
873 ChildProcess
*proc
= process
.getTyped
<ChildProcess
>();
874 return kill(proc
->child
, signal
<= 0 ? SIGTERM
: signal
) == 0;
877 int64_t f_proc_close(CResRef process
) {
878 return process
.getTyped
<ChildProcess
>()->close();
882 s_command("command"),
884 s_running("running"),
885 s_signaled("signaled"),
886 s_stopped("stopped"),
887 s_exitcode("exitcode"),
888 s_termsig("termsig"),
889 s_stopsig("stopsig");
891 Array
f_proc_get_status(CResRef process
) {
892 ChildProcess
*proc
= process
.getTyped
<ChildProcess
>();
897 LightProcess::waitpid(proc
->child
, &wstatus
, WNOHANG
|WUNTRACED
);
899 bool running
= true, signaled
= false, stopped
= false;
900 int exitcode
= -1, termsig
= 0, stopsig
= 0;
901 if (wait_pid
== proc
->child
) {
902 if (WIFEXITED(wstatus
)) {
904 exitcode
= WEXITSTATUS(wstatus
);
906 if (WIFSIGNALED(wstatus
)) {
909 termsig
= WTERMSIG(wstatus
);
911 if (WIFSTOPPED(wstatus
)) {
913 stopsig
= WSTOPSIG(wstatus
);
915 } else if (wait_pid
== -1) {
920 ret
.set(s_command
, proc
->command
);
921 ret
.set(s_pid
, proc
->child
);
922 ret
.set(s_running
, running
);
923 ret
.set(s_signaled
, signaled
);
924 ret
.set(s_stopped
, stopped
);
925 ret
.set(s_exitcode
, exitcode
);
926 ret
.set(s_termsig
, termsig
);
927 ret
.set(s_stopsig
, stopsig
);
931 bool f_proc_nice(int increment
) {
932 if (nice(increment
) < 0 && errno
) {
933 raise_warning("Only a super user may attempt to increase the "
934 "priority of a process");
940 ///////////////////////////////////////////////////////////////////////////////
943 String
f_escapeshellarg(CStrRef arg
) {
945 char *ret
= string_escape_shell_arg(arg
.c_str());
946 return String(ret
, AttachString
);
951 String
f_escapeshellcmd(CStrRef command
) {
952 if (!command
.empty()) {
953 char *ret
= string_escape_shell_cmd(command
.c_str());
954 return String(ret
, AttachString
);
959 ///////////////////////////////////////////////////////////////////////////////