2 +----------------------------------------------------------------------+
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/request-injection-data.h"
26 #include <boost/filesystem.hpp>
27 #include <folly/Optional.h>
28 #include <folly/Random.h>
29 #include <folly/portability/SysTime.h>
31 #include "hphp/util/logger.h"
32 #include "hphp/runtime/base/file.h"
33 #include "hphp/runtime/base/ini-setting.h"
34 #include "hphp/runtime/base/rds-header.h"
35 #include "hphp/runtime/base/runtime-option.h"
36 #include "hphp/runtime/base/thread-info.h"
37 #include "hphp/runtime/ext/string/ext_string.h"
38 #include "hphp/runtime/vm/debugger-hook.h"
39 #include "hphp/runtime/ext/std/ext_std_file.h"
43 //////////////////////////////////////////////////////////////////////
45 void RequestTimer::onTimeout() {
46 m_reqInjectionData
->onTimeout(this);
49 #if defined(__APPLE__)
51 RequestTimer::RequestTimer(RequestInjectionData
* data
)
52 : m_reqInjectionData(data
)
54 // Unlike the canonical Linux implementation, this does not distinguish
55 // between whether we wanted real seconds or CPU seconds -- you always get
56 // real seconds. There isn't a nice way to get CPU seconds on OS X that I've
57 // found, outside of setitimer, which has its own configurability issues. So
58 // we just always give real seconds, which is "close enough".
60 m_timerGroup
= dispatch_group_create();
63 RequestTimer::~RequestTimer() {
65 dispatch_release(m_timerGroup
);
68 void RequestTimer::cancelTimerSource() {
70 dispatch_source_cancel(m_timerSource
);
71 dispatch_group_wait(m_timerGroup
, DISPATCH_TIME_FOREVER
);
73 // At this point it is safe to free memory, the source or even ourselves (if
74 // this is part of the destructor). See the way we set up the timer group
75 // and cancellation handler in setTimeout() below.
77 dispatch_release(m_timerSource
);
78 m_timerSource
= nullptr;
82 void RequestTimer::setTimeout(int seconds
) {
83 m_timeoutSeconds
= seconds
> 0 ? seconds
: 0;
87 if (!m_timeoutSeconds
) {
92 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0);
93 m_timerSource
= dispatch_source_create(
94 DISPATCH_SOURCE_TYPE_TIMER
, 0, DISPATCH_TIMER_STRICT
, q
);
97 dispatch_time(DISPATCH_TIME_NOW
, m_timeoutSeconds
* NSEC_PER_SEC
);
98 dispatch_source_set_timer(m_timerSource
, t
, DISPATCH_TIME_FOREVER
, 0);
100 // Use the timer group as a semaphore. When the source is cancelled,
101 // libdispatch will make sure all pending event handlers have finished before
102 // invoking the cancel handler. This means that if we cancel the source and
103 // then wait on the timer group, when we are done waiting, we know the source
104 // is completely done and it's safe to free memory (e.g., in the destructor).
105 // See cancelTimerSource() above.
106 dispatch_group_enter(m_timerGroup
);
107 dispatch_source_set_event_handler(m_timerSource
, ^{
110 // Cancelling ourselves isn't needed for correctness, but we can go ahead
111 // and do it now instead of waiting on it later, so why not. (Also,
112 // getRemainingTime does use this opportunistically, but it's best effort.)
113 dispatch_source_cancel(m_timerSource
);
115 dispatch_source_set_cancel_handler(m_timerSource
, ^{
116 dispatch_group_leave(m_timerGroup
);
119 dispatch_resume(m_timerSource
);
122 int RequestTimer::getRemainingTime() const {
123 // Unfortunately, not a good way to detect this. The best we can say is if the
124 // timer exists and fired and cancelled itself, we can clip to 0, otherwise
125 // just return the full timeout seconds. In principle, we could use the
126 // interval configuration of the timer to count down one second at a time,
127 // but that doesn't seem worth it.
128 if (m_timerSource
&& dispatch_source_testcancel(m_timerSource
)) {
132 return m_timeoutSeconds
;
135 #elif defined(_MSC_VER)
137 RequestTimer::RequestTimer(RequestInjectionData
* data
)
138 : m_reqInjectionData(data
)
141 RequestTimer::~RequestTimer() {
148 void RequestTimer::setTimeout(int seconds
) {
149 m_timeoutSeconds
= seconds
> 0 ? seconds
: 0;
156 if (m_timeoutSeconds
) {
157 auto call
= new concurrency::call
<int>([this](int) {
163 auto timer
= new concurrency::timer
<int>(m_timeoutSeconds
* 1000,
166 concurrency::task
<void> event_set(*m_tce
);
167 event_set
.then([call
, timer
]() {
177 int RequestTimer::getRemainingTime() const {
178 return m_timeoutSeconds
;
183 RequestTimer::RequestTimer(RequestInjectionData
* data
, clockid_t clockType
)
184 : m_reqInjectionData(data
)
185 , m_clockType(clockType
)
187 , m_timerActive(false)
190 RequestTimer::~RequestTimer() {
192 timer_delete(m_timerId
);
197 * NB: this function must be nothrow when `seconds' is zero. RPCRequestHandler
200 void RequestTimer::setTimeout(int seconds
) {
201 m_timeoutSeconds
= seconds
> 0 ? seconds
: 0;
203 if (!m_timeoutSeconds
) {
204 // we don't have a timer, and we don't have a timeout
208 memset(&sev
, 0, sizeof(sev
));
209 sev
.sigev_notify
= SIGEV_SIGNAL
;
210 sev
.sigev_signo
= SIGVTALRM
;
211 sev
.sigev_value
.sival_ptr
= this;
212 if (timer_create(m_clockType
, &sev
, &m_timerId
)) {
213 raise_error("Failed to set timeout: %s", folly::errnoStr(errno
).c_str());
219 * There is a potential race here. Callers want to assume that
220 * if they cancel the timeout (seconds = 0), they *won't* get
221 * a signal after they call this (although they may get a signal
223 * So we need to clear the timeout, wait (if necessary) for a
224 * pending signal to be handled, and then set the new timeout
228 timer_settime(m_timerId
, 0, &ts
, &old
);
229 if (!old
.it_value
.tv_sec
&& !old
.it_value
.tv_nsec
) {
230 // the timer has gone off...
231 if (m_timerActive
.load(std::memory_order_acquire
)) {
232 // but m_timerActive is still set, so we haven't processed
234 // spin until its done.
235 while (m_timerActive
.load(std::memory_order_relaxed
)) {
239 if (m_timeoutSeconds
) {
240 m_timerActive
.store(true, std::memory_order_relaxed
);
241 ts
.it_value
.tv_sec
= m_timeoutSeconds
;
242 timer_settime(m_timerId
, 0, &ts
, nullptr);
244 m_timerActive
.store(false, std::memory_order_relaxed
);
248 int RequestTimer::getRemainingTime() const {
251 if (!timer_gettime(m_timerId
, &ts
)) {
252 int remaining
= ts
.it_value
.tv_sec
;
253 return remaining
> 1 ? remaining
: 1;
256 return m_timeoutSeconds
;
260 //////////////////////////////////////////////////////////////////////
262 bool RequestInjectionData::setAllowedDirectories(const std::string
& value
) {
263 // Backwards compat with ;
264 // but moving forward should use PATH_SEPARATOR
265 std::vector
<std::string
> boom
;
266 if (value
.find(";") != std::string::npos
) {
267 folly::split(";", value
, boom
, true);
268 m_open_basedir_separator
= ";";
270 m_open_basedir_separator
=
271 s_PATH_SEPARATOR
.toCppString();
272 folly::split(m_open_basedir_separator
, value
, boom
,
276 if (boom
.empty() && m_safeFileAccess
) return false;
277 for (auto& path
: boom
) {
278 // Canonicalise the path
280 File::TranslatePathKeepRelative(path
).empty()) {
284 m_safeFileAccess
= !boom
.empty();
286 folly::join(m_open_basedir_separator
, boom
, dirs
);
287 VirtualHost::SortAllowedDirectories(boom
);
288 m_allowedDirectoriesInfo
.reset(
289 new AllowedDirectoriesInfo(std::move(boom
), std::move(dirs
)));
293 const std::vector
<std::string
>&
294 RequestInjectionData::getAllowedDirectoriesProcessed() const {
295 return m_allowedDirectoriesInfo
?
296 m_allowedDirectoriesInfo
->vec
: VirtualHost::GetAllowedDirectories();
299 void RequestInjectionData::threadInit() {
302 auto setAndGetWall
= IniSetting::SetAndGet
<int64_t>(
303 [this](const int64_t &limit
) {
307 [this] { return getTimeout(); }
309 auto setAndGetCPU
= IniSetting::SetAndGet
<int64_t>(
310 [this](const int64_t &limit
) {
311 setCPUTimeout(limit
);
314 [this] { return getCPUTimeout(); }
316 auto setAndGet
= RuntimeOption::TimeoutsUseWallTime
317 ? setAndGetWall
: setAndGetCPU
;
318 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
319 "max_execution_time", setAndGet
);
320 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
321 "maximum_execution_time", setAndGet
);
322 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
323 "hhvm.max_wall_time", setAndGetWall
);
324 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
325 "hhvm.max_cpu_time", setAndGetCPU
);
329 std::string mem_def
= std::to_string(RuntimeOption::RequestMemoryMaxBytes
);
330 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
, "memory_limit",
332 IniSetting::SetAndGet
<std::string
>(
333 [this](const std::string
& value
) {
334 setMemoryLimit(value
);
341 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
342 "arg_separator.output", "&",
343 &m_argSeparatorOutput
);
344 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
345 "arg_separator.input", "&",
346 &m_argSeparatorInput
);
347 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
348 "variables_order", "EGPCS",
350 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
353 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
354 "default_charset", RuntimeOption::DefaultCharsetName
.c_str(),
356 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
357 "default_mimetype", "text/html",
360 // Paths and Directories
361 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
362 "include_path", getDefaultIncludePath().c_str(),
363 IniSetting::SetAndGet
<std::string
>(
364 [this](const std::string
& value
) {
365 m_include_paths
.clear();
366 int pos
= value
.find(':');
368 m_include_paths
.push_back(value
);
372 // Check for stream wrapper
373 if (value
.length() > pos
+ 2 &&
374 value
[pos
+ 1] == '/' &&
375 value
[pos
+ 2] == '/') {
376 // .:// or ..:// is not stream wrapper
377 if (((pos
- pos0
) >= 1 && value
[pos
- 1] != '.') ||
378 ((pos
- pos0
) >= 2 && value
[pos
- 2] != '.') ||
384 m_include_paths
.push_back(
385 value
.substr(pos0
, pos
- pos0
));
388 } while ((pos
= value
.find(':', pos
)) >= 0);
390 if (pos0
<= value
.length()) {
391 m_include_paths
.push_back(
399 folly::join(":", m_include_paths
, ret
);
404 // Paths and Directories
405 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
407 IniSetting::SetAndGet
<std::string
>(
408 [this](const std::string
& value
) {
409 return setAllowedDirectories(value
);
411 [this]() -> std::string
{
412 if (!hasSafeFileAccess()) {
415 if (m_allowedDirectoriesInfo
) {
416 return m_allowedDirectoriesInfo
->string
;
419 folly::join(m_open_basedir_separator
,
420 getAllowedDirectoriesProcessed(),
426 // Errors and Logging Configuration Options
427 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
429 std::to_string(RuntimeOption::RuntimeErrorReportingLevel
)
431 &m_errorReportingLevel
);
432 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
437 IniSetting::PHP_INI_ALL
,
439 IniSetting::SetAndGet
<bool>(
440 [&] (const bool& on
) {
444 [&] () { return m_htmlErrors
; }
449 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
451 IniSetting::SetAndGet
<bool>(
452 [this](const bool& on
) {
453 if (m_logErrors
!= on
) {
455 if (!m_errorLog
.empty()) {
456 Logger::SetThreadLog(m_errorLog
.data(), true);
459 Logger::ClearThreadLog();
467 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
469 IniSetting::SetAndGet
<std::string
>(
470 [this](const std::string
& value
) {
471 if (m_logErrors
&& !value
.empty()) {
472 Logger::SetThreadLog(value
.data(), true);
479 // Filesystem and Streams Configuration Options
480 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
481 "user_agent", "", &m_userAgent
);
482 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
483 "default_socket_timeout",
484 std::to_string(RuntimeOption::SocketDefaultTimeout
).c_str(),
485 &m_socketDefaultTimeout
);
487 // Response handling.
488 // TODO(T5601927): output_compression supports int values where the value
489 // represents the output buffer size. Also need to add a
490 // zlib.output_handler ini setting as well.
491 // http://php.net/zlib.configuration.php
492 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
493 "zlib.output_compression", &m_gzipCompression
);
494 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
495 "zlib.output_compression_level", &m_gzipCompressionLevel
);
497 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
498 "brotli.chunked_compression", &m_brotliChunkedEnabled
);
499 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
500 "brotli.compression", &m_brotliEnabled
);
503 IniSetting::PHP_INI_ALL
,
504 "brotli.compression_quality",
505 std::to_string(RuntimeOption::BrotliCompressionQuality
).c_str(),
509 IniSetting::PHP_INI_ALL
,
510 "brotli.compression_lgwin",
511 std::to_string(RuntimeOption::BrotliCompressionLgWindowSize
).c_str(),
512 &m_brotliLgWindowSize
);
515 IniSetting::Bind(IniSetting::CORE
, IniSetting::PHP_INI_ALL
,
516 "zend.assertions", "1",
517 IniSetting::SetAndGet
<int64_t>(
518 [this](const int64_t& value
) {
519 if ((value
>= 0) != RuntimeOption::AssertEmitted
) {
520 // Setting the option to < 0 changes a RuntimeOption which affects
521 // bytecode emission, so you can't move between < 0 and >= 0 at
522 // runtime. (This is also a restriction in PHP7 for similar reasons.)
523 raise_warning("zend.assertions may be completely enabled or "
524 "disabled only in php.ini");
527 m_zendAssertions
= value
;
531 return m_zendAssertions
;
536 std::string
RequestInjectionData::getDefaultIncludePath() {
538 folly::join(":", RuntimeOption::IncludeSearchPaths
, result
);
542 void RequestInjectionData::onSessionInit() {
543 static auto open_basedir_val
= []() -> folly::Optional
<std::string
> {
545 if (IniSetting::GetSystem("open_basedir", v
)) {
546 return { v
.toString().toCppString() };
552 m_sflagsAndStkPtr
= &rds::header()->stackLimitAndSurprise
;
553 m_allowedDirectoriesInfo
.reset();
554 m_open_basedir_separator
= s_PATH_SEPARATOR
.toCppString();
555 m_safeFileAccess
= RuntimeOption::SafeFileAccess
;
556 if (open_basedir_val
) {
557 setAllowedDirectories(*open_basedir_val
);
559 m_logFunctionCalls
= RuntimeOption::EvalFunctionCallSampleRate
> 0 &&
560 folly::Random::rand32(
561 RuntimeOption::EvalFunctionCallSampleRate
566 void RequestInjectionData::onTimeout(RequestTimer
* timer
) {
567 if (timer
== &m_timer
) {
568 setFlag(TimedOutFlag
);
569 #if !defined(__APPLE__) && !defined(_MSC_VER)
570 m_timer
.m_timerActive
.store(false, std::memory_order_relaxed
);
572 } else if (timer
== &m_cpuTimer
) {
573 setFlag(CPUTimedOutFlag
);
574 #if !defined(__APPLE__) && !defined(_MSC_VER)
575 m_cpuTimer
.m_timerActive
.store(false, std::memory_order_relaxed
);
578 always_assert(false && "Unknown timer fired");
582 void RequestInjectionData::setTimeout(int seconds
) {
583 m_timer
.setTimeout(seconds
);
586 void RequestInjectionData::setCPUTimeout(int seconds
) {
587 m_cpuTimer
.setTimeout(seconds
);
590 int RequestInjectionData::getRemainingTime() const {
591 return m_timer
.getRemainingTime();
594 int RequestInjectionData::getRemainingCPUTime() const {
595 return m_cpuTimer
.getRemainingTime();
599 * If seconds == 0, reset the timeout to the last one set
600 * If seconds < 0, set the timeout to -seconds if there's less than
601 * -seconds remaining.
602 * If seconds > 0, set the timeout to seconds.
604 void RequestInjectionData::resetTimer(int seconds
/* = 0 */) {
606 seconds
= getTimeout();
607 } else if (seconds
< 0) {
608 if (!getTimeout()) return;
610 if (seconds
< getRemainingTime()) return;
613 clearFlag(TimedOutFlag
);
616 void RequestInjectionData::resetCPUTimer(int seconds
/* = 0 */) {
618 seconds
= getCPUTimeout();
619 } else if (seconds
< 0) {
620 if (!getCPUTimeout()) return;
622 if (seconds
< getRemainingCPUTime()) return;
624 setCPUTimeout(seconds
);
625 clearFlag(CPUTimedOutFlag
);
628 void RequestInjectionData::reset() {
629 m_sflagsAndStkPtr
->fetch_and(kSurpriseFlagStackMask
);
630 m_coverage
= RuntimeOption::RecordCodeCoverage
;
631 m_debuggerAttached
= false;
632 m_debuggerIntr
= false;
633 m_debuggerStepIn
= false;
634 m_debuggerStepOut
= StepOutState::None
;
635 m_debuggerNext
= false;
636 m_suppressHackArrayCompatNotices
= false;
637 m_breakPointFilter
.clear();
638 m_flowFilter
.clear();
639 m_lineBreakPointFilter
.clear();
640 m_callBreakPointFilter
.clear();
641 m_retBreakPointFilter
.clear();
642 while (!m_activeLineBreaks
.empty()) {
643 m_activeLineBreaks
.pop();
646 while (!interrupts
.empty()) interrupts
.pop();
649 void RequestInjectionData::updateJit() {
650 m_jit
= RuntimeOption::EvalJit
&&
651 !(RuntimeOption::EvalJitDisabledByHphpd
&& m_debuggerAttached
) &&
653 isStandardRequest() &&
654 !getDebuggerForceIntr();
657 void RequestInjectionData::clearFlag(SurpriseFlag flag
) {
658 assertx(flag
>= 1ull << 48);
659 m_sflagsAndStkPtr
->fetch_and(~flag
);
662 void RequestInjectionData::setFlag(SurpriseFlag flag
) {
663 assertx(flag
>= 1ull << 48);
664 m_sflagsAndStkPtr
->fetch_or(flag
);
667 void RequestInjectionData::setMemoryLimit(folly::StringPiece limit
) {
668 int64_t newInt
= strtoll(limit
.begin(), nullptr, 10);
670 newInt
= std::numeric_limits
<int64_t>::max();
671 m_maxMemory
= std::to_string(newInt
);
673 m_maxMemory
= limit
.str();
674 newInt
= convert_bytes_to_long(limit
);
676 newInt
= std::numeric_limits
<int64_t>::max();
679 tl_heap
->setMemoryLimit(newInt
);
680 m_maxMemoryNumeric
= newInt
;