2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/base/program-functions.h"
18 #include "hphp/runtime/base/types.h"
19 #include "hphp/runtime/base/type-conversions.h"
20 #include "hphp/runtime/base/builtin_functions.h"
21 #include "hphp/runtime/base/execution_context.h"
22 #include "hphp/runtime/base/thread-init-fini.h"
23 #include "hphp/runtime/base/code_coverage.h"
24 #include "hphp/runtime/base/runtime-option.h"
25 #include "hphp/runtime/base/pprof-server.h"
26 #include "hphp/util/shared_memory_allocator.h"
27 #include "hphp/runtime/server/pagelet_server.h"
28 #include "hphp/runtime/server/xbox_server.h"
29 #include "hphp/runtime/server/http_server.h"
30 #include "hphp/runtime/server/replay_transport.h"
31 #include "hphp/runtime/server/http_request_handler.h"
32 #include "hphp/runtime/server/admin_request_handler.h"
33 #include "hphp/runtime/server/server_stats.h"
34 #include "hphp/runtime/server/server_name_indication.h"
35 #include "hphp/runtime/server/server_note.h"
36 #include "hphp/runtime/base/memory-manager.h"
37 #include "hphp/util/process.h"
38 #include "hphp/util/capability.h"
39 #include "hphp/util/embedded_data.h"
40 #include "hphp/util/timer.h"
41 #include "hphp/util/stack_trace.h"
42 #include "hphp/util/light_process.h"
43 #include "hphp/util/repo_schema.h"
44 #include "hphp/util/current_executable.h"
45 #include "hphp/runtime/base/stat-cache.h"
46 #include "hphp/runtime/ext/extension.h"
47 #include "hphp/runtime/ext/ext_fb.h"
48 #include "hphp/runtime/ext/ext_json.h"
49 #include "hphp/runtime/ext/ext_variable.h"
50 #include "hphp/runtime/ext/ext_apc.h"
51 #include "hphp/runtime/ext/ext_function.h"
52 #include "hphp/runtime/debugger/debugger.h"
53 #include "hphp/runtime/debugger/debugger_client.h"
54 #include "hphp/runtime/base/simple-counter.h"
55 #include "hphp/runtime/base/extended_logger.h"
56 #include "hphp/runtime/base/stream-wrapper-registry.h"
58 #include <boost/program_options/options_description.hpp>
59 #include <boost/program_options/positional_options.hpp>
60 #include <boost/program_options/variables_map.hpp>
61 #include <boost/program_options/parsers.hpp>
62 #include <boost/make_shared.hpp>
64 #include <oniguruma.h>
66 #include "libxml/parser.h"
68 #include "hphp/runtime/base/file-repository.h"
70 #include "hphp/runtime/vm/runtime.h"
71 #include "hphp/runtime/vm/repo.h"
72 #include "hphp/runtime/vm/jit/translator.h"
73 #include "hphp/compiler/builtin_symbols.h"
75 using namespace boost::program_options
;
77 extern char **environ
;
79 #define MAX_INPUT_NESTING_LEVEL 64
83 extern InitFiniNode
*extra_process_init
, *extra_process_exit
;
85 void initialize_repo();
88 * XXX: VM process initialization is handled through a function
89 * pointer so libhphp_runtime.a can be linked into programs that don't
90 * actually initialize the VM.
92 void (*g_vmProcessInit
)();
94 ///////////////////////////////////////////////////////////////////////////////
97 struct ProgramOptions
{
100 StringVec confStrings
;
110 bool noSafeAccessCheck
;
117 Eval::DebuggerClientOptions debugger_options
;
122 StartTime() : startTime(time(nullptr)) {}
125 static StartTime s_startTime
;
126 static string tempFile
;
128 time_t start_time() {
129 return s_startTime
.startTime
;
135 s_HHVM_JIT("HHVM_JIT"),
136 s_REQUEST_START_TIME("REQUEST_START_TIME"),
137 s_REQUEST_TIME("REQUEST_TIME"),
138 s_REQUEST_TIME_FLOAT("REQUEST_TIME_FLOAT"),
139 s_DOCUMENT_ROOT("DOCUMENT_ROOT"),
140 s_SCRIPT_FILENAME("SCRIPT_FILENAME"),
141 s_SCRIPT_NAME("SCRIPT_NAME"),
142 s_PHP_SELF("PHP_SELF"),
146 s_HOSTNAME("HOSTNAME"),
147 s__SERVER("_SERVER"),
150 static void process_cmd_arguments(int argc
, char **argv
) {
151 GlobalVariables
*g
= get_global_variables();
152 g
->set(s_argc
, Variant(argc
), false);
153 Array argvArray
= HphpArray::GetStaticEmptyArray();
154 for (int i
= 0; i
< argc
; i
++) {
155 argvArray
.append(String(argv
[i
]));
157 g
->set(s_argv
, argvArray
, false);
160 void process_env_variables(Variant
&variables
) {
161 for (std::map
<string
, string
>::const_iterator iter
=
162 RuntimeOption::EnvVariables
.begin();
163 iter
!= RuntimeOption::EnvVariables
.end(); ++iter
) {
164 variables
.set(String(iter
->first
), String(iter
->second
));
166 for (char **env
= environ
; env
&& *env
; env
++) {
167 char *p
= strchr(*env
, '=');
169 String
name(*env
, p
- *env
, CopyString
);
170 register_variable(variables
, (char*)name
.data(),
171 String(p
+ 1, CopyString
));
176 void register_variable(Variant
&variables
, char *name
, CVarRef value
,
177 bool overwrite
/* = true */) {
178 // ignore leading spaces in the variable name
180 while (*var
&& *var
== ' ') {
184 // ensure that we don't have spaces or dots in the variable name
186 bool is_array
= false;
187 char *ip
= nullptr; // index pointer
190 if (*p
== ' ' || *p
== '.') {
192 } else if (*p
== '[') {
199 int var_len
= p
- var
;
201 // empty variable name, or variable name with a space in it
205 vector
<Variant
> gpc_elements
;
206 gpc_elements
.reserve(MAX_INPUT_NESTING_LEVEL
); // important, so no resize
207 Variant
*symtable
= &variables
;
209 int index_len
= var_len
;
214 if (++nest_level
> MAX_INPUT_NESTING_LEVEL
) {
215 Logger::Warning("Input variable nesting level exceeded");
228 ip
= strchr(ip
, ']');
230 // PHP variables cannot contain '[' in their names,
231 // so we replace the character with a '_'
232 *(index_s
- 1) = '_';
236 index_len
= strlen(index
);
241 new_idx_len
= strlen(index_s
);
245 symtable
->append(Array::Create());
246 gpc_elements
.push_back(uninit_null());
247 gpc_elements
.back().assignRef(
248 symtable
->lvalAt((int)symtable
->toArray().size() - 1));
250 String
key(index
, index_len
, CopyString
);
251 Variant v
= symtable
->rvalAt(key
);
252 if (v
.isNull() || !v
.is(KindOfArray
)) {
253 symtable
->set(key
, Array::Create());
255 gpc_elements
.push_back(uninit_null());
256 gpc_elements
.back().assignRef(symtable
->lvalAt(key
));
258 symtable
= &gpc_elements
.back();
259 /* ip pointed to the '[' character, now obtain the key */
261 index_len
= new_idx_len
;
274 symtable
->append(value
);
276 String
key(index
, index_len
, CopyString
);
277 if (overwrite
|| !symtable
->toArray().exists(key
)) {
278 symtable
->set(key
, value
);
284 enum class ContextOfException
{
290 static void handle_exception_append_bt(std::string
& errorMsg
,
291 const ExtendedException
& e
) {
292 Array bt
= e
.getBackTrace();
294 errorMsg
+= ExtendedLogger::StringOfStackTrace(bt
);
298 static void handle_exception_helper(bool& ret
,
299 ExecutionContext
* context
,
300 std::string
& errorMsg
,
301 ContextOfException where
,
306 } catch (const Eval::DebuggerException
&e
) {
308 } catch (const ExitException
&e
) {
309 if (where
== ContextOfException::ReqInit
) {
311 } else if (where
!= ContextOfException::Handler
&&
312 !context
->getExitCallback().isNull() &&
313 f_is_callable(context
->getExitCallback())) {
314 Array stack
= e
.getBackTrace();
315 Array argv
= CREATE_VECTOR2(e
.ExitCode
, stack
);
316 vm_call_user_func(context
->getExitCallback(), argv
);
318 } catch (const PhpFileDoesNotExistException
&e
) {
320 if (where
!= ContextOfException::Handler
) {
321 raise_notice("%s", e
.getMessage().c_str());
323 Logger::Error("%s", e
.getMessage().c_str());
326 handle_exception_append_bt(errorMsg
, e
);
328 } catch (const UncatchableException
&e
) {
332 if (RuntimeOption::ServerStackTrace
) {
334 } else if (RuntimeOption::InjectedStackTrace
) {
335 errorMsg
= e
.getMessage();
337 errorMsg
+= ExtendedLogger::StringOfStackTrace(e
.getBackTrace());
339 Logger::Error("%s", errorMsg
.c_str());
341 handle_exception_append_bt(errorMsg
, e
);
343 } catch (const Exception
&e
) {
345 bool origError
= error
;
346 std::string origErrorMsg
= errorMsg
;
350 if (where
== ContextOfException::Handler
) {
351 errorMsg
= "Exception handler threw an exception: ";
353 errorMsg
+= e
.what();
354 if (where
== ContextOfException::Invoke
) {
355 bool handlerRet
= context
->onFatalError(e
);
359 errorMsg
= origErrorMsg
;
362 Logger::Error("%s", errorMsg
.c_str());
365 const ExtendedException
*ee
= dynamic_cast<const ExtendedException
*>(&e
);
367 handle_exception_append_bt(errorMsg
, *ee
);
370 } catch (const Object
&e
) {
372 bool origError
= error
;
373 std::string origErrorMsg
= errorMsg
;
377 if (where
== ContextOfException::Handler
) {
378 errorMsg
= "Exception handler threw an object exception: ";
381 errorMsg
+= e
.toString().data();
383 errorMsg
+= "(unable to call toString())";
385 if (where
== ContextOfException::Invoke
) {
386 bool handlerRet
= context
->onUnhandledException(e
);
390 errorMsg
= origErrorMsg
;
393 Logger::Error("%s", errorMsg
.c_str());
398 errorMsg
= "(unknown exception was thrown)";
399 Logger::Error("%s", errorMsg
.c_str());
403 static bool hphp_chdir_file(const string filename
) {
405 String s
= File::TranslatePath(filename
);
406 char *buf
= strndup(s
.data(), s
.size());
407 char *dir
= dirname(buf
);
410 if (File::IsVirtualDirectory(dir
)) {
411 g_context
->setCwd(String(dir
, CopyString
));
416 if ((sb
.st_mode
& S_IFMT
) == S_IFDIR
) {
419 g_context
->setCwd(String(dir
, CopyString
));
428 void handle_destructor_exception(const char* situation
) {
433 } catch (ExitException
&e
) {
434 // ExitException is fine, no need to show a warning.
435 ThreadInfo::s_threadInfo
->setPendingException(e
.clone());
437 } catch (Object
&e
) {
438 // For user exceptions, invoke the user exception handler
439 errorMsg
= situation
;
440 errorMsg
+= " threw an object exception: ";
442 errorMsg
+= e
.toString().data();
444 errorMsg
+= "(unable to call toString())";
446 } catch (Exception
&e
) {
447 ThreadInfo::s_threadInfo
->setPendingException(e
.clone());
448 errorMsg
= situation
;
449 errorMsg
+= " raised a fatal error: ";
450 errorMsg
+= e
.what();
452 errorMsg
= situation
;
453 errorMsg
+= " threw an unknown exception";
455 // For fatal errors and unknown exceptions, we raise a warning.
456 // If there is a user error handler it will be invoked, otherwise
457 // the default error handler will be invoked.
459 raise_debugging("%s", errorMsg
.c_str());
461 // The user error handler fataled or threw an exception,
462 // print out the error message directly to the log
463 Logger::Warning("%s", errorMsg
.c_str());
467 void execute_command_line_begin(int argc
, char **argv
, int xhprof
) {
468 StackTraceNoHeap::AddExtraLogging("ThreadType", "CLI");
470 for (int i
= 0; i
< argc
; i
++) {
474 StackTraceNoHeap::AddExtraLogging("Arguments", args
.c_str());
477 ExecutionContext
*context
= g_context
.getNoCheck();
478 context
->obSetImplicitFlush(true);
480 GlobalVariables
*g
= get_global_variables();
482 Variant
& env
= g
->getRef(s__ENV
);
483 process_env_variables(env
);
486 if (RuntimeOption::EvalJit
) {
487 env
.set(s_HHVM_JIT
, 1);
490 process_cmd_arguments(argc
, argv
);
492 Variant
& server
= g
->getRef(s__SERVER
);
493 process_env_variables(server
);
495 struct timeval tp
= {0};
497 if (!gettimeofday(&tp
, nullptr)) {
498 now_double
= (double)(tp
.tv_sec
+ tp
.tv_usec
/ 1000000.00);
502 now_double
= (double)now
;
504 String file
= empty_string
;
506 file
= StringData::Make(argv
[0], CopyString
);
508 server
.set(s_REQUEST_START_TIME
, now
);
509 server
.set(s_REQUEST_TIME
, now
);
510 server
.set(s_REQUEST_TIME_FLOAT
, now_double
);
511 server
.set(s_DOCUMENT_ROOT
, empty_string
);
512 server
.set(s_SCRIPT_FILENAME
, file
);
513 server
.set(s_SCRIPT_NAME
, file
);
514 server
.set(s_PHP_SELF
, file
);
515 server
.set(s_argv
, g
->get(s_argv
));
516 server
.set(s_argc
, g
->get(s_argc
));
517 server
.set(s_PWD
, g_context
->getCwd());
519 if (!gethostname(hostname
, 1024)) {
520 server
.set(s_HOSTNAME
, String(hostname
, CopyString
));
523 for(std::map
<string
,string
>::iterator it
=
524 RuntimeOption::ServerVariables
.begin(),
525 end
= RuntimeOption::ServerVariables
.end(); it
!= end
; ++it
) {
526 server
.set(String(it
->first
.c_str()), String(it
->second
.c_str()));
530 f_xhprof_enable(xhprof
, uninit_null().toArray());
534 void execute_command_line_end(int xhprof
, bool coverage
, const char *program
) {
535 ThreadInfo
*ti
= ThreadInfo::s_threadInfo
.getNoCheck();
537 if (RuntimeOption::EvalDumpTC
) {
538 HPHP::Transl::tc_dump();
542 f_var_dump(f_json_encode(f_xhprof_disable()));
544 hphp_context_exit(g_context
.getNoCheck(), true, true, program
);
546 if (coverage
&& ti
->m_reqInjectionData
.getCoverage() &&
547 !RuntimeOption::CodeCoverageOutputFile
.empty()) {
548 ti
->m_coverage
->Report(RuntimeOption::CodeCoverageOutputFile
);
552 static void pagein_self(void) {
553 unsigned long begin
, end
, inode
, pgoff
;
554 char mapname
[PATH_MAX
];
562 // pad due to the spaces between the inode number and the mapname
563 bufsz
= sizeof(unsigned long) * 4 + sizeof(mapname
) + sizeof(char) * 11 + 100;
564 buf
= (char *)malloc(bufsz
);
568 Timer
timer(Timer::WallTime
, "mapping self");
569 fp
= fopen("/proc/self/maps", "r");
572 if (fgets(buf
, bufsz
, fp
) == 0)
574 r
= sscanf(buf
, "%lx-%lx %4s %lx %5s %ld %s",
575 &begin
, &end
, perm
, &pgoff
, dev
, &inode
, mapname
);
577 // page in read-only segments that correspond to a file on disk
581 access(mapname
, F_OK
) != 0) {
585 if (mlock((void *)begin
, end
- begin
) == 0) {
586 if (!RuntimeOption::LockCodeMemory
) {
587 munlock((void *)begin
, end
- begin
);
596 /* Sets RuntimeOption::ExecutionMode according
597 * to commandline options prior to config load
599 static void set_execution_mode(string mode
) {
600 if (mode
== "daemon" || mode
== "server" || mode
== "replay") {
601 RuntimeOption::ExecutionMode
= "srv";
602 Logger::Escape
= true;
603 } else if (mode
== "run" || mode
== "debug") {
604 RuntimeOption::ExecutionMode
= "cli";
605 Logger::Escape
= false;
606 } else if (mode
== "translate") {
607 RuntimeOption::ExecutionMode
= "";
608 Logger::Escape
= false;
611 always_assert(false);
615 static int start_server(const std::string
&username
) {
616 // Before we start the webserver, make sure the entire
617 // binary is paged into memory.
620 set_execution_mode("server");
621 HttpRequestHandler::GetAccessLog().init
622 (RuntimeOption::AccessLogDefaultFormat
, RuntimeOption::AccessLogs
,
624 AdminRequestHandler::GetAccessLog().init
625 (RuntimeOption::AdminLogFormat
, RuntimeOption::AdminLogSymLink
,
626 RuntimeOption::AdminLogFile
,
629 void *sslCTX
= nullptr;
630 if (RuntimeOption::EnableSSL
) {
631 #ifdef _EVENT_USE_OPENSSL
632 struct ssl_config config
;
633 if (RuntimeOption::SSLCertificateFile
!= "" &&
634 RuntimeOption::SSLCertificateKeyFile
!= "") {
635 config
.cert_file
= (char*)RuntimeOption::SSLCertificateFile
.c_str();
636 config
.pk_file
= (char*)RuntimeOption::SSLCertificateKeyFile
.c_str();
637 sslCTX
= evhttp_init_openssl(&config
);
638 if (!RuntimeOption::SSLCertificateDir
.empty()) {
639 ServerNameIndication::load(sslCTX
, config
,
640 RuntimeOption::SSLCertificateDir
);
643 Logger::Error("Invalid certificate file or key file");
646 Logger::Error("A SSL enabled libevent is required");
650 #if !defined(SKIP_USER_CHANGE)
651 if (!username
.empty()) {
652 if (Logger::UseCronolog
) {
653 Cronolog::changeOwner(username
, RuntimeOption::LogFileSymLink
);
655 Capability::ChangeUnixUser(username
);
656 LightProcess::ChangeUser(username
);
658 Capability::SetDumpable();
661 // Create the HttpServer before any warmup requests to properly
662 // initialize the process
663 HttpServer::Server
= HttpServerPtr(new HttpServer(sslCTX
));
665 if (memory_profiling
) {
666 Logger::Info("Starting up profiling server");
667 HeapProfileServer::Server
= boost::make_shared
<HeapProfileServer
>();
670 // If we have any warmup requests, replay them before listening for
672 for (auto& file
: RuntimeOption::ServerWarmupRequests
) {
673 HttpRequestHandler
handler(0);
676 Timer::GetMonotonicTime(start
);
678 Logger::Info("Replaying warmup request %s", file
.c_str());
680 rt
.onRequestStart(start
);
681 rt
.replayInput(Hdf(file
));
682 handler
.handleRequest(&rt
);
683 Logger::Info("Finished successfully");
684 } catch (std::exception
& e
) {
688 Logger::Info("Got exception during warmup: %s", error
.c_str());
692 HttpServer::Server
->run();
696 string
translate_stack(const char *hexencoded
, bool with_frame_numbers
) {
697 if (!hexencoded
|| !*hexencoded
) {
701 StackTrace
st(hexencoded
);
702 StackTrace::FramePtrVec frames
;
705 std::ostringstream out
;
706 for (unsigned int i
= 0; i
< frames
.size(); i
++) {
707 StackTrace::FramePtr f
= frames
[i
];
708 if (with_frame_numbers
) {
709 out
<< "# " << (i
< 10 ? " " : "") << i
<< ' ';
711 out
<< f
->toString();
717 ///////////////////////////////////////////////////////////////////////////////
719 static void prepare_args(int &argc
, char **&argv
, const StringVec
&args
,
721 argv
= (char **)malloc((args
.size() + 2) * sizeof(char*));
724 argv
[argc
++] = (char*)file
;
726 for (int i
= 0; i
< (int)args
.size(); i
++) {
727 argv
[argc
++] = (char*)args
[i
].c_str();
729 argv
[argc
] = nullptr;
732 static int execute_program_impl(int argc
, char **argv
);
733 int execute_program(int argc
, char **argv
) {
737 init_thread_locals();
738 ret_code
= execute_program_impl(argc
, argv
);
739 } catch (const Exception
&e
) {
740 Logger::Error("Uncaught exception: %s", e
.what());
741 } catch (const FailedAssertion
& fa
) {
743 StackTraceNoHeap::AddExtraLogging("Assertion failure", fa
.summary
);
745 } catch (const std::exception
&e
) {
746 Logger::Error("Uncaught exception: %s", e
.what());
748 Logger::Error("Uncaught exception: (unknown)");
750 if (tempFile
.length() && boost::filesystem::exists(tempFile
)) {
751 boost::filesystem::remove(tempFile
);
756 /* -1 - cannot open file
757 * 0 - no need to open file
761 static int open_server_log_file() {
762 if (!RuntimeOption::LogFile
.empty()) {
763 if (Logger::UseCronolog
) {
764 if (strchr(RuntimeOption::LogFile
.c_str(), '%')) {
765 Logger::cronOutput
.m_template
= RuntimeOption::LogFile
;
766 Logger::cronOutput
.setPeriodicity();
767 Logger::cronOutput
.m_linkName
= RuntimeOption::LogFileSymLink
;
770 Logger::Output
= fopen(RuntimeOption::LogFile
.c_str(), "a");
771 if (Logger::Output
) return 1;
774 if (Logger::IsPipeOutput
) {
775 Logger::Output
= popen(RuntimeOption::LogFile
.substr(1).c_str(), "w");
776 if (Logger::Output
) return 2;
778 Logger::Output
= fopen(RuntimeOption::LogFile
.c_str(), "a");
779 if (Logger::Output
) return 1;
782 Logger::Error("Cannot open log file: %s", RuntimeOption::LogFile
.c_str());
788 static void close_server_log_file(int kind
) {
790 fclose(Logger::Output
);
791 } else if (kind
== 2) {
792 pclose(Logger::Output
);
794 always_assert(!Logger::Output
);
798 static int execute_program_impl(int argc
, char** argv
) {
799 string usage
= "Usage:\n\n ";
801 usage
+= " [-m <mode>] [<options>] [<arg1>] [<arg2>] ...\n\nOptions";
804 options_description
desc(usage
.c_str());
806 ("help", "display this message")
807 ("version", "display version number")
808 ("compiler-id", "display the git hash for the compiler id")
809 ("repo-schema", "display the repo schema id used by this app")
810 ("mode,m", value
<string
>(&po
.mode
)->default_value("run"),
811 "run | debug (d) | server (s) | daemon | replay | translate (t)")
812 ("config,c", value
<string
>(&po
.config
),
813 "load specified config file")
814 ("config-value,v", value
<StringVec
>(&po
.confStrings
)->composing(),
815 "individual configuration string in a format of name=value, where "
816 "name can be any valid configuration for a config file")
817 ("port,p", value
<int>(&po
.port
)->default_value(-1),
818 "start an HTTP server at specified port")
819 ("port-fd", value
<int>(&po
.portfd
)->default_value(-1),
820 "use specified fd instead of creating a socket")
821 ("ssl-port-fd", value
<int>(&po
.sslportfd
)->default_value(-1),
822 "use specified fd for SSL instead of creating a socket")
823 ("admin-port", value
<int>(&po
.admin_port
)->default_value(-1),
824 "start admin listener at specified port")
825 ("debug-config", value
<string
>(&po
.debugger_options
.configFName
),
826 "load specified debugger config file")
828 value
<string
>(&po
.debugger_options
.host
)->implicit_value("localhost"),
829 "connect to debugger server at specified address")
830 ("debug-port", value
<int>(&po
.debugger_options
.port
)->default_value(-1),
831 "connect to debugger server at specified port")
832 ("debug-extension", value
<string
>(&po
.debugger_options
.extension
),
833 "PHP file that extends y command")
834 ("debug-cmd", value
<StringVec
>(&po
.debugger_options
.cmds
)->composing(),
835 "executes this debugger command and returns its output in stdout")
837 value
<string
>(&po
.debugger_options
.sandbox
)->default_value("default"),
838 "initial sandbox to attach to when debugger is started")
839 ("user,u", value
<string
>(&po
.user
),
840 "run server under this user account")
841 ("file,f", value
<string
>(&po
.file
),
842 "executing specified file")
843 ("lint,l", value
<string
>(&po
.lint
),
844 "lint specified file")
845 ("show,w", value
<string
>(&po
.show
),
846 "output specified file and do nothing else")
847 ("parse", value
<string
>(&po
.parse
),
848 "parse specified file and dump the AST")
850 "file specified is temporary and removed after execution")
851 ("count", value
<int>(&po
.count
)->default_value(1),
852 "how many times to repeat execution")
853 ("no-safe-access-check",
854 value
<bool>(&po
.noSafeAccessCheck
)->default_value(false),
855 "whether to ignore safe file access check")
856 ("arg", value
<StringVec
>(&po
.args
)->composing(),
858 ("extra-header", value
<string
>(&Logger::ExtraHeader
),
859 "extra-header to add to log lines")
860 ("build-id", value
<string
>(&po
.buildId
),
861 "unique identifier of compiled server code")
862 ("xhprof-flags", value
<int>(&po
.xhprofFlags
)->default_value(0),
866 positional_options_description p
;
870 // We use the boost command line parser to do the initial parsing,
871 // and then we do a manual pass to find the first occurrence of
872 // either "--" or a non-option argument so that we can properly
873 // determine which arguments should be consumed by HHVM and which
874 // arguments should be passed to the PHP application.
876 // We instruct the boost command line parser to allow unrecognized
877 // options and we manually deal with raising an error when there is
878 // an unrecognized option; we need to do this because otherwise the
879 // boost parser may choke on arguments intended for PHP application.
880 // Also, during the manual pass we keep track of our position in the
881 // original argv array so that we can correctly detect the first
882 // occurrence of "--" (since the boost parser swallows this token).
883 auto opts
= command_line_parser(argc
, argv
)
886 .allow_unregistered()
888 // argvPos will track where we are in the original argv array
890 for (unsigned i
= 0; i
< opts
.options
.size(); ++i
) {
891 const auto& option
= opts
.options
[i
];
892 if (option
.unregistered
) {
893 Logger::Error("Error in command line: unknown option %s",
894 option
.original_tokens
[0].c_str());
898 // Check if we've encountered "--" and make sure that argvPos
900 bool foundDashDash
= !strcmp(argv
[argvPos
], "--");
904 // If we haven't encountered "--" or the first non-option
905 // argument, update argvPos appropriately and keep scanning
906 if (!foundDashDash
&& option
.position_key
== -1) {
907 argvPos
+= option
.original_tokens
.size();
910 // We've found the first occurrence of "--" or a non-option
911 // argument. Truncate opts.options, and then take all the
912 // remaining arguments from the original argv array and insert
913 // them into opts.options.
914 opts
.options
.resize(i
);
915 std::vector
<basic_option
<char> > vec
;
917 for (int m
= argvPos
; m
< argc
; ++m
) {
918 string str
= argv
[m
];
919 basic_option
<char> bo
;
920 bo
.string_key
= "arg";
921 bo
.position_key
= pos
++;
922 bo
.value
.push_back(str
);
923 bo
.original_tokens
.push_back(str
);
924 bo
.unregistered
= false;
925 bo
.case_insensitive
= false;
926 opts
.options
.push_back(bo
);
932 if (po
.mode
== "d") po
.mode
= "debug";
933 if (po
.mode
== "s") po
.mode
= "server";
934 if (po
.mode
== "t") po
.mode
= "translate";
935 if (po
.mode
== "") po
.mode
= "run";
936 if (po
.mode
== "daemon" || po
.mode
== "server" || po
.mode
== "replay" ||
937 po
.mode
== "run" || po
.mode
== "debug"|| po
.mode
== "translate") {
938 set_execution_mode(po
.mode
);
940 Logger::Error("Error in command line: invalid mode: %s", po
.mode
.c_str());
941 cout
<< desc
<< "\n";
945 Logger::Error("Error in command line: %s", e
.what());
946 cout
<< desc
<< "\n";
949 Logger::Error("Error in command line.");
950 cout
<< desc
<< "\n";
953 if (vm
.count("help")) {
954 cout
<< desc
<< "\n";
957 if (vm
.count("version")) {
959 #undefine HPHP_VERSION
961 #define HPHP_VERSION(v) const char *version = #v;
962 #include "../../version"
965 cout
<< " v" << version
<< " (" << (debug
? "dbg" : "rel") << ")\n";
966 cout
<< "Compiler: " << kCompilerId
<< "\n";
967 cout
<< "Repo schema: " << kRepoSchemaId
<< "\n";
970 if (vm
.count("compiler-id")) {
971 cout
<< kCompilerId
<< "\n";
975 if (vm
.count("repo-schema")) {
976 cout
<< kRepoSchemaId
<< "\n";
980 if (!po
.show
.empty()) {
982 f
.open(po
.show
, "r");
984 Logger::Error("Unable to open file %s", po
.show
.c_str());
992 po
.isTempFile
= vm
.count("temp-file");
994 // forget the source for systemlib.php unless we are debugging
995 if (po
.mode
!= "debug") SystemLib::s_source
= "";
997 // we need to initialize pcre cache table very early
1001 if (!po
.config
.empty()) {
1002 config
.open(po
.config
);
1004 RuntimeOption::Load(config
, &po
.confStrings
);
1005 vector
<string
> badnodes
;
1006 config
.lint(badnodes
);
1007 for (unsigned int i
= 0; i
< badnodes
.size(); i
++) {
1008 Logger::Error("Possible bad config node: %s", badnodes
[i
].c_str());
1011 vector
<int> inherited_fds
;
1012 RuntimeOption::BuildId
= po
.buildId
;
1013 if (po
.port
!= -1) {
1014 RuntimeOption::ServerPort
= po
.port
;
1016 if (po
.portfd
!= -1) {
1017 RuntimeOption::ServerPortFd
= po
.portfd
;
1018 inherited_fds
.push_back(po
.portfd
);
1020 if (po
.sslportfd
!= -1) {
1021 RuntimeOption::SSLPortFd
= po
.sslportfd
;
1022 inherited_fds
.push_back(po
.sslportfd
);
1024 if (po
.admin_port
!= -1) {
1025 RuntimeOption::AdminServerPort
= po
.admin_port
;
1027 if (po
.noSafeAccessCheck
) {
1028 RuntimeOption::SafeFileAccess
= false;
1031 if (po
.mode
== "daemon") {
1032 if (RuntimeOption::LogFile
.empty()) {
1033 Logger::Error("Log file not specified under daemon mode.\n\n");
1035 int ret
= open_server_log_file();
1036 Process::Daemonize();
1037 close_server_log_file(ret
);
1040 open_server_log_file();
1042 // Defer the initialization of light processes until the log file handle is
1043 // created, so that light processes can log to the right place. If we ever
1044 // lose a light process, stop the server instead of proceeding in an
1046 LightProcess::SetLostChildHandler([](pid_t child
) {
1047 if (!HttpServer::Server
) return;
1048 if (!HttpServer::Server
->isStopped()) {
1049 HttpServer::Server
->stop("lost light process child");
1052 LightProcess::Initialize(RuntimeOption::LightProcessFilePrefix
,
1053 RuntimeOption::LightProcessCount
,
1057 const size_t stackSizeMinimum
= 8 * 1024 * 1024;
1059 if (getrlimit(RLIMIT_STACK
, &rlim
) == 0 &&
1060 (rlim
.rlim_cur
== RLIM_INFINITY
||
1061 rlim
.rlim_cur
< stackSizeMinimum
)) {
1062 rlim
.rlim_cur
= stackSizeMinimum
;
1063 if (stackSizeMinimum
> rlim
.rlim_max
) {
1064 rlim
.rlim_max
= stackSizeMinimum
;
1066 if (setrlimit(RLIMIT_STACK
, &rlim
)) {
1067 Logger::Error("failed to set stack limit to %zd\n", stackSizeMinimum
);
1072 ShmCounters::initialize(true, Logger::Error
);
1073 // Initialize compiler state
1074 compile_file(0, 0, MD5(), 0);
1076 if (!po
.lint
.empty()) {
1077 if (po
.isTempFile
) {
1081 hphp_process_init();
1083 HPHP::Eval::PhpFile
* phpFile
= g_vmContext
->lookupPhpFile(
1084 StringData::GetStaticString(po
.lint
.c_str()), "", nullptr);
1085 if (phpFile
== nullptr) {
1086 throw FileOpenException(po
.lint
.c_str());
1088 Unit
* unit
= phpFile
->unit();
1089 const StringData
* msg
;
1091 if (unit
->compileTimeFatal(msg
, line
)) {
1092 VMParserFrame parserFrame
;
1093 parserFrame
.filename
= po
.lint
.c_str();
1094 parserFrame
.lineNumber
= line
;
1095 Array bt
= g_vmContext
->debugBacktrace(false, true,
1096 false, &parserFrame
);
1097 throw FatalErrorException(msg
->data(), bt
);
1099 } catch (FileOpenException
&e
) {
1100 Logger::Error("%s", e
.getMessage().c_str());
1102 } catch (const FatalErrorException
& e
) {
1103 RuntimeOption::CallUserHandlerOnFatals
= false;
1104 RuntimeOption::AlwaysLogUnhandledExceptions
= true;
1105 g_context
->onFatalError(e
);
1108 Logger::Info("No syntax errors detected in %s", po
.lint
.c_str());
1112 if (!po
.parse
.empty()) {
1113 Logger::Error("The 'parse' command line option is not supported\n\n");
1117 if (argc
<= 1 || po
.mode
== "run" || po
.mode
== "debug") {
1118 if (po
.isTempFile
) {
1122 set_execution_mode("run");
1126 prepare_args(new_argc
, new_argv
, po
.args
, po
.file
.c_str());
1128 if (!po
.file
.empty()) {
1129 Repo::setCliFile(po
.file
);
1130 } else if (new_argc
> 0) {
1131 Repo::setCliFile(new_argv
[0]);
1135 hphp_process_init();
1142 if (po
.mode
== "debug") {
1143 StackTraceNoHeap::AddExtraLogging("IsDebugger", "True");
1144 RuntimeOption::EnableDebugger
= true;
1145 po
.debugger_options
.fileName
= file
;
1146 po
.debugger_options
.user
= po
.user
;
1147 Eval::DebuggerProxyPtr localProxy
=
1148 Eval::Debugger::StartClient(po
.debugger_options
);
1150 Logger::Error("Failed to start debugger client\n\n");
1153 Eval::Debugger::RegisterSandbox(localProxy
->getDummyInfo());
1154 Eval::Debugger::RegisterThread();
1155 StringVecPtr client_args
;
1156 bool restart
= false;
1160 execute_command_line_begin(new_argc
, new_argv
, po
.xhprofFlags
);
1161 // Set the proxy for this thread to be the localProxy we just
1162 // created. If we're script debugging, this will be the proxy that
1163 // does all of our work. If we're remote debugging, this proxy will
1164 // go unused until we finally stop it when the user quits the
1166 g_context
->setSandboxId(localProxy
->getDummyInfo().id());
1167 Eval::Debugger::DebuggerSession(po
.debugger_options
, restart
);
1169 execute_command_line_end(po
.xhprofFlags
, true, file
.c_str());
1170 } catch (const Eval::DebuggerRestartException
&e
) {
1171 execute_command_line_end(0, false, nullptr);
1173 if (!e
.m_args
->empty()) {
1174 file
= e
.m_args
->at(0);
1175 client_args
= e
.m_args
;
1177 prepare_args(new_argc
, new_argv
, *client_args
, nullptr);
1179 // Systemlib.php is not loaded again, so we need this if we
1180 // are to hit any breakpoints in systemlib.
1181 phpSetBreakPoints(localProxy
.get());
1183 } catch (const Eval::DebuggerClientExitException
&e
) {
1184 execute_command_line_end(0, false, nullptr);
1185 break; // end user quitting debugger
1191 for (int i
= 0; i
< po
.count
; i
++) {
1192 execute_command_line_begin(new_argc
, new_argv
, po
.xhprofFlags
);
1194 if (hphp_invoke_simple(file
)) {
1195 ret
= ExitException::ExitCode
;
1197 execute_command_line_end(po
.xhprofFlags
, true, file
.c_str());
1202 hphp_process_exit();
1207 if (po
.mode
== "daemon" || po
.mode
== "server") {
1208 if (!po
.user
.empty()) RuntimeOption::ServerUser
= po
.user
;
1209 return start_server(RuntimeOption::ServerUser
);
1212 if (po
.mode
== "replay" && !po
.args
.empty()) {
1213 RuntimeOption::RecordInput
= false;
1214 set_execution_mode("server");
1215 HttpServer server
; // so we initialize runtime properly
1216 HttpRequestHandler
handler(0);
1217 for (int i
= 0; i
< po
.count
; i
++) {
1218 for (unsigned int j
= 0; j
< po
.args
.size(); j
++) {
1220 rt
.replayInput(po
.args
[j
].c_str());
1221 handler
.handleRequest(&rt
);
1222 printf("%s\n", rt
.getResponse().c_str());
1228 if (po
.mode
== "translate" && !po
.args
.empty()) {
1229 printf("%s", translate_stack(po
.args
[0].c_str()).c_str());
1233 cout
<< desc
<< "\n";
1237 String
canonicalize_path(CStrRef p
, const char* root
, int rootLen
) {
1238 String
path(Util::canonicalize(p
.c_str(), p
.size()), AttachString
);
1239 if (path
.charAt(0) == '/') {
1240 const string
&sourceRoot
= RuntimeOption::SourceRoot
;
1241 int len
= sourceRoot
.size();
1242 if (len
&& strncmp(path
.data(), sourceRoot
.c_str(), len
) == 0) {
1243 return path
.substr(len
);
1245 if (root
&& rootLen
&& strncmp(path
.data(), root
, rootLen
) == 0) {
1246 return path
.substr(rootLen
);
1252 static string
systemlib_split(string slib
, string
* hhas
) {
1253 auto pos
= slib
.find("\n<?hhas\n");
1254 if (pos
!= string::npos
) {
1255 if (hhas
) *hhas
= slib
.substr(pos
+ 8);
1256 return slib
.substr(0, pos
);
1261 // Search for systemlib.php in the following places:
1262 // 1) ${HHVM_SYSTEMLIB}
1263 // 2) section "systemlib" in the current executable
1264 // and return its contents
1265 string
get_systemlib(string
* hhas
) {
1266 if (char *file
= getenv("HHVM_SYSTEMLIB")) {
1267 std::ifstream
ifs(file
);
1269 return systemlib_split(std::string(
1270 std::istreambuf_iterator
<char>(ifs
),
1271 std::istreambuf_iterator
<char>()), hhas
);
1275 Util::embedded_data desc
;
1276 if (!Util::get_embedded_data("systemlib", &desc
)) return "";
1278 std::ifstream
ifs(desc
.m_filename
);
1279 if (!ifs
.good()) return "";
1280 ifs
.seekg(desc
.m_start
, std::ios::beg
);
1281 std::unique_ptr
<char[]> data(new char[desc
.m_len
]);
1282 ifs
.read(data
.get(), desc
.m_len
);
1283 string ret
= systemlib_split(string(data
.get(), desc
.m_len
), hhas
);
1287 ///////////////////////////////////////////////////////////////////////////////
1290 extern "C" void hphp_fatal_error(const char *s
) {
1294 static void on_timeout(int sig
, siginfo_t
* info
, void* context
) {
1295 if (sig
== SIGVTALRM
&& info
&& info
->si_code
== SI_TIMER
) {
1296 auto data
= (RequestInjectionData
*)info
->si_value
.sival_ptr
;
1301 void hphp_process_init() {
1302 pthread_attr_t attr
;
1303 #if defined(_GNU_SOURCE) && defined(__linux__) // Linux+GNU extension
1304 pthread_getattr_np(pthread_self(), &attr
);
1306 pthread_attr_init(&attr
);
1308 Util::init_stack_limits(&attr
);
1309 pthread_attr_destroy(&attr
);
1311 struct sigaction action
= {};
1312 action
.sa_sigaction
= on_timeout
;
1313 action
.sa_flags
= SA_SIGINFO
| SA_NODEFER
;
1314 sigaction(SIGVTALRM
, &action
, nullptr);
1316 init_thread_locals();
1318 Process::InitProcessStatics();
1320 // reinitialize pcre table
1323 // the liboniguruma docs say this isnt needed,
1324 // but the implementation of init is not
1325 // thread safe due to bugs
1328 // simple xml also needs one time init
1333 PageletServer::Restart();
1334 XboxServer::Restart();
1335 Stream::RegisterCoreWrappers();
1336 Extension::InitModules();
1337 for (InitFiniNode
*in
= extra_process_init
; in
; in
= in
->next
) {
1340 int64_t save
= RuntimeOption::SerializationSizeLimit
;
1341 RuntimeOption::SerializationSizeLimit
= StringData::MaxSize
;
1342 apc_load(apcExtension::LoadThread
);
1343 RuntimeOption::SerializationSizeLimit
= save
;
1345 Transl::TargetCache::requestExit();
1346 // Reset the preloaded g_context
1347 ExecutionContext
*context
= g_context
.getNoCheck();
1348 context
->~ExecutionContext();
1349 new (context
) ExecutionContext();
1352 static void handle_exception(bool& ret
, ExecutionContext
* context
,
1353 std::string
& errorMsg
, ContextOfException where
,
1354 bool& error
, bool richErrorMsg
) {
1355 assert(where
== ContextOfException::Invoke
||
1356 where
== ContextOfException::ReqInit
);
1358 handle_exception_helper(ret
, context
, errorMsg
, where
, error
, richErrorMsg
);
1359 } catch (const ExitException
&e
) {
1360 // Got an ExitException during exception handling, handle
1361 // similarly to the case below but don't call obEndAll().
1363 handle_exception_helper(ret
, context
, errorMsg
, ContextOfException::Handler
,
1364 error
, richErrorMsg
);
1365 context
->obEndAll();
1369 static void handle_reqinit_exception(bool &ret
, ExecutionContext
*context
,
1370 std::string
&errorMsg
, bool &error
) {
1371 handle_exception(ret
, context
, errorMsg
, ContextOfException::ReqInit
, error
,
1375 static void handle_invoke_exception(bool &ret
, ExecutionContext
*context
,
1376 std::string
&errorMsg
, bool &error
,
1377 bool richErrorMsg
) {
1378 handle_exception(ret
, context
, errorMsg
, ContextOfException::Invoke
, error
,
1382 static bool hphp_warmup(ExecutionContext
*context
,
1383 const string
&reqInitFunc
,
1384 const string
&reqInitDoc
, bool &error
) {
1387 std::string errorMsg
;
1389 MemoryManager
*mm
= MemoryManager::TheMemoryManager();
1390 if (mm
->isEnabled()) {
1391 ServerStatsHelper
ssh("reqinit");
1393 if (!reqInitDoc
.empty()) {
1394 include_impl_invoke(reqInitDoc
, true);
1396 if (!reqInitFunc
.empty()) {
1397 invoke(reqInitFunc
.c_str(), Array());
1399 context
->backupSession();
1401 handle_reqinit_exception(ret
, context
, errorMsg
, error
);
1408 void hphp_session_init() {
1409 init_thread_locals();
1410 ThreadInfo::s_threadInfo
->onSessionInit();
1411 MemoryManager::TheMemoryManager()->resetStats();
1413 #ifdef ENABLE_SIMPLE_COUNTER
1414 SimpleCounter::Enabled
= true;
1415 StackTrace::Enabled
= true;
1418 // Ordering is sensitive; StatCache::requestInit produces work that
1419 // must be done in VMExecutionContext::requestInit.
1420 StatCache::requestInit();
1422 g_vmContext
->requestInit();
1424 EnvConstants
*g
= get_env_constants();
1425 g
->k_PHP_SAPI
= StringData::GetStaticString(RuntimeOption::ExecutionMode
);
1426 g
->k_PHP_BINARY
= current_executable_path();
1427 g
->k_PHP_BINDIR
= current_executable_directory();
1430 bool hphp_is_warmup_enabled() {
1431 MemoryManager
*mm
= MemoryManager::TheMemoryManager();
1432 return mm
->isEnabled();
1435 ExecutionContext
*hphp_context_init() {
1436 ExecutionContext
*context
= g_context
.getNoCheck();
1438 context
->obProtect(true);
1442 bool hphp_invoke_simple(const std::string
&filename
,
1443 bool warmupOnly
/* = false */) {
1446 return hphp_invoke(g_context
.getNoCheck(), filename
, false, null_array
,
1447 uninit_null(), "", "", error
, errorMsg
, true, warmupOnly
);
1450 bool hphp_invoke(ExecutionContext
*context
, const std::string
&cmd
,
1451 bool func
, CArrRef funcParams
, VRefParam funcRet
,
1452 const string
&reqInitFunc
, const string
&reqInitDoc
,
1453 bool &error
, string
&errorMsg
,
1454 bool once
/* = true */, bool warmupOnly
/* = false */,
1455 bool richErrorMsg
/* = false */) {
1456 bool isServer
= RuntimeOption::ServerExecutionMode();
1461 oldCwd
= context
->getCwd();
1463 if (!hphp_warmup(context
, reqInitFunc
, reqInitDoc
, error
)) {
1464 if (isServer
) context
->setCwd(oldCwd
);
1471 ServerStatsHelper
ssh("invoke");
1473 funcRet
->assignVal(invoke(cmd
.c_str(), funcParams
));
1475 if (isServer
) hphp_chdir_file(cmd
);
1476 include_impl_invoke(cmd
.c_str(), once
);
1479 handle_invoke_exception(ret
, context
, errorMsg
, error
, richErrorMsg
);
1484 context
->onShutdownPreSend();
1486 handle_invoke_exception(ret
, context
, errorMsg
, error
, richErrorMsg
);
1489 if (isServer
) context
->setCwd(oldCwd
);
1493 void hphp_context_exit(ExecutionContext
*context
, bool psp
,
1494 bool shutdown
/* = true */,
1495 const char *program
/* = NULL */) {
1497 context
->onShutdownPostSend();
1499 if (RuntimeOption::EnableDebugger
) {
1501 Eval::Debugger::InterruptPSPEnded(program
);
1502 } catch (const Eval::DebuggerException
&e
) {}
1505 // Run shutdown handlers. This may cause user code to run.
1506 static_cast<VMExecutionContext
*>(context
)->destructObjects();
1508 context
->onRequestShutdown();
1511 // Clean up a bunch of request state. No user code after this point.
1512 context
->requestExit();
1513 context
->obProtect(false);
1514 context
->obEndAll();
1517 void hphp_thread_exit() {
1518 finish_thread_locals();
1521 void hphp_session_exit() {
1522 // Server note has to live long enough for the access log to fire.
1523 // RequestLocal is too early.
1524 ServerNote::Reset();
1525 g_context
.destroy();
1527 ThreadInfo::s_threadInfo
->clearPendingException();
1529 MemoryManager
*mm
= MemoryManager::TheMemoryManager();
1530 if (RuntimeOption::CheckMemory
) {
1535 if (mm
->isEnabled()) {
1536 ServerStatsHelper
ssh("rollback");
1537 // sweep may call g_context->, which is a noCheck, so we need to
1538 // reinitialize g_context here
1539 g_context
.getCheck();
1540 // MemoryManager::sweepAll() will handle sweeping for PHP objects and
1541 // PHP resources (ex. File, Collator, XmlReader, etc.)
1543 // Destroy g_context again because ExecutionContext has SmartAllocated
1544 // data members. These members cannot survive over rollback(), so we need
1545 // to destroy g_context before calling rollback().
1546 g_context
.destroy();
1547 // MemoryManager::rollback() will handle sweeping for all types that have
1548 // dedicated allocators (ex. StringData, HphpArray, etc.) and it reset all
1549 // of the allocators in preparation for the next request.
1551 // Do any post-sweep cleanup necessary for global variables
1552 free_global_variables_after_sweep();
1553 g_context
.getCheck();
1555 g_context
.getCheck();
1556 ServerStatsHelper
ssh("free");
1557 free_global_variables();
1560 ThreadInfo::s_threadInfo
->onSessionExit();
1563 void hphp_process_exit() {
1564 PageletServer::Stop();
1566 Eval::Debugger::Stop();
1567 Extension::ShutdownModules();
1568 LightProcess::Close();
1569 for (InitFiniNode
*in
= extra_process_exit
; in
; in
= in
->next
) {
1574 ///////////////////////////////////////////////////////////////////////////////