make PHP_SAPI dynamic based on execution mode
[hiphop-php.git] / hphp / runtime / base / program_functions.cpp
blobf11b03e063fbb812c114d0b5f543e86f51431df9
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/base/program_functions.h"
18 #include "hphp/runtime/base/types.h"
19 #include "hphp/runtime/base/type_conversions.h"
20 #include "hphp/runtime/base/builtin_functions.h"
21 #include "hphp/runtime/base/execution_context.h"
22 #include "hphp/runtime/base/thread_init_fini.h"
23 #include "hphp/runtime/base/code_coverage.h"
24 #include "hphp/runtime/base/runtime_option.h"
25 #include "hphp/util/shared_memory_allocator.h"
26 #include "hphp/runtime/base/server/pagelet_server.h"
27 #include "hphp/runtime/base/server/xbox_server.h"
28 #include "hphp/runtime/base/server/http_server.h"
29 #include "hphp/runtime/base/server/replay_transport.h"
30 #include "hphp/runtime/base/server/http_request_handler.h"
31 #include "hphp/runtime/base/server/admin_request_handler.h"
32 #include "hphp/runtime/base/server/server_stats.h"
33 #include "hphp/runtime/base/server/server_note.h"
34 #include "hphp/runtime/base/memory/memory_manager.h"
35 #include "hphp/util/process.h"
36 #include "hphp/util/capability.h"
37 #include "hphp/util/embedded_data.h"
38 #include "hphp/util/timer.h"
39 #include "hphp/util/stack_trace.h"
40 #include "hphp/util/light_process.h"
41 #include "hphp/util/repo_schema.h"
42 #include "hphp/runtime/base/stat_cache.h"
43 #include "hphp/runtime/ext/extension.h"
44 #include "hphp/runtime/ext/ext_fb.h"
45 #include "hphp/runtime/ext/ext_json.h"
46 #include "hphp/runtime/ext/ext_variable.h"
47 #include "hphp/runtime/ext/ext_apc.h"
48 #include "hphp/runtime/ext/ext_function.h"
49 #include "hphp/runtime/debugger/debugger.h"
50 #include "hphp/runtime/debugger/debugger_client.h"
51 #include "hphp/runtime/base/util/simple_counter.h"
52 #include "hphp/runtime/base/util/extended_logger.h"
53 #include "hphp/runtime/base/file/stream_wrapper_registry.h"
55 #include <boost/program_options/options_description.hpp>
56 #include <boost/program_options/positional_options.hpp>
57 #include <boost/program_options/variables_map.hpp>
58 #include <boost/program_options/parsers.hpp>
59 #include <libgen.h>
60 #include <oniguruma.h>
61 #include "libxml/parser.h"
63 #include "hphp/runtime/base/file_repository.h"
65 #include "hphp/runtime/vm/runtime.h"
66 #include "hphp/runtime/vm/repo.h"
67 #include "hphp/runtime/vm/jit/translator.h"
68 #include "hphp/compiler/builtin_symbols.h"
70 using namespace boost::program_options;
71 using std::cout;
72 extern char **environ;
74 #define MAX_INPUT_NESTING_LEVEL 64
76 namespace HPHP {
78 extern InitFiniNode *extra_process_init, *extra_process_exit;
80 void initialize_repo();
83 * XXX: VM process initialization is handled through a function
84 * pointer so libhphp_runtime.a can be linked into programs that don't
85 * actually initialize the VM.
87 void (*g_vmProcessInit)();
89 ///////////////////////////////////////////////////////////////////////////////
90 // helpers
92 struct ProgramOptions {
93 string mode;
94 string config;
95 StringVec confStrings;
96 int port;
97 int portfd;
98 int sslportfd;
99 int admin_port;
100 string user;
101 string file;
102 string lint;
103 bool isTempFile;
104 int count;
105 bool noSafeAccessCheck;
106 StringVec args;
107 string buildId;
108 int xhprofFlags;
109 string show;
110 string parse;
112 Eval::DebuggerClientOptions debugger_options;
115 class StartTime {
116 public:
117 StartTime() : startTime(time(nullptr)) {}
118 time_t startTime;
120 static StartTime s_startTime;
121 static string tempFile;
123 time_t start_time() {
124 return s_startTime.startTime;
127 static void process_cmd_arguments(int argc, char **argv) {
128 SystemGlobals *g = (SystemGlobals *)get_global_variables();
129 g->GV(argc) = argc;
130 for (int i = 0; i < argc; i++) {
131 g->GV(argv).lvalAt() = String(argv[i]);
135 void process_env_variables(Variant &variables) {
136 for (std::map<string, string>::const_iterator iter =
137 RuntimeOption::EnvVariables.begin();
138 iter != RuntimeOption::EnvVariables.end(); ++iter) {
139 variables.set(String(iter->first), String(iter->second));
141 for (char **env = environ; env && *env; env++) {
142 char *p = strchr(*env, '=');
143 if (p) {
144 String name(*env, p - *env, CopyString);
145 register_variable(variables, (char*)name.data(),
146 String(p + 1, CopyString));
151 void register_variable(Variant &variables, char *name, CVarRef value,
152 bool overwrite /* = true */) {
153 // ignore leading spaces in the variable name
154 char *var = name;
155 while (*var && *var == ' ') {
156 var++;
159 // ensure that we don't have spaces or dots in the variable name
160 // (not binary safe)
161 bool is_array = false;
162 char *ip = nullptr; // index pointer
163 char *p = var;
164 for (; *p; p++) {
165 if (*p == ' ' || *p == '.') {
166 *p = '_';
167 } else if (*p == '[') {
168 is_array = true;
169 ip = p;
170 *p = 0;
171 break;
174 int var_len = p - var;
175 if (var_len == 0) {
176 // empty variable name, or variable name with a space in it
177 return;
180 vector<Variant> gpc_elements;
181 gpc_elements.reserve(MAX_INPUT_NESTING_LEVEL); // important, so no resize
182 Variant *symtable = &variables;
183 char *index = var;
184 int index_len = var_len;
186 if (is_array) {
187 int nest_level = 0;
188 while (true) {
189 if (++nest_level > MAX_INPUT_NESTING_LEVEL) {
190 Logger::Warning("Input variable nesting level exceeded");
191 return;
194 ip++;
195 char *index_s = ip;
196 int new_idx_len = 0;
197 if (isspace(*ip)) {
198 ip++;
200 if (*ip == ']') {
201 index_s = nullptr;
202 } else {
203 ip = strchr(ip, ']');
204 if (!ip) {
205 // PHP variables cannot contain '[' in their names,
206 // so we replace the character with a '_'
207 *(index_s - 1) = '_';
209 index_len = 0;
210 if (index) {
211 index_len = strlen(index);
213 goto plain_var;
215 *ip = 0;
216 new_idx_len = strlen(index_s);
219 if (!index) {
220 symtable->append(Array::Create());
221 gpc_elements.push_back(uninit_null());
222 gpc_elements.back().assignRef(
223 symtable->lvalAt((int)symtable->toArray().size() - 1));
224 } else {
225 String key(index, index_len, CopyString);
226 Variant v = symtable->rvalAt(key);
227 if (v.isNull() || !v.is(KindOfArray)) {
228 symtable->set(key, Array::Create());
230 gpc_elements.push_back(uninit_null());
231 gpc_elements.back().assignRef(symtable->lvalAt(key));
233 symtable = &gpc_elements.back();
234 /* ip pointed to the '[' character, now obtain the key */
235 index = index_s;
236 index_len = new_idx_len;
238 ip++;
239 if (*ip == '[') {
240 is_array = true;
241 *ip = 0;
242 } else {
243 goto plain_var;
246 } else {
247 plain_var:
248 if (!index) {
249 symtable->append(value);
250 } else {
251 String key(index, index_len, CopyString);
252 if (overwrite || !symtable->toArray().exists(key)) {
253 symtable->set(key, value);
259 enum ContextOfException {
260 ReqInitException = 1,
261 InvokeException,
262 HandlerException,
265 static void handle_exception_append_bt(std::string& errorMsg,
266 const ExtendedException& e) {
267 Array bt = e.getBackTrace();
268 if (!bt.empty()) {
269 errorMsg += ExtendedLogger::StringOfStackTrace(bt);
273 static void handle_exception_helper(bool& ret,
274 ExecutionContext* context,
275 std::string& errorMsg,
276 ContextOfException where,
277 bool& error,
278 bool richErrorMsg) {
279 try {
280 throw;
281 } catch (const Eval::DebuggerException &e) {
282 throw;
283 } catch (const ExitException &e) {
284 if (where == ReqInitException) {
285 ret = false;
286 } else if (where != HandlerException &&
287 !context->getExitCallback().isNull() &&
288 f_is_callable(context->getExitCallback())) {
289 Array stack = e.getBackTrace();
290 Array argv = CREATE_VECTOR2(e.ExitCode, stack);
291 vm_call_user_func(context->getExitCallback(), argv);
293 } catch (const PhpFileDoesNotExistException &e) {
294 ret = false;
295 if (where != HandlerException) {
296 raise_notice("%s", e.getMessage().c_str());
297 } else {
298 Logger::Error("%s", e.getMessage().c_str());
300 if (richErrorMsg) {
301 handle_exception_append_bt(errorMsg, e);
303 } catch (const UncatchableException &e) {
304 ret = false;
305 error = true;
306 errorMsg = "";
307 if (RuntimeOption::ServerStackTrace) {
308 errorMsg = e.what();
309 } else if (RuntimeOption::InjectedStackTrace) {
310 errorMsg = e.getMessage();
311 errorMsg += "\n";
312 errorMsg += ExtendedLogger::StringOfStackTrace(e.getBackTrace());
314 Logger::Error("%s", errorMsg.c_str());
315 if (richErrorMsg) {
316 handle_exception_append_bt(errorMsg, e);
318 } catch (const Exception &e) {
319 bool oldRet = ret;
320 bool origError = error;
321 std::string origErrorMsg = errorMsg;
322 ret = false;
323 error = true;
324 errorMsg = "";
325 if (where == HandlerException) {
326 errorMsg = "Exception handler threw an exception: ";
328 errorMsg += e.what();
329 if (where == InvokeException) {
330 bool handlerRet = context->onFatalError(e);
331 if (handlerRet) {
332 ret = oldRet;
333 error = origError;
334 errorMsg = origErrorMsg;
336 } else {
337 Logger::Error("%s", errorMsg.c_str());
339 if (richErrorMsg) {
340 const ExtendedException *ee = dynamic_cast<const ExtendedException *>(&e);
341 if (ee) {
342 handle_exception_append_bt(errorMsg, *ee);
345 } catch (const Object &e) {
346 bool oldRet = ret;
347 bool origError = error;
348 std::string origErrorMsg = errorMsg;
349 ret = false;
350 error = true;
351 errorMsg = "";
352 if (where == HandlerException) {
353 errorMsg = "Exception handler threw an object exception: ";
355 try {
356 errorMsg += e.toString().data();
357 } catch (...) {
358 errorMsg += "(unable to call toString())";
360 if (where == InvokeException) {
361 bool handlerRet = context->onUnhandledException(e);
362 if (handlerRet) {
363 ret = oldRet;
364 error = origError;
365 errorMsg = origErrorMsg;
367 } else {
368 Logger::Error("%s", errorMsg.c_str());
370 } catch (...) {
371 ret = false;
372 error = true;
373 errorMsg = "(unknown exception was thrown)";
374 Logger::Error("%s", errorMsg.c_str());
378 static bool hphp_chdir_file(const string filename) {
379 bool ret = false;
380 String s = File::TranslatePath(filename);
381 char *buf = strndup(s.data(), s.size());
382 char *dir = dirname(buf);
383 assert(dir);
384 if (dir) {
385 if (File::IsVirtualDirectory(dir)) {
386 g_context->setCwd(String(dir, CopyString));
387 ret = true;
388 } else {
389 struct stat sb;
390 stat(dir, &sb);
391 if ((sb.st_mode & S_IFMT) == S_IFDIR) {
392 ret = true;
393 if (*dir != '.') {
394 g_context->setCwd(String(dir, CopyString));
399 free(buf);
400 return ret;
403 void handle_destructor_exception(const char* situation) {
404 string errorMsg;
406 try {
407 throw;
408 } catch (ExitException &e) {
409 // ExitException is fine, no need to show a warning.
410 ThreadInfo::s_threadInfo->setPendingException(e.clone());
411 return;
412 } catch (Object &e) {
413 // For user exceptions, invoke the user exception handler
414 errorMsg = situation;
415 errorMsg += " threw an object exception: ";
416 try {
417 errorMsg += e.toString().data();
418 } catch (...) {
419 errorMsg += "(unable to call toString())";
421 } catch (Exception &e) {
422 ThreadInfo::s_threadInfo->setPendingException(e.clone());
423 errorMsg = situation;
424 errorMsg += " raised a fatal error: ";
425 errorMsg += e.what();
426 } catch (...) {
427 errorMsg = situation;
428 errorMsg += " threw an unknown exception";
430 // For fatal errors and unknown exceptions, we raise a warning.
431 // If there is a user error handler it will be invoked, otherwise
432 // the default error handler will be invoked.
433 try {
434 raise_debugging("%s", errorMsg.c_str());
435 } catch (...) {
436 // The user error handler fataled or threw an exception,
437 // print out the error message directly to the log
438 Logger::Warning("%s", errorMsg.c_str());
442 static const StaticString
443 s_HPHP("HPHP"),
444 s_HHVM("HHVM"),
445 s_HHVM_JIT("HHVM_JIT"),
446 s_REQUEST_START_TIME("REQUEST_START_TIME"),
447 s_REQUEST_TIME("REQUEST_TIME"),
448 s_REQUEST_TIME_FLOAT("REQUEST_TIME_FLOAT"),
449 s_DOCUMENT_ROOT("DOCUMENT_ROOT"),
450 s_SCRIPT_FILENAME("SCRIPT_FILENAME"),
451 s_SCRIPT_NAME("SCRIPT_NAME"),
452 s_PHP_SELF("PHP_SELF"),
453 s_argc("argc"),
454 s_argv("argv"),
455 s_PWD("PWD"),
456 s_HOSTNAME("HOSTNAME");
458 void execute_command_line_begin(int argc, char **argv, int xhprof) {
459 StackTraceNoHeap::AddExtraLogging("ThreadType", "CLI");
460 string args;
461 for (int i = 0; i < argc; i++) {
462 if (i) args += " ";
463 args += argv[i];
465 StackTraceNoHeap::AddExtraLogging("Arguments", args.c_str());
467 hphp_session_init();
468 ExecutionContext *context = g_context.getNoCheck();
469 context->obSetImplicitFlush(true);
471 SystemGlobals *g = (SystemGlobals *)get_global_variables();
473 process_env_variables(g->GV(_ENV));
474 g->GV(_ENV).set(s_HPHP, 1);
475 g->GV(_ENV).set(s_HHVM, 1);
476 if (RuntimeOption::EvalJit) {
477 g->GV(_ENV).set(s_HHVM_JIT, 1);
480 process_cmd_arguments(argc, argv);
482 Variant &server = g->GV(_SERVER);
483 process_env_variables(server);
484 time_t now;
485 struct timeval tp = {0};
486 double now_double;
487 if (!gettimeofday(&tp, nullptr)) {
488 now_double = (double)(tp.tv_sec + tp.tv_usec / 1000000.00);
489 now = tp.tv_sec;
490 } else {
491 now = time(nullptr);
492 now_double = (double)now;
494 String file = empty_string;
495 if (argc > 0) {
496 file = NEW(StringData)(argv[0], AttachLiteral);
498 server.set(s_REQUEST_START_TIME, now);
499 server.set(s_REQUEST_TIME, now);
500 server.set(s_REQUEST_TIME_FLOAT, now_double);
501 server.set(s_DOCUMENT_ROOT, empty_string);
502 server.set(s_SCRIPT_FILENAME, file);
503 server.set(s_SCRIPT_NAME, file);
504 server.set(s_PHP_SELF, file);
505 server.set(s_argv, g->GV(argv));
506 server.set(s_argc, g->GV(argc));
507 server.set(s_PWD, g_context->getCwd());
508 char hostname[1024];
509 if (!gethostname(hostname, 1024)) {
510 server.set(s_HOSTNAME, String(hostname, CopyString));
513 for(std::map<string,string>::iterator it =
514 RuntimeOption::ServerVariables.begin(),
515 end = RuntimeOption::ServerVariables.end(); it != end; ++it) {
516 server.set(String(it->first.c_str()), String(it->second.c_str()));
519 if (xhprof) {
520 f_xhprof_enable(xhprof, uninit_null());
524 void execute_command_line_end(int xhprof, bool coverage, const char *program) {
525 ThreadInfo *ti = ThreadInfo::s_threadInfo.getNoCheck();
527 if (RuntimeOption::EvalJit && RuntimeOption::EvalDumpTC) {
528 HPHP::Transl::tc_dump();
531 if (xhprof) {
532 f_var_dump(f_json_encode(f_xhprof_disable()));
534 hphp_context_exit(g_context.getNoCheck(), true, true, program);
535 hphp_session_exit();
536 if (coverage && ti->m_reqInjectionData.getCoverage() &&
537 !RuntimeOption::CodeCoverageOutputFile.empty()) {
538 ti->m_coverage->Report(RuntimeOption::CodeCoverageOutputFile);
542 static void pagein_self(void) {
543 unsigned long begin, end, inode, pgoff;
544 char mapname[PATH_MAX];
545 char perm[5];
546 char dev[6];
547 char *buf;
548 int bufsz;
549 int r;
550 FILE *fp;
552 // pad due to the spaces between the inode number and the mapname
553 bufsz = sizeof(unsigned long) * 4 + sizeof(mapname) + sizeof(char) * 11 + 100;
554 buf = (char *)malloc(bufsz);
555 if (buf == nullptr)
556 return;
558 Timer timer(Timer::WallTime, "mapping self");
559 fp = fopen("/proc/self/maps", "r");
560 if (fp != nullptr) {
561 while (!feof(fp)) {
562 if (fgets(buf, bufsz, fp) == 0)
563 break;
564 r = sscanf(buf, "%lx-%lx %4s %lx %5s %ld %s",
565 &begin, &end, perm, &pgoff, dev, &inode, mapname);
567 // page in read-only segments that correspond to a file on disk
568 if (r != 7 ||
569 perm[0] != 'r' ||
570 perm[1] != '-' ||
571 access(mapname, F_OK) != 0) {
572 continue;
575 if (mlock((void *)begin, end - begin) == 0) {
576 if (!RuntimeOption::LockCodeMemory) {
577 munlock((void *)begin, end - begin);
581 fclose(fp);
583 free(buf);
586 /* Sets RuntimeOption::ExecutionMode according
587 * to commandline options prior to config load
589 static void set_execution_mode(string mode) {
590 if (mode == "daemon" || mode == "server" || mode == "replay") {
591 RuntimeOption::ExecutionMode = "srv";
592 Logger::Escape = true;
593 } else if (mode == "run" || mode == "debug") {
594 RuntimeOption::ExecutionMode = "cli";
595 Logger::Escape = false;
596 } else if (mode == "translate") {
597 RuntimeOption::ExecutionMode = "";
598 Logger::Escape = false;
599 } else {
600 // Undefined mode
601 always_assert(false);
605 static int start_server(const std::string &username) {
606 // Before we start the webserver, make sure the entire
607 // binary is paged into memory.
608 pagein_self();
610 set_execution_mode("server");
611 HttpRequestHandler::GetAccessLog().init
612 (RuntimeOption::AccessLogDefaultFormat, RuntimeOption::AccessLogs,
613 username);
614 AdminRequestHandler::GetAccessLog().init
615 (RuntimeOption::AdminLogFormat, RuntimeOption::AdminLogSymLink,
616 RuntimeOption::AdminLogFile,
617 username);
619 void *sslCTX = nullptr;
620 if (RuntimeOption::EnableSSL) {
621 #ifdef _EVENT_USE_OPENSSL
622 struct ssl_config config;
623 if (RuntimeOption::SSLCertificateFile != "" &&
624 RuntimeOption::SSLCertificateKeyFile != "") {
625 config.cert_file = (char*)RuntimeOption::SSLCertificateFile.c_str();
626 config.pk_file = (char*)RuntimeOption::SSLCertificateKeyFile.c_str();
627 sslCTX = evhttp_init_openssl(&config);
628 if (!RuntimeOption::SSLCertificateDir.empty()) {
629 ServerNameIndication::load(sslCTX, config,
630 RuntimeOption::SSLCertificateDir);
632 } else {
633 Logger::Error("Invalid certificate file or key file");
635 #else
636 Logger::Error("A SSL enabled libevent is required");
637 #endif
640 #if !defined(SKIP_USER_CHANGE)
641 if (!username.empty()) {
642 if (Logger::UseCronolog) {
643 Cronolog::changeOwner(username, RuntimeOption::LogFileSymLink);
645 Capability::ChangeUnixUser(username);
646 LightProcess::ChangeUser(username);
648 #endif
650 Capability::SetDumpable();
652 // Create the HttpServer before any warmup requests to properly
653 // initialize the process
654 HttpServer::Server = HttpServerPtr(new HttpServer(sslCTX));
656 // If we have any warmup requests, replay them before listening for
657 // real connections
658 for (auto& file : RuntimeOption::ServerWarmupRequests) {
659 HttpRequestHandler handler;
660 ReplayTransport rt;
661 timespec start;
662 gettime(CLOCK_MONOTONIC, &start);
663 std::string error;
664 Logger::Info("Replaying warmup request %s", file.c_str());
665 try {
666 rt.onRequestStart(start);
667 rt.replayInput(Hdf(file));
668 handler.handleRequest(&rt);
669 Logger::Info("Finished successfully");
670 } catch (std::exception& e) {
671 error = e.what();
673 if (error.size()) {
674 Logger::Info("Got exception during warmup: %s", error.c_str());
678 HttpServer::Server->run();
679 return 0;
682 string translate_stack(const char *hexencoded, bool with_frame_numbers) {
683 if (!hexencoded || !*hexencoded) {
684 return "";
687 StackTrace st(hexencoded);
688 StackTrace::FramePtrVec frames;
689 st.get(frames);
691 std::ostringstream out;
692 for (unsigned int i = 0; i < frames.size(); i++) {
693 StackTrace::FramePtr f = frames[i];
694 if (with_frame_numbers) {
695 out << "# " << (i < 10 ? " " : "") << i << ' ';
697 out << f->toString();
698 out << '\n';
700 return out.str();
703 ///////////////////////////////////////////////////////////////////////////////
705 static void prepare_args(int &argc, char **&argv, const StringVec &args,
706 const char *file) {
707 argv = (char **)malloc((args.size() + 2) * sizeof(char*));
708 argc = 0;
709 if (*file) {
710 argv[argc++] = (char*)file;
712 for (int i = 0; i < (int)args.size(); i++) {
713 argv[argc++] = (char*)args[i].c_str();
715 argv[argc] = nullptr;
718 static int execute_program_impl(int argc, char **argv);
719 int execute_program(int argc, char **argv) {
720 int ret_code = -1;
721 try {
722 initialize_repo();
723 init_thread_locals();
724 ret_code = execute_program_impl(argc, argv);
725 } catch (const Exception &e) {
726 Logger::Error("Uncaught exception: %s", e.what());
727 } catch (const FailedAssertion& fa) {
728 fa.print();
729 StackTraceNoHeap::AddExtraLogging("Assertion failure", fa.summary);
730 abort();
731 } catch (const std::exception &e) {
732 Logger::Error("Uncaught exception: %s", e.what());
733 } catch (...) {
734 Logger::Error("Uncaught exception: (unknown)");
736 if (tempFile.length() && boost::filesystem::exists(tempFile)) {
737 boost::filesystem::remove(tempFile);
739 return ret_code;
742 /* -1 - cannot open file
743 * 0 - no need to open file
744 * 1 - fopen
745 * 2 - popen
747 static int open_server_log_file() {
748 if (!RuntimeOption::LogFile.empty()) {
749 if (Logger::UseCronolog) {
750 if (strchr(RuntimeOption::LogFile.c_str(), '%')) {
751 Logger::cronOutput.m_template = RuntimeOption::LogFile;
752 Logger::cronOutput.setPeriodicity();
753 Logger::cronOutput.m_linkName = RuntimeOption::LogFileSymLink;
754 return 0;
755 } else {
756 Logger::Output = fopen(RuntimeOption::LogFile.c_str(), "a");
757 if (Logger::Output) return 1;
759 } else {
760 if (Logger::IsPipeOutput) {
761 Logger::Output = popen(RuntimeOption::LogFile.substr(1).c_str(), "w");
762 if (Logger::Output) return 2;
763 } else {
764 Logger::Output = fopen(RuntimeOption::LogFile.c_str(), "a");
765 if (Logger::Output) return 1;
768 Logger::Error("Cannot open log file: %s", RuntimeOption::LogFile.c_str());
769 return -1;
771 return 0;
774 static void close_server_log_file(int kind) {
775 if (kind == 1) {
776 fclose(Logger::Output);
777 } else if (kind == 2) {
778 pclose(Logger::Output);
779 } else {
780 always_assert(!Logger::Output);
784 static int execute_program_impl(int argc, char **argv) {
785 string usage = "Usage:\n\n\t";
786 usage += argv[0];
787 usage += " [-m <mode>] [<options>] [<arg1>] [<arg2>] ...\n\nOptions";
789 ProgramOptions po;
790 options_description desc(usage.c_str());
791 desc.add_options()
792 ("help", "display this message")
793 ("version", "display version number")
794 ("compiler-id", "display the git hash for the compiler id")
795 ("repo-schema", "display the repo schema id used by this app")
796 ("mode,m", value<string>(&po.mode)->default_value("run"),
797 "run | debug (d) | server (s) | daemon | replay | translate (t)")
798 ("config,c", value<string>(&po.config),
799 "load specified config file")
800 ("config-value,v", value<StringVec >(&po.confStrings)->composing(),
801 "individual configuration string in a format of name=value, where "
802 "name can be any valid configuration for a config file")
803 ("port,p", value<int>(&po.port)->default_value(-1),
804 "start an HTTP server at specified port")
805 ("port-fd", value<int>(&po.portfd)->default_value(-1),
806 "use specified fd instead of creating a socket")
807 ("ssl-port-fd", value<int>(&po.sslportfd)->default_value(-1),
808 "use specified fd for SSL instead of creating a socket")
809 ("admin-port", value<int>(&po.admin_port)->default_value(-1),
810 "start admin listener at specified port")
811 ("debug-host,h", value<string>(&po.debugger_options.host),
812 "connect to debugger server at specified address")
813 ("debug-port", value<int>(&po.debugger_options.port)->default_value(-1),
814 "connect to debugger server at specified port")
815 ("debug-extension", value<string>(&po.debugger_options.extension),
816 "PHP file that extends y command")
817 ("debug-cmd", value<StringVec>(&po.debugger_options.cmds)->composing(),
818 "executes this debugger command and returns its output in stdout")
819 ("debug-sandbox",
820 value<string>(&po.debugger_options.sandbox)->default_value("default"),
821 "initial sandbox to attach to when debugger is started")
822 ("user,u", value<string>(&po.user),
823 "run server under this user account")
824 ("file,f", value<string>(&po.file),
825 "executing specified file")
826 ("lint,l", value<string>(&po.lint),
827 "lint specified file")
828 ("show,w", value<string>(&po.show),
829 "output specified file and do nothing else")
830 ("parse", value<string>(&po.parse),
831 "parse specified file and dump the AST")
832 ("temp-file",
833 "file specified is temporary and removed after execution")
834 ("count", value<int>(&po.count)->default_value(1),
835 "how many times to repeat execution")
836 ("no-safe-access-check",
837 value<bool>(&po.noSafeAccessCheck)->default_value(false),
838 "whether to ignore safe file access check")
839 ("arg", value<StringVec >(&po.args)->composing(),
840 "arguments")
841 ("extra-header", value<string>(&Logger::ExtraHeader),
842 "extra-header to add to log lines")
843 ("build-id", value<string>(&po.buildId),
844 "unique identifier of compiled server code")
845 ("xhprof-flags", value<int>(&po.xhprofFlags)->default_value(0),
846 "Set XHProf flags")
849 positional_options_description p;
850 p.add("arg", -1);
851 variables_map vm;
852 try {
853 store(command_line_parser(argc, argv).options(desc).positional(p).run(),
854 vm);
855 notify(vm);
856 if (po.mode == "d") po.mode = "debug";
857 if (po.mode == "s") po.mode = "server";
858 if (po.mode == "t") po.mode = "translate";
859 if (po.mode == "") po.mode = "run";
860 set_execution_mode(po.mode);
861 } catch (error &e) {
862 Logger::Error("Error in command line: %s\n\n", e.what());
863 cout << desc << "\n";
864 return -1;
865 } catch (...) {
866 Logger::Error("Error in command line:\n\n");
867 cout << desc << "\n";
868 return -1;
870 if (vm.count("help")) {
871 cout << desc << "\n";
872 return 0;
874 if (vm.count("version")) {
875 #ifdef HPHP_VERSION
876 #undefine HPHP_VERSION
877 #endif
878 #define HPHP_VERSION(v) const char *version = #v;
879 #include "../../version"
881 cout << "HipHop VM";
882 cout << " v" << version << " (" << (debug ? "dbg" : "rel") << ")\n";
883 cout << "Compiler: " << kCompilerId << "\n";
884 cout << "Repo schema: " << kRepoSchemaId << "\n";
885 return 0;
887 if (vm.count("compiler-id")) {
888 cout << kCompilerId << "\n";
889 return 0;
892 if (vm.count("repo-schema")) {
893 cout << kRepoSchemaId << "\n";
894 return 0;
897 if (!po.show.empty()) {
898 PlainFile f;
899 f.open(po.show, "r");
900 if (!f.valid()) {
901 Logger::Error("Unable to open file %s", po.show.c_str());
902 return 1;
904 f.print();
905 f.close();
906 return 0;
909 po.isTempFile = vm.count("temp-file");
911 // we need to initialize pcre cache table very early
912 pcre_init();
914 Hdf config;
915 if (!po.config.empty()) {
916 config.open(po.config);
918 RuntimeOption::Load(config, &po.confStrings);
919 vector<string> badnodes;
920 config.lint(badnodes);
921 for (unsigned int i = 0; i < badnodes.size(); i++) {
922 Logger::Error("Possible bad config node: %s", badnodes[i].c_str());
925 vector<int> inherited_fds;
926 RuntimeOption::BuildId = po.buildId;
927 if (po.port != -1) {
928 RuntimeOption::ServerPort = po.port;
930 if (po.portfd != -1) {
931 RuntimeOption::ServerPortFd = po.portfd;
932 inherited_fds.push_back(po.portfd);
934 if (po.sslportfd != -1) {
935 RuntimeOption::SSLPortFd = po.sslportfd;
936 inherited_fds.push_back(po.sslportfd);
938 if (po.admin_port != -1) {
939 RuntimeOption::AdminServerPort = po.admin_port;
941 if (po.noSafeAccessCheck) {
942 RuntimeOption::SafeFileAccess = false;
945 if (po.mode == "daemon") {
946 if (RuntimeOption::LogFile.empty()) {
947 Logger::Error("Log file not specified under daemon mode.\n\n");
949 int ret = open_server_log_file();
950 Process::Daemonize();
951 close_server_log_file(ret);
954 open_server_log_file();
956 // Defer the initialization of light processes until the log file handle is
957 // created, so that light processes can log to the right place. If we ever
958 // lose a light process, stop the server instead of proceeding in an
959 // uncertain state.
960 LightProcess::SetLostChildHandler([](pid_t child) {
961 if (!HttpServer::Server) return;
962 if (!HttpServer::Server->isStopped()) {
963 HttpServer::Server->stop("lost light process child");
966 LightProcess::Initialize(RuntimeOption::LightProcessFilePrefix,
967 RuntimeOption::LightProcessCount,
968 inherited_fds);
971 const size_t stackSizeMinimum = 8 * 1024 * 1024;
972 struct rlimit rlim;
973 if (getrlimit(RLIMIT_STACK, &rlim) == 0 &&
974 (rlim.rlim_cur == RLIM_INFINITY ||
975 rlim.rlim_cur < stackSizeMinimum)) {
976 rlim.rlim_cur = stackSizeMinimum;
977 if (stackSizeMinimum > rlim.rlim_max) {
978 rlim.rlim_max = stackSizeMinimum;
980 if (setrlimit(RLIMIT_STACK, &rlim)) {
981 Logger::Error("failed to set stack limit to %lld\n", stackSizeMinimum);
986 ShmCounters::initialize(true, Logger::Error);
987 // Initialize compiler state
988 compile_file(0, 0, MD5(), 0);
990 if (!po.lint.empty()) {
991 if (po.isTempFile) {
992 tempFile = po.lint;
995 hphp_process_init();
996 try {
997 HPHP::Eval::PhpFile* phpFile = g_vmContext->lookupPhpFile(
998 StringData::GetStaticString(po.lint.c_str()), "", nullptr);
999 if (phpFile == nullptr) {
1000 throw FileOpenException(po.lint.c_str());
1002 Unit* unit = phpFile->unit();
1003 const StringData* msg;
1004 int line;
1005 if (unit->compileTimeFatal(msg, line)) {
1006 VMParserFrame parserFrame;
1007 parserFrame.filename = po.lint.c_str();
1008 parserFrame.lineNumber = line;
1009 Array bt = g_vmContext->debugBacktrace(false, true,
1010 false, &parserFrame);
1011 throw FatalErrorException(msg->data(), bt);
1013 } catch (FileOpenException &e) {
1014 Logger::Error("%s", e.getMessage().c_str());
1015 return 1;
1016 } catch (const FatalErrorException& e) {
1017 RuntimeOption::CallUserHandlerOnFatals = false;
1018 RuntimeOption::AlwaysLogUnhandledExceptions = true;
1019 g_context->onFatalError(e);
1020 return 1;
1022 Logger::Info("No syntax errors detected in %s", po.lint.c_str());
1023 return 0;
1026 if (!po.parse.empty()) {
1027 Logger::Error("The 'parse' command line option is not supported\n\n");
1028 return 1;
1031 if (argc <= 1 || po.mode == "run" || po.mode == "debug") {
1032 if (po.isTempFile) {
1033 tempFile = po.file;
1036 set_execution_mode("run");
1038 int new_argc;
1039 char **new_argv;
1040 prepare_args(new_argc, new_argv, po.args, po.file.c_str());
1042 if (!po.file.empty()) {
1043 Repo::setCliFile(po.file);
1044 } else if (new_argc > 0) {
1045 Repo::setCliFile(new_argv[0]);
1048 int ret = 0;
1049 hphp_process_init();
1051 string file;
1052 if (new_argc > 0) {
1053 file = new_argv[0];
1056 if (po.mode == "debug") {
1057 StackTraceNoHeap::AddExtraLogging("IsDebugger", "True");
1058 RuntimeOption::EnableDebugger = true;
1059 Eval::DebuggerProxyPtr proxy =
1060 Eval::Debugger::StartClient(po.debugger_options);
1061 if (!proxy) {
1062 Logger::Error("Failed to start debugger client\n\n");
1063 return 1;
1065 Eval::Debugger::RegisterSandbox(proxy->getDummyInfo());
1066 Eval::Debugger::RegisterThread();
1067 StringVecPtr client_args;
1068 bool restart = false;
1069 ret = 0;
1070 while (true) {
1071 try {
1072 execute_command_line_begin(new_argc, new_argv, po.xhprofFlags);
1073 g_context->setSandboxId(proxy->getDummyInfo().id());
1074 Eval::Debugger::DebuggerSession(po.debugger_options, file, restart);
1075 restart = false;
1076 execute_command_line_end(po.xhprofFlags, true, file.c_str());
1077 } catch (const Eval::DebuggerRestartException &e) {
1078 execute_command_line_end(0, false, nullptr);
1080 if (!e.m_args->empty()) {
1081 file = e.m_args->at(0);
1082 client_args = e.m_args;
1083 free(new_argv);
1084 prepare_args(new_argc, new_argv, *client_args, nullptr);
1086 restart = true;
1087 } catch (const Eval::DebuggerClientExitException &e) {
1088 execute_command_line_end(0, false, nullptr);
1089 break; // end user quitting debugger
1093 } else {
1094 ret = 0;
1095 for (int i = 0; i < po.count; i++) {
1096 execute_command_line_begin(new_argc, new_argv, po.xhprofFlags);
1097 ret = 1;
1098 if (hphp_invoke_simple(file)) {
1099 ret = ExitException::ExitCode;
1101 execute_command_line_end(po.xhprofFlags, true, file.c_str());
1105 free(new_argv);
1106 Eval::DebuggerClient::Shutdown();
1107 hphp_process_exit();
1109 return ret;
1112 if (po.mode == "daemon" || po.mode == "server") {
1113 if (!po.user.empty()) RuntimeOption::ServerUser = po.user;
1114 return start_server(RuntimeOption::ServerUser);
1117 if (po.mode == "replay" && !po.args.empty()) {
1118 RuntimeOption::RecordInput = false;
1119 set_execution_mode("server");
1120 HttpServer server; // so we initialize runtime properly
1121 HttpRequestHandler handler;
1122 for (int i = 0; i < po.count; i++) {
1123 for (unsigned int j = 0; j < po.args.size(); j++) {
1124 ReplayTransport rt;
1125 rt.replayInput(po.args[j].c_str());
1126 handler.handleRequest(&rt);
1127 printf("%s\n", rt.getResponse().c_str());
1130 return 0;
1133 if (po.mode == "translate" && !po.args.empty()) {
1134 printf("%s", translate_stack(po.args[0].c_str()).c_str());
1135 return 0;
1138 cout << desc << "\n";
1139 return -1;
1142 String canonicalize_path(CStrRef p, const char* root, int rootLen) {
1143 String path(Util::canonicalize(p.c_str(), p.size()), AttachString);
1144 if (path.charAt(0) == '/') {
1145 const string &sourceRoot = RuntimeOption::SourceRoot;
1146 int len = sourceRoot.size();
1147 if (len && strncmp(path.data(), sourceRoot.c_str(), len) == 0) {
1148 return path.substr(len);
1150 if (root && rootLen && strncmp(path.data(), root, rootLen) == 0) {
1151 return path.substr(rootLen);
1154 return path;
1157 static string systemlib_split(string slib, string* hhas) {
1158 auto pos = slib.find("\n<?hhas\n");
1159 if (pos != string::npos) {
1160 if (hhas) *hhas = slib.substr(pos + 8);
1161 return slib.substr(0, pos);
1163 return slib;
1166 // Search for systemlib.php in the following places:
1167 // 1) ${HHVM_SYSTEMLIB}
1168 // 2) section "systemlib" in the current executable
1169 // and return its contents
1170 string get_systemlib(string* hhas) {
1171 if (char *file = getenv("HHVM_SYSTEMLIB")) {
1172 std::ifstream ifs(file);
1173 if (ifs.good()) {
1174 return systemlib_split(std::string(
1175 std::istreambuf_iterator<char>(ifs),
1176 std::istreambuf_iterator<char>()), hhas);
1180 Util::embedded_data desc;
1181 if (!Util::get_embedded_data("systemlib", &desc)) return "";
1183 std::ifstream ifs(desc.m_filename);
1184 if (!ifs.good()) return "";
1185 ifs.seekg(desc.m_start, std::ios::beg);
1186 std::unique_ptr<char[]> data(new char[desc.m_len]);
1187 ifs.read(data.get(), desc.m_len);
1188 return systemlib_split(string(data.get(), desc.m_len), hhas);
1191 ///////////////////////////////////////////////////////////////////////////////
1192 // C++ ffi
1194 extern "C" void hphp_fatal_error(const char *s) {
1195 throw_fatal(s);
1198 void hphp_process_init() {
1199 pthread_attr_t attr;
1200 pthread_getattr_np(pthread_self(), &attr);
1201 Util::init_stack_limits(&attr);
1202 pthread_attr_destroy(&attr);
1204 init_thread_locals();
1205 ClassInfo::Load();
1206 Process::InitProcessStatics();
1208 // reinitialize pcre table
1209 pcre_reinit();
1211 // the liboniguruma docs say this isnt needed,
1212 // but the implementation of init is not
1213 // thread safe due to bugs
1214 onig_init();
1216 // simple xml also needs one time init
1217 xmlInitParser();
1219 g_vmProcessInit();
1221 PageletServer::Restart();
1222 XboxServer::Restart();
1223 Stream::RegisterCoreWrappers();
1224 Extension::InitModules();
1225 for (InitFiniNode *in = extra_process_init; in; in = in->next) {
1226 in->func();
1228 int64_t save = RuntimeOption::SerializationSizeLimit;
1229 RuntimeOption::SerializationSizeLimit = StringData::MaxSize;
1230 apc_load(RuntimeOption::ApcLoadThread);
1231 RuntimeOption::SerializationSizeLimit = save;
1233 Transl::TargetCache::requestExit();
1234 // Reset the preloaded g_context
1235 ExecutionContext *context = g_context.getNoCheck();
1236 context->~ExecutionContext();
1237 new (context) ExecutionContext();
1240 static void handle_exception(bool& ret, ExecutionContext* context,
1241 std::string& errorMsg, ContextOfException where,
1242 bool& error, bool richErrorMsg) {
1243 assert(where == InvokeException || where == ReqInitException);
1244 try {
1245 handle_exception_helper(ret, context, errorMsg, where, error, richErrorMsg);
1246 } catch (const ExitException &e) {
1247 // Got an ExitException during exception handling, handle
1248 // similarly to the case below but don't call obEndAll().
1249 } catch (...) {
1250 handle_exception_helper(ret, context, errorMsg, HandlerException, error,
1251 richErrorMsg);
1252 context->obEndAll();
1256 static void handle_reqinit_exception(bool &ret, ExecutionContext *context,
1257 std::string &errorMsg, bool &error) {
1258 handle_exception(ret, context, errorMsg, ReqInitException, error, false);
1261 static void handle_invoke_exception(bool &ret, ExecutionContext *context,
1262 std::string &errorMsg, bool &error,
1263 bool richErrorMsg) {
1264 handle_exception(ret, context, errorMsg, InvokeException, error,
1265 richErrorMsg);
1268 static bool hphp_warmup(ExecutionContext *context,
1269 const string &reqInitFunc,
1270 const string &reqInitDoc, bool &error) {
1271 bool ret = true;
1272 error = false;
1273 std::string errorMsg;
1275 MemoryManager *mm = MemoryManager::TheMemoryManager();
1276 if (mm->isEnabled()) {
1277 ServerStatsHelper ssh("reqinit");
1278 try {
1279 if (!reqInitDoc.empty()) {
1280 include_impl_invoke(reqInitDoc, true);
1282 if (!reqInitFunc.empty()) {
1283 invoke(reqInitFunc.c_str(), Array());
1285 context->backupSession();
1286 } catch (...) {
1287 handle_reqinit_exception(ret, context, errorMsg, error);
1291 return ret;
1294 void hphp_session_init() {
1295 init_thread_locals();
1296 ThreadInfo::s_threadInfo->onSessionInit();
1297 MemoryManager::TheMemoryManager()->resetStats();
1299 #ifdef ENABLE_SIMPLE_COUNTER
1300 SimpleCounter::Enabled = true;
1301 StackTrace::Enabled = true;
1302 #endif
1304 // Ordering is sensitive; StatCache::requestInit produces work that
1305 // must be done in VMExecutionContext::requestInit.
1306 StatCache::requestInit();
1308 g_vmContext->requestInit();
1310 SystemGlobals *g = (SystemGlobals *)get_global_variables();
1311 g->k_PHP_SAPI = StringData::GetStaticString(RuntimeOption::ExecutionMode);
1314 bool hphp_is_warmup_enabled() {
1315 MemoryManager *mm = MemoryManager::TheMemoryManager();
1316 return mm->isEnabled();
1319 ExecutionContext *hphp_context_init() {
1320 ExecutionContext *context = g_context.getNoCheck();
1321 context->obStart();
1322 context->obProtect(true);
1323 return context;
1326 bool hphp_invoke_simple(const std::string &filename,
1327 bool warmupOnly /* = false */) {
1328 bool error;
1329 string errorMsg;
1330 return hphp_invoke(g_context.getNoCheck(), filename, false, null_array,
1331 uninit_null(), "", "", error, errorMsg, true, warmupOnly);
1334 bool hphp_invoke(ExecutionContext *context, const std::string &cmd,
1335 bool func, CArrRef funcParams, VRefParam funcRet,
1336 const string &reqInitFunc, const string &reqInitDoc,
1337 bool &error, string &errorMsg,
1338 bool once /* = true */, bool warmupOnly /* = false */,
1339 bool richErrorMsg /* = false */) {
1340 bool isServer = RuntimeOption::ServerExecutionMode();
1341 error = false;
1343 String oldCwd;
1344 if (isServer) {
1345 oldCwd = context->getCwd();
1347 if (!hphp_warmup(context, reqInitFunc, reqInitDoc, error)) {
1348 if (isServer) context->setCwd(oldCwd);
1349 return false;
1352 bool ret = true;
1353 if (!warmupOnly) {
1354 try {
1355 ServerStatsHelper ssh("invoke");
1356 if (func) {
1357 funcRet->assignVal(invoke(cmd.c_str(), funcParams));
1358 } else {
1359 if (isServer) hphp_chdir_file(cmd);
1360 include_impl_invoke(cmd.c_str(), once);
1362 } catch (...) {
1363 handle_invoke_exception(ret, context, errorMsg, error, richErrorMsg);
1367 try {
1368 context->onShutdownPreSend();
1369 } catch (...) {
1370 handle_invoke_exception(ret, context, errorMsg, error, richErrorMsg);
1373 if (isServer) context->setCwd(oldCwd);
1374 return ret;
1377 void hphp_context_exit(ExecutionContext *context, bool psp,
1378 bool shutdown /* = true */,
1379 const char *program /* = NULL */) {
1380 if (psp) {
1381 context->onShutdownPostSend();
1383 if (RuntimeOption::EnableDebugger) {
1384 try {
1385 Eval::Debugger::InterruptPSPEnded(program);
1386 } catch (const Eval::DebuggerException &e) {}
1388 context->requestExit();
1390 if (shutdown) {
1391 context->onRequestShutdown();
1393 context->obProtect(false);
1394 context->obEndAll();
1397 void hphp_thread_exit() {
1398 finish_thread_locals();
1401 void hphp_session_exit() {
1402 // Server note has to live long enough for the access log to fire.
1403 // RequestLocal is too early.
1404 ServerNote::Reset();
1405 g_context.destroy();
1407 ThreadInfo::s_threadInfo->clearPendingException();
1409 MemoryManager *mm = MemoryManager::TheMemoryManager();
1410 if (RuntimeOption::CheckMemory) {
1411 mm->checkMemory();
1413 if (RuntimeOption::EnableStats && RuntimeOption::EnableMemoryStats) {
1414 mm->logStats();
1416 mm->resetStats();
1418 if (mm->isEnabled()) {
1419 ServerStatsHelper ssh("rollback");
1420 // sweep may call g_context->, which is a noCheck, so we need to
1421 // reinitialize g_context here
1422 g_context.getCheck();
1423 // MemoryManager::sweepAll() will handle sweeping for PHP objects and
1424 // PHP resources (ex. File, Collator, XmlReader, etc.)
1425 mm->sweepAll();
1426 // Destroy g_context again because ExecutionContext has SmartAllocated
1427 // data members. These members cannot survive over rollback(), so we need
1428 // to destroy g_context before calling rollback().
1429 g_context.destroy();
1430 // MemoryManager::rollback() will handle sweeping for all types that have
1431 // dedicated allocators (ex. StringData, HphpArray, etc.) and it reset all
1432 // of the allocators in preparation for the next request.
1433 mm->rollback();
1434 // Do any post-sweep cleanup necessary for global variables
1435 free_global_variables_after_sweep();
1436 g_context.getCheck();
1437 } else {
1438 g_context.getCheck();
1439 ServerStatsHelper ssh("free");
1440 free_global_variables();
1443 ThreadInfo::s_threadInfo->onSessionExit();
1446 void hphp_process_exit() {
1447 XboxServer::Stop();
1448 Eval::Debugger::Stop();
1449 Extension::ShutdownModules();
1450 LightProcess::Close();
1451 for (InitFiniNode *in = extra_process_exit; in; in = in->next) {
1452 in->func();
1456 ///////////////////////////////////////////////////////////////////////////////