Eliminate ArrayData::LvalForceNew
[hiphop-php.git] / hphp / runtime / base / program-functions.cpp
blob803d474f9f415de05533621e3ea0876d0815249c
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present 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 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/program-functions.h"
19 #include "hphp/runtime/base/array-init.h"
20 #include "hphp/runtime/base/backtrace.h"
21 #include "hphp/runtime/base/builtin-functions.h"
22 #include "hphp/runtime/base/code-coverage.h"
23 #include "hphp/runtime/base/config.h"
24 #include "hphp/runtime/base/exceptions.h"
25 #include "hphp/runtime/base/execution-context.h"
26 #include "hphp/runtime/base/extended-logger.h"
27 #include "hphp/runtime/base/file-util.h"
28 #include "hphp/runtime/base/file-util-defs.h"
29 #include "hphp/runtime/base/hhprof.h"
30 #include "hphp/runtime/base/ini-setting.h"
31 #include "hphp/runtime/base/init-fini-node.h"
32 #include "hphp/runtime/base/member-reflection.h"
33 #include "hphp/runtime/base/memory-manager.h"
34 #include "hphp/runtime/base/perf-mem-event.h"
35 #include "hphp/runtime/base/php-globals.h"
36 #include "hphp/runtime/base/plain-file.h"
37 #include "hphp/runtime/base/runtime-error.h"
38 #include "hphp/runtime/base/runtime-option.h"
39 #include "hphp/runtime/base/stat-cache.h"
40 #include "hphp/runtime/base/stream-wrapper-registry.h"
41 #include "hphp/runtime/base/surprise-flags.h"
42 #include "hphp/runtime/base/thread-safe-setlocale.h"
43 #include "hphp/runtime/base/tracing.h"
44 #include "hphp/runtime/base/unit-cache.h"
45 #include "hphp/runtime/base/variable-serializer.h"
46 #include "hphp/runtime/debugger/debugger.h"
47 #include "hphp/runtime/debugger/debugger_client.h"
48 #include "hphp/runtime/debugger/debugger_hook_handler.h"
49 #include "hphp/runtime/ext/apc/ext_apc.h"
50 #include "hphp/runtime/ext/extension-registry.h"
51 #include "hphp/runtime/ext/json/ext_json.h"
52 #include "hphp/runtime/ext/std/ext_std_file.h"
53 #include "hphp/runtime/ext/std/ext_std_function.h"
54 #include "hphp/runtime/ext/std/ext_std_variable.h"
55 #include "hphp/runtime/ext/strobelight/ext_strobelight.h"
56 #include "hphp/runtime/ext/xenon/ext_xenon.h"
57 #include "hphp/runtime/ext/xhprof/ext_xhprof.h"
58 #include "hphp/runtime/server/admin-request-handler.h"
59 #include "hphp/runtime/server/cli-server.h"
60 #include "hphp/runtime/server/http-request-handler.h"
61 #include "hphp/runtime/server/http-server.h"
62 #include "hphp/runtime/server/log-writer.h"
63 #include "hphp/runtime/server/pagelet-server.h"
64 #include "hphp/runtime/server/replay-transport.h"
65 #include "hphp/runtime/server/rpc-request-handler.h"
66 #include "hphp/runtime/server/server-note.h"
67 #include "hphp/runtime/server/server-stats.h"
68 #include "hphp/runtime/server/warmup-request-handler.h"
69 #include "hphp/runtime/server/xbox-server.h"
70 #include "hphp/runtime/vm/debug/debug.h"
71 #include "hphp/runtime/vm/extern-compiler.h"
72 #include "hphp/runtime/vm/jit/code-cache.h"
73 #include "hphp/runtime/vm/jit/mcgen-translate.h"
74 #include "hphp/runtime/vm/jit/mcgen.h"
75 #include "hphp/runtime/vm/jit/prof-data-serialize.h"
76 #include "hphp/runtime/vm/jit/prof-data.h"
77 #include "hphp/runtime/vm/jit/tc.h"
78 #include "hphp/runtime/vm/jit/translator.h"
79 #include "hphp/runtime/vm/repo.h"
80 #include "hphp/runtime/vm/runtime-compiler.h"
81 #include "hphp/runtime/vm/treadmill.h"
83 #include "hphp/util/alloc.h"
84 #include "hphp/util/arch.h"
85 #include "hphp/util/boot-stats.h"
86 #include "hphp/util/build-info.h"
87 #include "hphp/util/capability.h"
88 #include "hphp/util/compatibility.h"
89 #include "hphp/util/embedded-data.h"
90 #include "hphp/util/hardware-counter.h"
91 #include "hphp/util/kernel-version.h"
92 #ifndef _MSC_VER
93 #include "hphp/util/light-process.h"
94 #endif
95 #include "hphp/util/managed-arena.h"
96 #include "hphp/util/maphuge.h"
97 #include "hphp/util/perf-event.h"
98 #include "hphp/util/process-exec.h"
99 #include "hphp/util/process.h"
100 #include "hphp/util/rds-local.h"
101 #include "hphp/util/service-data.h"
102 #include "hphp/util/shm-counter.h"
103 #include "hphp/util/stack-trace.h"
104 #include "hphp/util/sync-signal.h"
105 #include "hphp/util/timer.h"
106 #include "hphp/util/type-scan.h"
108 #include "hphp/zend/zend-math.h"
109 #include "hphp/zend/zend-string.h"
110 #include "hphp/zend/zend-strtod.h"
112 #include <folly/CPortability.h>
113 #include <folly/Portability.h>
114 #include <folly/Random.h>
115 #include <folly/Range.h>
116 #include <folly/Singleton.h>
117 #include <folly/portability/Fcntl.h>
118 #include <folly/portability/Libgen.h>
119 #include <folly/portability/Stdlib.h>
120 #include <folly/portability/Unistd.h>
122 #include <boost/program_options/options_description.hpp>
123 #include <boost/program_options/positional_options.hpp>
124 #include <boost/program_options/variables_map.hpp>
125 #include <boost/filesystem.hpp>
127 #ifndef FACEBOOK
128 // Needed on libevent2
129 #include <event2/thread.h>
130 #endif
132 #include <oniguruma.h>
133 // Onigurama defines UChar to unsigned char, but ICU4C defines it to signed
134 // 16-bit int. This is supposed to be resolved by ONIG_ESCAPE_UCHAR_COLLISION,
135 // however this isn't fully supported in 6.8.0 or 6.8.1.
137 // As of 2018-03-21, not in any release; hopefully will be in 6.8.2 - it's
138 // resolved by this commit:
140 // https://github.com/kkos/oniguruma/commit/e79406479b6be4a56e40ede6c1a87b51fba073a2
141 #undef UChar
142 #include <signal.h>
143 #include <libxml/parser.h>
145 #include <chrono>
146 #include <exception>
147 #include <fstream>
148 #include <iterator>
149 #include <map>
150 #include <memory>
151 #include <string>
152 #include <vector>
154 #ifdef _MSC_VER
155 #include <windows.h>
156 #include <winuser.h>
157 #endif
159 using namespace boost::program_options;
160 using std::cout;
162 constexpr auto MAX_INPUT_NESTING_LEVEL = 64;
164 namespace HPHP {
166 ///////////////////////////////////////////////////////////////////////////////
167 // Forward declarations.
169 void initialize_repo();
172 * XXX: VM process initialization is handled through a function
173 * pointer so libhphp_runtime.a can be linked into programs that don't
174 * actually initialize the VM.
176 void (*g_vmProcessInit)();
178 void timezone_init();
180 void pcre_init();
181 void pcre_reinit();
183 ///////////////////////////////////////////////////////////////////////////////
184 // helpers
186 struct ProgramOptions {
187 std::string mode;
188 std::vector<std::string> config;
189 std::vector<std::string> confStrings;
190 std::vector<std::string> iniStrings;
191 int port;
192 int portfd;
193 int sslportfd;
194 int admin_port;
195 std::string user;
196 std::string file;
197 std::string lint;
198 bool isTempFile;
199 int count;
200 bool noSafeAccessCheck;
201 std::vector<std::string> args;
202 std::string buildId;
203 std::string instanceId;
204 int xhprofFlags;
205 std::string show;
206 std::string parse;
207 int vsDebugPort;
208 std::string vsDebugDomainSocket;
209 bool vsDebugNoWait;
210 Eval::DebuggerClientOptions debugger_options;
213 struct StartTime {
214 StartTime() : startTime(time(nullptr)) {}
215 time_t startTime;
218 static bool registrationComplete = false;
219 static StartTime s_startTime;
220 static std::string tempFile;
221 std::vector<std::string> s_config_files;
222 std::vector<std::string> s_ini_strings;
224 time_t start_time() {
225 return s_startTime.startTime;
228 const StaticString
229 s_HPHP("HPHP"),
230 s_HHVM("HHVM"),
231 s_HHVM_JIT("HHVM_JIT"),
232 s_HHVM_ARCH("HHVM_ARCH"),
233 s_REQUEST_START_TIME("REQUEST_START_TIME"),
234 s_REQUEST_TIME("REQUEST_TIME"),
235 s_REQUEST_TIME_FLOAT("REQUEST_TIME_FLOAT"),
236 s_DOCUMENT_ROOT("DOCUMENT_ROOT"),
237 s_SCRIPT_FILENAME("SCRIPT_FILENAME"),
238 s_SCRIPT_NAME("SCRIPT_NAME"),
239 s_PHP_SELF("PHP_SELF"),
240 s_argc("argc"),
241 s_argv("argv"),
242 s_PWD("PWD"),
243 s_HOSTNAME("HOSTNAME"),
244 s__SERVER("_SERVER"),
245 s__ENV("_ENV"),
246 s_toStringErr("(unable to call toString())");
248 static RDS_LOCAL(bool, s_sessionInitialized);
250 static void process_cmd_arguments(int argc, char **argv) {
251 php_global_set(s_argc, Variant(argc));
252 VArrayInit argvArray(argc);
253 for (int i = 0; i < argc; i++) {
254 argvArray.append(String(argv[i]));
256 php_global_set(s_argv, argvArray.toArray());
259 static void process_env_variables(
260 Array& variables,
261 char** envp,
262 const std::map<std::string, std::string>& envVariables
264 for (auto const& kv : envVariables) {
265 String idx(kv.first);
266 auto const arrkey = variables.convertKey<IntishCast::Cast>(idx);
267 String str(kv.second);
268 variables.set(arrkey, make_tv<KindOfString>(str.get()));
270 for (char **env = envp; env && *env; env++) {
271 char *p = strchr(*env, '=');
272 if (p) {
273 String name(*env, p - *env, CopyString);
274 register_variable(variables, (char*)name.data(),
275 String(p + 1, CopyString));
280 void process_env_variables(Array& variables) {
281 process_env_variables(variables, environ, RuntimeOption::EnvVariables);
284 // Handle adding a variable to an array, supporting keys that look
285 // like array expressions (like 'FOO[][key1][k2]').
286 void register_variable(Array& variables, char *name, const Variant& value,
287 bool overwrite /* = true */) {
288 // ignore leading spaces in the variable name
289 char *var = name;
290 while (*var && *var == ' ') {
291 var++;
294 // ensure that we don't have spaces or dots in the variable name
295 // (not binary safe)
296 bool is_array = false;
297 char *ip = nullptr; // index pointer
298 char *p = var;
299 for (; *p; p++) {
300 if (*p == ' ' || *p == '.') {
301 *p = '_';
302 } else if (*p == '[') {
303 is_array = true;
304 ip = p;
305 *p = 0;
306 break;
309 int var_len = p - var;
310 if (var_len == 0) {
311 // empty variable name, or variable name with a space in it
312 return;
315 // The array pointer we're currently adding to. If we're doing a
316 // multi-dimensional set. Note that as we're essentially using this as an
317 // interior array pointer it's not safe to allow reentry here.
318 Array* symtable = &variables;
320 char* index = var;
321 int index_len = var_len;
323 if (is_array) {
324 int nest_level = 0;
325 while (true) {
326 if (++nest_level > MAX_INPUT_NESTING_LEVEL) {
327 Logger::Warning("Input variable nesting level exceeded");
328 return;
331 ip++;
332 char *index_s = ip;
333 int new_idx_len = 0;
334 if (isspace(*ip)) {
335 ip++;
337 if (*ip == ']') {
338 index_s = nullptr;
339 } else {
340 ip = strchr(ip, ']');
341 if (!ip) {
342 // PHP variables cannot contain '[' in their names,
343 // so we replace the character with a '_'
344 *(index_s - 1) = '_';
346 index_len = 0;
347 if (index) {
348 index_len = strlen(index);
350 goto plain_var;
352 *ip = 0;
353 new_idx_len = strlen(index_s);
356 if (!index) {
357 symtable->append(make_persistent_array_like_tv(ArrayData::Create()));
358 auto const key = symtable->get()->getKey(symtable->get()->iter_last());
359 symtable = &asArrRef(symtable->lval(key));
360 } else {
361 String key_str(index, index_len, CopyString);
362 auto const key =
363 symtable->convertKey<IntishCast::Cast>(key_str.asTypedValue());
364 auto const v = symtable->rval(key);
365 if (isNullType(v.type()) || !isArrayLikeType(v.type())) {
366 symtable->set(key, make_persistent_array_like_tv(ArrayData::Create()));
368 symtable = &asArrRef(symtable->lval(key));
370 /* ip pointed to the '[' character, now obtain the key */
371 index = index_s;
372 index_len = new_idx_len;
374 ip++;
375 if (*ip == '[') {
376 is_array = true;
377 *ip = 0;
378 } else {
379 goto plain_var;
382 } else {
383 plain_var:
384 if (!index) {
385 symtable->append(value);
386 } else {
387 String key_str(index, index_len, CopyString);
388 auto key =
389 symtable->convertKey<IntishCast::Cast>(key_str.asTypedValue());
390 if (overwrite || !symtable->exists(key)) {
391 symtable->set(key, *value.asTypedValue(), true);
397 enum class ContextOfException {
398 ReqInit = 1,
399 Invoke,
400 Handler,
403 static void handle_exception_append_bt(std::string& errorMsg,
404 const ExtendedException& e) {
405 Array bt = e.getBacktrace();
406 if (!bt.empty()) {
407 errorMsg += ExtendedLogger::StringOfStackTrace(bt);
411 void bump_counter_and_rethrow(bool isPsp) {
412 try {
413 throw;
414 } catch (const RequestTimeoutException& e) {
415 if (isPsp) {
416 static auto requestTimeoutPSPCounter = ServiceData::createTimeSeries(
417 "requests_timed_out_psp", {ServiceData::StatsType::COUNT});
418 requestTimeoutPSPCounter->addValue(1);
419 ServerStats::Log("request.timed_out.psp", 1);
420 } else {
421 static auto requestTimeoutCounter = ServiceData::createTimeSeries(
422 "requests_timed_out_non_psp", {ServiceData::StatsType::COUNT});
423 requestTimeoutCounter->addValue(1);
424 ServerStats::Log("request.timed_out.non_psp", 1);
426 throw;
427 } catch (const RequestCPUTimeoutException& e) {
428 if (isPsp) {
429 static auto requestCPUTimeoutPSPCounter = ServiceData::createTimeSeries(
430 "requests_cpu_timed_out_psp", {ServiceData::StatsType::COUNT});
431 requestCPUTimeoutPSPCounter->addValue(1);
432 ServerStats::Log("request.cpu_timed_out.psp", 1);
433 } else {
434 static auto requestCPUTimeoutCounter = ServiceData::createTimeSeries(
435 "requests_cpu_timed_out_non_psp", {ServiceData::StatsType::COUNT});
436 requestCPUTimeoutCounter->addValue(1);
437 ServerStats::Log("request.cpu_timed_out.non_psp", 1);
439 throw;
440 } catch (const RequestMemoryExceededException& e) {
441 if (isPsp) {
442 static auto requestMemoryExceededPSPCounter =
443 ServiceData::createTimeSeries(
444 "requests_memory_exceeded_psp", {ServiceData::StatsType::COUNT});
445 requestMemoryExceededPSPCounter->addValue(1);
446 ServerStats::Log("request.memory_exceeded.psp", 1);
447 } else {
448 static auto requestMemoryExceededCounter = ServiceData::createTimeSeries(
449 "requests_memory_exceeded_non_psp", {ServiceData::StatsType::COUNT});
450 requestMemoryExceededCounter->addValue(1);
451 ServerStats::Log("request.memory_exceeded.non_psp", 1);
453 throw;
454 } catch (const RequestOOMKilledException& e) {
455 if (isPsp) {
456 static auto requestHostOOMPSPCounter = ServiceData::createTimeSeries(
457 "requests_oom_killed_psp", {ServiceData::StatsType::COUNT});
458 requestHostOOMPSPCounter->addValue(1);
459 ServerStats::Log("request.oom_killed.psp", 1);
460 } else {
461 static auto requestHostOOMCounter = ServiceData::createTimeSeries(
462 "requests_oom_killed_non_psp", {ServiceData::StatsType::COUNT});
463 requestHostOOMCounter->addValue(1);
464 ServerStats::Log("request.oom_killed.non_psp", 1);
466 throw;
470 static void handle_exception_helper(bool& ret,
471 ExecutionContext* context,
472 std::string& errorMsg,
473 ContextOfException where,
474 bool& error,
475 bool richErrorMsg) {
476 // Clear oom/timeout while handling exception and restore them afterwards.
477 auto& flags = stackLimitAndSurprise();
478 auto const origFlags = flags.fetch_and(~ResourceFlags) & ResourceFlags;
480 SCOPE_EXIT {
481 flags.fetch_or(origFlags);
484 try {
485 bump_counter_and_rethrow(false /* isPsp */);
486 } catch (const Eval::DebuggerException& e) {
487 throw;
488 } catch (const ExitException& e) {
489 if (where == ContextOfException::ReqInit) {
490 ret = false;
491 } else if (where != ContextOfException::Handler &&
492 !context->getExitCallback().isNull() &&
493 is_callable(context->getExitCallback())) {
494 Array stack = e.getBacktrace();
495 Array argv = make_vec_array_tagged(ARRPROV_HERE(),
496 *rl_exit_code,
497 stack);
498 vm_call_user_func(context->getExitCallback(), argv);
500 } catch (const PhpFileDoesNotExistException& e) {
501 ret = false;
502 if (where != ContextOfException::Handler) {
503 raise_notice(e.getMessage());
504 } else {
505 Logger::Error(e.getMessage());
507 if (richErrorMsg) {
508 handle_exception_append_bt(errorMsg, e);
510 } catch (const Exception& e) {
511 bool oldRet = ret;
512 bool origError = error;
513 std::string origErrorMsg = errorMsg;
514 ret = false;
515 error = true;
516 errorMsg = "";
517 if (where == ContextOfException::Handler) {
518 errorMsg = "Exception handler threw an exception: ";
520 errorMsg += e.what();
521 if (where == ContextOfException::Invoke) {
522 bool handlerRet = context->onFatalError(e);
523 if (handlerRet) {
524 ret = oldRet;
525 error = origError;
526 errorMsg = origErrorMsg;
528 } else {
529 Logger::Error(errorMsg);
531 if (richErrorMsg) {
532 auto const ee = dynamic_cast<const ExtendedException*>(&e);
533 if (ee) {
534 handle_exception_append_bt(errorMsg, *ee);
537 } catch (const Object& e) {
538 bool oldRet = ret;
539 bool origError = error;
540 auto const origErrorMsg = errorMsg;
541 ret = false;
542 error = true;
543 errorMsg = "";
544 if (where == ContextOfException::Handler) {
545 errorMsg = "Exception handler threw an object exception: ";
547 try {
548 errorMsg += throwable_to_string(e.get()).data();
549 } catch (...) {
550 errorMsg += s_toStringErr.data();
552 if (where == ContextOfException::Invoke) {
553 bool handlerRet = context->onUnhandledException(e);
554 if (handlerRet) {
555 ret = oldRet;
556 error = origError;
557 errorMsg = origErrorMsg;
559 } else {
560 Logger::Error(errorMsg);
562 } catch (...) {
563 ret = false;
564 error = true;
565 errorMsg = "(unknown exception was thrown)";
566 Logger::Error(errorMsg);
570 static bool hphp_chdir_file(const std::string& filename) {
571 bool ret = false;
572 String s = File::TranslatePath(filename);
573 char *buf = strndup(s.data(), s.size());
574 char *dir = dirname(buf);
575 assertx(dir);
576 if (dir) {
577 if (File::IsVirtualDirectory(dir)) {
578 g_context->setCwd(String(dir, CopyString));
579 ret = true;
580 } else {
581 struct stat sb;
582 stat(dir, &sb);
583 if ((sb.st_mode & S_IFMT) == S_IFDIR) {
584 ret = true;
585 if (*dir != '.') {
586 g_context->setCwd(String(dir, CopyString));
591 free(buf);
592 return ret;
595 static void handle_resource_exceeded_exception() {
596 try {
597 throw;
598 } catch (RequestTimeoutException&) {
599 RID().triggerTimeout(TimeoutTime);
600 } catch (RequestCPUTimeoutException&) {
601 RID().triggerTimeout(TimeoutCPUTime);
602 } catch (RequestMemoryExceededException&) {
603 setSurpriseFlag(MemExceededFlag);
604 } catch (...) {}
607 void handle_destructor_exception(const char* situation) {
608 std::string errorMsg;
610 try {
611 throw;
612 } catch (ExitException& e) {
613 // ExitException is fine, no need to show a warning.
614 RI().setPendingException(e.clone());
615 return;
616 } catch (Object &e) {
617 // For user exceptions, invoke the user exception handler
618 errorMsg = situation;
619 errorMsg += " threw an object exception: ";
620 try {
621 errorMsg += throwable_to_string(e.get()).data();
622 } catch (...) {
623 handle_resource_exceeded_exception();
624 errorMsg += "(unable to call toString())";
626 } catch (Exception& e) {
627 RI().setPendingException(e.clone());
628 errorMsg = situation;
629 errorMsg += " raised a fatal error: ";
630 errorMsg += e.what();
631 } catch (...) {
632 errorMsg = situation;
633 errorMsg += " threw an unknown exception";
635 // For fatal errors and unknown exceptions, we raise a warning.
636 // If there is a user error handler it will be invoked, otherwise
637 // the default error handler will be invoked.
638 try {
639 raise_warning_unsampled(errorMsg);
640 } catch (...) {
641 handle_resource_exceeded_exception();
643 // The user error handler fataled or threw an exception,
644 // print out the error message directly to the log
645 Logger::Warning(errorMsg);
649 static RDS_LOCAL(rqtrace::Trace, tl_cmdTrace);
651 void init_command_line_session(int argc, char** argv) {
652 StackTraceNoHeap::AddExtraLogging("ThreadType", "CLI");
653 std::string args;
654 for (int i = 0; i < argc; i++) {
655 if (i) args += " ";
656 args += argv[i];
658 StackTraceNoHeap::AddExtraLogging("Arguments", args.c_str());
660 hphp_session_init(Treadmill::SessionKind::CLISession);
661 auto const context = g_context.getNoCheck();
662 context->obSetImplicitFlush(true);
663 if (RuntimeOption::EvalTraceCommandLineRequest) {
664 tl_cmdTrace.destroy();
665 context->setRequestTrace(tl_cmdTrace.getCheck());
669 void
670 init_command_line_globals(
671 int argc, char** argv, char** envp,
672 int xhprof,
673 const std::map<std::string, std::string>& serverVariables,
674 const std::map<std::string, std::string>& envVariables
676 ARRPROV_USE_RUNTIME_LOCATION();
677 auto& variablesOrder = RID().getVariablesOrder();
679 if (variablesOrder.find('e') != std::string::npos ||
680 variablesOrder.find('E') != std::string::npos) {
681 auto envArr = Array::CreateDArray();
682 process_env_variables(envArr, envp, envVariables);
683 envArr.set(s_HPHP, 1);
684 envArr.set(s_HHVM, 1);
685 if (RuntimeOption::EvalJit) {
686 envArr.set(s_HHVM_JIT, 1);
688 switch (arch()) {
689 case Arch::X64:
690 envArr.set(s_HHVM_ARCH, "x64");
691 break;
692 case Arch::ARM:
693 envArr.set(s_HHVM_ARCH, "arm");
694 break;
695 case Arch::PPC64:
696 envArr.set(s_HHVM_ARCH, "ppc64");
697 break;
699 php_global_set(s__ENV, std::move(envArr));
702 process_cmd_arguments(argc, argv);
704 if (variablesOrder.find('s') != std::string::npos ||
705 variablesOrder.find('S') != std::string::npos) {
706 auto serverArr = Array::CreateDArray();
707 process_env_variables(serverArr, envp, envVariables);
708 time_t now;
709 struct timeval tp = {0};
710 double now_double;
711 if (!gettimeofday(&tp, nullptr)) {
712 now_double = (double)(tp.tv_sec + tp.tv_usec / 1000000.00);
713 now = tp.tv_sec;
714 } else {
715 now = time(nullptr);
716 now_double = (double)now;
718 String file = empty_string();
719 if (argc > 0) {
720 file = String::attach(StringData::Make(argv[0], CopyString));
722 serverArr.set(s_REQUEST_START_TIME, now);
723 serverArr.set(s_REQUEST_TIME, now);
724 serverArr.set(s_REQUEST_TIME_FLOAT, now_double);
725 serverArr.set(s_DOCUMENT_ROOT, empty_string_tv());
726 serverArr.set(s_SCRIPT_FILENAME, file);
727 serverArr.set(s_SCRIPT_NAME, file);
728 serverArr.set(s_PHP_SELF, file);
729 serverArr.set(s_argv, php_global(s_argv));
730 serverArr.set(s_argc, php_global(s_argc));
731 serverArr.set(s_PWD, g_context->getCwd());
732 char hostname[1024];
733 if (RuntimeOption::ServerExecutionMode() &&
734 !is_cli_server_mode() &&
735 !gethostname(hostname, sizeof(hostname))) {
736 // gethostname may not null-terminate
737 hostname[sizeof(hostname) - 1] = '\0';
738 serverArr.set(s_HOSTNAME, String(hostname, CopyString));
741 for (auto const& kv : serverVariables) {
742 serverArr.set(String{kv.first}, String{kv.second});
745 php_global_set(s__SERVER, std::move(serverArr));
748 if (xhprof) {
749 HHVM_FN(xhprof_enable)(xhprof, null_array);
752 if (RuntimeOption::RequestTimeoutSeconds) {
753 RID().setTimeout(RuntimeOption::RequestTimeoutSeconds);
756 if (RuntimeOption::XenonForceAlwaysOn) {
757 Xenon::getInstance().surpriseAll();
760 // Initialize the debugger
761 DEBUGGER_ATTACHED_ONLY(phpDebuggerRequestInitHook());
764 void execute_command_line_begin(int argc, char **argv, int xhprof) {
765 init_command_line_session(argc, argv);
766 init_command_line_globals(argc, argv, environ, xhprof,
767 RuntimeOption::ServerVariables,
768 RuntimeOption::EnvVariables);
771 void execute_command_line_end(int xhprof, bool coverage, const char *program) {
772 if (xhprof) {
773 Variant profileData = HHVM_FN(xhprof_disable)();
774 if (!profileData.isNull()) {
775 HHVM_FN(var_dump)(Variant::attach(
776 HHVM_FN(json_encode)(HHVM_FN(xhprof_disable)())
780 auto& ti = RI();
781 if (coverage && ti.m_reqInjectionData.getCoverage() &&
782 !RuntimeOption::CodeCoverageOutputFile.empty()) {
783 ti.m_coverage.dumpOnExit();
785 g_context->onShutdownPostSend(); // runs more php
786 Eval::Debugger::InterruptPSPEnded(program);
787 hphp_context_exit();
788 hphp_session_exit();
791 #if defined(__APPLE__) || defined(_MSC_VER)
792 const void* __hot_start = nullptr;
793 const void* __hot_end = nullptr;
794 #define AT_END_OF_TEXT
795 #else
796 #define AT_END_OF_TEXT __attribute__((__section__(".stub")))
797 #endif
799 #define ALIGN_HUGE_PAGE __attribute__((__aligned__(2 * 1024 * 1024)))
801 static void
802 NEVER_INLINE AT_END_OF_TEXT ALIGN_HUGE_PAGE __attribute__((__optimize__("2")))
803 EXTERNALLY_VISIBLE
804 hugifyText(char* from, char* to) {
805 #if !FOLLY_SANITIZE && defined MADV_HUGEPAGE
806 if (from > to || (to - from) < sizeof(uint64_t)) {
807 // This shouldn't happen if HHVM is behaving correctly (I think),
808 // but if it does then there is nothing to do and we should bail
809 // out early because the call to wordcpy() below can't handle
810 // zero size or negative sizes.
811 return;
813 size_t sz = to - from;
815 #ifdef FACEBOOK
816 if (RuntimeOption::EvalNewTHPHotText) {
817 auto const hasKernelSupport = [] () -> bool {
818 KernelVersion version;
819 if (version.m_major < 5) return false;
820 if (version.m_major > 5) return true;
821 if (version.m_minor > 2) return true;
822 if ((version.m_minor == 2) && (version.m_fbk >= 5)) return true;
823 return false;
825 if (hasKernelSupport()) {
826 // The new way doesn't work if the region is locked. Note that this means
827 // Server.LockCodeMemory won't be applied to the region--there is no
828 // guarantee that the region would stay in memory, especially if the
829 // kernel fails to find huge pages for us.
830 munlock(from, sz);
831 madvise(from, sz, MADV_HUGEPAGE);
832 return;
835 #endif
837 void* mem = malloc(sz);
838 memcpy(mem, from, sz);
840 // This maps out a portion of our executable
841 // We need to be very careful about what we do
842 // until we replace the original code
843 mmap(from, sz,
844 PROT_READ | PROT_WRITE | PROT_EXEC,
845 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
846 -1, 0);
847 // This is in glibc, which isn't a problem, except for
848 // the trampoline code in .plt, which we dealt with
849 // in the linker script
850 madvise(from, sz, MADV_HUGEPAGE);
851 // Don't use memcpy because its probably one of the
852 // functions thats been mapped out.
853 // Needs the attribute((optimize("2")) to prevent
854 // g++ from turning this back into memcpy(!)
855 wordcpy((uint64_t*)from, (uint64_t*)mem, sz / sizeof(uint64_t));
856 mprotect(from, sz, PROT_READ | PROT_EXEC);
857 free(mem);
858 mlock(from, to - from);
859 Debug::DebugInfo::setPidMapOverlay(from, to);
860 std::stringstream ss;
861 ss << "Mapped text section onto huge pages from " <<
862 std::hex << (uint64_t*)from << " to " << (uint64_t*)to;
863 Logger::Info(ss.str());
864 #endif
867 static void pagein_self(void) {
868 #if defined(USE_JEMALLOC) && (JEMALLOC_VERSION_MAJOR >= 5)
869 // jemalloc 5 has background threads, which handle purging asynchronously.
870 bool background_threads = false;
871 if (mallctlRead<bool, true>("background_thread", &background_threads)) {
872 background_threads = false;
873 Logger::Warning("Failed to determine jemalloc background thread state");
875 if (background_threads &&
876 mallctlWrite<bool, true>("background_thread", false)) {
877 Logger::Warning("Failed to disable jemalloc background threads");
879 SCOPE_EXIT {
880 if (background_threads &&
881 mallctlWrite<bool, true>("background_thread", true)) {
882 Logger::Warning("Failed to enable jemalloc background threads");
885 #endif
887 // Other than the jemalloc background threads, which should've been stopped by
888 // now, the only thread allowed here is the current one. Check that and alarm
889 // people when they accidentally created threads before this point.
890 int nThreads = Process::GetNumThreads();
891 if (nThreads > 1) {
892 usleep(1000);
893 nThreads = Process::GetNumThreads();
894 if (nThreads > 1) {
895 Logger::Error("%d threads running, cannot hugify text!", nThreads);
896 fprintf(stderr,
897 "HHVM is broken: %u threads running in hugifyText()!\n",
898 nThreads);
899 if (debug) {
900 throw std::runtime_error{
901 "you cannot create threads before pagein_self"
907 auto mapped_huge = false;
908 #ifdef __linux__
909 auto const try_map_huge =
910 hugePagesSupported() &&
911 RuntimeOption::EvalMaxHotTextHugePages > 0 &&
912 (char*)__hot_start != nullptr && (char*)__hot_end != nullptr &&
913 nThreads <= 1;
915 SCOPE_EXIT {
916 if (try_map_huge != mapped_huge) {
917 Logger::Warning("Failed to hugify the .text section");
920 #else
921 // MacOS doesn't have transparent huge pages. It uses mmap() with
922 // VM_FLAGS_SUPERPAGE_SIZE_2MB, which we don't do here, so don't bother.
923 auto constexpr try_map_huge = false;
924 #endif
926 char mapname[PATH_MAX];
927 // pad due to the spaces between the inode number and the mapname
928 auto const bufsz =
929 sizeof(unsigned long) * 4 + sizeof(mapname) + sizeof(char) * 11 + 100;
930 auto buf = static_cast<char*>(malloc(bufsz));
931 if (auto fp = fopen("/proc/self/maps", "r")) {
932 while (!feof(fp)) {
933 if (fgets(buf, bufsz, fp) == 0)
934 break;
935 unsigned long begin, end, inode, pgoff;
936 char perm[5];
937 char dev[11];
938 int r = sscanf(buf, "%lx-%lx %4s %lx %10s %ld %s",
939 &begin, &end, perm, &pgoff, dev, &inode, mapname);
941 // page in read-only segments that correspond to a file on disk
942 if (r != 7 ||
943 perm[0] != 'r' ||
944 perm[1] != '-' ||
945 access(mapname, F_OK) != 0) {
946 continue;
949 auto beginPtr = (char*)begin;
950 auto endPtr = (char*)end;
951 auto hotStart = (char*)__hot_start;
952 auto hotEnd = (char*)__hot_end;
953 const size_t hugePageBytes = 2L * 1024 * 1024;
955 if (mlock(beginPtr, end - begin) == 0) {
956 if (try_map_huge && beginPtr <= hotStart && hotEnd <= endPtr) {
957 char* from = hotStart - ((intptr_t)hotStart & (hugePageBytes - 1));
958 char* to = hotEnd + (hugePageBytes - 1);
959 to -= (intptr_t)to & (hugePageBytes - 1);
960 const size_t maxHugeHotTextBytes =
961 RuntimeOption::EvalMaxHotTextHugePages * hugePageBytes;
962 if (to - from > maxHugeHotTextBytes) {
963 to = from + maxHugeHotTextBytes;
965 // Check that hugifyText() does not start in hot text.
966 if (to <= (void*)hugifyText || from > (void*)hugifyText) {
967 mapped_huge = true;
968 hugifyText(from, to);
971 if (!RuntimeOption::LockCodeMemory) {
972 munlock(beginPtr, end - begin);
976 fclose(fp);
978 free(buf);
981 /* Sets RuntimeOption::ExecutionMode according to commandline options prior to
982 * config load. Returns false upon unrecognized mode.
984 static bool set_execution_mode(folly::StringPiece mode) {
985 if (mode == "daemon" || mode == "server" || mode == "replay") {
986 RuntimeOption::ServerMode = true;
987 Logger::Escape = true;
988 return true;
989 } else if (mode == "run" || mode == "debug" || mode == "translate" ||
990 mode == "dumphhas" || mode == "verify" || mode == "vsdebug" ||
991 mode == "getoption") {
992 // We don't run PHP in "translate" mode, so just treat it like cli mode.
993 RuntimeOption::ServerMode = false;
994 Logger::Escape = false;
995 return true;
997 // Invalid mode.
998 return false;
1001 /* Reads a file into the OS page cache, with rate limiting. */
1002 static bool readahead_rate(const char* path, int64_t mbPerSec) {
1003 int ret = open(path, O_RDONLY);
1004 if (ret < 0) return false;
1005 const int fd = ret;
1006 SCOPE_EXIT { close(fd); };
1008 constexpr size_t kReadaheadBytes = 1 << 20;
1009 std::unique_ptr<char[]> buf(new char[kReadaheadBytes]);
1010 int64_t total = 0;
1011 auto startTime = std::chrono::steady_clock::now();
1012 do {
1013 ret = read(fd, buf.get(), kReadaheadBytes);
1014 if (ret > 0) {
1015 total += ret;
1016 // Unit math: bytes / (MB / seconds) = microseconds
1017 auto endTime = startTime + std::chrono::microseconds(total / mbPerSec);
1018 auto sleepT = endTime - std::chrono::steady_clock::now();
1019 // Don't sleep too frequently.
1020 if (sleepT >= std::chrono::seconds(1)) {
1021 Logger::Info(folly::sformat(
1022 "readahead sleeping {}ms after total {}b",
1023 std::chrono::duration_cast<std::chrono::milliseconds>(sleepT).count(),
1024 total));
1025 /* sleep override */ std::this_thread::sleep_for(sleepT);
1028 } while (ret > 0);
1029 return ret == 0;
1032 static int start_server(const std::string &username, int xhprof) {
1033 if (!registrationComplete) {
1034 folly::SingletonVault::singleton()->registrationComplete();
1035 registrationComplete = true;
1037 BootStats::start();
1038 HttpServer::CheckMemAndWait();
1039 InitFiniNode::ServerPreInit();
1041 if (!RuntimeOption::EvalUnixServerPath.empty()) {
1042 init_cli_server(RuntimeOption::EvalUnixServerPath.c_str());
1045 // Before we start the webserver, make sure the entire
1046 // binary is paged into memory.
1047 pagein_self();
1048 BootStats::mark("pagein_self");
1050 set_execution_mode("server");
1052 #if !defined(SKIP_USER_CHANGE)
1053 if (!username.empty()) {
1054 if (Logger::UseCronolog) {
1055 for (const auto& el : RuntimeOption::ErrorLogs) {
1056 Cronolog::changeOwner(username, el.second.symLink);
1059 if (!Capability::ChangeUnixUser(username, RuntimeOption::AllowRunAsRoot)) {
1060 _exit(1);
1062 LightProcess::ChangeUser(username);
1063 compilers_set_user(username);
1064 } else if (getuid() == 0 && !RuntimeOption::AllowRunAsRoot) {
1065 Logger::Error("hhvm not allowed to run as root unless "
1066 "-vServer.AllowRunAsRoot=1 is used.");
1067 _exit(1);
1069 Capability::SetDumpable();
1070 #endif
1071 // Include hugetlb pages in core dumps.
1072 Process::SetCoreDumpHugePages();
1074 hphp_process_init();
1075 SCOPE_EXIT {
1076 hphp_process_exit();
1077 Logger::Info("all servers stopped");
1078 Logger::FlushAll();
1081 HttpRequestHandler::GetAccessLog().init
1082 (RuntimeOption::AccessLogDefaultFormat, RuntimeOption::AccessLogs,
1083 username);
1084 AdminRequestHandler::GetAccessLog().init
1085 (RuntimeOption::AdminLogFormat, RuntimeOption::AdminLogSymLink,
1086 RuntimeOption::AdminLogFile,
1087 username);
1088 RPCRequestHandler::GetAccessLog().init
1089 (RuntimeOption::AccessLogDefaultFormat, RuntimeOption::RPCLogs,
1090 username);
1091 SCOPE_EXIT {
1092 Logger::FlushAll();
1093 HttpRequestHandler::GetAccessLog().flushAllWriters();
1094 AdminRequestHandler::GetAccessLog().flushAllWriters();
1095 RPCRequestHandler::GetAccessLog().flushAllWriters();
1098 if (RuntimeOption::ServerInternalWarmupThreads > 0) {
1099 HttpServer::CheckMemAndWait();
1100 InitFiniNode::WarmupConcurrentStart(
1101 RuntimeOption::ServerInternalWarmupThreads);
1104 HttpServer::CheckMemAndWait();
1105 // Create the HttpServer before any warmup requests to properly
1106 // initialize the process
1107 HttpServer::Server = std::make_shared<HttpServer>();
1109 if (xhprof) {
1110 HHVM_FN(xhprof_enable)(xhprof, uninit_null().toArray());
1113 std::unique_ptr<std::thread> readaheadThread;
1115 if (RuntimeOption::RepoLocalReadaheadRate > 0 &&
1116 !RuntimeOption::RepoLocalPath.empty()) {
1117 HttpServer::CheckMemAndWait();
1118 readaheadThread = std::make_unique<std::thread>([&] {
1119 assertx(RuntimeOption::ServerExecutionMode());
1120 BootStats::Block timer("Readahead Repo", true);
1121 auto path = RuntimeOption::RepoLocalPath.c_str();
1122 Logger::Info("readahead %s", path);
1123 #ifdef __linux__
1124 // glibc doesn't have a wrapper for ioprio_set(), so we need to use
1125 // syscall(). The constants here are consistent with the kernel source.
1126 // See http://lxr.free-electrons.com/source/include/linux/ioprio.h
1127 auto constexpr IOPRIO_CLASS_SHIFT = 13;
1128 enum {
1129 IOPRIO_CLASS_NONE,
1130 IOPRIO_CLASS_RT,
1131 IOPRIO_CLASS_BE,
1132 IOPRIO_CLASS_IDLE,
1134 // Set to lowest IO priority.
1135 constexpr int ioprio = (IOPRIO_CLASS_IDLE << IOPRIO_CLASS_SHIFT);
1137 // ioprio_set() is available starting kernel 2.6.13
1138 KernelVersion version;
1139 if (version.m_major > 2 ||
1140 (version.m_major == 2 &&
1141 (version.m_minor > 6 ||
1142 (version.m_minor == 6 && version.m_release >= 13)))) {
1143 syscall(SYS_ioprio_set,
1144 1 /* IOPRIO_WHO_PROCESS, in fact, it is this thread */,
1145 0 /* current thread */,
1146 ioprio);
1148 #endif
1149 const auto mbPerSec = RuntimeOption::RepoLocalReadaheadRate;
1150 if (!readahead_rate(path, mbPerSec)) {
1151 Logger::Error("readahead failed: %s", strerror(errno));
1154 if (!RuntimeOption::RepoLocalReadaheadConcurrent) {
1155 // TODO(10152762): Run this concurrently with non-disk warmup.
1156 readaheadThread->join();
1157 readaheadThread.reset();
1161 if (RuntimeOption::ServerInternalWarmupThreads > 0) {
1162 BootStats::Block timer("concurrentWaitForEnd", true);
1163 InitFiniNode::WarmupConcurrentWaitForEnd();
1166 // If we have any warmup requests, replay them before listening for
1167 // real connections
1169 Logger::Info("Warming up");
1170 if (!RuntimeOption::EvalJitProfileWarmupRequests) profileWarmupStart();
1171 SCOPE_EXIT { profileWarmupEnd(); };
1172 InternalWarmupRequestPlayer(RuntimeOption::ServerWarmupThreadCount,
1173 RuntimeOption::ServerDedupeWarmupRequests)
1174 .runAfterDelay(RuntimeOption::ServerWarmupRequests);
1176 BootStats::mark("warmup");
1178 if (RuntimeOption::StopOldServer) HttpServer::StopOldServer();
1180 if (RuntimeOption::EvalEnableNuma) {
1181 purge_all();
1182 enable_numa();
1183 BootStats::mark("enable_numa");
1185 HttpServer::CheckMemAndWait(true); // Final wait
1186 if (readaheadThread.get()) {
1187 readaheadThread->join();
1188 readaheadThread.reset();
1191 if (!RuntimeOption::EvalUnixServerPath.empty()) {
1192 start_cli_server();
1195 if (jit::mcgen::retranslateAllScheduled()) {
1196 // We ran retranslateAll from deserialized profile.
1197 BootStats::Block timer("waitForRetranslateAll", true);
1198 jit::mcgen::joinWorkerThreads();
1201 #ifdef USE_JEMALLOC
1202 // Eventually, we are going to remove options Eval.Num1GPagesForSlabs and
1203 // Eval.Num2MPagesForSlabs, and use the ForReqHeap spec together with
1204 // Eval.NumReservedSlabs. For now, we keep the old options working.
1205 auto const reqHeapSpec = PageSpec{
1206 std::max(RuntimeOption::EvalNum1GPagesForReqHeap,
1207 RuntimeOption::EvalNum1GPagesForSlabs),
1208 std::max(RuntimeOption::EvalNum2MPagesForReqHeap,
1209 RuntimeOption::EvalNum2MPagesForSlabs)
1211 auto const nSlabs =
1212 std::max(RuntimeOption::EvalNumReservedSlabs,
1213 RuntimeOption::EvalNum2MPagesForSlabs +
1214 512 * RuntimeOption::EvalNum1GPagesForSlabs);
1215 setup_local_arenas(reqHeapSpec, nSlabs);
1216 #endif
1218 HttpServer::Server->runOrExitProcess();
1219 HttpServer::Server.reset();
1221 return 0;
1224 static void logSettings() {
1225 if (RuntimeOption::ServerLogSettingsOnStartup) {
1226 Logger::Info("Settings: %s\n", IniSetting::GetAllAsJSON().c_str());
1230 static InitFiniNode s_logSettings(logSettings, InitFiniNode::When::ServerInit);
1232 std::string translate_stack(const char *hexencoded, bool with_frame_numbers) {
1233 if (!hexencoded || !*hexencoded) {
1234 return "";
1237 StackTrace st(hexencoded);
1238 std::vector<std::shared_ptr<StackFrameExtra>> frames;
1239 st.get(frames);
1241 std::ostringstream out;
1242 for (size_t i = 0; i < frames.size(); i++) {
1243 auto f = frames[i];
1244 if (with_frame_numbers) {
1245 out << "# " << (i < 10 ? " " : "") << i << ' ';
1247 out << f->toString();
1248 out << '\n';
1250 return out.str();
1253 ///////////////////////////////////////////////////////////////////////////////
1255 static void prepare_args(int &argc,
1256 char **&argv,
1257 const std::vector<std::string> &args,
1258 const char *file) {
1259 argv = (char **)malloc((args.size() + 2) * sizeof(char*));
1260 argc = 0;
1261 if (file && *file) {
1262 argv[argc++] = (char*)file;
1264 for (int i = 0; i < (int)args.size(); i++) {
1265 argv[argc++] = (char*)args[i].c_str();
1267 argv[argc] = nullptr;
1270 static int execute_program_impl(int argc, char **argv);
1271 int execute_program(int argc, char **argv) {
1272 int ret_code = -1;
1273 try {
1274 try {
1275 initialize_repo();
1276 ret_code = execute_program_impl(argc, argv);
1277 } catch (const Exception& e) {
1278 Logger::Error("Uncaught exception: %s", e.what());
1279 throw;
1280 } catch (const std::exception& e) {
1281 Logger::Error("Uncaught exception: %s", e.what());
1282 throw;
1283 } catch (...) {
1284 Logger::Error("Uncaught exception: (unknown)");
1285 throw;
1287 if (tempFile.length() && boost::filesystem::exists(tempFile)) {
1288 boost::filesystem::remove(tempFile);
1290 } catch (...) {
1291 if (HttpServer::Server ||
1292 folly::SingletonVault::singleton()->livingSingletonCount()) {
1293 // an exception was thrown that prevented proper shutdown. Its not
1294 // safe to destroy the globals, or run atexit handlers.
1295 // abort() so it shows up as a crash, and we can diagnose/fix the
1296 // exception
1297 abort();
1301 return ret_code;
1304 static bool open_server_log_files() {
1305 bool openedLog = false;
1306 for (const auto& el : RuntimeOption::ErrorLogs) {
1307 bool ok = true;
1308 const auto& name = el.first;
1309 const auto& errlog = el.second;
1310 if (!errlog.logFile.empty()) {
1311 if (errlog.isPipeOutput()) {
1312 auto output = popen(errlog.logFile.substr(1).c_str(), "w");
1313 ok = (output != nullptr);
1314 Logger::SetOutput(name, output, true);
1315 } else if (Logger::UseCronolog && errlog.hasTemplate()) {
1316 auto cronoLog = Logger::CronoOutput(name);
1317 always_assert(cronoLog);
1318 cronoLog->m_template = errlog.logFile;
1319 cronoLog->setPeriodicity();
1320 if (errlog.periodMultiplier) {
1321 cronoLog->m_periodMultiple = errlog.periodMultiplier;
1323 cronoLog->m_linkName = errlog.symLink;
1324 } else {
1325 auto output = fopen(errlog.logFile.c_str(), "a");
1326 ok = (output != nullptr);
1327 Logger::SetOutput(name, output, false);
1329 if (!ok) Logger::Error("Can't open log file: %s", errlog.logFile.c_str());
1330 openedLog |= ok;
1333 return openedLog;
1336 static int compute_hhvm_argc(const options_description& desc,
1337 int argc, char** argv) {
1338 enum ArgCode {
1339 NO_ARG = 0,
1340 ARG_REQUIRED = 1,
1341 ARG_OPTIONAL = 2
1343 const auto& vec = desc.options();
1344 std::map<std::string,ArgCode> long_options;
1345 std::map<std::string,ArgCode> short_options;
1346 // Build lookup maps for the short options and the long options
1347 for (unsigned i = 0; i < vec.size(); ++i) {
1348 auto opt = vec[i];
1349 auto long_name = opt->long_name();
1350 ArgCode code = NO_ARG;
1351 if (opt->semantic()->max_tokens() == 1) {
1352 if (opt->semantic()->min_tokens() == 1) {
1353 code = ARG_REQUIRED;
1354 } else {
1355 code = ARG_OPTIONAL;
1358 long_options[long_name] = code;
1359 auto format_name = opt->format_name();
1360 if (format_name.size() >= 2 && format_name[0] == '-' &&
1361 format_name[1] != '-') {
1362 auto short_name = format_name.substr(1,1);
1363 short_options[short_name] = code;
1366 // Loop over the args
1367 int pos = 1;
1368 while (pos < argc) {
1369 const char* str = argv[pos];
1370 int len = strlen(str);
1371 if (len == 2 && memcmp(str, "--", 2) == 0) {
1372 // We found "--". All args after this are intended for the
1373 // PHP application
1374 ++pos;
1375 break;
1377 if (len >= 3 && str[0] == '-' && str[1] == '-') {
1378 // Handle long options
1379 ++pos;
1380 std::string s(str+2);
1381 auto it = long_options.find(s);
1382 if (it != long_options.end() && it->second != NO_ARG && pos < argc &&
1383 (it->second == ARG_REQUIRED || argv[pos][0] != '-')) {
1384 ++pos;
1386 } else if (len >= 2 && str[0] == '-') {
1387 // Handle short options
1388 ++pos;
1389 std::string s;
1390 s.append(1, str[1]);
1391 auto it = short_options.find(s);
1392 if (it != short_options.end() && it->second != 0 && len == 2 &&
1393 pos < argc && (it->second == ARG_REQUIRED || argv[pos][0] != '-')) {
1394 ++pos;
1396 } else {
1397 // We've found a non-option argument. This arg and all args
1398 // that follow are intended for the PHP application
1399 break;
1402 return pos;
1406 * alloc.h defines a minimum C++ stack size but that only applies to threads we
1407 * manually create. When the main thread will be executing PHP rather than just
1408 * managing a server, make sure its stack is big enough.
1410 static void set_stack_size() {
1411 struct rlimit rlim;
1412 if (getrlimit(RLIMIT_STACK, &rlim) != 0) return;
1414 if (rlim.rlim_cur < kStackSizeMinimum || rlim.rlim_cur == RLIM_INFINITY) {
1415 #ifdef _WIN32
1416 Logger::Error("stack limit too small, use peflags -x to increase %zd\n",
1417 kStackSizeMinimum);
1418 #else
1419 rlim.rlim_cur = kStackSizeMinimum;
1420 if (setrlimit(RLIMIT_STACK, &rlim)) {
1421 Logger::Error("failed to set stack limit to %zd\n", kStackSizeMinimum);
1423 #endif
1427 static int execute_program_impl(int argc, char** argv) {
1428 std::string usage = "Usage:\n\n ";
1429 usage += argv[0];
1430 usage += " [-m <mode>] [<options>] [<arg1>] [<arg2>] ...\n\nOptions";
1432 ProgramOptions po;
1433 options_description desc(usage.c_str());
1434 desc.add_options()
1435 ("help", "display this message")
1436 ("version", "display version number")
1437 ("modules", "display modules")
1438 ("info", "PHP information")
1439 ("php", "emulate the standard php command line")
1440 ("compiler-id", "display the git hash for the compiler")
1441 ("repo-schema", "display the repository schema id")
1442 ("mode,m", value<std::string>(&po.mode)->default_value("run"),
1443 "run | debug (d) | vsdebug | server (s) | daemon | replay | "
1444 "translate (t) | verify | getoption")
1445 ("interactive,a", "Shortcut for --mode debug") // -a is from PHP5
1446 ("config,c", value<std::vector<std::string>>(&po.config)->composing(),
1447 "load specified config file")
1448 ("config-value,v",
1449 value<std::vector<std::string>>(&po.confStrings)->composing(),
1450 "individual configuration string in a format of name=value, where "
1451 "name can be any valid configuration for a config file")
1452 ("define,d", value<std::vector<std::string>>(&po.iniStrings)->composing(),
1453 "define an ini setting in the same format ( foo[=bar] ) as provided in a "
1454 ".ini file")
1455 ("no-config", "don't use the default php.ini")
1456 ("port,p", value<int>(&po.port)->default_value(-1),
1457 "start an HTTP server at specified port")
1458 ("port-fd", value<int>(&po.portfd)->default_value(-1),
1459 "use specified fd instead of creating a socket")
1460 ("ssl-port-fd", value<int>(&po.sslportfd)->default_value(-1),
1461 "use specified fd for SSL instead of creating a socket")
1462 ("admin-port", value<int>(&po.admin_port)->default_value(-1),
1463 "start admin listener at specified port")
1464 ("debug-config", value<std::string>(&po.debugger_options.configFName),
1465 "load specified debugger config file")
1466 ("debug-host,h",
1467 value<std::string>(&po.debugger_options.host)->implicit_value("localhost"),
1468 "connect to debugger server at specified address")
1469 ("debug-port", value<int>(&po.debugger_options.port)->default_value(-1),
1470 "connect to debugger server at specified port")
1471 ("debug-extension", value<std::string>(&po.debugger_options.extension),
1472 "PHP file that extends command 'arg'")
1473 ("debug-cmd", value<std::vector<std::string>>(
1474 &po.debugger_options.cmds)->composing(),
1475 "executes this debugger command and returns its output in stdout")
1476 ("debug-sandbox",
1477 value<std::string>(&po.debugger_options.sandbox)->default_value("default"),
1478 "initial sandbox to attach to when debugger is started")
1479 ("user,u", value<std::string>(&po.user),
1480 "run server under this user account")
1481 ("file,f", value<std::string>(&po.file),
1482 "execute specified file")
1483 ("lint,l", value<std::string>(&po.lint),
1484 "lint specified file")
1485 ("show,w", value<std::string>(&po.show),
1486 "output specified file and do nothing else")
1487 ("temp-file",
1488 "file specified is temporary and removed after execution")
1489 ("count", value<int>(&po.count)->default_value(1),
1490 "how many times to repeat execution")
1491 ("no-safe-access-check",
1492 value<bool>(&po.noSafeAccessCheck)->default_value(false),
1493 "whether to ignore safe file access check")
1494 ("arg", value<std::vector<std::string>>(&po.args)->composing(),
1495 "arguments")
1496 ("extra-header", value<std::string>(&Logger::ExtraHeader),
1497 "extra-header to add to log lines")
1498 ("build-id", value<std::string>(&po.buildId),
1499 "unique identifier of compiled server code")
1500 ("instance-id", value<std::string>(&po.instanceId),
1501 "unique identifier of server instance")
1502 ("xhprof-flags", value<int>(&po.xhprofFlags)->default_value(0),
1503 "Set XHProf flags")
1504 ("vsDebugPort", value<int>(&po.vsDebugPort)->default_value(-1),
1505 "Debugger TCP port to listen on for the VS Code debugger extension")
1506 ("vsDebugDomainSocketPath",
1507 value<std::string>(&po.vsDebugDomainSocket)->default_value(""),
1508 "Debugger port to listen on for the VS Code debugger extension")
1509 ("vsDebugNoWait", value<bool>(&po.vsDebugNoWait)->default_value(false),
1510 "Indicates the debugger should not block script startup waiting for "
1511 "a debugger client to attach. Only applies if vsDebugPort or "
1512 "vsDebugDomainSocketPath is specified.")
1515 positional_options_description p;
1516 p.add("arg", -1);
1517 variables_map vm;
1519 // Before invoking the boost command line parser, we do a manual pass
1520 // to find the first occurrence of either "--" or a non-option argument
1521 // in order to determine which arguments should be consumed by HHVM and
1522 // which arguments should be passed along to the PHP application. This
1523 // is necessary so that the boost command line parser doesn't choke on
1524 // args intended for the PHP application.
1525 int hhvm_argc = compute_hhvm_argc(desc, argc, argv);
1526 // Need to have a parent try for opts so I can use opts in the catch of
1527 // one of the sub-tries below.
1528 try {
1529 // Invoke the boost command line parser to parse the args for HHVM.
1530 auto opts = command_line_parser(hhvm_argc, argv)
1531 .options(desc)
1532 .positional(p)
1533 // If these style options are changed, compute_hhvm_argc() will
1534 // need to be updated appropriately
1535 .style(command_line_style::default_style &
1536 ~command_line_style::allow_guessing &
1537 ~command_line_style::allow_sticky &
1538 ~command_line_style::long_allow_adjacent)
1539 .run();
1540 try {
1541 // Manually append the args for the PHP application.
1542 int pos = 0;
1543 for (unsigned m = 0; m < opts.options.size(); ++m) {
1544 const auto& bo = opts.options[m];
1545 if (bo.string_key == "arg") {
1546 ++pos;
1549 for (unsigned m = hhvm_argc; m < argc; ++m) {
1550 std::string str = argv[m];
1551 basic_option<char> bo;
1552 bo.string_key = "arg";
1553 bo.position_key = pos++;
1554 bo.value.push_back(str);
1555 bo.original_tokens.push_back(str);
1556 bo.unregistered = false;
1557 bo.case_insensitive = false;
1558 opts.options.push_back(bo);
1560 // Process the options
1561 store(opts, vm);
1562 notify(vm);
1563 if (vm.count("interactive") /* or -a */) po.mode = "debug";
1564 else if (po.mode.empty()) po.mode = "run";
1565 else if (po.mode == "d") po.mode = "debug";
1566 else if (po.mode == "s") po.mode = "server";
1567 else if (po.mode == "t") po.mode = "translate";
1569 if (!set_execution_mode(po.mode)) {
1570 Logger::Error("Error in command line: invalid mode: %s",
1571 po.mode.c_str());
1572 cout << desc << "\n";
1573 return -1;
1575 if (po.config.empty() && !vm.count("no-config")
1576 && ::getenv("HHVM_NO_DEFAULT_CONFIGS") == nullptr) {
1577 auto file_callback = [&po] (const char *filename) {
1578 Logger::Verbose("Using default config file: %s", filename);
1579 po.config.push_back(filename);
1581 add_default_config_files_globbed(DEFAULT_CONFIG_DIR "/php*.ini",
1582 file_callback);
1583 add_default_config_files_globbed(DEFAULT_CONFIG_DIR "/config*.hdf",
1584 file_callback);
1586 const auto env_config = ::getenv("HHVM_CONFIG_FILE");
1587 if (env_config != nullptr) {
1588 add_default_config_files_globbed(
1589 env_config,
1590 [&po](const char* filename) {
1591 Logger::Verbose("Using config file from environment: %s", filename);
1592 po.config.push_back(filename);
1596 } catch (const error &e) {
1597 Logger::Error("Error in command line: %s", e.what());
1598 cout << desc << "\n";
1599 return -1;
1600 } catch (...) {
1601 Logger::Error("Error in command line.");
1602 cout << desc << "\n";
1603 return -1;
1605 } catch (const error &e) {
1606 Logger::Error("Error in command line: %s", e.what());
1607 cout << desc << "\n";
1608 return -1;
1609 } catch (...) {
1610 Logger::Error("Error in command line parsing.");
1611 cout << desc << "\n";
1612 return -1;
1614 // reuse -h for help command if possible
1615 if (vm.count("help") || (vm.count("debug-host") && po.mode != "debug")) {
1616 cout << desc << "\n";
1617 return 0;
1619 if (vm.count("version")) {
1620 cout << "HipHop VM";
1621 cout << " " << HHVM_VERSION;
1622 cout << " (" << (debug ? "dbg" : "rel") << ")\n";
1623 cout << "Compiler: " << compilerId() << "\n";
1624 cout << "Repo schema: " << repoSchemaId() << "\n";
1625 return 0;
1627 if (vm.count("modules")) {
1628 tl_heap.getCheck();
1629 Array exts = ExtensionRegistry::getLoaded();
1630 cout << "[PHP Modules]" << "\n";
1631 for (ArrayIter iter(exts); iter; ++iter) {
1632 cout << iter.second().toString().toCppString() << "\n";
1634 return 0;
1636 if (vm.count("compiler-id")) {
1637 cout << compilerId() << "\n";
1638 return 0;
1641 if (vm.count("repo-schema")) {
1642 cout << repoSchemaId() << "\n";
1643 return 0;
1646 if (!po.show.empty()) {
1647 hphp_thread_init();
1648 g_context.getCheck();
1649 SCOPE_EXIT { hphp_thread_exit(); };
1651 auto f = req::make<PlainFile>();
1652 f->open(po.show, "r");
1653 if (!f->valid()) {
1654 Logger::Error("Unable to open file %s", po.show.c_str());
1655 return 1;
1657 f->print();
1658 f->close();
1659 return 0;
1662 po.isTempFile = vm.count("temp-file");
1664 // forget the source for systemlib.php unless we are debugging
1665 if (po.mode != "debug" && po.mode != "vsdebug") SystemLib::s_source = "";
1666 if (po.mode == "vsdebug") {
1667 RuntimeOption::EnableVSDebugger = true;
1668 RuntimeOption::VSDebuggerListenPort = po.vsDebugPort;
1669 RuntimeOption::VSDebuggerDomainSocketPath = po.vsDebugDomainSocket;
1670 RuntimeOption::VSDebuggerNoWait = po.vsDebugNoWait;
1673 // we need to to initialize these very early
1674 pcre_init();
1675 // this is needed for libevent2 to be thread-safe, which backs Hack ASIO.
1676 #ifndef FACEBOOK
1677 // FB uses a custom libevent 1
1678 evthread_use_pthreads();
1679 #endif
1681 rds::local::init();
1682 SCOPE_EXIT { rds::local::fini(); };
1683 tl_heap.getCheck();
1684 if (RuntimeOption::ServerExecutionMode()) {
1685 // Create the hardware counter before reading options,
1686 // so that the main thread never has inherit set in server
1687 // mode
1688 HardwareCounter::s_counter.getCheck();
1690 std::vector<std::string> messages;
1691 // We want the ini map to be freed after processing and loading the options
1692 // So put this in its own block
1694 IniSettingMap ini = IniSettingMap();
1695 Hdf config;
1696 s_config_files = po.config;
1697 // Start with .hdf and .ini files
1698 for (auto& filename : s_config_files) {
1699 if (boost::filesystem::exists(filename)) {
1700 Config::ParseConfigFile(filename, ini, config);
1701 } else {
1702 Logger::Warning(
1703 "The configuration file %s does not exist",
1704 filename.c_str()
1709 auto const scriptFilePath =
1710 !po.file.empty() ? po.file :
1711 !po.args.empty() ? po.args[0] :
1712 std::string("");
1714 // Now, take care of CLI options and then officially load and bind things
1715 s_ini_strings = po.iniStrings;
1716 RuntimeOption::Load(
1717 ini,
1718 config,
1719 po.iniStrings,
1720 po.confStrings,
1721 &messages,
1722 scriptFilePath
1724 std::vector<std::string> badnodes;
1725 config.lint(badnodes);
1726 for (const auto& badnode : badnodes) {
1727 const auto msg = "Possible bad config node: " + badnode;
1728 fprintf(stderr, "%s\n", msg.c_str());
1729 messages.push_back(msg);
1732 if (po.mode == "getoption") {
1733 if (po.args.size() < 1) {
1734 fprintf(stderr, "Must specify an option to load\n");
1735 return 1;
1737 Variant value;
1738 bool ret = IniSetting::Get(po.args[0], value);
1739 if (!ret) {
1740 fprintf(stderr, "No such option: %s\n", po.args[0].data());
1741 return 1;
1743 if (!value.isString()) {
1744 VariableSerializer vs{VariableSerializer::Type::JSON};
1745 value = vs.serializeValue(value, false);
1747 printf("%s\n", value.toString().data());
1748 return 0;
1752 std::vector<int> inherited_fds;
1753 RuntimeOption::BuildId = po.buildId;
1754 RuntimeOption::InstanceId = po.instanceId;
1756 // Do this as early as possible to avoid creating temp files and spawing
1757 // light processes. Correct compilation still requires loading all of the
1758 // ini/hdf/cli options.
1759 if (po.mode == "dumphhas" || po.mode == "verify") {
1760 if (po.file.empty() && po.args.empty()) {
1761 std::cerr << "Nothing to do. Pass a hack file to compile.\n";
1762 return 1;
1765 auto const file = [] (std::string file) -> std::string {
1766 if (!FileUtil::isAbsolutePath(file)) {
1767 return SourceRootInfo::GetCurrentSourceRoot() + std::move(file);
1769 return file;
1770 }(po.file.empty() ? po.args[0] : po.file);
1772 RuntimeOption::RepoCommit = false; // avoid initializing a repo
1774 std::fstream fs(file, std::ios::in);
1775 if (!fs) {
1776 std::cerr << "Unable to open \"" << file << "\"\n";
1777 return 1;
1779 std::stringstream contents;
1780 contents << fs.rdbuf();
1782 auto const str = contents.str();
1783 auto const sha1 = SHA1{
1784 mangleUnitSha1(string_sha1(str), file, RepoOptions::defaults())
1787 compilers_start();
1788 hphp_thread_init();
1789 g_context.getCheck();
1790 SCOPE_EXIT { hphp_thread_exit(); };
1792 // Initialize compiler state
1793 hphp_compiler_init();
1795 if (po.mode == "dumphhas") RuntimeOption::EvalDumpHhas = true;
1796 else RuntimeOption::EvalVerifyOnly = true;
1797 SystemLib::s_inited = true;
1799 // Ensure write to SystemLib::s_inited is visible by other threads.
1800 std::atomic_thread_fence(std::memory_order_release);
1802 auto compiled = compile_file(str.c_str(), str.size(), sha1, file.c_str(),
1803 Native::s_noNativeFuncs,
1804 RepoOptions::defaults(), nullptr);
1806 if (po.mode == "verify") {
1807 return 0;
1810 // This will dump the hhas for file as EvalDumpHhas was set
1811 if (!compiled) {
1812 std::cerr << "Unable to compile \"" << file << "\"\n";
1813 return 1;
1816 return 0;
1819 if (po.port != -1) {
1820 RuntimeOption::ServerPort = po.port;
1822 if (po.portfd != -1) {
1823 RuntimeOption::ServerPortFd = po.portfd;
1824 inherited_fds.push_back(po.portfd);
1826 if (po.sslportfd != -1) {
1827 RuntimeOption::SSLPortFd = po.sslportfd;
1828 inherited_fds.push_back(po.sslportfd);
1830 if (po.admin_port != -1) {
1831 RuntimeOption::AdminServerPort = po.admin_port;
1833 if (po.noSafeAccessCheck) {
1834 RuntimeOption::SafeFileAccess = false;
1836 IniSetting::s_system_settings_are_set = true;
1837 tl_heap->resetRuntimeOptions();
1839 auto opened_logs = open_server_log_files();
1840 if (po.mode == "daemon") {
1841 if (!opened_logs) {
1842 Logger::Error("Log file not specified under daemon mode.\n\n");
1844 proc::daemonize();
1847 if (RuntimeOption::ServerExecutionMode()) {
1848 for (auto const& m : messages) {
1849 Logger::Info(m);
1853 #ifndef _MSC_VER
1854 // Defer the initialization of light processes until the log file handle is
1855 // created, so that light processes can log to the right place. If we ever
1856 // lose a light process, stop the server instead of proceeding in an
1857 // uncertain state. Don't start them in DumpHhas mode because
1858 // it _Exit()s after loading the first non-systemlib unit.
1859 if (!RuntimeOption::EvalDumpHhas) {
1860 LightProcess::SetLostChildHandler([](pid_t /*child*/) {
1861 if (!HttpServer::Server) return;
1862 if (!HttpServer::Server->isStopped()) {
1863 HttpServer::Server->stopOnSignal(SIGCHLD);
1866 LightProcess::Initialize(RuntimeOption::LightProcessFilePrefix,
1867 RuntimeOption::LightProcessCount,
1868 RuntimeOption::EvalRecordSubprocessTimes,
1869 inherited_fds);
1871 #endif
1872 #if USE_JEMALLOC_EXTENT_HOOKS
1873 if (RuntimeOption::EvalEnableArenaMetadata1GPage) {
1874 // Set up extent hook so that we can place jemalloc metadata on 1G pages.
1875 // This needs to be done after initializing LightProcess (which forks),
1876 // because the child process does malloc which won't work with jemalloc
1877 // metadata on 1G huge pages.
1878 setup_jemalloc_metadata_extent_hook(
1879 RuntimeOption::EvalEnableArenaMetadata1GPage,
1880 RuntimeOption::EvalEnableNumaArenaMetadata1GPage,
1881 RuntimeOption::EvalArenaMetadataReservedSize
1883 } else if (RuntimeOption::ServerExecutionMode()) {
1884 purge_all();
1885 setup_arena0({RuntimeOption::EvalNum1GPagesForA0,
1886 RuntimeOption::EvalNum2MPagesForA0});
1888 #endif
1890 auto const addTypeToEmbeddedPath = [&](std::string path, const char* type) {
1891 auto const typePlaceholder = "%{type}";
1892 assertx(strstr(type, typePlaceholder) == nullptr);
1893 size_t idx;
1894 if ((idx = path.find(typePlaceholder)) != std::string::npos) {
1895 path.replace(idx, strlen(typePlaceholder), type);
1897 return path;
1900 // We want to initialize the type-scanners as early as possible
1901 // because any allocations before-hand will get a generic unknown
1902 // type type-index.
1903 SCOPE_EXIT {
1904 // this would be handled by hphp_process_exit, but some paths
1905 // short circuit before getting there.
1906 embedded_data_cleanup();
1908 try {
1909 type_scan::init(
1910 addTypeToEmbeddedPath(
1911 RuntimeOption::EvalEmbeddedDataExtractPath,
1912 "type_scanners"
1914 addTypeToEmbeddedPath(
1915 RuntimeOption::EvalEmbeddedDataFallbackPath,
1916 "type_scanners"
1918 RuntimeOption::EvalEmbeddedDataTrustExtract
1920 } catch (const type_scan::InitException& exn) {
1921 Logger::Error("Unable to initialize GC type-scanners: %s", exn.what());
1922 exit(HPHP_EXIT_FAILURE);
1924 ThreadLocalManager::GetManager().initTypeIndices();
1926 // It's okay if this fails.
1927 init_member_reflection(
1928 addTypeToEmbeddedPath(
1929 RuntimeOption::EvalEmbeddedDataExtractPath,
1930 "member_reflection"
1932 addTypeToEmbeddedPath(
1933 RuntimeOption::EvalEmbeddedDataFallbackPath,
1934 "member_reflection"
1936 RuntimeOption::EvalEmbeddedDataTrustExtract
1939 if (!ShmCounters::initialize(true, Logger::Error)) {
1940 exit(HPHP_EXIT_FAILURE);
1943 // Initialize compiler state
1944 hphp_compiler_init();
1946 if (!po.lint.empty()) {
1947 Logger::LogHeader = false;
1948 Logger::LogLevel = Logger::LogInfo;
1949 Logger::UseCronolog = false;
1950 Logger::UseLogFile = true;
1951 // we're linting, reset whatever logger settings and write once to stdout
1952 Logger::ClearThreadLog();
1953 for (auto& el : RuntimeOption::ErrorLogs) {
1954 const auto& name = el.first;
1955 Logger::SetTheLogger(name, nullptr);
1957 Logger::SetTheLogger(Logger::DEFAULT, new Logger());
1959 if (po.isTempFile) {
1960 tempFile = po.lint;
1963 hphp_process_init();
1964 SCOPE_EXIT { hphp_process_exit(); };
1966 try {
1967 auto const filename = makeStaticString(po.lint.c_str());
1968 auto const unit = lookupUnit(filename, "", nullptr,
1969 Native::s_noNativeFuncs, false);
1970 if (unit == nullptr) {
1971 throw FileOpenException(po.lint);
1973 const StringData* msg;
1974 int line;
1975 if (unit->compileTimeFatal(msg, line)) {
1976 VMParserFrame parserFrame;
1977 parserFrame.filename = filename;
1978 parserFrame.lineNumber = line;
1979 Array bt = createBacktrace(BacktraceArgs()
1980 .withSelf()
1981 .setParserFrame(&parserFrame));
1982 raise_fatal_error(msg->data(), bt);
1984 } catch (FileOpenException& e) {
1985 Logger::Error(e.getMessage());
1986 return 1;
1987 } catch (const FatalErrorException& e) {
1988 RuntimeOption::CallUserHandlerOnFatals = false;
1989 RuntimeOption::AlwaysLogUnhandledExceptions = false;
1990 g_context->onFatalError(e);
1991 return 1;
1993 Logger::Info("No syntax errors detected in %s", po.lint.c_str());
1994 return 0;
1997 if (argc <= 1 || po.mode == "run" || po.mode == "debug" ||
1998 po.mode == "vsdebug") {
1999 set_stack_size();
2001 if (po.isTempFile) {
2002 tempFile = po.file;
2005 set_execution_mode("run");
2006 /* recreate the hardware counters for the main thread now that we know
2007 * whether to include subprocess times */
2008 HardwareCounter::s_counter.destroy();
2009 HardwareCounter::s_counter.getCheck();
2011 int new_argc;
2012 char **new_argv;
2013 prepare_args(new_argc, new_argv, po.args, po.file.c_str());
2015 std::string const cliFile = !po.file.empty() ? po.file :
2016 new_argv[0] ? new_argv[0] : "";
2017 if (po.mode != "debug" && cliFile.empty()) {
2018 std::cerr << "Nothing to do. Either pass a hack file to run, or "
2019 "use -m server\n";
2020 return 1;
2022 Repo::setCliFile(cliFile);
2024 int ret = 0;
2025 hphp_process_init();
2026 SCOPE_EXIT { hphp_process_exit(); };
2028 block_sync_signals_and_start_handler_thread();
2030 if (RuntimeOption::EvalUseRemoteUnixServer != "no" &&
2031 !RuntimeOption::EvalUnixServerPath.empty() &&
2032 (!po.file.empty() || !po.args.empty())) {
2033 std::vector<std::string> args;
2034 if (!po.file.empty()) {
2035 args.emplace_back(po.file);
2037 args.insert(args.end(), po.args.begin(), po.args.end());
2038 run_command_on_cli_server(
2039 RuntimeOption::EvalUnixServerPath.c_str(), args, po.count
2041 if (RuntimeOption::EvalUseRemoteUnixServer == "only") {
2042 Logger::Error("Failed to connect to unix server.");
2043 exit(255);
2047 std::string file;
2048 if (new_argc > 0) {
2049 file = new_argv[0];
2052 if (po.mode == "debug") {
2053 StackTraceNoHeap::AddExtraLogging("IsDebugger", "True");
2054 RuntimeOption::EnableHphpdDebugger = true;
2055 po.debugger_options.fileName = file;
2056 po.debugger_options.user = po.user;
2057 Eval::DebuggerProxyPtr localProxy =
2058 Eval::Debugger::StartClient(po.debugger_options);
2059 if (!localProxy) {
2060 Logger::Error("Failed to start debugger client\n\n");
2061 return 1;
2063 Eval::Debugger::RegisterSandbox(localProxy->getDummyInfo());
2064 std::shared_ptr<std::vector<std::string>> client_args;
2065 bool restart = false;
2066 ret = 0;
2067 while (true) {
2068 try {
2069 assertx(po.debugger_options.fileName == file);
2070 execute_command_line_begin(new_argc, new_argv, po.xhprofFlags);
2071 // Set the proxy for this thread to be the localProxy we just
2072 // created. If we're script debugging, this will be the proxy that
2073 // does all of our work. If we're remote debugging, this proxy will
2074 // go unused until we finally stop it when the user quits the
2075 // debugger.
2076 g_context->setSandboxId(localProxy->getDummyInfo().id());
2077 if (restart) {
2078 // Systemlib.php is not loaded again, so we need this if we
2079 // are to hit any breakpoints in systemlib.
2080 proxySetBreakPoints(localProxy.get());
2082 Eval::Debugger::DebuggerSession(po.debugger_options, restart);
2083 restart = false;
2084 execute_command_line_end(po.xhprofFlags, true, file.c_str());
2085 } catch (const Eval::DebuggerRestartException& e) {
2086 execute_command_line_end(0, false, nullptr);
2088 if (!e.m_args->empty()) {
2089 file = e.m_args->at(0);
2090 po.debugger_options.fileName = file;
2091 client_args = e.m_args;
2092 free(new_argv);
2093 prepare_args(new_argc, new_argv, *client_args, nullptr);
2095 restart = true;
2096 } catch (const Eval::DebuggerClientExitException& e) {
2097 execute_command_line_end(0, false, nullptr);
2098 break; // end user quitting debugger
2102 } else {
2103 tracing::Request _{
2104 "cli-request",
2105 file,
2106 [&] { return tracing::Props{}.add("file", file); }
2109 ret = 0;
2111 for (int i = 0; i < po.count; i++) {
2112 execute_command_line_begin(new_argc, new_argv, po.xhprofFlags);
2113 ret = 255;
2114 if (hphp_invoke_simple(file, false /* warmup only */)) {
2115 ret = *rl_exit_code;
2117 const bool last = i == po.count - 1;
2118 if (last && jit::tc::dumpEnabled()) {
2119 jit::mcgen::joinWorkerThreads();
2120 jit::tc::dump();
2122 execute_command_line_end(po.xhprofFlags, true, file.c_str());
2126 free(new_argv);
2128 return ret;
2131 if (po.mode == "daemon" || po.mode == "server") {
2132 if (!po.user.empty()) RuntimeOption::ServerUser = po.user;
2133 return start_server(RuntimeOption::ServerUser, po.xhprofFlags);
2136 if (po.mode == "replay" && !po.args.empty()) {
2137 RuntimeOption::RecordInput = false;
2138 set_execution_mode("server");
2139 HttpServer server; // so we initialize runtime properly
2140 HttpRequestHandler handler(0);
2141 for (int i = 0; i < po.count; i++) {
2142 for (unsigned int j = 0; j < po.args.size(); j++) {
2143 ReplayTransport rt;
2144 rt.replayInput(po.args[j].c_str());
2145 handler.run(&rt);
2146 printf("%s\n", rt.getResponse().c_str());
2149 return 0;
2152 if (po.mode == "translate" && !po.args.empty()) {
2153 printf("%s", translate_stack(po.args[0].c_str()).c_str());
2154 return 0;
2157 cout << desc << "\n";
2158 return -1;
2161 String canonicalize_path(const String& p, const char* root, int rootLen) {
2162 String path = FileUtil::canonicalize(p);
2163 if (path.charAt(0) == '/') {
2164 auto const& sourceRoot = RuntimeOption::SourceRoot;
2165 int len = sourceRoot.size();
2166 if (len && strncmp(path.data(), sourceRoot.c_str(), len) == 0) {
2167 return path.substr(len);
2169 if (root && rootLen && strncmp(path.data(), root, rootLen) == 0) {
2170 return path.substr(rootLen);
2173 return path;
2176 static std::string systemlib_split(const std::string& slib, std::string* hhas) {
2177 auto pos = slib.find("\n<?hhas\n");
2178 if (pos != std::string::npos) {
2179 if (hhas) *hhas = slib.substr(pos + 8);
2180 return slib.substr(0, pos);
2182 return slib;
2185 // Retrieve a systemlib (or mini systemlib) from the
2186 // current executable or another ELF object file.
2188 // Additionally, when retrieving the main systemlib
2189 // from the current executable, honor the
2190 // HHVM_SYSTEMLIB environment variable as an override.
2191 std::string get_systemlib(std::string* hhas,
2192 const std::string &section /*= "systemlib" */,
2193 const std::string &filename /*= "" */) {
2194 if (filename.empty() && section == "systemlib") {
2195 if (auto const file = getenv("HHVM_SYSTEMLIB")) {
2196 std::ifstream ifs(file);
2197 if (ifs.good()) {
2198 return systemlib_split(std::string(
2199 std::istreambuf_iterator<char>(ifs),
2200 std::istreambuf_iterator<char>()), hhas);
2205 embedded_data desc;
2206 if (!get_embedded_data(section.c_str(), &desc, filename)) return "";
2208 auto const data = read_embedded_data(desc);
2209 return systemlib_split(data, hhas);
2212 ///////////////////////////////////////////////////////////////////////////////
2213 // C++ ffi
2215 #ifndef _MSC_VER
2216 namespace {
2218 void on_timeout(int sig, siginfo_t* info, void* /*context*/) {
2219 if (sig == SIGVTALRM && info && info->si_code == SI_TIMER) {
2220 auto data = (RequestTimer*)info->si_value.sival_ptr;
2221 if (data) {
2222 data->onTimeout();
2228 #endif
2231 * Update constants to their real values and sync some runtime options
2233 static void update_constants_and_options() {
2234 assertx(ExtensionRegistry::modulesInitialised());
2235 // If extension constants were used in the ini files (e.g., E_ALL) they
2236 // would have come out as 0 in the previous pass until we load and
2237 // initialize our extensions, which we do in RuntimeOption::Load() via
2238 // ExtensionRegistry::ModuleLoad() and in ExtensionRegistry::ModuleInit()
2239 // in hphp_process_init(). We will re-import and set only the constants that
2240 // have been now bound to their proper value.
2241 IniSettingMap ini = IniSettingMap();
2242 for (auto& filename: s_config_files) {
2243 Config::ParseIniFile(filename, ini, true);
2245 // Reset the INI settings from the CLI.
2246 for (auto& iniStr: s_ini_strings) {
2247 Config::ParseIniString(iniStr, ini, true);
2250 // Reset, possibly, some request dependent runtime options based on certain
2251 // setting values. Do this here so we ensure the constants have been loaded
2252 // correctly (e.g., error_reporting E_ALL, etc.)
2253 Variant sys;
2254 if (IniSetting::GetSystem("error_reporting", sys)) {
2255 RuntimeOption::RuntimeErrorReportingLevel = sys.toInt64();
2256 RID().setErrorReportingLevel(RuntimeOption::RuntimeErrorReportingLevel);
2258 if (IniSetting::GetSystem("memory_limit", sys)) {
2259 RID().setMemoryLimit(sys.toString().toCppString());
2260 RuntimeOption::RequestMemoryMaxBytes = RID().getMemoryLimitNumeric();
2264 void hphp_thread_init() {
2265 #if USE_JEMALLOC_EXTENT_HOOKS
2266 arenas_thread_init();
2267 #endif
2268 rds::threadInit();
2269 ServerStats::GetLogger();
2270 zend_get_bigint_data();
2271 zend_rand_init();
2272 get_server_note();
2273 tl_heap.getCheck()->init();
2275 assertx(RequestInfo::s_requestInfo.isNull());
2276 RequestInfo::s_requestInfo.getCheck()->init();
2278 HardwareCounter::s_counter.getCheck();
2279 ExtensionRegistry::threadInit();
2280 InitFiniNode::ThreadInit();
2282 // Ensure that there's no request-allocated memory. This call must happen at
2283 // least once after RDS has been initialized to ensure
2284 // MemoryManager::resetGC() sets a proper trigger threshold.
2285 hphp_memory_cleanup();
2288 void hphp_thread_exit() {
2289 InitFiniNode::ThreadFini();
2290 ExtensionRegistry::threadShutdown();
2291 if (!g_context.isNull()) g_context.destroy();
2292 rds::threadExit();
2293 #if USE_JEMALLOC_EXTENT_HOOKS
2294 arenas_thread_exit();
2295 #endif
2298 void hphp_process_init() {
2299 pthread_attr_t attr;
2300 // Linux+GNU extension
2301 #if defined(_GNU_SOURCE) && defined(__linux__)
2302 if (pthread_getattr_np(pthread_self(), &attr) != 0 ) {
2303 Logger::Error("pthread_getattr_np failed before checking stack limits");
2304 _exit(1);
2306 #else
2307 if (pthread_attr_init(&attr) != 0 ) {
2308 Logger::Error("pthread_attr_init failed before checking stack limits");
2309 _exit(1);
2311 #endif
2312 init_stack_limits(&attr);
2313 if (pthread_attr_destroy(&attr) != 0 ) {
2314 Logger::Error("pthread_attr_destroy failed after checking stack limits");
2315 _exit(1);
2317 BootStats::mark("pthread_init");
2319 Process::InitProcessStatics();
2320 BootStats::mark("Process::InitProcessStatics");
2322 HHProf::Init();
2324 // initialize the tzinfo cache.
2325 timezone_init();
2326 BootStats::mark("timezone_init");
2328 // start any external compilers
2329 compilers_start();
2330 BootStats::mark("compilers_start");
2332 rds::processInit();
2334 hphp_thread_init();
2336 #ifndef _MSC_VER
2337 struct sigaction action = {};
2338 action.sa_sigaction = on_timeout;
2339 action.sa_flags = SA_SIGINFO | SA_NODEFER | SA_RESTART;
2340 sigaction(SIGVTALRM, &action, nullptr);
2341 #endif
2342 // start takes milliseconds, Period is a double in seconds
2343 Xenon::getInstance().start(1000 * RuntimeOption::XenonPeriodSeconds);
2344 BootStats::mark("xenon");
2346 // set up strobelight signal handling
2347 Strobelight::getInstance().init();
2348 BootStats::mark("strobelight");
2350 // reinitialize pcre table
2351 pcre_reinit();
2352 BootStats::mark("pcre_reinit");
2354 // the liboniguruma docs say this isnt needed,
2355 // but the implementation of init is not
2356 // thread safe due to bugs
2357 onig_init();
2358 BootStats::mark("onig_init");
2360 g_context.getCheck();
2361 // Some event handlers are registered during the startup process.
2362 g_context->acceptRequestEventHandlers(true);
2363 if (!registrationComplete) {
2364 folly::SingletonVault::singleton()->registrationComplete();
2365 registrationComplete = true;
2367 InitFiniNode::ProcessPreInit();
2368 // TODO(9795696): Race in thread map may trigger spurious logging at
2369 // thread exit, so for now, only spawn threads if we're a server.
2370 const uint32_t maxWorkers = RuntimeOption::ServerExecutionMode() ? 3 : 0;
2371 InitFiniNode::ProcessInitConcurrentStart(maxWorkers);
2372 SCOPE_EXIT {
2373 InitFiniNode::ProcessInitConcurrentWaitForEnd();
2374 BootStats::mark("extra_process_init_concurrent_wait");
2376 jit::mcgen::processInit();
2377 jit::processInitProfData();
2378 g_vmProcessInit();
2379 BootStats::mark("g_vmProcessInit");
2381 PageletServer::Restart();
2382 BootStats::mark("PageletServer::Restart");
2383 XboxServer::Restart();
2384 BootStats::mark("XboxServer::Restart");
2385 Stream::RegisterCoreWrappers();
2386 BootStats::mark("Stream::RegisterCoreWrappers");
2387 ExtensionRegistry::moduleInit();
2388 BootStats::mark("ExtensionRegistry::moduleInit");
2390 if (!RuntimeOption::DeploymentId.empty()) {
2391 StackTraceNoHeap::AddExtraLogging(
2392 "DeploymentId", RuntimeOption::DeploymentId);
2395 // Now that constants have been bound we can update options using constants
2396 // in ini files (e.g., E_ALL) and sync some other options
2397 update_constants_and_options();
2399 InitFiniNode::ProcessInit();
2400 BootStats::mark("extra_process_init");
2402 std::unique_ptr<std::thread> apcLoadingThread;
2403 if (!apcExtension::PrimeLibrary.empty()) {
2404 apcLoadingThread = std::make_unique<std::thread>([&] {
2405 hphp_thread_init();
2406 hphp_session_init(Treadmill::SessionKind::APCPrime);
2407 SCOPE_EXIT {
2408 hphp_context_exit();
2409 hphp_session_exit();
2410 hphp_thread_exit();
2412 UnlimitSerializationScope unlimit;
2413 // TODO(9755792): Add real execution mode for snapshot generation.
2414 if (apcExtension::PrimeLibraryUpgradeDest != "") {
2415 Timer timer(Timer::WallTime, "optimizeApcPrime");
2416 apc_load(apcExtension::LoadThread);
2417 } else {
2418 apc_load(apcExtension::LoadThread);
2424 if (RuntimeOption::RepoAuthoritative &&
2425 !RuntimeOption::EvalJitSerdesFile.empty() &&
2426 jit::mcgen::retranslateAllEnabled()) {
2427 auto const mode = RuntimeOption::EvalJitSerdesMode;
2428 if (isJitDeserializing()) {
2429 if (RuntimeOption::ServerExecutionMode()) {
2430 Logger::FInfo("JitDeserializeFrom: {}",
2431 RuntimeOption::EvalJitSerdesFile);
2433 auto const numWorkers = RuntimeOption::EvalJitWorkerThreadsForSerdes ?
2434 RuntimeOption::EvalJitWorkerThreadsForSerdes : Process::GetCPUCount();
2435 #if USE_JEMALLOC_EXTENT_HOOKS
2436 auto const numArenas =
2437 std::min(RuntimeOption::EvalJitWorkerArenas,
2438 std::max(RuntimeOption::EvalJitWorkerThreads, numWorkers));
2439 setup_extra_arenas(numArenas);
2440 #endif
2441 auto const errMsg = jit::deserializeProfData(
2442 RuntimeOption::EvalJitSerdesFile,
2443 RuntimeOption::EvalJitParallelDeserialize ? numWorkers : 1);
2445 if (mode == JitSerdesMode::DeserializeAndDelete) {
2446 // Delete the serialized profile data when we finish reading
2447 if (RuntimeOption::ServerExecutionMode()) {
2448 Logger::FInfo("Deleting serialized profile-data file: {}",
2449 RuntimeOption::EvalJitSerdesFile);
2451 unlink(RuntimeOption::EvalJitSerdesFile.c_str());
2454 if (errMsg.empty()) {
2455 if (RuntimeOption::ServerExecutionMode()) {
2456 Logger::FInfo("JitDeserialize: Loaded {} Units with {} workers",
2457 numLoadedUnits(), numWorkers);
2459 BootStats::mark("jit::deserializeProfData");
2460 BootStats::set("prof_data_source_host",
2461 jit::ProfData::buildHost()->toCppString());
2462 BootStats::set("prof_data_timestamp", jit::ProfData::buildTime());
2463 RuntimeOption::EvalJitProfileRequests = 0;
2464 RuntimeOption::EvalJitWorkerThreads = numWorkers;
2466 // Run retranslateAll asynchronously, without waiting for it to finish
2467 // here.
2468 jit::mcgen::checkRetranslateAll(true);
2469 if (mode == JitSerdesMode::DeserializeAndExit) {
2470 if (RuntimeOption::ServerExecutionMode()) {
2471 Logger::Info("JitDeserialize finished; exiting");
2473 if (jit::tc::dumpEnabled()) {
2474 jit::mcgen::joinWorkerThreads();
2475 jit::tc::dump();
2477 hphp_process_exit();
2478 exit(0);
2480 } else { // failed to deserialize
2481 if (mode == JitSerdesMode::DeserializeOrFail ||
2482 mode == JitSerdesMode::DeserializeAndExit) {
2483 Logger::Error(errMsg);
2484 hphp_process_exit();
2485 exit(1);
2487 if (mode == JitSerdesMode::DeserializeOrGenerate) {
2488 Logger::Info(errMsg +
2489 ", scheduling one time serialization and restart");
2490 RuntimeOption::EvalJitSerdesMode = JitSerdesMode::SerializeAndExit;
2491 } else {
2492 Logger::Info(errMsg + ", will profile then retranslateAll");
2498 rds::requestExit();
2499 BootStats::mark("rds::requestExit");
2500 // Reset the preloaded g_context
2501 ExecutionContext *context = g_context.getNoCheck();
2502 context->onRequestShutdown(); // TODO T20898959 kill early REH usage.
2503 context->~ExecutionContext();
2504 new (context) ExecutionContext();
2505 BootStats::mark("ExecutionContext");
2507 if (apcLoadingThread) {
2508 apcLoadingThread->join();
2510 // TODO(9755792): Add real execution mode for snapshot generation.
2511 if (apcExtension::PrimeLibraryUpgradeDest != "") {
2512 Logger::Info("APC PrimeLibrary upgrade mode completed; exiting.");
2513 hphp_process_exit();
2514 exit(0);
2518 static void handle_exception(bool& ret, ExecutionContext* context,
2519 std::string& errorMsg, ContextOfException where,
2520 bool& error, bool richErrorMsg) {
2521 assertx(where == ContextOfException::Invoke ||
2522 where == ContextOfException::ReqInit);
2523 try {
2524 handle_exception_helper(ret, context, errorMsg, where, error, richErrorMsg);
2525 } catch (const ExitException& e) {
2526 // Got an ExitException during exception handling, handle
2527 // similarly to the case below but don't call obEndAll().
2528 } catch (...) {
2529 handle_exception_helper(ret, context, errorMsg, ContextOfException::Handler,
2530 error, richErrorMsg);
2531 context->obEndAll();
2535 static void handle_reqinit_exception(bool &ret, ExecutionContext *context,
2536 std::string &errorMsg, bool &error) {
2537 handle_exception(ret, context, errorMsg, ContextOfException::ReqInit, error,
2538 false);
2541 static void handle_invoke_exception(bool &ret, ExecutionContext *context,
2542 std::string &errorMsg, bool &error,
2543 bool richErrorMsg) {
2544 handle_exception(ret, context, errorMsg, ContextOfException::Invoke, error,
2545 richErrorMsg);
2548 void invoke_prelude_script(
2549 const char* currentDir,
2550 const std::string& document,
2551 const std::string& prelude,
2552 const char* root
2554 // If $SOURCE_ROOT is found in prelude path
2555 // Execute the script from PHP root folder
2556 // or from current folder
2557 static const std::string s_phpRootVar("${SOURCE_ROOT}");
2558 std::string preludeScript(prelude);
2559 auto posPhpRoot = preludeScript.find(s_phpRootVar);
2560 if (std::string::npos != posPhpRoot){
2561 preludeScript.replace(posPhpRoot, s_phpRootVar.length(),
2562 root ? root : SourceRootInfo::GetCurrentSourceRoot().c_str());
2564 FileUtil::runRelative(
2565 preludeScript,
2566 String(document, CopyString),
2567 currentDir,
2568 [currentDir] (const String& f) {
2569 auto const w = Stream::getWrapperFromURI(f, nullptr, false);
2570 if (w->access(f, R_OK) == 0) {
2571 include_impl_invoke(f, true, currentDir, true);
2572 return true;
2574 return false;
2579 static bool hphp_warmup(ExecutionContext *context,
2580 const std::string& cmd,
2581 const std::string &reqInitFunc,
2582 const std::string &reqInitDoc,
2583 const std::string &prelude,
2584 bool &error,
2585 bool runEntryPoint) {
2586 tracing::Block _{
2587 "warmup",
2588 [&] {
2589 return tracing::Props{}
2590 .add("cmd", cmd)
2591 .add("req_init_doc", reqInitDoc)
2592 .add("req_init_func", reqInitFunc)
2593 .add("prelude", prelude);
2597 bool ret = true;
2598 error = false;
2599 std::string errorMsg;
2601 ServerStatsHelper ssh("reqinit");
2602 try {
2603 if (!prelude.empty() && (!cmd.empty() || !reqInitDoc.empty())) {
2604 auto const currentDir = context->getCwd();
2605 auto const& document = !reqInitDoc.empty() ? reqInitDoc : cmd;
2606 invoke_prelude_script(currentDir.data(), document, prelude);
2609 if (!reqInitDoc.empty()) {
2610 include_impl_invoke(reqInitDoc, true, "", runEntryPoint);
2612 if (!reqInitFunc.empty()) {
2613 invoke(reqInitFunc, Array());
2615 context->backupSession();
2616 } catch (...) {
2617 handle_reqinit_exception(ret, context, errorMsg, error);
2620 return ret;
2623 void hphp_session_init(Treadmill::SessionKind session_kind,
2624 Transport* transport) {
2625 assertx(!*s_sessionInitialized);
2626 g_context.getCheck();
2627 AsioSession::Init();
2628 Socket::clearLastError();
2629 RI().onSessionInit();
2630 tl_heap->resetExternalStats();
2632 g_thread_safe_locale_handler->reset();
2633 Treadmill::startRequest(session_kind);
2635 // Ordering is sensitive; StatCache::requestInit produces work that
2636 // must be done in ExecutionContext::requestInit.
2637 StatCache::requestInit();
2639 // Allow request event handlers to be created now that a new request has
2640 // started.
2641 g_context->acceptRequestEventHandlers(true);
2643 g_context->requestInit(); // must happen after treadmill start
2644 if (transport != nullptr) g_context->setTransport(transport);
2645 *s_sessionInitialized = true;
2647 ExtensionRegistry::requestInit();
2649 // Sample function calls for this request
2650 if (RID().logFunctionCalls()) {
2651 EventHook::Enable();
2654 auto const pme_freq = RuntimeOption::EvalPerfMemEventRequestFreq;
2655 if (pme_freq > 0 && folly::Random::rand32(pme_freq) == 0) {
2656 // Enable memory access sampling for this request.
2657 perf_event_enable(
2658 RuntimeOption::EvalPerfMemEventSampleFreq,
2659 [] (PerfEvent) { setSurpriseFlag(PendingPerfEventFlag); }
2664 bool hphp_invoke_simple(const std::string& filename, bool warmupOnly) {
2665 bool error;
2666 std::string errorMsg;
2667 return hphp_invoke(g_context.getNoCheck(), filename, false, null_array,
2668 nullptr, "", "", error, errorMsg,
2669 true /* once */,
2670 warmupOnly,
2671 false /* richErrorMsg */,
2672 RuntimeOption::EvalPreludePath);
2675 bool hphp_invoke(ExecutionContext *context, const std::string &cmd,
2676 bool func, const Array& funcParams, Variant* funcRet,
2677 const std::string &reqInitFunc, const std::string &reqInitDoc,
2678 bool &error, std::string &errorMsg,
2679 bool once, bool warmupOnly,
2680 bool richErrorMsg, const std::string& prelude,
2681 bool allowDynCallNoPointer /* = false */) {
2682 tracing::Block _{"invoke", [&] { return tracing::Props{}.add("cmd", cmd); }};
2684 bool isServer =
2685 RuntimeOption::ServerExecutionMode() && !is_cli_server_mode();
2686 error = false;
2688 // Make sure we have the right current working directory within the repo
2689 // based on what server.source_root was set to (current process directory
2690 // being the default)
2691 if (RuntimeOption::RepoAuthoritative) {
2692 context->setCwd(RuntimeOption::SourceRoot);
2695 String oldCwd;
2696 if (isServer) {
2697 oldCwd = context->getCwd();
2699 if (!hphp_warmup(context, cmd, reqInitFunc, reqInitDoc, prelude, error,
2700 func)) {
2701 if (isServer) context->setCwd(oldCwd);
2702 return false;
2705 tl_heap->resetCouldOOM(isStandardRequest());
2706 RID().resetTimers();
2708 bool ret = true;
2709 if (!warmupOnly) {
2710 try {
2711 ServerStatsHelper ssh("invoke");
2712 if (!RuntimeOption::AutoPrependFile.empty() &&
2713 RuntimeOption::AutoPrependFile.compare("none") ) {
2714 require(RuntimeOption::AutoPrependFile, false,
2715 context->getCwd().data(), true);
2717 if (func) {
2718 auto const ret = invoke(cmd, funcParams, allowDynCallNoPointer);
2719 if (funcRet) *funcRet = ret;
2720 } else {
2721 if (isServer) hphp_chdir_file(cmd);
2722 include_impl_invoke(cmd.c_str(), once, "", true);
2724 if (!RuntimeOption::AutoAppendFile.empty() &&
2725 RuntimeOption::AutoAppendFile.compare("none")) {
2726 require(RuntimeOption::AutoAppendFile, false,
2727 context->getCwd().data(), true);
2729 } catch (...) {
2730 handle_invoke_exception(ret, context, errorMsg, error, richErrorMsg);
2734 try {
2735 context->onShutdownPreSend();
2736 } catch (...) {
2737 handle_invoke_exception(ret, context, errorMsg, error, richErrorMsg);
2740 if (isServer) context->setCwd(oldCwd);
2741 return ret;
2744 void hphp_context_exit() {
2745 // Run shutdown handlers. This may cause user code to run.
2746 g_thread_safe_locale_handler->reset();
2748 auto const context = g_context.getNoCheck();
2749 context->onRequestShutdown();
2751 // Extensions could have shutdown handlers
2752 ExtensionRegistry::requestShutdown();
2753 InitFiniNode::RequestFini();
2755 // Extension shutdown could have re-initialized some
2756 // request locals
2757 context->onRequestShutdown();
2759 // This causes request event handler registration to fail until the next
2760 // request starts.
2761 context->acceptRequestEventHandlers(false);
2763 // Clean up a bunch of request state. No user code after this point.
2764 MemoryManager::setExiting();
2765 context->requestExit();
2766 context->obProtect(false);
2767 context->obEndAll();
2770 void hphp_memory_cleanup() {
2771 auto& mm = *tl_heap;
2772 // sweep functions are allowed to access g_context,
2773 // so we can't destroy it yet
2774 mm.sweep();
2776 // We should never have any registered RequestEventHandlers. If we do
2777 // something after onRequestShutdown registered a RequestEventHandler.
2778 // Its now too late to run the requestShutdown functions, but if we carry
2779 // on, requestInit and requestShutdown will never be called again.
2780 // I considered just clearing the inited flags; which works for some
2781 // RequestEventHandlers - but its a disaster for others. So just fail hard
2782 // here.
2783 always_assert(g_context.isNull() || !g_context->hasRequestEventHandlers());
2785 // g_context is request allocated, and has some members that need
2786 // cleanup, so destroy it before its too late
2787 g_context.destroy();
2789 weakref_cleanup();
2790 mm.resetAllocator();
2791 mm.resetCouldOOM();
2794 void hphp_session_exit(Transport* transport) {
2795 assertx(*s_sessionInitialized);
2796 // Server note and INI have to live long enough for the access log to fire.
2797 // RequestLocal is too early.
2798 ServerNote::Reset();
2799 IniSetting::ResetSavedDefaults();
2800 // In JitPGO mode, check if it's time to schedule the retranslation of all
2801 // profiled functions and, if so, schedule it.
2802 jit::mcgen::checkRetranslateAll();
2803 jit::mcgen::checkSerializeOptProf();
2804 jit::tc::requestExit();
2805 // Similarly, apc strings could be in the ServerNote array, and
2806 // it's possible they are scheduled to be destroyed after this request
2807 // finishes.
2808 Treadmill::finishRequest();
2810 RI().onSessionExit();
2812 // We might have events from after the final surprise flag check of the
2813 // request, so consume them here.
2814 perf_event_consume(record_perf_mem_event);
2815 perf_event_disable();
2817 // Get some memory-related counters before tearing down the MemoryManager.
2818 auto entry = transport ? transport->getStructuredLogEntry() : nullptr;
2819 if (entry) tl_heap->recordStats(*entry);
2822 ServerStatsHelper ssh("rollback");
2823 hphp_memory_cleanup();
2826 assertx(tl_heap->empty());
2828 *s_sessionInitialized = false;
2829 s_extra_request_nanoseconds = 0;
2831 if (transport) {
2832 HardwareCounter::UpdateServiceData(transport->getCpuTime(),
2833 transport->getWallTime(),
2834 entry,
2835 true /*psp*/);
2836 if (entry) {
2837 entry->setInt("response_code", transport->getResponseCode());
2838 StructuredLog::log("hhvm_request_perf", *entry);
2839 transport->resetStructuredLogEntry();
2844 void hphp_process_exit() noexcept {
2845 // We want to do clean up on a best-effort basis: don't skip later steps if
2846 // an earlier step fails, and don't propagate exceptions ouf of this function
2847 #define LOG_AND_IGNORE(voidexpr) try { voidexpr; } catch (...) { \
2848 Logger::Error("got exception in cleanup step: " #voidexpr); }
2849 LOG_AND_IGNORE(teardown_cli_server())
2850 LOG_AND_IGNORE(Xenon::getInstance().stop())
2851 LOG_AND_IGNORE(jit::mcgen::joinWorkerThreads())
2852 LOG_AND_IGNORE(jit::tc::processExit())
2853 LOG_AND_IGNORE(PageletServer::Stop())
2854 LOG_AND_IGNORE(XboxServer::Stop())
2855 // Debugger::Stop() needs an execution context
2856 LOG_AND_IGNORE(g_context.getCheck())
2857 LOG_AND_IGNORE(Eval::Debugger::Stop())
2858 LOG_AND_IGNORE(g_context.destroy())
2859 LOG_AND_IGNORE(ExtensionRegistry::moduleShutdown())
2860 LOG_AND_IGNORE(compilers_shutdown())
2861 #ifndef _MSC_VER
2862 LOG_AND_IGNORE(LightProcess::Close())
2863 #endif
2864 LOG_AND_IGNORE(InitFiniNode::ProcessFini())
2865 LOG_AND_IGNORE(folly::SingletonVault::singleton()->destroyInstances())
2866 LOG_AND_IGNORE(embedded_data_cleanup())
2867 LOG_AND_IGNORE(Debug::destroyDebugInfo())
2868 LOG_AND_IGNORE(clearUnitCacheForExit())
2869 #undef LOG_AND_IGNORE
2872 bool is_hphp_session_initialized() {
2873 return *s_sessionInitialized;
2876 static struct SetThreadInitFini {
2877 template<class ThreadT> static typename std::enable_if<
2878 std::is_integral<ThreadT>::value || std::is_pointer<ThreadT>::value>::type
2879 recordThreadAddr(ThreadT threadId, char* stackAddr, size_t stackSize) {
2880 // In the current glibc implementation, pthread_t is a 64-bit unsigned
2881 // integer, whose value equals the address of the thread control block
2882 // (TCB). In x64_64, this is right above the TLS block. In addition,
2883 // TLS and TCB sits at the high end of the stack, i.e.,
2885 // stackAddr + stackSize ----> +---------------+
2886 // | TCB |
2887 // threadId ----> +---------------+
2888 // | TLS |
2889 // +---------------+
2890 // | Stack |
2891 // . .
2892 // . .
2893 // stackAddr ----> +---------------+
2894 auto const tcbBase = reinterpret_cast<char*>(threadId);
2895 auto stackEnd = stackAddr + stackSize;
2896 if (tcbBase > stackAddr && tcbBase < stackEnd) { // the expected layout
2897 // TCB
2898 Debug::DebugInfo::recordDataMap(
2899 tcbBase, stackEnd,
2900 folly::sformat("Thread-{}", static_cast<void*>(tcbBase)));
2901 // TLS
2902 auto const tlsRange = getCppTdata();
2903 auto const tlsSize = (tlsRange.second + 15) / 16 * 16;
2904 stackEnd = tcbBase - tlsSize;
2905 if (tlsSize) {
2906 Debug::DebugInfo::recordDataMap(
2907 stackEnd, tcbBase,
2908 folly::sformat("TLS-{}", static_cast<void*>(tcbBase)));
2911 Debug::DebugInfo::recordDataMap(
2912 stackAddr, stackEnd,
2913 folly::sformat("Stack-{}", static_cast<void*>(tcbBase)));
2915 template <class ThreadT>
2916 static typename std::enable_if<!std::is_integral<ThreadT>::value &&
2917 !std::is_pointer<ThreadT>::value>::type
2918 recordThreadAddr(ThreadT /*threadId*/, char* stackAddr, size_t stackSize) {
2919 // pthread_t is not an integer or pointer to TCB in this pthread
2920 // implementation. But we can still figure out where TLS is.
2921 auto const tlsRange = getCppTdata();
2922 auto const tlsSize = (tlsRange.second + 15) / 16 * 16;
2923 auto const tlsBaseAddr = reinterpret_cast<char*>(tlsBase());
2924 Debug::DebugInfo::recordDataMap(
2925 tlsBaseAddr, tlsBaseAddr + tlsSize,
2926 folly::sformat("TLS-{}", static_cast<void*>(stackAddr)));
2927 Debug::DebugInfo::recordDataMap(
2928 stackAddr, stackAddr + stackSize,
2929 folly::sformat("Stack-{}", static_cast<void*>(stackAddr)));
2932 SetThreadInitFini() {
2933 AsyncFuncImpl::SetThreadInitFunc(
2934 [] (void*) {
2935 #if defined(_GNU_SOURCE) && defined(__linux__)
2936 if (RuntimeOption::EvalPerfDataMap) {
2937 pthread_t threadId = pthread_self();
2938 pthread_attr_t attr;
2939 pthread_getattr_np(threadId, &attr);
2940 void* stackAddr{nullptr};
2941 size_t stackSize{0};
2942 pthread_attr_getstack(&attr, &stackAddr, &stackSize);
2943 pthread_attr_destroy(&attr);
2944 recordThreadAddr(threadId, static_cast<char*>(stackAddr), stackSize);
2946 #endif
2947 hphp_thread_init();
2949 nullptr);
2950 AsyncFuncImpl::SetThreadFiniFunc([](void*) { hphp_thread_exit(); },
2951 nullptr);
2953 } s_SetThreadInitFini;
2955 ///////////////////////////////////////////////////////////////////////////////