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/ini-setting.h"
19 #include "hphp/runtime/base/array-init.h"
20 #include "hphp/runtime/base/builtin-functions.h"
21 #include "hphp/runtime/base/execution-context.h"
22 #include "hphp/runtime/base/req-containers.h"
23 #include "hphp/runtime/base/runtime-option.h"
24 #include "hphp/runtime/base/zend-strtod.h"
26 #include "hphp/runtime/base/ini-parser/zend-ini.h"
28 #include "hphp/runtime/ext/std/ext_std_misc.h"
29 #include "hphp/runtime/ext/extension.h"
30 #include "hphp/runtime/ext/extension-registry.h"
31 #include "hphp/runtime/ext/json/ext_json.h"
33 #include "hphp/util/lock.h"
34 #include "hphp/util/portability.h"
35 #include "hphp/util/logger.h"
41 #define __STDC_LIMIT_MACROS
43 #include <boost/range/join.hpp>
44 #include <boost/algorithm/string/split.hpp>
45 #include <boost/algorithm/string.hpp>
49 ///////////////////////////////////////////////////////////////////////////////
51 const Extension
* IniSetting::CORE
= (Extension
*)(-1);
53 bool IniSetting::s_config_is_a_constant
= false;
54 std::set
<std::string
> IniSetting::config_names_that_use_constants
;
55 bool IniSetting::s_system_settings_are_set
= false;
58 s_global_value("global_value"),
59 s_local_value("local_value"),
63 std::vector
<std::string
> split_brackets(const std::string
& s
) {
64 std::vector
<std::string
> split_value
;
65 boost::split(split_value
, s
, boost::is_any_of("[]"));
66 // Splitting this way might give us an empty string at the end
67 if (split_value
.back() == "") {
68 split_value
.pop_back();
73 int64_t convert_bytes_to_long(folly::StringPiece value
) {
77 int64_t newInt
= strtoll(value
.begin(), nullptr, 10);
78 auto const lastChar
= value
[value
.size() - 1];
79 if (lastChar
== 'K' || lastChar
== 'k') {
81 } else if (lastChar
== 'M' || lastChar
== 'm') {
83 } else if (lastChar
== 'G' || lastChar
== 'g') {
89 std::string
convert_long_to_bytes(int64_t value
) {
90 // Only return a larger value if it wouldn't
91 // be truncating the precision.
92 if ((value
& ((1 << 30) - 1)) == 0) {
93 return std::to_string(value
/ (1 << 30)) + "G";
94 } else if ((value
& ((1 << 20) - 1)) == 0) {
95 return std::to_string(value
/ (1 << 20)) + "M";
96 } else if ((value
& ((1 << 10) - 1)) == 0) {
97 return std::to_string(value
/ (1 << 10)) + "K";
99 return std::to_string(value
);
103 #define INI_ASSERT_STR(v) \
104 if (!v.isScalar()) { \
107 auto str = v.toString().toCppString();
109 #define INI_ASSERT_ARR(v) \
110 if (!value.isArray() && !value.isObject()) { \
114 bool ini_on_update(const Variant
& value
, bool& p
) {
115 INI_ASSERT_STR(value
);
116 if ((str
.size() == 0) ||
117 (str
.size() == 1 && strcasecmp("0", str
.data()) == 0) ||
118 (str
.size() == 2 && strcasecmp("no", str
.data()) == 0) ||
119 (str
.size() == 3 && strcasecmp("off", str
.data()) == 0) ||
120 (str
.size() == 5 && strcasecmp("false", str
.data()) == 0)) {
128 bool ini_on_update(const Variant
& value
, double& p
) {
129 INI_ASSERT_STR(value
);
130 p
= zend_strtod(str
.data(), nullptr);
134 bool ini_on_update(const Variant
& value
, char& p
) {
135 INI_ASSERT_STR(value
);
136 auto n
= convert_bytes_to_long(str
);
137 auto maxValue
= 0x7FL
;
138 if (n
> maxValue
|| n
< (- maxValue
- 1)) {
145 bool ini_on_update(const Variant
& value
, int16_t& p
) {
146 INI_ASSERT_STR(value
);
147 auto n
= convert_bytes_to_long(str
);
148 auto maxValue
= 0x7FFFL
;
149 if (n
> maxValue
|| n
< (- maxValue
- 1)) {
156 bool ini_on_update(const Variant
& value
, int32_t& p
) {
157 INI_ASSERT_STR(value
);
158 auto n
= convert_bytes_to_long(str
);
159 auto maxValue
= 0x7FFFFFFFL
;
160 if (n
> maxValue
|| n
< (- maxValue
- 1)) {
167 bool ini_on_update(const Variant
& value
, int64_t& p
) {
168 INI_ASSERT_STR(value
);
169 p
= convert_bytes_to_long(str
);
173 bool ini_on_update(const Variant
& value
, unsigned char& p
) {
174 INI_ASSERT_STR(value
);
175 auto n
= convert_bytes_to_long(str
);
177 if (((uint64_t)n
& mask
)) {
184 bool ini_on_update(const Variant
& value
, uint16_t& p
) {
185 INI_ASSERT_STR(value
);
186 auto n
= convert_bytes_to_long(str
);
187 auto mask
= ~0xFFFFUL
;
188 if (((uint64_t)n
& mask
)) {
195 bool ini_on_update(const Variant
& value
, uint32_t& p
) {
196 INI_ASSERT_STR(value
);
197 auto n
= convert_bytes_to_long(str
);
198 auto mask
= ~0x7FFFFFFFUL
;
199 if (((uint64_t)n
& mask
)) {
206 bool ini_on_update(const Variant
& value
, uint64_t& p
) {
207 INI_ASSERT_STR(value
);
208 p
= convert_bytes_to_long(str
);
212 bool ini_on_update(const Variant
& value
, std::string
& p
) {
213 INI_ASSERT_STR(value
);
218 bool ini_on_update(const Variant
& value
, String
& p
) {
219 INI_ASSERT_STR(value
);
224 bool ini_on_update(const Variant
& value
, Array
& p
) {
225 INI_ASSERT_ARR(value
);
230 bool ini_on_update(const Variant
& value
, std::set
<std::string
>& p
) {
231 INI_ASSERT_ARR(value
);
232 for (ArrayIter
iter(value
.toArray()); iter
; ++iter
) {
233 p
.insert(iter
.second().toString().toCppString());
238 bool ini_on_update(const Variant
& value
,
239 std::set
<std::string
, stdltistr
>& p
) {
240 INI_ASSERT_ARR(value
);
241 for (ArrayIter
iter(value
.toArray()); iter
; ++iter
) {
242 p
.insert(iter
.second().toString().toCppString());
247 bool ini_on_update(const Variant
& value
,
248 boost::container::flat_set
<std::string
>& p
) {
249 INI_ASSERT_ARR(value
);
250 for (ArrayIter
iter(value
.toArray()); iter
; ++iter
) {
251 p
.insert(iter
.second().toString().toCppString());
256 bool ini_on_update(const Variant
& value
, std::vector
<std::string
>& p
) {
257 INI_ASSERT_ARR(value
);
258 for (ArrayIter
iter(value
.toArray()); iter
; ++iter
) {
259 p
.push_back(iter
.second().toString().toCppString());
264 bool ini_on_update(const Variant
& value
,
265 std::map
<std::string
, std::string
>& p
) {
266 INI_ASSERT_ARR(value
);
267 for (ArrayIter
iter(value
.toArray()); iter
; ++iter
) {
268 p
[iter
.first().toString().toCppString()] =
269 iter
.second().toString().toCppString();
274 bool ini_on_update(const Variant
& value
,
275 std::map
<std::string
, std::string
, stdltistr
>& p
) {
276 INI_ASSERT_ARR(value
);
277 for (ArrayIter
iter(value
.toArray()); iter
; ++iter
) {
278 p
[iter
.first().toString().toCppString()] =
279 iter
.second().toString().toCppString();
284 bool ini_on_update(const Variant
& value
,
285 hphp_string_imap
<std::string
>& p
) {
286 INI_ASSERT_ARR(value
);
287 for (ArrayIter
iter(value
.toArray()); iter
; ++iter
) {
288 p
[iter
.first().toString().toCppString()] =
289 iter
.second().toString().toCppString();
294 Variant
ini_get(bool& p
) {
298 Variant
ini_get(double& p
) {
302 Variant
ini_get(char& p
) {
306 Variant
ini_get(int16_t& p
) {
310 Variant
ini_get(int32_t& p
) {
314 Variant
ini_get(int64_t& p
) {
318 Variant
ini_get(unsigned char& p
) {
322 Variant
ini_get(uint16_t& p
) {
326 Variant
ini_get(uint32_t& p
) {
330 Variant
ini_get(uint64_t& p
) {
334 Variant
ini_get(std::string
& p
) {
338 Variant
ini_get(String
& p
) {
342 Variant
ini_get(std::map
<std::string
, std::string
>& p
) {
343 ArrayInit
ret(p
.size(), ArrayInit::Map
{});
344 for (auto& pair
: p
) {
345 ret
.add(String(pair
.first
), pair
.second
);
347 return ret
.toArray();
350 Variant
ini_get(std::map
<std::string
, std::string
, stdltistr
>& p
) {
351 ArrayInit
ret(p
.size(), ArrayInit::Map
{});
352 for (auto& pair
: p
) {
353 ret
.add(String(pair
.first
), pair
.second
);
355 return ret
.toArray();
358 Variant
ini_get(hphp_string_imap
<std::string
>& p
) {
359 ArrayInit
ret(p
.size(), ArrayInit::Map
{});
360 for (auto& pair
: p
) {
361 ret
.add(String(pair
.first
), pair
.second
);
363 return ret
.toArray();
366 Variant
ini_get(Array
& p
) {
370 Variant
ini_get(std::set
<std::string
>& p
) {
371 ArrayInit
ret(p
.size(), ArrayInit::Map
{});
376 return ret
.toArray();
379 Variant
ini_get(std::set
<std::string
, stdltistr
>& p
) {
380 ArrayInit
ret(p
.size(), ArrayInit::Map
{});
385 return ret
.toArray();
388 Variant
ini_get(boost::container::flat_set
<std::string
>& p
) {
389 ArrayInit
ret(p
.size(), ArrayInit::Map
{});
394 return ret
.toArray();
397 Variant
ini_get(std::vector
<std::string
>& p
) {
398 ArrayInit
ret(p
.size(), ArrayInit::Map
{});
403 return ret
.toArray();
406 const IniSettingMap
ini_iterate(const IniSettingMap
&ini
,
407 const std::string
&name
) {
408 // This should never happen, but handle it anyway.
413 // If for some reason we are passed a string (i.e., a leaf value),
414 // just return it back
415 if (ini
.isString()) {
419 // If we just passed in a name that already has a value like:
420 // hhvm.server.apc.ttl_limit
421 // max_execution_time
422 // then we just return the value now.
423 // i.e., a value that didn't look like
424 // hhvm.a.b[c][d], where name = hhvm.a.b.c.d
425 // c[d] (where ini is already hhvm.a.b), where name = c.d
426 auto value
= ini
[name
];
427 if (!value
.isNull()) {
431 // Otherwise, we split on the dots (if any) to see if we can get a real value
432 std::vector
<std::string
> dot_parts
;
433 folly::split('.', name
, dot_parts
);
436 int dot_parts_size
= dot_parts
.size();
437 std::string part
= dot_parts
[0];
439 // Loop through the dot parts, getting a pointer to each
440 // We may need to concatenate dots to be able to get a real value
441 // e.g., if someone passed in hhvm.a.b.c.d, which in ini was equal
442 // to hhvm.a.b[c][d], then we would start with hhvm and get null,
443 // then hhvm.a and get null, then hhvm.a.b and actually get an object
445 while (value
.isNull() && dot_loc
< dot_parts_size
- 1) {
447 part
= part
+ "." + dot_parts
[dot_loc
];
450 // Get to the last dot part and get its value, if it exists
451 for (int i
= dot_loc
+ 1; i
< dot_parts_size
; i
++) {
452 if (!value
.isNull()) {
455 } else { // If we reach a bad point, just return null
462 ///////////////////////////////////
465 IniSettingMap::IniSettingMap() {
466 m_map
= Variant(Array::Create());
469 IniSettingMap::IniSettingMap(Type
/*t*/) : IniSettingMap() {}
471 IniSettingMap::IniSettingMap(const IniSettingMap
& i
) {
475 IniSettingMap::IniSettingMap(IniSettingMap
&& i
) noexcept
{
476 m_map
= std::move(i
.m_map
);
479 /* implicit */ IniSettingMap::IniSettingMap(const Variant
& v
) {
483 const IniSettingMap
IniSettingMap::operator[](const String
& key
) const {
484 assert(this->isArray());
485 return IniSettingMap(m_map
.toCArrRef()[key
]);
488 IniSettingMap
& IniSettingMap::operator=(const IniSettingMap
& i
) {
494 void mergeSettings(member_lval curval
, TypedValue v
) {
495 auto const cur_inner
= curval
.unboxed();
496 auto const cell
= tvToCell(v
);
498 if (isArrayLikeType(cell
.m_type
) &&
499 isArrayLikeType(cur_inner
.type())) {
500 for (auto i
= ArrayIter(cell
.m_data
.parr
); !i
.end(); i
.next()) {
501 mergeSettings(asArrRef(cur_inner
).lvalAt(i
.first()), i
.secondVal());
504 tvSet(tvToInitCell(v
), curval
);
509 void IniSettingMap::set(const String
& key
, const Variant
& v
) {
510 assert(this->isArray());
511 auto const curval
= m_map
.toArrRef().lvalAt(key
);
512 mergeSettings(curval
, *v
.asTypedValue());
515 ///////////////////////////////////////////////////////////////////////////////
516 // callbacks for creating arrays out of ini
518 void IniSetting::ParserCallback::onSection(const std::string
& /*name*/,
523 void IniSetting::ParserCallback::onLabel(const std::string
& /*name*/,
528 void IniSetting::ParserCallback::onEntry(
529 const std::string
&key
, const std::string
&value
, void *arg
) {
530 auto arr
= static_cast<Variant
*>(arg
);
533 forceToArray(*arr
).set(skey
, sval
);
536 void IniSetting::ParserCallback::onPopEntry(
537 const std::string
&key
,
538 const std::string
&value
,
539 const std::string
&offset
,
541 auto arr
= static_cast<Variant
*>(arg
);
544 bool oEmpty
= offset
.empty();
545 // Substitution copy or symlink
546 // Offset come in like: hhvm.a.b\0c\0@
547 // Check for `\0` because it is possible, although unlikely, to have
548 // something like hhvm.a.b[c@]. Thus we wouldn't want to make a substitution.
549 if (!oEmpty
&& (offset
.size() == 1 || offset
[offset
.size() - 2] == '\0') &&
550 (offset
.back() == '@' || offset
.back() == ':')) {
551 makeSettingSub(key
, offset
, value
, *arr
);
552 } else { // Normal array value
554 auto const hash
= arr
->toArrRef().lvalAt(skey
);
556 if (!oEmpty
) { // a[b]
557 makeArray(tvAsVariant(hash
.tv_ptr()), offset
, value
);
559 asArrRef(hash
).append(value
);
564 void IniSetting::ParserCallback::makeArray(Variant
& hash
,
565 const std::string
& offset
,
566 const std::string
& value
) {
567 assert(!offset
.empty());
568 Variant
*val
= &hash
;
569 assert(val
->isArray());
570 auto start
= offset
.c_str();
575 last
= p
+ index
.size() >= start
+ offset
.size();
576 // This is mandatory in case we have a nested array like:
578 // b will be hash and an array already, but c and d might
579 // not exist and will need to be made an array
581 val
= &tvAsVariant(val
->toArrRef().lvalAt(index
).tv_ptr());
583 *val
= Variant(value
);
585 p
+= index
.size() + 1;
590 void IniSetting::ParserCallback::makeSettingSub(const String
& key
,
591 const std::string
& offset
,
592 const std::string
& value
,
593 Variant
& cur_settings
) {
594 assert(offset
.size() == 1 ||
595 (offset
.size() >=2 && offset
[offset
.size()-2] == 0));
596 auto type
= offset
.substr(offset
.size() - 1);
597 assert(type
== ":" || type
== "@");
598 std::vector
<std::string
> copy_name_parts
= split_brackets(value
);
599 assert(!copy_name_parts
.empty());
600 Variant
* base
= &cur_settings
;
602 for (auto& part
: copy_name_parts
) {
603 if (!base
->isArray()) {
604 *base
= Array::Create();
606 auto lval
= base
->toArrRef().lvalAt(String(part
));
607 if (isNullType(lval
.unboxed().type())) {
610 base
= &tvAsVariant(lval
.tv_ptr());
613 // if skip is true we have something like:
614 // hhvm.env_variables["MYINT"][:] = 3
615 // hhvm.stats.slot_duration[:] = "hhvm.stats.slot_duration"
617 Logger::Warning("A false recursive setting at key %s with offset %s and "
618 "value %s. Value is literal or pointing to setting that "
619 "does not exist. Skipping!", key
.toCppString().c_str(),
620 offset
.c_str(), value
.c_str());
621 } else if (offset
== ":") {
622 cur_settings
.toArrRef().setRef(key
, *base
);
623 } else if (offset
== "@") {
624 cur_settings
.toArrRef().set(key
, *base
);
626 traverseToSet(key
, offset
, *base
, cur_settings
, type
);
630 void IniSetting::ParserCallback::traverseToSet(const String
&key
,
631 const std::string
& offset
,
633 Variant
& cur_settings
,
634 const std::string
& stopChar
) {
635 assert(stopChar
== "@" || stopChar
== ":");
636 assert(offset
!= stopChar
);
637 assert(cur_settings
.isArray());
638 auto isSymlink
= stopChar
== ":";
639 auto start
= offset
.c_str();
641 auto const first(cur_settings
.toArrRef().lvalAt(key
));
643 Variant
*setting
= &tvAsVariant(first
.tv_ptr());
648 p
+= index
.size() + 1;
649 if (strcmp(p
, stopChar
.c_str()) != 0) {
650 forceToArray(*setting
);
651 setting
= &tvAsVariant(setting
->toArrRef().lvalAt(index
).tv_ptr());
657 setting
->toArrRef().setRef(index
, value
);
659 setting
->toArrRef().set(index
, value
);
663 void IniSetting::ParserCallback::onConstant(std::string
&result
,
664 const std::string
&name
) {
665 if (f_defined(name
)) {
666 result
= f_constant(name
).toString().toCppString();
672 void IniSetting::ParserCallback::onVar(std::string
&result
,
673 const std::string
& name
) {
675 if (IniSetting::Get(name
, curval
)) {
679 String value
= g_context
->getenv(name
);
680 if (!value
.isNull()) {
681 result
= value
.toCppString();
687 void IniSetting::ParserCallback::onOp(
688 std::string
&result
, char type
, const std::string
& op1
,
689 const std::string
& op2
) {
690 int i_op1
= strtoll(op1
.c_str(), nullptr, 10);
691 int i_op2
= strtoll(op2
.c_str(), nullptr, 10);
694 case '|': i_result
= i_op1
| i_op2
; break;
695 case '&': i_result
= i_op1
& i_op2
; break;
696 case '^': i_result
= i_op1
^ i_op2
; break;
697 case '~': i_result
= ~i_op1
; break;
698 case '!': i_result
= !i_op1
; break;
700 result
= std::to_string((int64_t)i_result
);
703 void IniSetting::SectionParserCallback::onSection(
704 const std::string
&name
, void *arg
) {
705 auto const data
= (CallbackData
*)arg
;
706 data
->active_section
.unset(); // break ref() from previous section
707 data
->active_section
= Array::Create();
708 data
->arr
.toArrRef().setRef(String(name
), data
->active_section
);
711 Variant
* IniSetting::SectionParserCallback::activeArray(CallbackData
* data
) {
712 if (!data
->active_section
.isNull()) {
713 return &data
->active_section
;
719 void IniSetting::SectionParserCallback::onLabel(const std::string
&name
,
721 IniSetting::ParserCallback::onLabel(name
, activeArray((CallbackData
*)arg
));
724 void IniSetting::SectionParserCallback::onEntry(
725 const std::string
&key
, const std::string
&value
, void *arg
) {
726 IniSetting::ParserCallback::onEntry(key
, value
,
727 activeArray((CallbackData
*)arg
));
730 void IniSetting::SectionParserCallback::onPopEntry(
731 const std::string
&key
, const std::string
&value
, const std::string
&offset
,
733 IniSetting::ParserCallback::onPopEntry(key
, value
, offset
,
734 activeArray((CallbackData
*)arg
));
737 void IniSetting::SystemParserCallback::onEntry(
738 const std::string
&key
, const std::string
&value
, void *arg
) {
739 assert(!key
.empty());
740 // onConstant will always be called before onEntry, so we can check
742 if (IniSetting::s_config_is_a_constant
) {
743 IniSetting::config_names_that_use_constants
.insert(key
);
744 IniSetting::s_config_is_a_constant
= false;
746 ParserCallback::onEntry(key
, value
, arg
);
750 void IniSetting::SystemParserCallback::onPopEntry(const std::string
& key
,
751 const std::string
& value
,
752 const std::string
& offset
,
754 assert(!key
.empty());
755 if (IniSetting::s_config_is_a_constant
) {
756 IniSetting::config_names_that_use_constants
.insert(key
);
757 IniSetting::s_config_is_a_constant
= false;
759 ParserCallback::onPopEntry(key
, value
, offset
, arg
);
762 void IniSetting::SystemParserCallback::onConstant(std::string
&result
,
763 const std::string
&name
) {
764 IniSetting::s_config_is_a_constant
= true;
765 if (f_defined(name
, false)) {
766 result
= f_constant(name
).toString().toCppString();
772 ///////////////////////////////////////////////////////////////////////////////
774 static Mutex s_mutex
;
775 Variant
IniSetting::FromString(const String
& ini
, const String
& filename
,
776 bool process_sections
/* = false */,
777 int scanner_mode
/* = NormalScanner */) {
778 Lock
lock(s_mutex
); // ini parser is not thread-safe
779 // We are parsing something new, so reset this flag
780 s_config_is_a_constant
= false;
781 auto ini_cpp
= ini
.toCppString();
782 auto filename_cpp
= filename
.toCppString();
784 if (process_sections
) {
786 SectionParserCallback cb
;
787 data
.arr
= Array::Create();
788 if (zend_parse_ini_string(ini_cpp
, filename_cpp
, scanner_mode
, cb
, &data
)) {
793 Variant arr
= Array::Create();
794 if (zend_parse_ini_string(ini_cpp
, filename_cpp
, scanner_mode
, cb
, &arr
)) {
801 IniSettingMap
IniSetting::FromStringAsMap(const std::string
& ini
,
802 const std::string
& filename
) {
803 Lock
lock(s_mutex
); // ini parser is not thread-safe
804 // We are parsing something new, so reset this flag
805 s_config_is_a_constant
= false;
806 SystemParserCallback cb
;
808 zend_parse_ini_string(ini
, filename
, NormalScanner
, cb
, &parsed
);
809 if (parsed
.isNull()) {
810 return uninit_null();
812 // We have the final values for our ini settings.
813 // Unbox everything so that we have no more references in the map since we do
814 // things that might require us not to have references
815 // (e.g. calling Variant::SetEvalScalar(), which will assert if an
816 // arraydata's elements are KindOfRef)
817 std::set
<ArrayData
*> seen
;
818 bool use_defaults
= false;
819 Variant ret
= Unbox(parsed
, seen
, use_defaults
, empty_string());
821 return uninit_null();
826 Variant
IniSetting::Unbox(const Variant
& boxed
, std::set
<ArrayData
*>& seen
,
827 bool& use_defaults
, const String
& array_key
) {
828 assert(boxed
.isArray());
829 Variant
unboxed(Array::Create());
830 auto ad
= boxed
.getArrayData();
831 if (seen
.insert(ad
).second
) {
832 for (auto it
= boxed
.toArray().begin(); it
; it
.next()) {
833 auto key
= it
.first();
834 // asserting here to ensure that key is a scalar type that can be
835 // converted to a string.
836 assert(key
.isScalar());
837 auto& elem
= tvAsCVarRef(it
.secondRval().tv_ptr());
838 unboxed
.asArrRef().set(
840 elem
.isArray() ? Unbox(elem
, seen
, use_defaults
, key
.toString()) : elem
845 // The insert into seen wasn't successful. We have recursion.
846 // break the recursive cycle, so the elements can be freed by the MM.
847 // The const_cast is ok because we fully own the array, with no sharing.
849 // Use the current array key to give a little help in the log message
850 const_cast<Variant
&>(boxed
).unset();
852 Logger::Warning("INI Recursion Detected at offset named %s. "
853 "Using default runtime settings.",
854 array_key
.toCppString().c_str());
859 struct IniCallbackData
{
862 mode
= IniSetting::PHP_INI_NONE
;
864 updateCallback
= nullptr;
865 getCallback
= nullptr;
867 virtual ~IniCallbackData() {
872 const Extension
* extension
;
873 IniSetting::Mode mode
;
874 UserIniData
*iniData
;
875 std::function
<bool(const Variant
& value
)> updateCallback
;
876 std::function
<Variant()> getCallback
;
879 typedef std::map
<std::string
, IniCallbackData
> CallbackMap
;
882 // These are for settings/callbacks only settable at startup.
884 // Empirically and surprisingly (20Jan2015):
885 // * server mode: the contents of system map are destructed on SIGTERM
886 // * CLI mode: the contents of system map are NOT destructed on SIGTERM
888 static CallbackMap s_system_ini_callbacks
;
891 // These are for settings/callbacks that the script
892 // can change during the request.
894 // Empirically and surprisingly (20Jan2015), when there are N threads:
895 // * server mode: the contents of user map are destructed N-1 times
896 // * CLI mode: the contents of user map are NOT destructed on SIGTERM
898 static THREAD_LOCAL(CallbackMap
, s_user_callbacks
);
900 struct SystemSettings
{
901 std::unordered_map
<std::string
,Variant
> settings
;
902 TYPE_SCAN_IGNORE_ALL
; // the variants are always static
905 struct LocalSettings
{
906 using Map
= req::hash_map
<std::string
,Variant
>;
907 req::Optional
<Map
> settings
;
909 if (!settings
) settings
.emplace();
910 return settings
.value();
912 void clear() { settings
.clear(); }
913 bool empty() { return !settings
.hasValue() || settings
.value().empty(); }
916 // Set by a .ini file at the start
917 static SystemSettings s_system_settings
;
919 // Changed during the course of the request
920 static THREAD_LOCAL(LocalSettings
, s_saved_defaults
);
922 struct IniSettingExtension final
: Extension
{
923 IniSettingExtension() : Extension("hhvm.ini", NO_EXTENSION_VERSION_YET
) {}
925 // s_saved_defaults should be clear at the beginning of any request
926 void requestInit() override
{
927 assert(!s_saved_defaults
->settings
.hasValue());
931 void IniSetting::Bind(
932 const Extension
* extension
,
934 const std::string
& name
,
935 std::function
<bool(const Variant
&)> updateCallback
,
936 std::function
<Variant()> getCallback
,
937 std::function
<struct UserIniData
*(void)> userDataCallback
939 assert(!name
.empty());
942 * WATCH OUT: unlike php5, a Mode is not necessarily a bit mask.
943 * PHP_INI_ALL is NOT encoded as the union:
944 * PHP_INI_USER|PHP_INI_PERDIR|PHP_INI_SYSTEM
946 * Note that Mode value PHP_INI_SET_USER and PHP_INI_SET_EVERY are bit
947 * sets; "SET" in this use means "bitset", and not "assignment".
949 bool is_thread_local
;
950 if (RuntimeOption::EnableZendIniCompat
) {
952 (mode
== PHP_INI_USER
) ||
953 (mode
== PHP_INI_PERDIR
) ||
954 (mode
== PHP_INI_ALL
) || /* See note above */
955 (mode
& PHP_INI_USER
) ||
956 (mode
& PHP_INI_PERDIR
) ||
960 is_thread_local
= (mode
== PHP_INI_USER
|| mode
== PHP_INI_ALL
);
961 assert(is_thread_local
|| !ExtensionRegistry::modulesInitialised() ||
962 !s_system_settings_are_set
);
965 // When the debugger is loading its configuration, there will be some
966 // cases where Extension::ModulesInitialised(), but the name appears
967 // in neither s_user_callbacks nor s_system_ini_callbacks. The bottom
968 // line is that we can't really use ModulesInitialised() to help steer
972 bool use_user
= is_thread_local
;
973 if (RuntimeOption::EnableZendIniCompat
&& !use_user
) {
975 // If it is already in the user callbacks, continue to use it from
976 // there. We don't expect it to be already there, but it has been
977 // observed during development.
979 bool in_user_callbacks
=
980 (s_user_callbacks
->find(name
) != s_user_callbacks
->end());
981 assert (!in_user_callbacks
); // See note above
982 use_user
= in_user_callbacks
;
986 // For now, we require the extensions to use their own thread local
987 // memory for user-changeable settings. This means you need to use
988 // the default field to Bind and can't statically initialize them.
989 // The main reasoning to do that is so that the extensions have the
990 // values already parsed into their types. If you are setting an int,
991 // it does the string parsing once and then when you read it, it is
992 // already an int. If we did some shared thing, we would just hand you
993 // back the strings and you'd have to parse them on every request or
994 // build some convoluted caching mechanism which is slower than just
997 // We could conceivably let you use static memory and have our own
998 // thread local here that users can change and then reset it back to
999 // the default, but we haven't built that yet.
1002 IniCallbackData
&data
=
1003 use_user
? (*s_user_callbacks
)[name
] : s_system_ini_callbacks
[name
];
1005 data
.extension
= extension
;
1007 data
.updateCallback
= updateCallback
;
1008 data
.getCallback
= getCallback
;
1009 if (data
.iniData
== nullptr && userDataCallback
!= nullptr) {
1010 data
.iniData
= userDataCallback();
1014 void IniSetting::Unbind(const std::string
& name
) {
1015 assert(!name
.empty());
1016 s_user_callbacks
->erase(name
);
1019 static IniCallbackData
* get_callback(const std::string
& name
) {
1020 CallbackMap::iterator iter
= s_system_ini_callbacks
.find(name
);
1021 if (iter
== s_system_ini_callbacks
.end()) {
1022 iter
= s_user_callbacks
->find(name
);
1023 if (iter
== s_user_callbacks
->end()) {
1027 return &iter
->second
;
1030 bool IniSetting::Get(const std::string
& name
, std::string
&value
) {
1032 auto ret
= Get(name
, b
);
1033 value
= b
.toString().toCppString();
1034 return ret
&& !value
.empty();
1037 bool IniSetting::Get(const String
& name
, String
& value
) {
1039 auto ret
= Get(name
, b
);
1040 value
= b
.toString();
1044 static bool shouldHideSetting(const std::string
& name
) {
1045 for (auto& sub
: RuntimeOption::EvalIniGetHide
) {
1046 if (name
.find(sub
) != std::string::npos
) {
1053 bool IniSetting::Get(const String
& name
, Variant
& value
) {
1054 auto nameStr
= name
.toCppString();
1055 if (shouldHideSetting(nameStr
)) {
1058 auto cb
= get_callback(nameStr
);
1062 value
= cb
->getCallback();
1066 std::string
IniSetting::Get(const std::string
& name
) {
1072 static bool ini_set(const std::string
& name
, const Variant
& value
,
1073 IniSetting::Mode mode
) {
1074 auto cb
= get_callback(name
);
1075 if (!cb
|| !(cb
->mode
& mode
)) {
1078 return cb
->updateCallback(value
);
1081 bool IniSetting::FillInConstant(const std::string
& name
,
1082 const Variant
& value
) {
1084 if (config_names_that_use_constants
.find(name
) ==
1085 config_names_that_use_constants
.end()) {
1088 // We can cheat here since we fill in constants a while after
1089 // runtime options are loaded.
1090 s_system_settings_are_set
= false;
1091 auto const ret
= IniSetting::SetSystem(name
, value
);
1092 s_system_settings_are_set
= true;
1096 bool IniSetting::SetSystem(const String
& name
, const Variant
& value
) {
1097 // Shouldn't be calling this function after the runtime options are loaded.
1098 assert(!s_system_settings_are_set
);
1099 // Since we're going to keep these settings for the lifetime of the program,
1100 // we need to make them static.
1101 Variant eval_scalar_variant
= value
;
1102 eval_scalar_variant
.setEvalScalar();
1103 s_system_settings
.settings
[name
.toCppString()] = eval_scalar_variant
;
1104 return ini_set(name
.toCppString(), value
, PHP_INI_SET_EVERY
);
1107 bool IniSetting::GetSystem(const String
& name
, Variant
& value
) {
1108 auto it
= s_system_settings
.settings
.find(name
.toCppString());
1109 if (it
== s_system_settings
.settings
.end()) {
1116 bool IniSetting::SetUser(const String
& name
, const Variant
& value
) {
1117 auto& defaults
= s_saved_defaults
->init();
1118 auto it
= defaults
.find(name
.toCppString());
1119 if (it
== defaults
.end()) {
1121 auto success
= Get(name
, def
); // def gets populated here
1123 defaults
[name
.toCppString()] = def
;
1126 return ini_set(name
.toCppString(), value
, PHP_INI_SET_USER
);
1129 void IniSetting::RestoreUser(const String
& name
) {
1130 if (!s_saved_defaults
->empty()) {
1131 auto& defaults
= s_saved_defaults
->settings
.value();
1132 auto it
= defaults
.find(name
.toCppString());
1133 if (it
!= defaults
.end() &&
1134 ini_set(name
.toCppString(), it
->second
, PHP_INI_SET_USER
)) {
1140 bool IniSetting::ResetSystemDefault(const std::string
& name
) {
1141 auto it
= s_system_settings
.settings
.find(name
);
1142 if (it
== s_system_settings
.settings
.end()) {
1145 return ini_set(name
, it
->second
, PHP_INI_SET_EVERY
);
1148 void IniSetting::ResetSavedDefaults() {
1149 if (!s_saved_defaults
->empty()) {
1150 for (auto& item
: s_saved_defaults
->settings
.value()) {
1151 ini_set(item
.first
, item
.second
, PHP_INI_SET_USER
);
1154 // destroy the local settings hashtable even if it's empty
1155 s_saved_defaults
->clear();
1158 bool IniSetting::GetMode(const std::string
& name
, Mode
& mode
) {
1159 auto cb
= get_callback(name
);
1167 Array
IniSetting::GetAll(const String
& ext_name
, bool details
) {
1168 Array r
= Array::Create();
1170 const Extension
* ext
= nullptr;
1171 if (!ext_name
.empty()) {
1172 if (ext_name
== s_core
) {
1173 ext
= IniSetting::CORE
;
1175 ext
= ExtensionRegistry::get(ext_name
);
1177 raise_warning("Unable to find extension '%s'",
1178 ext_name
.toCppString().c_str());
1184 for (auto& iter
: boost::join(s_system_ini_callbacks
, *s_user_callbacks
)) {
1185 if (ext
&& ext
!= iter
.second
.extension
) {
1188 if (shouldHideSetting(iter
.first
)) {
1192 auto value
= iter
.second
.getCallback();
1193 // Cast all non-arrays to strings since that is what everything used ot be
1194 if (!value
.isArray()) {
1195 value
= value
.toString();
1198 Array item
= Array::Create();
1199 item
.add(s_global_value
, value
);
1200 item
.add(s_local_value
, value
);
1201 if (iter
.second
.mode
== PHP_INI_ALL
) {
1204 Variant(PHP_INI_USER
| PHP_INI_SYSTEM
| PHP_INI_PERDIR
)
1206 } else if (iter
.second
.mode
== PHP_INI_ONLY
) {
1207 item
.add(s_access
, Variant(PHP_INI_SYSTEM
));
1209 item
.add(s_access
, Variant(iter
.second
.mode
));
1211 r
.add(String(iter
.first
), item
);
1213 r
.add(String(iter
.first
), value
);
1219 std::string
IniSetting::GetAllAsJSON() {
1220 Array settings
= GetAll(empty_string(), true);
1221 String out
= Variant::attach(HHVM_FN(json_encode
)(settings
)).toString();
1222 return std::string(out
.c_str());
1225 void add_default_config_files_globbed(
1226 const char *default_config_file
,
1227 std::function
<void (const char *filename
)> cb
1230 memset(&globbuf
, 0, sizeof(glob_t
));
1231 int flags
= 0; // Use default glob semantics
1232 int nret
= glob(default_config_file
, flags
, nullptr, &globbuf
);
1233 if (nret
== GLOB_NOMATCH
||
1234 globbuf
.gl_pathc
== 0 ||
1235 globbuf
.gl_pathv
== 0 ||
1241 for (int n
= 0; n
< (int)globbuf
.gl_pathc
; n
++) {
1242 if (access(globbuf
.gl_pathv
[n
], R_OK
) != -1) {
1243 cb(globbuf
.gl_pathv
[n
]);
1249 ///////////////////////////////////////////////////////////////////////////////