Rename runtime/base/zend_* to zend-
[hiphop-php.git] / hphp / runtime / ext / ext_process.cpp
blob7fbd68707b4267b803e5e2081011f96dae317984
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
24 #include <unistd.h>
25 #include <fcntl.h>
26 #include <signal.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)
35 # define _NSIG NSIG
36 #endif
38 extern char **environ;
40 namespace HPHP {
42 ///////////////////////////////////////////////////////////////////////////////
44 // build environment pair list
45 static char **build_envp(CArrRef envs, std::vector<String> &senvs) {
46 char **envp = NULL;
47 int size = envs.size();
48 if (size) {
49 envp = (char **)malloc((size + 1) * sizeof(char *));
50 int i = 0;
51 for (ArrayIter iter(envs); iter; ++iter, ++i) {
52 StringBuffer nvpair;
53 nvpair += iter.first().toString();
54 nvpair += '=';
55 nvpair += iter.second().toString();
57 String env = nvpair.detach();
58 senvs.push_back(env);
59 *(envp + i) = (char *)env.data();
61 *(envp + i) = NULL;
63 return envp;
66 // check whitelist
67 static bool check_cmd(const char *cmd) {
68 const char *cmd_tmp = cmd;
69 while (true) {
70 bool allow = false;
71 while (isblank(*cmd_tmp)) cmd_tmp++;
72 const char *space = strchr(cmd_tmp, ' ');
73 unsigned int cmd_len = strlen(cmd_tmp);
74 if (space) {
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) {
80 continue;
82 if (strncmp(allowedCmd.c_str(), cmd_tmp, allowedCmd.size()) == 0) {
83 allow = true;
84 break;
87 if (!allow) {
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) {
93 return false;
96 const char *bar = strchr(cmd_tmp, '|');
97 if (!bar) { // no pipe, we are done
98 return true;
100 cmd_tmp = bar + 1;
102 return false;
105 ///////////////////////////////////////////////////////////////////////////////
106 // pcntl
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())) {
117 return;
119 if (Repo::prefork()) {
120 raise_error("execing is disallowed in multi-threaded mode");
121 return;
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();
129 int i = 1;
130 if (size) {
131 sargs.reserve(size);
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();
138 *(argv + i) = NULL;
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());
148 free(envp);
149 free(argv);
152 int64_t f_pcntl_fork() {
153 if (RuntimeOption::ServerExecutionMode()) {
154 raise_error("forking is disallowed in server mode");
155 return -1;
157 if (Repo::prefork()) {
158 raise_error("forking is disallowed in multi-threaded mode");
159 return -1;
162 std::cout.flush();
163 std::cerr.flush();
164 pid_t pid = fork();
165 Repo::postfork(pid);
166 return pid;
169 Variant f_pcntl_getpriority(int pid /* = 0 */,
170 int process_identifier /* = 0 */) {
171 if (pid == 0) {
172 pid = getpid();
174 if (process_identifier == 0) {
175 process_identifier = PRIO_PROCESS;
178 // this isn't thread-safe, but probably not a huge deal
179 errno = 0;
180 int pri = getpriority(process_identifier, pid);
181 if (errno) {
182 switch (errno) {
183 case ESRCH:
184 raise_warning("Error %d: No process was located using the given "
185 "parameters", errno);
186 break;
187 case EINVAL:
188 raise_warning("Error %d: Invalid identifier flag", errno);
189 break;
190 default:
191 raise_warning("Unknown error %d has occured", errno);
192 break;
194 return false;
196 return pri;
199 bool f_pcntl_setpriority(int priority, int pid /* = 0 */,
200 int process_identifier /* = 0 */) {
201 if (pid == 0) {
202 pid = getpid();
204 if (process_identifier == 0) {
205 process_identifier = PRIO_PROCESS;
208 if (setpriority(process_identifier, pid, priority)) {
209 switch (errno) {
210 case ESRCH:
211 raise_warning("Error %d: No process was located using the given "
212 "parameters", errno);
213 break;
214 case EINVAL:
215 raise_warning("Error %d: Invalid identifier flag", errno);
216 break;
217 case EPERM:
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);
221 break;
222 case EACCES:
223 raise_warning("Error %d: Only a super user may attempt to increase "
224 "the process priority", errno);
225 break;
226 default:
227 raise_warning("Unknown error %d has occured", errno);
228 break;
230 return false;
232 return true;
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);
242 act.sa_flags = 0;
243 if (signo == SIGALRM || (!restart)) {
244 #ifdef SA_INTERRUPT
245 act.sa_flags |= SA_INTERRUPT; /* SunOS */
246 #endif
247 } else {
248 #ifdef SA_RESTART
249 act.sa_flags |= SA_RESTART; /* SVR4, 4.3+BSD */
250 #endif
252 if (sigaction(signo, &act, &oact) < 0)
253 return SIG_ERR;
255 return oact.sa_handler;
258 /* Our custom signal handler that calls the appropriate php_function */
259 class SignalHandlers : public RequestEventHandler {
260 public:
261 SignalHandlers() {
262 memset(signaled, 0, sizeof(signaled));
263 pthread_sigmask(SIG_SETMASK, NULL, &oldSet);
265 virtual void requestInit() {
266 handlers.reset();
267 // restore the old signal mask, thus unblock those that should be
268 pthread_sigmask(SIG_SETMASK, &oldSet, NULL);
270 virtual void requestShutdown() {
271 // block all signals
272 sigset_t set;
273 sigfillset(&set);
274 pthread_sigmask(SIG_BLOCK, &set, NULL);
276 handlers.reset();
279 Array handlers;
280 int signaled[_NSIG];
281 sigset_t oldSet;
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
289 // handler.
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()->
299 m_reqInjectionData;
300 data.setSignaledFlag();
304 class SignalHandlersStaticInitializer {
305 public:
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++) {
317 if (signaled[i]) {
318 signaled[i] = 0;
319 if (s_signal_handlers->handlers.exists(i)) {
320 vm_call_user_func(s_signal_handlers->handlers[i],
321 CREATE_VECTOR1(i));
325 return true;
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");
338 return false;
340 return true;
343 if (!f_is_callable(handler)) {
344 raise_warning("%s is not a callable function name error",
345 handler.toString().data());
346 return false;
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");
353 return false;
355 return true;
358 int64_t f_pcntl_wait(VRefParam status, int options /* = 0 */) {
359 int child_id;
360 int nstatus = 0;
361 child_id = LightProcess::pcntl_waitpid(-1, &nstatus, options);
362 /* if (options) {
363 child_id = wait3(&nstatus, options, NULL);
364 } else {
365 child_id = wait(&nstatus);
367 status = nstatus;
368 return child_id;
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);
374 status = nstatus;
375 return child_id;
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 ///////////////////////////////////////////////////////////////////////////////
389 // popen
391 #define EXEC_INPUT_BUF 4096
393 class ShellExecContext {
394 public:
395 ShellExecContext() : m_proc(NULL) {
396 m_sig_handler = signal(SIGCHLD, SIG_DFL);
399 ~ShellExecContext() {
400 if (m_proc) {
401 LightProcess::pclose(m_proc);
403 if (m_sig_handler) {
404 signal(SIGCHLD, m_sig_handler);
408 FILE *exec(const char *cmd) {
409 assert(m_proc == NULL);
410 if (RuntimeOption::WhitelistExec && !check_cmd(cmd)) {
411 return NULL;
413 m_proc = LightProcess::popen(cmd, "r", g_context->getCwd().data());
414 if (m_proc == NULL) {
415 raise_warning("Unable to execute '%s'", cmd);
417 return m_proc;
420 int exit() {
421 int status = LightProcess::pclose(m_proc);
422 m_proc = NULL;
423 return status;
426 private:
427 void (*m_sig_handler)(int);
428 FILE *m_proc;
431 String f_shell_exec(CStrRef cmd) {
432 ShellExecContext ctx;
433 FILE *fp = ctx.exec(cmd.c_str());
434 if (!fp) return "";
435 StringBuffer sbuf;
436 sbuf.read(fp);
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());
444 if (!fp) return "";
445 StringBuffer sbuf;
446 sbuf.read(fp);
448 Array lines = StringUtil::Explode(sbuf.detach(), "\n").toArray();
449 int ret = ctx.exit();
450 if (WIFEXITED(ret)) ret = WEXITSTATUS(ret);
451 return_var = 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()) {
465 return String();
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());
475 if (!fp) return;
477 char buffer[1024];
478 while (true) {
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
482 buffer[len] = '\0';
483 echo(String(buffer, len, CopyString));
485 int ret = ctx.exit();
486 if (WIFEXITED(ret)) ret = WEXITSTATUS(ret);
487 return_var = ret;
490 String f_system(CStrRef command, VRefParam return_var /* = null */) {
491 ShellExecContext ctx;
492 FILE *fp = ctx.exec(command.c_str());
493 if (!fp) return "";
494 StringBuffer sbuf;
495 if (fp) {
496 sbuf.read(fp);
499 Array lines = StringUtil::Explode(sbuf.detach(), "\n").toArray();
500 int ret = ctx.exit();
501 if (WIFEXITED(ret)) ret = WEXITSTATUS(ret);
502 return_var = 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());
510 echo("\n");
512 if (!count || lines.empty()) {
513 return String();
516 return StringUtil::Trim(lines[count - 1].toString(),
517 StringUtil::TrimType::Right);
520 ///////////////////////////////////////////////////////////////////////////////
521 // proc
523 class ChildProcess : public SweepableResourceData {
524 public:
525 DECLARE_OBJECT_ALLOCATION(ChildProcess)
527 pid_t child;
528 Array pipes;
529 String command;
530 Variant env;
532 static StaticString s_class_name;
533 // overriding ResourceData
534 virtual CStrRef o_getClassNameHook() const { return s_class_name; }
536 int close() {
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();
543 pipes.clear();
545 pid_t wait_pid;
546 int wstatus;
547 do {
548 wait_pid = LightProcess::waitpid(child, &wstatus, 0,
549 RuntimeOption::RequestTimeoutSeconds);
550 } while (wait_pid == -1 && errno == EINTR);
552 if (wait_pid == -1) {
553 return -1;
556 if (WIFEXITED(wstatus)) {
557 wstatus = WEXITSTATUS(wstatus);
559 return 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");
569 #define DESC_PIPE 1
570 #define DESC_FILE 2
571 #define DESC_PARENT_MODE_WRITE 8
573 class DescriptorItem {
574 public:
575 DescriptorItem() :
576 index(-1), parentend(-1), childend(-1), mode(-1), mode_flags(-1) {
579 ~DescriptorItem() {
582 void cleanup() {
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) {
597 mode = DESC_FILE;
598 childend = dup(file->fd());
599 if (childend < 0) {
600 raise_warning("unable to dup File-Handle for descriptor %d - %s",
601 index, Util::safe_strerror(errno).c_str());
602 return false;
604 return true;
607 bool readPipe(CStrRef zmode) {
608 mode = DESC_PIPE;
609 int newpipe[2];
610 if (0 != pipe(newpipe)) {
611 raise_warning("unable to create pipe %s",
612 Util::safe_strerror(errno).c_str());
613 return false;
616 if (zmode != "w") {
617 parentend = newpipe[1];
618 childend = newpipe[0];
619 mode |= DESC_PARENT_MODE_WRITE;
620 } else {
621 parentend = newpipe[0];
622 childend = newpipe[1];
624 mode_flags = mode & DESC_PARENT_MODE_WRITE ? O_WRONLY : O_RDONLY;
625 return true;
628 bool openFile(CStrRef zfile, CStrRef zmode) {
629 mode = DESC_FILE;
630 /* try a wrapper */
631 FILE *file = fopen(zfile.c_str(), zmode.c_str());
632 if (!file) {
633 raise_warning("Unable to open specified file: %s (mode %s)",
634 zfile.data(), zmode.data());
635 return false;
637 childend = fileno(file);
638 return true;
641 void dupChild() {
642 if ((mode & ~DESC_PARENT_MODE_WRITE) == DESC_PIPE) {
643 close(parentend); parentend = -1;
645 if (dup2(childend, index) < 0) {
646 perror("dup2");
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));
665 return Resource();
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());
679 int i = 0;
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");
686 break;
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");
696 break;
697 } else {
698 Array descarr = descitem.toArray();
699 if (!descarr.exists(int64_t(0))) {
700 raise_warning("Missing handle qualifier in array");
701 break;
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'");
707 break;
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'");
713 break;
715 if (!descarr.exists(int64_t(2))) {
716 raise_warning("Missing mode parameter for 'file'");
717 break;
719 if (!item.openFile(descarr[int64_t(1)].toString(), descarr[int64_t(2)].toString())) {
720 break;
722 } else {
723 raise_warning("%s is not a valid descriptor spec", ztype.data());
724 break;
729 if (i >= descriptorspec.size()) return true;
730 for (int j = 0; j < i; j++) {
731 items[j].cleanup();
733 return false;
736 static Variant post_proc_open(CStrRef cmd, Variant &pipes,
737 CVarRef env, vector<DescriptorItem> &items,
738 pid_t child) {
739 if (child < 0) {
740 /* failed to fork() */
741 for (int i = 0; i < (int)items.size(); i++) {
742 items[i].cleanup();
744 raise_warning("fork failed - %s", Util::safe_strerror(errno).c_str());
745 return false;
748 /* we forked/spawned and this is the parent */
749 ChildProcess *proc = NEWOBJ(ChildProcess)();
750 proc->command = cmd;
751 proc->child = child;
752 proc->env = env;
753 for (int i = 0; i < (int)items.size(); i++) {
754 Resource f = items[i].dupParent();
755 if (!f.isNull()) {
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())) {
768 return false;
771 std::vector<DescriptorItem> items;
773 string scwd = "";
774 if (!cwd.empty()) {
775 scwd = cwd.c_str();
776 } else if (!g_context->getCwd().empty()) {
777 scwd = g_context->getCwd().c_str();
780 Array enva;
782 if (env.isNull()) {
783 // Build out an environment that conceptually matches what we'd
784 // see if we were to iterate the environment and call getenv()
785 // for each name.
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, '=');
797 if (p) {
798 String name(*env, p - *env, CopyString);
799 String val(p + 1, CopyString);
800 enva.set(name, val);
804 // and then any putenv() changes take precedence
805 for (ArrayIter iter(g_context->getEnvs()); iter; ++iter) {
806 enva.set(iter.first(), iter.second());
808 } else {
809 enva = env.toArray();
812 pid_t child;
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) {
828 StringBuffer nvpair;
829 nvpair += iter.first().toString();
830 nvpair += '=';
831 nvpair += iter.second().toString();
832 string tmp = nvpair.detach().c_str();
833 if (tmp.find('\n') == string::npos) {
834 envs.push_back(tmp);
838 child = LightProcess::proc_open(cmd.c_str(), created, intended,
839 scwd.c_str(), envs);
840 assert(child);
841 return post_proc_open(cmd, pipes, enva, items, child);
842 } else {
843 /* the unix way */
844 Lock lock(DescriptorItem::s_mutex);
845 if (!pre_proc_open(descriptorspec, items)) return false;
846 child = fork();
847 if (child) {
848 // the parent process
849 return post_proc_open(cmd, pipes, enva, items, child);
853 assert(child == 0);
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
858 * cruft */
859 for (int i = 0; i < (int)items.size(); i++) {
860 items[i].dupChild();
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);
868 free(envp);
869 _exit(127);
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();
881 const StaticString
882 s_command("command"),
883 s_pid("pid"),
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>();
894 errno = 0;
895 int wstatus;
896 pid_t wait_pid =
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)) {
903 running = false;
904 exitcode = WEXITSTATUS(wstatus);
906 if (WIFSIGNALED(wstatus)) {
907 running = false;
908 signaled = true;
909 termsig = WTERMSIG(wstatus);
911 if (WIFSTOPPED(wstatus)) {
912 stopped = true;
913 stopsig = WSTOPSIG(wstatus);
915 } else if (wait_pid == -1) {
916 running = false;
919 ArrayInit ret(8);
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);
928 return ret.create();
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");
935 return false;
937 return true;
940 ///////////////////////////////////////////////////////////////////////////////
941 // string functions
943 String f_escapeshellarg(CStrRef arg) {
944 if (!arg.empty()) {
945 char *ret = string_escape_shell_arg(arg.c_str());
946 return String(ret, AttachString);
948 return arg;
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);
956 return command;
959 ///////////////////////////////////////////////////////////////////////////////