Add sub-controls for Hack array compat runtime checks
[hiphop-php.git] / hphp / runtime / base / ini-setting.cpp
blob5746eece413a01a6248d3c481950d3f2c2ebd8b8
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/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"
37 #ifndef _MSC_VER
38 #include <glob.h>
39 #endif
41 #define __STDC_LIMIT_MACROS
42 #include <cstdint>
43 #include <boost/range/join.hpp>
44 #include <boost/algorithm/string/split.hpp>
45 #include <boost/algorithm/string.hpp>
46 #include <map>
48 namespace HPHP {
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;
57 const StaticString
58 s_global_value("global_value"),
59 s_local_value("local_value"),
60 s_access("access"),
61 s_core("core");
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();
70 return split_value;
73 int64_t convert_bytes_to_long(folly::StringPiece value) {
74 if (value.empty()) {
75 return 0;
77 int64_t newInt = strtoll(value.begin(), nullptr, 10);
78 auto const lastChar = value[value.size() - 1];
79 if (lastChar == 'K' || lastChar == 'k') {
80 newInt <<= 10;
81 } else if (lastChar == 'M' || lastChar == 'm') {
82 newInt <<= 20;
83 } else if (lastChar == 'G' || lastChar == 'g') {
84 newInt <<= 30;
86 return newInt;
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";
98 } else {
99 return std::to_string(value);
103 #define INI_ASSERT_STR(v) \
104 if (!v.isScalar()) { \
105 return false; \
107 auto str = v.toString().toCppString();
109 #define INI_ASSERT_ARR(v) \
110 if (!value.isArray() && !value.isObject()) { \
111 return false; \
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)) {
121 p = false;
122 } else {
123 p = true;
125 return true;
128 bool ini_on_update(const Variant& value, double& p) {
129 INI_ASSERT_STR(value);
130 p = zend_strtod(str.data(), nullptr);
131 return true;
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)) {
139 return false;
141 p = n;
142 return true;
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)) {
150 return false;
152 p = n;
153 return true;
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)) {
161 return false;
163 p = n;
164 return true;
167 bool ini_on_update(const Variant& value, int64_t& p) {
168 INI_ASSERT_STR(value);
169 p = convert_bytes_to_long(str);
170 return true;
173 bool ini_on_update(const Variant& value, unsigned char& p) {
174 INI_ASSERT_STR(value);
175 auto n = convert_bytes_to_long(str);
176 auto mask = ~0xFFUL;
177 if (((uint64_t)n & mask)) {
178 return false;
180 p = n;
181 return true;
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)) {
189 return false;
191 p = n;
192 return true;
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)) {
200 return false;
202 p = n;
203 return true;
206 bool ini_on_update(const Variant& value, uint64_t& p) {
207 INI_ASSERT_STR(value);
208 p = convert_bytes_to_long(str);
209 return true;
212 bool ini_on_update(const Variant& value, std::string& p) {
213 INI_ASSERT_STR(value);
214 p = str;
215 return true;
218 bool ini_on_update(const Variant& value, String& p) {
219 INI_ASSERT_STR(value);
220 p = str.data();
221 return true;
224 bool ini_on_update(const Variant& value, Array& p) {
225 INI_ASSERT_ARR(value);
226 p = value.toArray();
227 return true;
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());
235 return true;
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());
244 return true;
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());
253 return true;
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());
261 return true;
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();
271 return true;
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();
281 return true;
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();
291 return true;
294 Variant ini_get(bool& p) {
295 return p ? "1" : "";
298 Variant ini_get(double& p) {
299 return p;
302 Variant ini_get(char& p) {
303 return p;
306 Variant ini_get(int16_t& p) {
307 return p;
310 Variant ini_get(int32_t& p) {
311 return p;
314 Variant ini_get(int64_t& p) {
315 return p;
318 Variant ini_get(unsigned char& p) {
319 return p;
322 Variant ini_get(uint16_t& p) {
323 return p;
326 Variant ini_get(uint32_t& p) {
327 return (uint64_t) p;
330 Variant ini_get(uint64_t& p) {
331 return p;
334 Variant ini_get(std::string& p) {
335 return p.data();
338 Variant ini_get(String& p) {
339 return p.data();
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) {
367 return p;
370 Variant ini_get(std::set<std::string>& p) {
371 ArrayInit ret(p.size(), ArrayInit::Map{});
372 auto idx = 0;
373 for (auto& s : p) {
374 ret.add(idx++, s);
376 return ret.toArray();
379 Variant ini_get(std::set<std::string, stdltistr>& p) {
380 ArrayInit ret(p.size(), ArrayInit::Map{});
381 auto idx = 0;
382 for (auto& s : p) {
383 ret.add(idx++, s);
385 return ret.toArray();
388 Variant ini_get(boost::container::flat_set<std::string>& p) {
389 ArrayInit ret(p.size(), ArrayInit::Map{});
390 auto idx = 0;
391 for (auto& s : p) {
392 ret.add(idx++, s);
394 return ret.toArray();
397 Variant ini_get(std::vector<std::string>& p) {
398 ArrayInit ret(p.size(), ArrayInit::Map{});
399 auto idx = 0;
400 for (auto& s : p) {
401 ret.add(idx++, s);
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.
409 if (ini.isNull()) {
410 return init_null();
413 // If for some reason we are passed a string (i.e., a leaf value),
414 // just return it back
415 if (ini.isString()) {
416 return ini;
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()) {
428 return value;
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);
435 int dot_loc = 0;
436 int dot_parts_size = dot_parts.size();
437 std::string part = dot_parts[0];
438 value = ini[part];
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
444 // to point to.
445 while (value.isNull() && dot_loc < dot_parts_size - 1) {
446 dot_loc++;
447 part = part + "." + dot_parts[dot_loc];
448 value = ini[part];
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()) {
453 part = dot_parts[i];
454 value = value[part];
455 } else { // If we reach a bad point, just return null
456 return init_null();
459 return value;
462 ///////////////////////////////////
463 // IniSettingMap
465 IniSettingMap::IniSettingMap() {
466 m_map = Variant(Array::Create());
469 IniSettingMap::IniSettingMap(Type /*t*/) : IniSettingMap() {}
471 IniSettingMap::IniSettingMap(const IniSettingMap& i) {
472 m_map = i.m_map;
475 IniSettingMap::IniSettingMap(IniSettingMap&& i) noexcept {
476 m_map = std::move(i.m_map);
479 /* implicit */ IniSettingMap::IniSettingMap(const Variant& v) {
480 m_map = 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) {
489 m_map = i.m_map;
490 return *this;
493 namespace {
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());
503 } else {
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*/,
519 void* /*arg*/) {
520 // do nothing
523 void IniSetting::ParserCallback::onLabel(const std::string& /*name*/,
524 void* /*arg*/) {
525 // do nothing
528 void IniSetting::ParserCallback::onEntry(
529 const std::string &key, const std::string &value, void *arg) {
530 auto arr = static_cast<Variant*>(arg);
531 String skey(key);
532 Variant sval(value);
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,
540 void *arg) {
541 auto arr = static_cast<Variant*>(arg);
542 forceToArray(*arr);
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
553 String skey(key);
554 auto const hash = arr->toArrRef().lvalAt(skey);
555 forceToArray(hash);
556 if (!oEmpty) { // a[b]
557 makeArray(tvAsVariant(hash.tv_ptr()), offset, value);
558 } else { // a[]
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();
571 auto p = start;
572 bool last = false;
573 do {
574 String index(p);
575 last = p + index.size() >= start + offset.size();
576 // This is mandatory in case we have a nested array like:
577 // hhvm.a[b][c][d]
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
580 forceToArray(*val);
581 val = &tvAsVariant(val->toArrRef().lvalAt(index).tv_ptr());
582 if (last) {
583 *val = Variant(value);
584 } else {
585 p += index.size() + 1;
587 } while (!last);
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;
601 bool skip = false;
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())) {
608 skip = true;
609 } else {
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"
616 if (skip) {
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);
625 } else {
626 traverseToSet(key, offset, *base, cur_settings, type);
630 void IniSetting::ParserCallback::traverseToSet(const String &key,
631 const std::string& offset,
632 Variant& value,
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();
640 auto p = start;
641 auto const first(cur_settings.toArrRef().lvalAt(key));
642 forceToArray(first);
643 Variant *setting = &tvAsVariant(first.tv_ptr());
644 String index;
645 bool done = false;
646 while (!done) {
647 index = String(p);
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());
652 } else {
653 done = true;
656 if (isSymlink) {
657 setting->toArrRef().setRef(index, value);
658 } else {
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();
667 } else {
668 result = name;
672 void IniSetting::ParserCallback::onVar(std::string &result,
673 const std::string& name) {
674 std::string curval;
675 if (IniSetting::Get(name, curval)) {
676 result = curval;
677 return;
679 String value = g_context->getenv(name);
680 if (!value.isNull()) {
681 result = value.toCppString();
682 return;
684 result.clear();
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);
692 int i_result = 0;
693 switch (type) {
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;
714 } else {
715 return &data->arr;
719 void IniSetting::SectionParserCallback::onLabel(const std::string &name,
720 void *arg) {
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,
732 void *arg) {
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
741 // here
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,
753 void* arg) {
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();
767 } else {
768 result = name;
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();
783 Variant ret = false;
784 if (process_sections) {
785 CallbackData data;
786 SectionParserCallback cb;
787 data.arr = Array::Create();
788 if (zend_parse_ini_string(ini_cpp, filename_cpp, scanner_mode, cb, &data)) {
789 ret = data.arr;
791 } else {
792 ParserCallback cb;
793 Variant arr = Array::Create();
794 if (zend_parse_ini_string(ini_cpp, filename_cpp, scanner_mode, cb, &arr)) {
795 ret = arr;
798 return ret;
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;
807 Variant parsed;
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());
820 if (use_defaults) {
821 return uninit_null();
823 return ret;
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(
839 key,
840 elem.isArray() ? Unbox(elem, seen, use_defaults, key.toString()) : elem
843 seen.erase(ad);
844 } else {
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();
851 use_defaults = true;
852 Logger::Warning("INI Recursion Detected at offset named %s. "
853 "Using default runtime settings.",
854 array_key.toCppString().c_str());
856 return unboxed;
859 struct IniCallbackData {
860 IniCallbackData() {
861 extension = nullptr;
862 mode = IniSetting::PHP_INI_NONE;
863 iniData = nullptr;
864 updateCallback = nullptr;
865 getCallback = nullptr;
867 virtual ~IniCallbackData() {
868 delete iniData;
869 iniData = nullptr;
871 public:
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;
908 Map& init() {
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());
929 } s_ini_extension;
931 void IniSetting::Bind(
932 const Extension* extension,
933 const Mode mode,
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) {
951 is_thread_local = (
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) ||
957 (mode & PHP_INI_ALL)
959 } else {
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
969 // the choices here.
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
995 // the int access.
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;
1006 data.mode = mode;
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()) {
1024 return nullptr;
1027 return &iter->second;
1030 bool IniSetting::Get(const std::string& name, std::string &value) {
1031 Variant b;
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) {
1038 Variant b;
1039 auto ret = Get(name, b);
1040 value = b.toString();
1041 return ret;
1044 static bool shouldHideSetting(const std::string& name) {
1045 for (auto& sub : RuntimeOption::EvalIniGetHide) {
1046 if (name.find(sub) != std::string::npos) {
1047 return true;
1050 return false;
1053 bool IniSetting::Get(const String& name, Variant& value) {
1054 auto nameStr = name.toCppString();
1055 if (shouldHideSetting(nameStr)) {
1056 return false;
1058 auto cb = get_callback(nameStr);
1059 if (!cb) {
1060 return false;
1062 value = cb->getCallback();
1063 return true;
1066 std::string IniSetting::Get(const std::string& name) {
1067 std::string ret;
1068 Get(name, ret);
1069 return ret;
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)) {
1076 return false;
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()) {
1086 return false;
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;
1093 return ret;
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()) {
1110 return false;
1112 value = it->second;
1113 return true;
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()) {
1120 Variant def;
1121 auto success = Get(name, def); // def gets populated here
1122 if (success) {
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)) {
1135 defaults.erase(it);
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()) {
1143 return false;
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);
1160 if (!cb) {
1161 return false;
1163 mode = cb->mode;
1164 return true;
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;
1174 } else {
1175 ext = ExtensionRegistry::get(ext_name);
1176 if (!ext) {
1177 raise_warning("Unable to find extension '%s'",
1178 ext_name.toCppString().c_str());
1179 return r;
1184 for (auto& iter: boost::join(s_system_ini_callbacks, *s_user_callbacks)) {
1185 if (ext && ext != iter.second.extension) {
1186 continue;
1188 if (shouldHideSetting(iter.first)) {
1189 continue;
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();
1197 if (details) {
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) {
1202 item.add(
1203 s_access,
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));
1208 } else {
1209 item.add(s_access, Variant(iter.second.mode));
1211 r.add(String(iter.first), item);
1212 } else {
1213 r.add(String(iter.first), value);
1216 return r;
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
1229 glob_t globbuf;
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 ||
1236 nret != 0) {
1237 globfree(&globbuf);
1238 return;
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]);
1246 globfree(&globbuf);
1249 ///////////////////////////////////////////////////////////////////////////////