bool IniSetting::ResetSystemDefault(const std::string& name) { auto it = s_system_settings.find(name); if (it == s_system_settings.end()) { return false; } return ini_set(name, it->second, PHP_INI_SET_EVERY); }
bool IniSetting::GetSystem(const String& name, Variant& value) { auto it = s_system_settings.find(name.toCppString()); if (it == s_system_settings.end()) { return false; } value = it->second; return true; }
void operator()(SettingMap &map) { auto it = map.find(myComponents[myIndex]); if (it != map.end()) { if (myIndex == myComponents.size() - 1) map.erase(it); else { ++myIndex; boost::apply_visitor(*this, it->second); } } }
boost::optional<SettingValue> operator()(const SettingMap &map) const { auto it = map.find(myComponents[myIndex]); if (it == map.end()) return boost::none; const SettingValue &value = it->second; if (myIndex == myComponents.size() - 1) return value; else { if (!boost::apply_visitor(IsMap(), value)) return boost::none; ++myIndex; return boost::apply_visitor(*this, value); } }
void operator()(const SettingMap &map) { myWriter.StartObject(); // Output in sorted order. std::vector<std::string> keys; keys.reserve(map.size()); for (auto &&pair : map) keys.push_back(pair.first); std::sort(keys.begin(), keys.end()); for (auto &&key : keys) { myWriter.Key(key.c_str()); boost::apply_visitor(*this, map.at(key)); } myWriter.EndObject(); }
bool IniSetting::Set(const std::string& name, const folly::dynamic& value, FollyDynamic) { // Need to make sure to update the value if the pair exists already // A general insert(make_pair) won't actually update new values. bool found = false; for (auto& pair : s_system_settings) { if (pair.first == name) { pair.second = value; found = true; break; } } if (!found) { s_system_settings.insert(make_pair(name, value)); } return ini_set(name, value, PHP_INI_SET_EVERY); }
namespace HPHP { /////////////////////////////////////////////////////////////////////////////// const Extension* IniSetting::CORE = (Extension*)(-1); bool IniSetting::s_config_is_a_constant = false; std::set<std::string> IniSetting::config_names_that_use_constants; bool IniSetting::s_system_settings_are_set = false; const StaticString s_global_value("global_value"), s_local_value("local_value"), s_access("access"), s_core("core"); int64_t convert_bytes_to_long(const std::string& value) { if (value.size() == 0) { return 0; } int64_t newInt = strtoll(value.data(), nullptr, 10); char lastChar = value.data()[value.size() - 1]; if (lastChar == 'K' || lastChar == 'k') { newInt <<= 10; } else if (lastChar == 'M' || lastChar == 'm') { newInt <<= 20; } else if (lastChar == 'G' || lastChar == 'g') { newInt <<= 30; } return newInt; } #define INI_ASSERT_STR(v) \ if (!v.isScalar()) { \ return false; \ } \ auto str = v.toString().toCppString(); #define INI_ASSERT_ARR(v) \ if (!value.isArray() && !value.isObject()) { \ return false; \ } bool ini_on_update(const Variant& value, bool& p) { INI_ASSERT_STR(value); if ((str.size() == 0) || (str.size() == 1 && strcasecmp("0", str.data()) == 0) || (str.size() == 2 && strcasecmp("no", str.data()) == 0) || (str.size() == 3 && strcasecmp("off", str.data()) == 0) || (str.size() == 5 && strcasecmp("false", str.data()) == 0)) { p = false; } else { p = true; } return true; } bool ini_on_update(const Variant& value, double& p) { INI_ASSERT_STR(value); p = zend_strtod(str.data(), nullptr); return true; } bool ini_on_update(const Variant& value, char& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto maxValue = 0x7FL; if (n > maxValue || n < (- maxValue - 1)) { return false; } p = n; return true; } bool ini_on_update(const Variant& value, int16_t& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto maxValue = 0x7FFFL; if (n > maxValue || n < (- maxValue - 1)) { return false; } p = n; return true; } bool ini_on_update(const Variant& value, int32_t& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto maxValue = 0x7FFFFFFFL; if (n > maxValue || n < (- maxValue - 1)) { return false; } p = n; return true; } bool ini_on_update(const Variant& value, int64_t& p) { INI_ASSERT_STR(value); p = convert_bytes_to_long(str); return true; } bool ini_on_update(const Variant& value, unsigned char& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto mask = ~0xFFUL; if (((uint64_t)n & mask)) { return false; } p = n; return true; } bool ini_on_update(const Variant& value, uint16_t& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto mask = ~0xFFFFUL; if (((uint64_t)n & mask)) { return false; } p = n; return true; } bool ini_on_update(const Variant& value, uint32_t& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto mask = ~0x7FFFFFFFUL; if (((uint64_t)n & mask)) { return false; } p = n; return true; } bool ini_on_update(const Variant& value, uint64_t& p) { INI_ASSERT_STR(value); p = convert_bytes_to_long(str); return true; } bool ini_on_update(const Variant& value, std::string& p) { INI_ASSERT_STR(value); p = str; return true; } bool ini_on_update(const Variant& value, String& p) { INI_ASSERT_STR(value); p = str.data(); return true; } bool ini_on_update(const Variant& value, Array& p) { INI_ASSERT_ARR(value); p = value.toArray(); return true; } bool ini_on_update(const Variant& value, std::set<std::string>& p) { INI_ASSERT_ARR(value); for (ArrayIter iter(value.toArray()); iter; ++iter) { p.insert(iter.second().toString().toCppString()); } return true; } bool ini_on_update(const Variant& value, std::set<std::string, stdltistr>& p) { INI_ASSERT_ARR(value); for (ArrayIter iter(value.toArray()); iter; ++iter) { p.insert(iter.second().toString().toCppString()); } return true; } bool ini_on_update(const Variant& value, boost::container::flat_set<std::string>& p) { INI_ASSERT_ARR(value); for (ArrayIter iter(value.toArray()); iter; ++iter) { p.insert(iter.second().toString().toCppString()); } return true; } bool ini_on_update(const Variant& value, std::vector<std::string>& p) { INI_ASSERT_ARR(value); for (ArrayIter iter(value.toArray()); iter; ++iter) { p.push_back(iter.second().toString().toCppString()); } return true; } bool ini_on_update(const Variant& value, std::map<std::string, std::string>& p) { INI_ASSERT_ARR(value); for (ArrayIter iter(value.toArray()); iter; ++iter) { p[iter.first().toString().toCppString()] = iter.second().toString().toCppString(); } return true; } bool ini_on_update(const Variant& value, std::map<std::string, std::string, stdltistr>& p) { INI_ASSERT_ARR(value); for (ArrayIter iter(value.toArray()); iter; ++iter) { p[iter.first().toString().toCppString()] = iter.second().toString().toCppString(); } return true; } bool ini_on_update(const Variant& value, hphp_string_imap<std::string>& p) { INI_ASSERT_ARR(value); for (ArrayIter iter(value.toArray()); iter; ++iter) { p[iter.first().toString().toCppString()] = iter.second().toString().toCppString(); } return true; } Variant ini_get(bool& p) { return p ? "1" : ""; } Variant ini_get(double& p) { return p; } Variant ini_get(char& p) { return p; } Variant ini_get(int16_t& p) { return p; } Variant ini_get(int32_t& p) { return p; } Variant ini_get(int64_t& p) { return p; } Variant ini_get(unsigned char& p) { return p; } Variant ini_get(uint16_t& p) { return p; } Variant ini_get(uint32_t& p) { return (uint64_t) p; } Variant ini_get(uint64_t& p) { return p; } Variant ini_get(std::string& p) { return p.data(); } Variant ini_get(String& p) { return p.data(); } Variant ini_get(std::map<std::string, std::string>& p) { ArrayInit ret(p.size(), ArrayInit::Map{}); for (auto& pair : p) { ret.add(String(pair.first), pair.second); } return ret.toArray(); } Variant ini_get(std::map<std::string, std::string, stdltistr>& p) { ArrayInit ret(p.size(), ArrayInit::Map{}); for (auto& pair : p) { ret.add(String(pair.first), pair.second); } return ret.toArray(); } Variant ini_get(hphp_string_imap<std::string>& p) { ArrayInit ret(p.size(), ArrayInit::Map{}); for (auto& pair : p) { ret.add(String(pair.first), pair.second); } return ret.toArray(); } Variant ini_get(Array& p) { return p; } Variant ini_get(std::set<std::string>& p) { ArrayInit ret(p.size(), ArrayInit::Map{}); auto idx = 0; for (auto& s : p) { ret.add(idx++, s); } return ret.toArray(); } Variant ini_get(std::set<std::string, stdltistr>& p) { ArrayInit ret(p.size(), ArrayInit::Map{}); auto idx = 0; for (auto& s : p) { ret.add(idx++, s); } return ret.toArray(); } Variant ini_get(boost::container::flat_set<std::string>& p) { ArrayInit ret(p.size(), ArrayInit::Map{}); auto idx = 0; for (auto& s : p) { ret.add(idx++, s); } return ret.toArray(); } Variant ini_get(std::vector<std::string>& p) { ArrayInit ret(p.size(), ArrayInit::Map{}); auto idx = 0; for (auto& s : p) { ret.add(idx++, s); } return ret.toArray(); } const IniSettingMap ini_iterate(const IniSettingMap &ini, const std::string &name) { // This should never happen, but handle it anyway. if (ini.isNull()) { return init_null(); } // If for some reason we are passed a string (i.e., a leaf value), // just return it back if (ini.isString()) { return ini; } // If we just passed in a name that already has a value like: // hhvm.server.apc.ttl_limit // max_execution_time // then we just return the value now. // i.e., a value that didn't look like // hhvm.a.b[c][d], where name = hhvm.a.b.c.d // c[d] (where ini is already hhvm.a.b), where name = c.d auto value = ini[name]; if (!value.isNull()) { return value; } // Otherwise, we split on the dots (if any) to see if we can get a real value std::vector<std::string> dot_parts; folly::split('.', name, dot_parts); int dot_loc = 0; int dot_parts_size = dot_parts.size(); std::string part = dot_parts[0]; value = ini[part]; // Loop through the dot parts, getting a pointer to each // We may need to concatenate dots to be able to get a real value // e.g., if someone passed in hhvm.a.b.c.d, which in ini was equal // to hhvm.a.b[c][d], then we would start with hhvm and get null, // then hhvm.a and get null, then hhvm.a.b and actually get an object // to point to. while (value.isNull() && dot_loc < dot_parts_size - 1) { dot_loc++; part = part + "." + dot_parts[dot_loc]; value = ini[part]; } // Get to the last dot part and get its value, if it exists for (int i = dot_loc + 1; i < dot_parts_size; i++) { if (!value.isNull()) { part = dot_parts[i]; value = value[part]; } else { // If we reach a bad point, just return null return init_null(); } } return value; } /////////////////////////////////// // IniSettingMap IniSettingMap::IniSettingMap() { m_map = Variant(Array::Create()); } IniSettingMap::IniSettingMap(Type t) : IniSettingMap() {} IniSettingMap::IniSettingMap(const IniSettingMap& i) { m_map = i.m_map; } IniSettingMap::IniSettingMap(IniSettingMap&& i) noexcept { m_map = std::move(i.m_map); } /* implicit */ IniSettingMap::IniSettingMap(const Variant& v) { m_map = v; } const IniSettingMap IniSettingMap::operator[](const String& key) const { assert(this->isArray()); return IniSettingMap(m_map.toCArrRef()[key]); } IniSettingMap& IniSettingMap::operator=(const IniSettingMap& i) { m_map = i.m_map; return *this; } void IniSettingMap::set(const String& key, const Variant& v) { assert(this->isArray()); m_map.toArrRef().set(key, v); } /////////////////////////////////////////////////////////////////////////////// // callbacks for creating arrays out of ini void IniSetting::ParserCallback::onSection(const std::string &name, void *arg) { // do nothing } void IniSetting::ParserCallback::onLabel(const std::string &name, void *arg) { // do nothing } void IniSetting::ParserCallback::onEntry( const std::string &key, const std::string &value, void *arg) { Variant *arr = (Variant*)arg; forceToArray(*arr).set(String(key), Variant(value)); } void IniSetting::ParserCallback::onPopEntry( const std::string &key, const std::string &value, const std::string &offset, void *arg) { Variant *arr = (Variant*)arg; forceToArray(*arr); auto& hash = arr->toArrRef().lvalAt(String(key)); forceToArray(hash); if (!offset.empty()) { makeArray(hash, offset, value); } else { hash.toArrRef().append(value); } } void IniSetting::ParserCallback::makeArray(Variant& hash, const std::string& offset, const std::string& value) { assert(!offset.empty()); Variant *val = &hash; assert(val->isArray()); auto start = offset.c_str(); auto p = start; bool last = false; do { String index(p); last = p + index.size() >= start + offset.size(); // This is mandatory in case we have a nested array like: // hhvm.a[b][c][d] // b will be hash and an array already, but c and d might // not exist and will need to be made an array forceToArray(*val); val = &val->toArrRef().lvalAt(index); if (last) { *val = Variant(value); } else { p += index.size() + 1; } } while (!last); } void IniSetting::ParserCallback::onConstant(std::string &result, const std::string &name) { if (f_defined(name)) { result = f_constant(name).toString().toCppString(); } else { result = name; } } void IniSetting::ParserCallback::onVar(std::string &result, const std::string& name) { std::string curval; if (IniSetting::Get(name, curval)) { result = curval; return; } String value = g_context->getenv(name); if (!value.isNull()) { result = value.toCppString(); return; } result.clear(); } void IniSetting::ParserCallback::onOp( std::string &result, char type, const std::string& op1, const std::string& op2) { int i_op1 = strtoll(op1.c_str(), nullptr, 10); int i_op2 = strtoll(op2.c_str(), nullptr, 10); int i_result = 0; switch (type) { case '|': i_result = i_op1 | i_op2; break; case '&': i_result = i_op1 & i_op2; break; case '^': i_result = i_op1 ^ i_op2; break; case '~': i_result = ~i_op1; break; case '!': i_result = !i_op1; break; } result = std::to_string((int64_t)i_result); } void IniSetting::SectionParserCallback::onSection( const std::string &name, void *arg) { auto const data = (CallbackData*)arg; data->active_section.unset(); // break ref() from previous section data->active_section = Array::Create(); data->arr.toArrRef().setRef(String(name), data->active_section); } Variant* IniSetting::SectionParserCallback::activeArray(CallbackData* data) { if (!data->active_section.isNull()) { return &data->active_section; } else { return &data->arr; } } void IniSetting::SectionParserCallback::onLabel(const std::string &name, void *arg) { IniSetting::ParserCallback::onLabel(name, activeArray((CallbackData*)arg)); } void IniSetting::SectionParserCallback::onEntry( const std::string &key, const std::string &value, void *arg) { IniSetting::ParserCallback::onEntry(key, value, activeArray((CallbackData*)arg)); } void IniSetting::SectionParserCallback::onPopEntry( const std::string &key, const std::string &value, const std::string &offset, void *arg) { IniSetting::ParserCallback::onPopEntry(key, value, offset, activeArray((CallbackData*)arg)); } void IniSetting::SystemParserCallback::onEntry( const std::string &key, const std::string &value, void *arg) { assert(!key.empty()); // onConstant will always be called before onEntry, so we can check // here if (IniSetting::s_config_is_a_constant) { IniSetting::config_names_that_use_constants.insert(key); IniSetting::s_config_is_a_constant = false; } ParserCallback::onEntry(key, value, arg); } void IniSetting::SystemParserCallback::onPopEntry(const std::string& key, const std::string& value, const std::string& offset, void* arg) { assert(!key.empty()); if (IniSetting::s_config_is_a_constant) { IniSetting::config_names_that_use_constants.insert(key); IniSetting::s_config_is_a_constant = false; } ParserCallback::onPopEntry(key, value, offset, arg); } void IniSetting::SystemParserCallback::onConstant(std::string &result, const std::string &name) { IniSetting::s_config_is_a_constant = true; if (f_defined(name, false)) { result = f_constant(name).toString().toCppString(); } else { result = name; } } /////////////////////////////////////////////////////////////////////////////// static Mutex s_mutex; Variant IniSetting::FromString(const String& ini, const String& filename, bool process_sections /* = false */, int scanner_mode /* = NormalScanner */) { Lock lock(s_mutex); // ini parser is not thread-safe // We are parsing something new, so reset this flag s_config_is_a_constant = false; auto ini_cpp = ini.toCppString(); auto filename_cpp = filename.toCppString(); if (process_sections) { CallbackData data; SectionParserCallback cb; data.arr = Array::Create(); if (zend_parse_ini_string(ini_cpp, filename_cpp, scanner_mode, cb, &data)) { return data.arr; } } else { ParserCallback cb; Variant ret = Array::Create(); if (zend_parse_ini_string(ini_cpp, filename_cpp, scanner_mode, cb, &ret)) { return ret; } } return false; } IniSettingMap IniSetting::FromStringAsMap(const std::string& ini, const std::string& filename) { Lock lock(s_mutex); // ini parser is not thread-safe // We are parsing something new, so reset this flag s_config_is_a_constant = false; SystemParserCallback cb; Variant parsed; zend_parse_ini_string(ini, filename, NormalScanner, cb, &parsed); return std::move(parsed); } class IniCallbackData { public: IniCallbackData() { extension = nullptr; mode = IniSetting::PHP_INI_NONE; iniData = nullptr; updateCallback = nullptr; getCallback = nullptr; } virtual ~IniCallbackData() { delete iniData; iniData = nullptr; } public: const Extension* extension; IniSetting::Mode mode; UserIniData *iniData; std::function<bool(const Variant& value)> updateCallback; std::function<Variant()> getCallback; }; typedef std::map<std::string, IniCallbackData> CallbackMap; // // These are for settings/callbacks only settable at startup. // // Empirically and surprisingly (20Jan2015): // * server mode: the contents of system map are destructed on SIGTERM // * CLI mode: the contents of system map are NOT destructed on SIGTERM // static CallbackMap s_system_ini_callbacks; // // These are for settings/callbacks that the script // can change during the request. // // Empirically and surprisingly (20Jan2015), when there are N threads: // * server mode: the contents of user map are destructed N-1 times // * CLI mode: the contents of user map are NOT destructed on SIGTERM // static IMPLEMENT_THREAD_LOCAL(CallbackMap, s_user_callbacks); typedef std::map<std::string, Variant> SettingMap; // Set by a .ini file at the start static SettingMap s_system_settings; // Changed during the course of the request static IMPLEMENT_THREAD_LOCAL(SettingMap, s_saved_defaults); struct IniSettingExtension final : Extension { IniSettingExtension() : Extension("hhvm.ini", NO_EXTENSION_VERSION_YET) {} // s_saved_defaults should be clear at the beginning of any request void requestInit() override { assert(s_saved_defaults->empty()); } void requestShutdown() override { IniSetting::ResetSavedDefaults(); assert(s_saved_defaults->empty()); } void vscan(IMarker& mark) const override { for (auto& e : s_system_settings) mark(e); for (auto& e : *s_saved_defaults) mark(e); } } s_ini_extension; void IniSetting::Bind( const Extension* extension, const Mode mode, const std::string& name, std::function<bool(const Variant&)> updateCallback, std::function<Variant()> getCallback, std::function<class UserIniData *(void)> userDataCallback ) { assert(!name.empty()); /* * WATCH OUT: unlike php5, a Mode is not necessarily a bit mask. * PHP_INI_ALL is NOT encoded as the union: * PHP_INI_USER|PHP_INI_PERDIR|PHP_INI_SYSTEM * * Note that Mode value PHP_INI_SET_USER and PHP_INI_SET_EVERY are bit * sets; "SET" in this use means "bitset", and not "assignment". */ bool is_thread_local; if (RuntimeOption::EnableZendIniCompat) { is_thread_local = ( (mode == PHP_INI_USER) || (mode == PHP_INI_PERDIR) || (mode == PHP_INI_ALL) || /* See note above */ (mode & PHP_INI_USER) || (mode & PHP_INI_PERDIR) || (mode & PHP_INI_ALL) ); } else { is_thread_local = (mode == PHP_INI_USER || mode == PHP_INI_ALL); assert(is_thread_local || !ExtensionRegistry::modulesInitialised() || !s_system_settings_are_set); } // // When the debugger is loading its configuration, there will be some // cases where Extension::ModulesInitialised(), but the name appears // in neither s_user_callbacks nor s_system_ini_callbacks. The bottom // line is that we can't really use ModulesInitialised() to help steer // the choices here. // bool use_user = is_thread_local; if (RuntimeOption::EnableZendIniCompat && !use_user) { // // If it is already in the user callbacks, continue to use it from // there. We don't expect it to be already there, but it has been // observed during development. // bool in_user_callbacks = (s_user_callbacks->find(name) != s_user_callbacks->end()); assert (!in_user_callbacks); // See note above use_user = in_user_callbacks; } // // For now, we require the extensions to use their own thread local // memory for user-changeable settings. This means you need to use // the default field to Bind and can't statically initialize them. // The main reasoning to do that is so that the extensions have the // values already parsed into their types. If you are setting an int, // it does the string parsing once and then when you read it, it is // already an int. If we did some shared thing, we would just hand you // back the strings and you'd have to parse them on every request or // build some convoluted caching mechanism which is slower than just // the int access. // // We could conceivably let you use static memory and have our own // thread local here that users can change and then reset it back to // the default, but we haven't built that yet. // IniCallbackData &data = use_user ? (*s_user_callbacks)[name] : s_system_ini_callbacks[name]; data.extension = extension; data.mode = mode; data.updateCallback = updateCallback; data.getCallback = getCallback; if (data.iniData == nullptr && userDataCallback != nullptr) { data.iniData = userDataCallback(); } } void IniSetting::Unbind(const std::string& name) { assert(!name.empty()); s_user_callbacks->erase(name); } static IniCallbackData* get_callback(const std::string& name) { CallbackMap::iterator iter = s_system_ini_callbacks.find(name.data()); if (iter == s_system_ini_callbacks.end()) { iter = s_user_callbacks->find(name.data()); if (iter == s_user_callbacks->end()) { return nullptr; } } return &iter->second; } bool IniSetting::Get(const std::string& name, std::string &value) { Variant b; auto ret = Get(name, b); value = b.toString().toCppString(); return ret && !value.empty(); } bool IniSetting::Get(const String& name, String& value) { Variant b; auto ret = Get(name, b); value = b.toString(); return ret; } bool IniSetting::Get(const String& name, Variant& value) { auto cb = get_callback(name.toCppString()); if (!cb) { return false; } value = cb->getCallback(); return true; } std::string IniSetting::Get(const std::string& name) { std::string ret; Get(name, ret); return ret; } static bool ini_set(const std::string& name, const Variant& value, IniSetting::Mode mode) { auto cb = get_callback(name); if (!cb || !(cb->mode & mode)) { return false; } return cb->updateCallback(value); } bool IniSetting::FillInConstant(const std::string& name, const Variant& value) { if (config_names_that_use_constants.find(name) == config_names_that_use_constants.end()) { return false; } // We can cheat here since we fill in constants a while after // runtime options are loaded. s_system_settings_are_set = false; return IniSetting::SetSystem(name, value); s_system_settings_are_set = true; } bool IniSetting::SetSystem(const String& name, const Variant& value) { // Shouldn't be calling this function after the runtime options are loaded. assert(!s_system_settings_are_set); // Since we're going to keep these settings for the lifetime of the program, // we need to make them static. Variant eval_scalar_variant = value; eval_scalar_variant.setEvalScalar(); s_system_settings[name.toCppString()] = eval_scalar_variant; return ini_set(name.toCppString(), value, PHP_INI_SET_EVERY); } bool IniSetting::GetSystem(const String& name, Variant& value) { auto it = s_system_settings.find(name.toCppString()); if (it == s_system_settings.end()) { return false; } value = it->second; return true; } bool IniSetting::SetUser(const String& name, const Variant& value) { auto it = s_saved_defaults->find(name.toCppString()); if (it == s_saved_defaults->end()) { Variant def; auto success = Get(name, def); // def gets populated here if (success) { (*s_saved_defaults)[name.toCppString()] = def; } } return ini_set(name.toCppString(), value, PHP_INI_SET_USER); } bool IniSetting::ResetSystemDefault(const std::string& name) { auto it = s_system_settings.find(name); if (it == s_system_settings.end()) { return false; } return ini_set(name, it->second, PHP_INI_SET_EVERY); } void IniSetting::ResetSavedDefaults() { for (auto& item : *s_saved_defaults) { ini_set(item.first, item.second, PHP_INI_SET_USER); } s_saved_defaults->clear(); } bool IniSetting::GetMode(const std::string& name, Mode& mode) { auto cb = get_callback(name); if (!cb) { return false; } mode = cb->mode; return true; } Array IniSetting::GetAll(const String& ext_name, bool details) { Array r = Array::Create(); const Extension* ext = nullptr; if (!ext_name.empty()) { if (ext_name == s_core) { ext = IniSetting::CORE; } else { ext = ExtensionRegistry::get(ext_name); if (!ext) { raise_warning("Unable to find extension '%s'", ext_name.toCppString().c_str()); return r; } } } for (auto& iter: boost::join(s_system_ini_callbacks, *s_user_callbacks)) { if (ext && ext != iter.second.extension) { continue; } auto value = iter.second.getCallback(); // Cast all non-arrays to strings since that is what everything used ot be if (!value.isArray()) { value = value.toString(); } if (details) { Array item = Array::Create(); item.add(s_global_value, value); item.add(s_local_value, value); if (iter.second.mode == PHP_INI_ALL) { item.add( s_access, Variant(PHP_INI_USER | PHP_INI_SYSTEM | PHP_INI_PERDIR) ); } else if (iter.second.mode == PHP_INI_ONLY) { item.add(s_access, Variant(PHP_INI_SYSTEM)); } else { item.add(s_access, Variant(iter.second.mode)); } r.add(String(iter.first), item); } else { r.add(String(iter.first), value); } } return r; } void add_default_config_files_globbed( const char *default_config_file, std::function<void (const char *filename)> cb ) { glob_t globbuf; memset(&globbuf, 0, sizeof(glob_t)); int flags = 0; // Use default glob semantics int nret = glob(default_config_file, flags, nullptr, &globbuf); if (nret == GLOB_NOMATCH || globbuf.gl_pathc == 0 || globbuf.gl_pathv == 0 || nret != 0) { globfree(&globbuf); return; } for (int n = 0; n < (int)globbuf.gl_pathc; n++) { if (access(globbuf.gl_pathv[n], R_OK) != -1) { cb(globbuf.gl_pathv[n]); } } globfree(&globbuf); } /////////////////////////////////////////////////////////////////////////////// }
bool IniSetting::Set(const std::string& name, const folly::dynamic& value, FollyDynamic) { s_system_settings.insert(make_pair(name, value)); return ini_set(name, value, PHP_INI_SET_EVERY); }
namespace HPHP { /////////////////////////////////////////////////////////////////////////////// const Extension* IniSetting::CORE = (Extension*)(-1); bool IniSetting::s_pretendExtensionsHaveNotBeenLoaded = false; const StaticString s_global_value("global_value"), s_local_value("local_value"), s_access("access"), s_core("core"); int64_t convert_bytes_to_long(const std::string& value) { if (value.size() == 0) { return 0; } int64_t newInt = strtoll(value.data(), nullptr, 10); char lastChar = value.data()[value.size() - 1]; if (lastChar == 'K' || lastChar == 'k') { newInt <<= 10; } else if (lastChar == 'M' || lastChar == 'm') { newInt <<= 20; } else if (lastChar == 'G' || lastChar == 'g') { newInt <<= 30; } return newInt; } static std::string dynamic_to_std_string(const folly::dynamic& v) { switch (v.type()) { case folly::dynamic::Type::NULLT: case folly::dynamic::Type::ARRAY: case folly::dynamic::Type::OBJECT: return ""; case folly::dynamic::Type::BOOL: return std::to_string(v.asBool()); case folly::dynamic::Type::DOUBLE: return std::to_string(v.asDouble()); case folly::dynamic::Type::INT64: return std::to_string(v.asInt()); case folly::dynamic::Type::STRING: return v.data(); } not_reached(); } static Variant dynamic_to_variant(const folly::dynamic& v) { switch (v.type()) { case folly::dynamic::Type::NULLT: return init_null(); case folly::dynamic::Type::BOOL: return v.asBool(); case folly::dynamic::Type::DOUBLE: return v.asDouble(); case folly::dynamic::Type::INT64: return v.asInt(); case folly::dynamic::Type::STRING: return v.data(); case folly::dynamic::Type::ARRAY: case folly::dynamic::Type::OBJECT: ArrayInit ret(v.size(), ArrayInit::Mixed{}); for (auto& item : v.items()) { ret.add(dynamic_to_variant(item.first), dynamic_to_variant(item.second)); } return ret.toArray(); } not_reached(); } static folly::dynamic variant_to_dynamic(const Variant& v) { switch (v.getType()) { case KindOfUninit: case KindOfNull: default: return nullptr; case KindOfBoolean: return v.toBoolean(); case KindOfDouble: return v.toDouble(); case KindOfInt64: return v.toInt64(); case KindOfString: case KindOfStaticString: return v.toString().data(); case KindOfArray: case KindOfObject: case KindOfResource: folly::dynamic ret = folly::dynamic::object; for (ArrayIter iter(v.toArray()); iter; ++iter) { ret.insert(variant_to_dynamic(iter.first()), variant_to_dynamic(iter.second())); } return ret; } not_reached(); } #define INI_ASSERT_STR(v) \ if (value.isArray() || value.isObject()) { \ return false; \ } \ auto str = dynamic_to_std_string(v); #define INI_ASSERT_ARR(v) \ if (!value.isArray() && !value.isObject()) { \ return false; \ } bool ini_on_update(const folly::dynamic& value, bool& p) { INI_ASSERT_STR(value); if ((str.size() == 0) || (str.size() == 1 && strcasecmp("0", str.data()) == 0) || (str.size() == 2 && strcasecmp("no", str.data()) == 0) || (str.size() == 3 && strcasecmp("off", str.data()) == 0) || (str.size() == 5 && strcasecmp("false", str.data()) == 0)) { p = false; } else { p = true; } return true; } bool ini_on_update(const folly::dynamic& value, double& p) { INI_ASSERT_STR(value); p = zend_strtod(str.data(), nullptr); return true; } bool ini_on_update(const folly::dynamic& value, char& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto maxValue = 0x7FL; if (n > maxValue || n < (- maxValue - 1)) { return false; } p = n; return true; } bool ini_on_update(const folly::dynamic& value, int16_t& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto maxValue = 0x7FFFL; if (n > maxValue || n < (- maxValue - 1)) { return false; } p = n; return true; } bool ini_on_update(const folly::dynamic& value, int32_t& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto maxValue = 0x7FFFFFFFL; if (n > maxValue || n < (- maxValue - 1)) { return false; } p = n; return true; } bool ini_on_update(const folly::dynamic& value, int64_t& p) { INI_ASSERT_STR(value); p = convert_bytes_to_long(str); return true; } bool ini_on_update(const folly::dynamic& value, unsigned char& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto mask = ~0xFFUL; if (((uint64_t)n & mask)) { return false; } p = n; return true; } bool ini_on_update(const folly::dynamic& value, uint16_t& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto mask = ~0xFFFFUL; if (((uint64_t)n & mask)) { return false; } p = n; return true; } bool ini_on_update(const folly::dynamic& value, uint32_t& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto mask = ~0x7FFFFFFFUL; if (((uint64_t)n & mask)) { return false; } p = n; return true; } bool ini_on_update(const folly::dynamic& value, uint64_t& p) { INI_ASSERT_STR(value); p = convert_bytes_to_long(str); return true; } bool ini_on_update(const folly::dynamic& value, std::string& p) { INI_ASSERT_STR(value); p = str; return true; } bool ini_on_update(const folly::dynamic& value, String& p) { INI_ASSERT_STR(value); p = str.data(); return true; } bool ini_on_update(const folly::dynamic& value, Array& p) { INI_ASSERT_ARR(value); p = dynamic_to_variant(value).toArray(); return true; } bool ini_on_update(const folly::dynamic& value, std::set<std::string>& p) { INI_ASSERT_ARR(value); for (auto& v : value.values()) { p.insert(v.data()); } return true; } folly::dynamic ini_get(bool& p) { return p ? "1" : ""; } folly::dynamic ini_get(double& p) { return p; } folly::dynamic ini_get(int16_t& p) { return p; } folly::dynamic ini_get(int32_t& p) { return p; } folly::dynamic ini_get(int64_t& p) { return p; } folly::dynamic ini_get(uint16_t& p) { return p; } folly::dynamic ini_get(uint32_t& p) { return p; } folly::dynamic ini_get(uint64_t& p) { return p; } folly::dynamic ini_get(std::string& p) { return p.data(); } folly::dynamic ini_get(String& p) { return p.data(); } folly::dynamic ini_get(Array& p) { folly::dynamic ret = folly::dynamic::object; for (ArrayIter iter(p); iter; ++iter) { ret.insert(variant_to_dynamic(iter.first()), variant_to_dynamic(iter.second())); } return ret; } folly::dynamic ini_get(std::set<std::string>& p) { folly::dynamic ret = folly::dynamic::object; for (auto& s : p) { ret.push_back(s); } return ret; } /////////////////////////////////////////////////////////////////////////////// // callbacks for creating arrays out of ini void IniSetting::ParserCallback::onSection(const std::string &name, void *arg) { // do nothing } void IniSetting::ParserCallback::onLabel(const std::string &name, void *arg) { // do nothing } void IniSetting::ParserCallback::onEntry( const std::string &key, const std::string &value, void *arg) { Variant *arr = (Variant*)arg; forceToArray(*arr).set(String(key), String(value)); } void IniSetting::ParserCallback::onPopEntry( const std::string &key, const std::string &value, const std::string &offset, void *arg) { Variant *arr = (Variant*)arg; forceToArray(*arr); auto& hash = arr->toArrRef().lvalAt(String(key)); forceToArray(hash); if (!offset.empty()) { makeArray(hash, offset, value); } else { hash.toArrRef().append(value); } } void IniSetting::ParserCallback::makeArray(Variant &hash, const std::string &offset, const std::string &value) { assert(!offset.empty()); Variant val = strongBind(hash); auto start = offset.c_str(); auto p = start; bool last = false; do { String index(p); last = p + index.size() >= start + offset.size(); Variant newval; if (last) { newval = Variant(value); } else { if (val.toArrRef().exists(index)) { newval = val.toArrRef().rvalAt(index); } else { newval = Variant(Array::Create()); } } val.toArrRef().setRef(index, newval); if (!last) { val.assignRef(newval); p += index.size() + 1; } } while (!last); } void IniSetting::ParserCallback::onConstant(std::string &result, const std::string &name) { if (f_defined(name)) { result = f_constant(name).toString().toCppString(); } else { result = name; } } void IniSetting::ParserCallback::onVar(std::string &result, const std::string& name) { std::string curval; if (IniSetting::Get(name, curval)) { result = curval; return; } String value = g_context->getenv(name); if (!value.isNull()) { result = value.toCppString(); return; } result.clear(); } void IniSetting::ParserCallback::onOp( std::string &result, char type, const std::string& op1, const std::string& op2) { int i_op1 = strtoll(op1.c_str(), nullptr, 10); int i_op2 = strtoll(op2.c_str(), nullptr, 10); int i_result = 0; switch (type) { case '|': i_result = i_op1 | i_op2; break; case '&': i_result = i_op1 & i_op2; break; case '^': i_result = i_op1 ^ i_op2; break; case '~': i_result = ~i_op1; break; case '!': i_result = !i_op1; break; } result = std::to_string((int64_t)i_result); } void IniSetting::SectionParserCallback::onSection( const std::string &name, void *arg) { auto const data = (CallbackData*)arg; data->active_section.unset(); // break ref() from previous section data->active_section = Array::Create(); data->arr.toArrRef().setRef(String(name), data->active_section); } Variant* IniSetting::SectionParserCallback::activeArray(CallbackData* data) { if (!data->active_section.isNull()) { return &data->active_section; } else { return &data->arr; } } void IniSetting::SectionParserCallback::onLabel(const std::string &name, void *arg) { IniSetting::ParserCallback::onLabel(name, activeArray((CallbackData*)arg)); } void IniSetting::SectionParserCallback::onEntry( const std::string &key, const std::string &value, void *arg) { IniSetting::ParserCallback::onEntry(key, value, activeArray((CallbackData*)arg)); } void IniSetting::SectionParserCallback::onPopEntry( const std::string &key, const std::string &value, const std::string &offset, void *arg) { IniSetting::ParserCallback::onPopEntry(key, value, offset, activeArray((CallbackData*)arg)); } void IniSetting::SystemParserCallback::onSection(const std::string &name, void *arg) { // do nothing } void IniSetting::SystemParserCallback::onLabel(const std::string &name, void *arg) { // do nothing } void IniSetting::SystemParserCallback::onEntry( const std::string &key, const std::string &value, void *arg) { assert(!key.empty()); auto& arr = *(IniSetting::Map*)arg; arr[key] = value; } void IniSetting::SystemParserCallback::onPopEntry(const std::string& key, const std::string& value, const std::string& offset, void* arg) { assert(!key.empty()); auto& arr = *(IniSetting::Map*)arg; auto* ptr = arr.get_ptr(key); if (!ptr || !ptr->isObject()) { arr[key] = IniSetting::Map::object; ptr = arr.get_ptr(key); } if (!offset.empty()) { makeArray(*ptr, offset, value); } else { // Find the highest index auto max = 0; for (auto &a : ptr->keys()) { if (a.isInt() && a >= max) { max = a.asInt() + 1; } } (*ptr)[std::to_string(max)] = value; } } void IniSetting::SystemParserCallback::makeArray(Map &hash, const std::string &offset, const std::string &value) { assert(!offset.empty()); Map* val = &hash; auto start = offset.c_str(); auto p = start; bool last = false; do { std::string index(p); last = p + index.size() >= start + offset.size(); Map newval = last ? Map(value) : val->getDefault(index, Map::object()); val = &(*val)[index]; *val = newval; if (!last) { p += index.size() + 1; } } while (!last); } void IniSetting::SystemParserCallback::onConstant(std::string &result, const std::string &name) { if (MemoryManager::TlsWrapper::isNull()) { // We can't load constants before the memory manger is up, so lets just // pretend they are strings I guess result = name; return; } if (f_defined(name, false)) { result = f_constant(name).toString().toCppString(); } else { result = name; } } /////////////////////////////////////////////////////////////////////////////// static Mutex s_mutex; Variant IniSetting::FromString(const String& ini, const String& filename, bool process_sections, int scanner_mode) { Lock lock(s_mutex); // ini parser is not thread-safe auto ini_cpp = ini.toCppString(); auto filename_cpp = filename.toCppString(); if (process_sections) { CallbackData data; SectionParserCallback cb; data.arr = Array::Create(); if (zend_parse_ini_string(ini_cpp, filename_cpp, scanner_mode, cb, &data)) { return data.arr; } } else { ParserCallback cb; Variant ret = Array::Create(); if (zend_parse_ini_string(ini_cpp, filename_cpp, scanner_mode, cb, &ret)) { return ret; } } return false; } IniSetting::Map IniSetting::FromStringAsMap(const std::string& ini, const std::string& filename) { Lock lock(s_mutex); // ini parser is not thread-safe SystemParserCallback cb; Map ret = IniSetting::Map::object; zend_parse_ini_string(ini, filename, NormalScanner, cb, &ret); return ret; } struct IniCallbackData { const Extension* extension; IniSetting::Mode mode; std::function<bool(const folly::dynamic& value)> updateCallback; std::function<folly::dynamic()> getCallback; }; typedef std::map<std::string, IniCallbackData> CallbackMap; // Only settable at startup static CallbackMap s_system_ini_callbacks; // The script can change these during the request static IMPLEMENT_THREAD_LOCAL(CallbackMap, s_user_callbacks); typedef std::map<std::string, folly::dynamic> SettingMap; // Set by a .ini file at the start static SettingMap s_system_settings; // Changed during the course of the request static IMPLEMENT_THREAD_LOCAL(SettingMap, s_saved_defaults); class IniSettingExtension : public Extension { public: IniSettingExtension() : Extension("hhvm.ini", NO_EXTENSION_VERSION_YET) {} void requestShutdown() { // Put all the defaults back to the way they were before any ini_set() for (auto &item : *s_saved_defaults) { IniSetting::SetUser(item.first, item.second, IniSetting::FollyDynamic()); } s_saved_defaults->clear(); } } s_ini_extension; void IniSetting::Bind(const Extension* extension, const Mode mode, const std::string& name, std::function<bool(const folly::dynamic& value)> updateCallback, std::function<folly::dynamic()> getCallback) { assert(!name.empty()); bool is_thread_local = (mode == PHP_INI_USER || mode == PHP_INI_ALL); // For now, we require the extensions to use their own thread local memory for // user-changeable settings. This means you need to use the default field to // Bind and can't statically initialize them. We could conceivably let you // use static memory and have our own thread local here that users can change // and then reset it back to the default, but we haven't built that yet. auto &data = is_thread_local ? (*s_user_callbacks)[name] : s_system_ini_callbacks[name]; // I would love if I could verify p is thread local or not instead of // this dumb hack assert(is_thread_local || !Extension::ModulesInitialised() || s_pretendExtensionsHaveNotBeenLoaded); data.extension = extension; data.mode = mode; data.updateCallback = updateCallback; data.getCallback = getCallback; } void IniSetting::Unbind(const std::string& name) { assert(!name.empty()); s_user_callbacks->erase(name); } static IniCallbackData* get_callback(const std::string& name) { CallbackMap::iterator iter = s_system_ini_callbacks.find(name.data()); if (iter == s_system_ini_callbacks.end()) { iter = s_user_callbacks->find(name.data()); if (iter == s_user_callbacks->end()) { return nullptr; } } return &iter->second; } bool IniSetting::Get(const std::string& name, folly::dynamic& value) { auto cb = get_callback(name); if (!cb) { return false; } value = cb->getCallback(); return true; } bool IniSetting::Get(const std::string& name, std::string &value) { folly::dynamic b = nullptr; auto ret = Get(name, b); value = dynamic_to_std_string(b); return ret && !value.empty(); } bool IniSetting::Get(const String& name, String& value) { Variant b; auto ret = Get(name, b); value = b.toString(); return ret; } bool IniSetting::Get(const String& name, Variant& value) { folly::dynamic b = nullptr; auto ret = Get(name.toCppString(), b); value = dynamic_to_variant(b); return ret; } std::string IniSetting::Get(const std::string& name) { std::string ret; Get(name, ret); return ret; } static bool ini_set(const std::string& name, const folly::dynamic& value, IniSetting::Mode mode) { auto cb = get_callback(name); if (!cb || !(cb->mode & mode)) { return false; } return cb->updateCallback(value); } bool IniSetting::Set(const std::string& name, const folly::dynamic& value, FollyDynamic) { s_system_settings.insert(make_pair(name, value)); return ini_set(name, value, PHP_INI_SET_EVERY); } bool IniSetting::Set(const String& name, const Variant& value) { return Set(name.toCppString(), variant_to_dynamic(value), FollyDynamic()); } bool IniSetting::SetUser(const std::string& name, const folly::dynamic& value, FollyDynamic) { auto it = s_saved_defaults->find(name); if (it == s_saved_defaults->end()) { folly::dynamic def = nullptr; auto success = Get(name, def); if (success) { s_saved_defaults->insert(make_pair(name, def)); } } return ini_set(name, value, PHP_INI_SET_USER); } bool IniSetting::SetUser(const String& name, const Variant& value) { return SetUser(name.toCppString(), variant_to_dynamic(value), FollyDynamic()); } bool IniSetting::ResetSystemDefault(const std::string& name) { auto it = s_system_settings.find(name); if (it == s_system_settings.end()) { return false; } return ini_set(name, it->second, PHP_INI_SET_EVERY); } Array IniSetting::GetAll(const String& ext_name, bool details) { Array r = Array::Create(); const Extension* ext = nullptr; if (!ext_name.empty()) { if (ext_name == s_core) { ext = IniSetting::CORE; } else { ext = Extension::GetExtension(ext_name); if (!ext) { raise_warning("Unable to find extension '%s'", ext_name.toCppString().c_str()); return r; } } } for (auto& iter: boost::join(s_system_ini_callbacks, *s_user_callbacks)) { if (ext && ext != iter.second.extension) { continue; } auto value = dynamic_to_variant(iter.second.getCallback()); // Cast all non-arrays to strings since that is what everything used ot be if (!value.isArray()) { value = value.toString(); } if (details) { Array item = Array::Create(); item.add(s_global_value, value); item.add(s_local_value, value); if (iter.second.mode == PHP_INI_ALL) { item.add( s_access, Variant(PHP_INI_USER | PHP_INI_SYSTEM | PHP_INI_PERDIR) ); } else if (iter.second.mode == PHP_INI_ONLY) { item.add(s_access, Variant(PHP_INI_SYSTEM)); } else { item.add(s_access, Variant(iter.second.mode)); } r.add(String(iter.first), item); } else { r.add(String(iter.first), value); } } return r; } /////////////////////////////////////////////////////////////////////////////// }
namespace HPHP { /////////////////////////////////////////////////////////////////////////////// const Extension* IniSetting::CORE = (Extension*)(-1); bool IniSetting::s_pretendExtensionsHaveNotBeenLoaded = false; bool IniSetting::s_config_is_a_constant = false; std::set<std::string> IniSetting::config_names_that_use_constants; const StaticString s_global_value("global_value"), s_local_value("local_value"), s_access("access"), s_core("core"); int64_t convert_bytes_to_long(const std::string& value) { if (value.size() == 0) { return 0; } int64_t newInt = strtoll(value.data(), nullptr, 10); char lastChar = value.data()[value.size() - 1]; if (lastChar == 'K' || lastChar == 'k') { newInt <<= 10; } else if (lastChar == 'M' || lastChar == 'm') { newInt <<= 20; } else if (lastChar == 'G' || lastChar == 'g') { newInt <<= 30; } return newInt; } static std::string dynamic_to_std_string(const folly::dynamic& v) { switch (v.type()) { case folly::dynamic::Type::NULLT: case folly::dynamic::Type::ARRAY: case folly::dynamic::Type::OBJECT: return ""; case folly::dynamic::Type::BOOL: return std::to_string(v.asBool()); case folly::dynamic::Type::DOUBLE: return convDblToStrWithPhpFormat(v.asDouble()); case folly::dynamic::Type::INT64: return std::to_string(v.asInt()); case folly::dynamic::Type::STRING: return v.data(); } not_reached(); } static Variant dynamic_to_variant(const folly::dynamic& v) { switch (v.type()) { case folly::dynamic::Type::NULLT: return init_null(); case folly::dynamic::Type::BOOL: return v.asBool(); case folly::dynamic::Type::DOUBLE: return v.asDouble(); case folly::dynamic::Type::INT64: return v.asInt(); case folly::dynamic::Type::STRING: return v.data(); case folly::dynamic::Type::ARRAY: case folly::dynamic::Type::OBJECT: ArrayInit arr_init(v.size(), ArrayInit::Mixed{}); for (auto& item : v.items()) { arr_init.add(dynamic_to_variant(item.first), dynamic_to_variant(item.second)); } Array ret = arr_init.toArray(); // Sort the array since folly::dynamic has a tendency to iterate from // back to front. This way a var_dump of the array, for example, looks // ordered. ret.sort(Array::SortNaturalAscending, true, false); return ret; } not_reached(); } static folly::dynamic variant_to_dynamic(const Variant& v) { switch (v.getType()) { case KindOfUninit: case KindOfNull: return nullptr; case KindOfBoolean: return v.toBoolean(); case KindOfDouble: return v.toDouble(); case KindOfInt64: return v.toInt64(); case KindOfString: case KindOfStaticString: return v.toString().data(); case KindOfArray: case KindOfObject: case KindOfResource: { folly::dynamic ret = folly::dynamic::object; for (ArrayIter iter(v.toArray()); iter; ++iter) { ret.insert(variant_to_dynamic(iter.first()), variant_to_dynamic(iter.second())); } return ret; } case KindOfRef: case KindOfClass: break; } not_reached(); } #define INI_ASSERT_STR(v) \ if (value.isArray() || value.isObject()) { \ return false; \ } \ auto str = dynamic_to_std_string(v); #define INI_ASSERT_ARR(v) \ if (!value.isArray() && !value.isObject()) { \ return false; \ } bool ini_on_update(const folly::dynamic& value, bool& p) { INI_ASSERT_STR(value); if ((str.size() == 0) || (str.size() == 1 && strcasecmp("0", str.data()) == 0) || (str.size() == 2 && strcasecmp("no", str.data()) == 0) || (str.size() == 3 && strcasecmp("off", str.data()) == 0) || (str.size() == 5 && strcasecmp("false", str.data()) == 0)) { p = false; } else { p = true; } return true; } bool ini_on_update(const folly::dynamic& value, double& p) { INI_ASSERT_STR(value); p = zend_strtod(str.data(), nullptr); return true; } bool ini_on_update(const folly::dynamic& value, char& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto maxValue = 0x7FL; if (n > maxValue || n < (- maxValue - 1)) { return false; } p = n; return true; } bool ini_on_update(const folly::dynamic& value, int16_t& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto maxValue = 0x7FFFL; if (n > maxValue || n < (- maxValue - 1)) { return false; } p = n; return true; } bool ini_on_update(const folly::dynamic& value, int32_t& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto maxValue = 0x7FFFFFFFL; if (n > maxValue || n < (- maxValue - 1)) { return false; } p = n; return true; } bool ini_on_update(const folly::dynamic& value, int64_t& p) { INI_ASSERT_STR(value); p = convert_bytes_to_long(str); return true; } bool ini_on_update(const folly::dynamic& value, unsigned char& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto mask = ~0xFFUL; if (((uint64_t)n & mask)) { return false; } p = n; return true; } bool ini_on_update(const folly::dynamic& value, uint16_t& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto mask = ~0xFFFFUL; if (((uint64_t)n & mask)) { return false; } p = n; return true; } bool ini_on_update(const folly::dynamic& value, uint32_t& p) { INI_ASSERT_STR(value); auto n = convert_bytes_to_long(str); auto mask = ~0x7FFFFFFFUL; if (((uint64_t)n & mask)) { return false; } p = n; return true; } bool ini_on_update(const folly::dynamic& value, uint64_t& p) { INI_ASSERT_STR(value); p = convert_bytes_to_long(str); return true; } bool ini_on_update(const folly::dynamic& value, std::string& p) { INI_ASSERT_STR(value); p = str; return true; } bool ini_on_update(const folly::dynamic& value, String& p) { INI_ASSERT_STR(value); p = str.data(); return true; } bool ini_on_update(const folly::dynamic& value, Array& p) { INI_ASSERT_ARR(value); p = dynamic_to_variant(value).toArray(); return true; } bool ini_on_update(const folly::dynamic& value, std::set<std::string>& p) { INI_ASSERT_ARR(value); for (auto& v : value.values()) { p.insert(v.data()); } return true; } bool ini_on_update(const folly::dynamic& value, std::set<std::string, stdltistr>& p) { INI_ASSERT_ARR(value); for (auto& v : value.values()) { p.insert(v.data()); } return true; } bool ini_on_update(const folly::dynamic& value, boost::container::flat_set<std::string>& p) { INI_ASSERT_ARR(value); for (auto& v : value.values()) { p.insert(v.data()); } return true; } bool ini_on_update(const folly::dynamic& value, std::vector<std::string>& p) { INI_ASSERT_ARR(value); for (auto& v : value.values()) { p.push_back(v.data()); } return true; } bool ini_on_update(const folly::dynamic& value, std::map<std::string, std::string>& p) { INI_ASSERT_ARR(value); for (auto& pair : value.items()) { p[pair.first.data()] = pair.second.data(); } return true; } bool ini_on_update(const folly::dynamic& value, hphp_string_imap<std::string>& p) { INI_ASSERT_ARR(value); for (auto& pair : value.items()) { p[pair.first.data()] = pair.second.data(); } return true; } folly::dynamic ini_get(bool& p) { return p ? "1" : ""; } folly::dynamic ini_get(double& p) { return p; } folly::dynamic ini_get(char& p) { return p; } folly::dynamic ini_get(int16_t& p) { return p; } folly::dynamic ini_get(int32_t& p) { return p; } folly::dynamic ini_get(int64_t& p) { return p; } folly::dynamic ini_get(unsigned char& p) { return p; } folly::dynamic ini_get(uint16_t& p) { return p; } folly::dynamic ini_get(uint32_t& p) { return p; } folly::dynamic ini_get(uint64_t& p) { return p; } folly::dynamic ini_get(std::string& p) { return p.data(); } folly::dynamic ini_get(String& p) { return p.data(); } folly::dynamic ini_get(std::map<std::string, std::string>& p) { folly::dynamic ret = folly::dynamic::object; for (auto& pair : p) { ret.insert(pair.first, pair.second); } return ret; } folly::dynamic ini_get(hphp_string_imap<std::string>& p) { folly::dynamic ret = folly::dynamic::object; for (auto& pair : p) { ret.insert(pair.first, pair.second); } return ret; } folly::dynamic ini_get(Array& p) { folly::dynamic ret = folly::dynamic::object; for (ArrayIter iter(p); iter; ++iter) { ret.insert(variant_to_dynamic(iter.first()), variant_to_dynamic(iter.second())); } return ret; } folly::dynamic ini_get(std::set<std::string>& p) { folly::dynamic ret = folly::dynamic::object; auto idx = 0; for (auto& s : p) { ret.insert(idx++, s); } return ret; } folly::dynamic ini_get(std::set<std::string, stdltistr>& p) { folly::dynamic ret = folly::dynamic::object; auto idx = 0; for (auto& s : p) { ret.insert(idx++, s); } return ret; } folly::dynamic ini_get(boost::container::flat_set<std::string>& p) { folly::dynamic ret = folly::dynamic::object; auto idx = 0; for (auto& s : p) { ret.insert(idx++, s); } return ret; } folly::dynamic ini_get(std::vector<std::string>& p) { folly::dynamic ret = folly::dynamic::object; auto idx = 0; for (auto& s : p) { ret.insert(idx++, s); } return ret; } const folly::dynamic* ini_iterate(const folly::dynamic &ini, const std::string &name) { // This should never happen, but handle it anyway. if (ini == nullptr) { return nullptr; } // If for some reason we are passed a string (i.e., a leaf value), // just return it back if (ini.isString()) { return &ini; } // If we just passed in a name that already has a value like: // hhvm.server.apc.ttl_limit // max_execution_time // then we just return the value now. // i.e., a value that didn't look like // hhvm.a.b[c][d], where name = hhvm.a.b.c.d // c[d] (where ini is already hhvm.a.b), where name = c.d auto* value = ini.get_ptr(name); if (value) { return value; } // Otherwise, we split on the dots (if any) to see if we can get a real value std::vector<std::string> dot_parts; folly::split('.', name, dot_parts); int dot_loc = 0; int dot_parts_size = dot_parts.size(); std::string part = dot_parts[0]; // If this is null, then all the loops below will be skipped and // we will return it as null. value = ini.get_ptr(part); // Loop through the dot parts, getting a pointer to each // We may need to concatenate dots to be able to get a real value // e.g., if someone passed in hhvm.a.b.c.d, which in ini was equal // to hhvm.a.b[c][d], then we would start with hhvm and get null, // then hhvm.a and get null, then hhvm.a.b and actually get an object // to point to. while (!value && dot_loc < dot_parts_size - 1) { dot_loc++; part = part + "." + dot_parts[dot_loc]; value = ini.get_ptr(part); } // Get to the last dot part and get its value, if it exists for (int i = dot_loc + 1; i < dot_parts_size; i++) { if (value) { part = dot_parts[i]; value = value->get_ptr(part); } else { // If we reach a bad point, just return null return nullptr; } } return value; } /////////////////////////////////////////////////////////////////////////////// // callbacks for creating arrays out of ini void IniSetting::ParserCallback::onSection(const std::string &name, void *arg) { // do nothing } void IniSetting::ParserCallback::onLabel(const std::string &name, void *arg) { // do nothing } void IniSetting::ParserCallback::onEntry( const std::string &key, const std::string &value, void *arg) { Variant *arr = (Variant*)arg; forceToArray(*arr).set(String(key), String(value)); } void IniSetting::ParserCallback::onPopEntry( const std::string &key, const std::string &value, const std::string &offset, void *arg) { Variant *arr = (Variant*)arg; forceToArray(*arr); auto& hash = arr->toArrRef().lvalAt(String(key)); forceToArray(hash); if (!offset.empty()) { makeArray(hash, offset, value); } else { hash.toArrRef().append(value); } } void IniSetting::ParserCallback::makeArray(Variant& hash, const std::string& offset, const std::string& value) { assert(!offset.empty()); Variant val(Variant::StrongBind{}, hash); auto start = offset.c_str(); auto p = start; bool last = false; do { String index(p); last = p + index.size() >= start + offset.size(); Variant newval; if (last) { newval = Variant(value); } else { if (val.toArrRef().exists(index)) { newval = val.toArrRef().rvalAt(index); } else { newval = Variant(Array::Create()); } } val.toArrRef().setRef(index, newval); if (!last) { val.assignRef(newval); p += index.size() + 1; } } while (!last); } void IniSetting::ParserCallback::onConstant(std::string &result, const std::string &name) { if (f_defined(name)) { result = f_constant(name).toString().toCppString(); } else { result = name; } } void IniSetting::ParserCallback::onVar(std::string &result, const std::string& name) { std::string curval; if (IniSetting::Get(name, curval)) { result = curval; return; } String value = g_context->getenv(name); if (!value.isNull()) { result = value.toCppString(); return; } result.clear(); } void IniSetting::ParserCallback::onOp( std::string &result, char type, const std::string& op1, const std::string& op2) { int i_op1 = strtoll(op1.c_str(), nullptr, 10); int i_op2 = strtoll(op2.c_str(), nullptr, 10); int i_result = 0; switch (type) { case '|': i_result = i_op1 | i_op2; break; case '&': i_result = i_op1 & i_op2; break; case '^': i_result = i_op1 ^ i_op2; break; case '~': i_result = ~i_op1; break; case '!': i_result = !i_op1; break; } result = std::to_string((int64_t)i_result); } void IniSetting::SectionParserCallback::onSection( const std::string &name, void *arg) { auto const data = (CallbackData*)arg; data->active_section.unset(); // break ref() from previous section data->active_section = Array::Create(); data->arr.toArrRef().setRef(String(name), data->active_section); } Variant* IniSetting::SectionParserCallback::activeArray(CallbackData* data) { if (!data->active_section.isNull()) { return &data->active_section; } else { return &data->arr; } } void IniSetting::SectionParserCallback::onLabel(const std::string &name, void *arg) { IniSetting::ParserCallback::onLabel(name, activeArray((CallbackData*)arg)); } void IniSetting::SectionParserCallback::onEntry( const std::string &key, const std::string &value, void *arg) { IniSetting::ParserCallback::onEntry(key, value, activeArray((CallbackData*)arg)); } void IniSetting::SectionParserCallback::onPopEntry( const std::string &key, const std::string &value, const std::string &offset, void *arg) { IniSetting::ParserCallback::onPopEntry(key, value, offset, activeArray((CallbackData*)arg)); } void IniSetting::SystemParserCallback::onSection(const std::string &name, void *arg) { // do nothing } void IniSetting::SystemParserCallback::onLabel(const std::string &name, void *arg) { // do nothing } void IniSetting::SystemParserCallback::onEntry( const std::string &key, const std::string &value, void *arg) { assert(!key.empty()); // onConstant will always be called before onEntry, so we can check // here if (IniSetting::s_config_is_a_constant) { IniSetting::config_names_that_use_constants.insert(key); IniSetting::s_config_is_a_constant = false; } auto& arr = *(IniSetting::Map*)arg; arr[key] = value; } void IniSetting::SystemParserCallback::onPopEntry(const std::string& key, const std::string& value, const std::string& offset, void* arg) { assert(!key.empty()); if (IniSetting::s_config_is_a_constant) { IniSetting::config_names_that_use_constants.insert(key); IniSetting::s_config_is_a_constant = false; } auto& arr = *(IniSetting::Map*)arg; auto* ptr = arr.get_ptr(key); if (!ptr || !ptr->isObject()) { arr[key] = IniSetting::Map::object; ptr = arr.get_ptr(key); } if (!offset.empty()) { makeArray(*ptr, offset, value); } else { // Find the highest index auto max = 0; for (auto &a : ptr->keys()) { try { if (a.asInt() >= max) { max = a.asInt() + 1; } } catch (std::range_error const& e) { /* not an int */ } } (*ptr)[std::to_string(max)] = value; } } void IniSetting::SystemParserCallback::makeArray(Map &hash, const std::string &offset, const std::string &value) { assert(!offset.empty()); Map* val = &hash; auto start = offset.c_str(); auto p = start; bool last = false; do { std::string index(p); last = p + index.size() >= start + offset.size(); Map newval = last ? Map(value) : val->getDefault(index, Map::object()); val = &(*val)[index]; *val = newval; if (!last) { p += index.size() + 1; } } while (!last); } void IniSetting::SystemParserCallback::onConstant(std::string &result, const std::string &name) { IniSetting::s_config_is_a_constant = true; if (MemoryManager::TlsWrapper::isNull()) { // We can't load constants before the memory manger is up, so lets just // pretend they are strings I guess result = name; return; } if (f_defined(name, false)) { result = f_constant(name).toString().toCppString(); } else { result = name; } } /////////////////////////////////////////////////////////////////////////////// static Mutex s_mutex; Variant IniSetting::FromString(const String& ini, const String& filename, bool process_sections, int scanner_mode) { Lock lock(s_mutex); // ini parser is not thread-safe // We are parsing something new, so reset this flag s_config_is_a_constant = false; auto ini_cpp = ini.toCppString(); auto filename_cpp = filename.toCppString(); if (process_sections) { CallbackData data; SectionParserCallback cb; data.arr = Array::Create(); if (zend_parse_ini_string(ini_cpp, filename_cpp, scanner_mode, cb, &data)) { return data.arr; } } else { ParserCallback cb; Variant ret = Array::Create(); if (zend_parse_ini_string(ini_cpp, filename_cpp, scanner_mode, cb, &ret)) { return ret; } } return false; } IniSetting::Map IniSetting::FromStringAsMap(const std::string& ini, const std::string& filename) { Lock lock(s_mutex); // ini parser is not thread-safe // We are parsing something new, so reset this flag s_config_is_a_constant = false; SystemParserCallback cb; Map ret = IniSetting::Map::object; zend_parse_ini_string(ini, filename, NormalScanner, cb, &ret); return ret; } class IniCallbackData { public: IniCallbackData() { extension = nullptr; mode = IniSetting::PHP_INI_NONE; iniData = nullptr; updateCallback = nullptr; getCallback = nullptr; } virtual ~IniCallbackData() { delete iniData; iniData = nullptr; } public: const Extension* extension; IniSetting::Mode mode; UserIniData *iniData; std::function<bool(const folly::dynamic& value)> updateCallback; std::function<folly::dynamic()> getCallback; }; typedef std::map<std::string, IniCallbackData> CallbackMap; // // These are for settings/callbacks only settable at startup. // // Empirically and surprisingly (20Jan2015): // * server mode: the contents of system map are destructed on SIGTERM // * CLI mode: the contents of system map are NOT destructed on SIGTERM // static CallbackMap s_system_ini_callbacks; // // These are for settings/callbacks that the script // can change during the request. // // Empirically and surprisingly (20Jan2015), when there are N threads: // * server mode: the contents of user map are destructed N-1 times // * CLI mode: the contents of user map are NOT destructed on SIGTERM // static IMPLEMENT_THREAD_LOCAL(CallbackMap, s_user_callbacks); typedef std::map<std::string, folly::dynamic> SettingMap; // Set by a .ini file at the start static SettingMap s_system_settings; // Changed during the course of the request static IMPLEMENT_THREAD_LOCAL(SettingMap, s_saved_defaults); class IniSettingExtension final : public Extension { public: IniSettingExtension() : Extension("hhvm.ini", NO_EXTENSION_VERSION_YET) {} void requestShutdown() override { // Put all the defaults back to the way they were before any ini_set() for (auto &item : *s_saved_defaults) { IniSetting::SetUser(item.first, item.second, IniSetting::FollyDynamic()); } s_saved_defaults->clear(); } } s_ini_extension; void IniSetting::Bind( const Extension* extension, const Mode mode, const std::string& name, std::function<bool(const folly::dynamic&)> updateCallback, std::function<folly::dynamic()> getCallback, std::function<class UserIniData *(void)> userDataCallback ) { assert(!name.empty()); /* * WATCH OUT: unlike php5, a Mode is not necessarily a bit mask. * PHP_INI_ALL is NOT encoded as the union: * PHP_INI_USER|PHP_INI_PERDIR|PHP_INI_SYSTEM * * Note that Mode value PHP_INI_SET_USER and PHP_INI_SET_EVERY are bit * sets; "SET" in this use means "bitset", and not "assignment". */ bool is_thread_local = ( (mode == PHP_INI_USER) || (mode == PHP_INI_PERDIR) || (mode == PHP_INI_ALL) || /* See note above */ (mode & PHP_INI_USER) || (mode & PHP_INI_PERDIR) || (mode & PHP_INI_ALL) ); // // When the debugger is loading its configuration, there will be some // cases where Extension::ModulesInitialised(), but the name appears // in neither s_user_callbacks nor s_system_ini_callbacks. The bottom // line is that we can't really use ModulesInitialised() to help steer // the choices here. // bool use_user = is_thread_local; if (!use_user) { // // If it is already in the user callbacks, continue to use it from // there. We don't expect it to be already there, but it has been // observed during development. // bool in_user_callbacks = (s_user_callbacks->find(name) != s_user_callbacks->end()); assert (!in_user_callbacks); // See note above use_user = in_user_callbacks; } // // For now, we require the extensions to use their own thread local // memory for user-changeable settings. This means you need to use // the default field to Bind and can't statically initialize them. // The main reasoning to do that is so that the extensions have the // values already parsed into their types. If you are setting an int, // it does the string parsing once and then when you read it, it is // already an int. If we did some shared thing, we would just hand you // back the strings and you'd have to parse them on every request or // build some convoluted caching mechanism which is slower than just // the int access. // // We could conceivably let you use static memory and have our own // thread local here that users can change and then reset it back to // the default, but we haven't built that yet. // IniCallbackData &data = use_user ? (*s_user_callbacks)[name] : s_system_ini_callbacks[name]; data.extension = extension; data.mode = mode; data.updateCallback = updateCallback; data.getCallback = getCallback; if (data.iniData == nullptr && userDataCallback != nullptr) { data.iniData = userDataCallback(); } } void IniSetting::Unbind(const std::string& name) { assert(!name.empty()); s_user_callbacks->erase(name); } static IniCallbackData* get_callback(const std::string& name) { CallbackMap::iterator iter = s_system_ini_callbacks.find(name.data()); if (iter == s_system_ini_callbacks.end()) { iter = s_user_callbacks->find(name.data()); if (iter == s_user_callbacks->end()) { return nullptr; } } return &iter->second; } bool IniSetting::Get(const std::string& name, folly::dynamic& value) { auto cb = get_callback(name); if (!cb) { return false; } value = cb->getCallback(); return true; } bool IniSetting::Get(const std::string& name, std::string &value) { folly::dynamic b = nullptr; auto ret = Get(name, b); value = dynamic_to_std_string(b); return ret && !value.empty(); } bool IniSetting::Get(const String& name, String& value) { Variant b; auto ret = Get(name, b); value = b.toString(); return ret; } bool IniSetting::Get(const String& name, Variant& value) { folly::dynamic b = nullptr; auto ret = Get(name.toCppString(), b); value = dynamic_to_variant(b); return ret; } std::string IniSetting::Get(const std::string& name) { std::string ret; Get(name, ret); return ret; } static bool ini_set(const std::string& name, const folly::dynamic& value, IniSetting::Mode mode) { auto cb = get_callback(name); if (!cb || !(cb->mode & mode)) { return false; } return cb->updateCallback(value); } bool IniSetting::FillInConstant(const std::string& name, const folly::dynamic& value, FollyDynamic) { if (config_names_that_use_constants.find(name) == config_names_that_use_constants.end()) { return false; } return IniSetting::Set(name, value, FollyDynamic()); } bool IniSetting::Set(const std::string& name, const folly::dynamic& value, FollyDynamic) { // Need to make sure to update the value if the pair exists already // A general insert(make_pair) won't actually update new values. bool found = false; for (auto& pair : s_system_settings) { if (pair.first == name) { pair.second = value; found = true; break; } } if (!found) { s_system_settings.insert(make_pair(name, value)); } return ini_set(name, value, PHP_INI_SET_EVERY); } bool IniSetting::Set(const String& name, const Variant& value) { return Set(name.toCppString(), variant_to_dynamic(value), FollyDynamic()); } bool IniSetting::SetUser(const std::string& name, const folly::dynamic& value, FollyDynamic) { auto it = s_saved_defaults->find(name); if (it == s_saved_defaults->end()) { folly::dynamic def = nullptr; auto success = Get(name, def); if (success) { s_saved_defaults->insert(make_pair(name, def)); } } return ini_set(name, value, PHP_INI_SET_USER); } bool IniSetting::SetUser(const String& name, const Variant& value) { return SetUser(name.toCppString(), variant_to_dynamic(value), FollyDynamic()); } bool IniSetting::ResetSystemDefault(const std::string& name) { auto it = s_system_settings.find(name); if (it == s_system_settings.end()) { return false; } return ini_set(name, it->second, PHP_INI_SET_EVERY); } bool IniSetting::GetMode(const std::string& name, Mode& mode) { auto cb = get_callback(name); if (!cb) { return false; } mode = cb->mode; return true; } Array IniSetting::GetAll(const String& ext_name, bool details) { Array r = Array::Create(); const Extension* ext = nullptr; if (!ext_name.empty()) { if (ext_name == s_core) { ext = IniSetting::CORE; } else { ext = ExtensionRegistry::get(ext_name); if (!ext) { raise_warning("Unable to find extension '%s'", ext_name.toCppString().c_str()); return r; } } } for (auto& iter: boost::join(s_system_ini_callbacks, *s_user_callbacks)) { if (ext && ext != iter.second.extension) { continue; } auto value = dynamic_to_variant(iter.second.getCallback()); // Cast all non-arrays to strings since that is what everything used ot be if (!value.isArray()) { value = value.toString(); } if (details) { Array item = Array::Create(); item.add(s_global_value, value); item.add(s_local_value, value); if (iter.second.mode == PHP_INI_ALL) { item.add( s_access, Variant(PHP_INI_USER | PHP_INI_SYSTEM | PHP_INI_PERDIR) ); } else if (iter.second.mode == PHP_INI_ONLY) { item.add(s_access, Variant(PHP_INI_SYSTEM)); } else { item.add(s_access, Variant(iter.second.mode)); } r.add(String(iter.first), item); } else { r.add(String(iter.first), value); } } return r; } void add_default_config_files_globbed( const char *default_config_file, std::function<void (const char *filename)> cb ) { glob_t globbuf; memset(&globbuf, 0, sizeof(glob_t)); int flags = 0; // Use default glob semantics int nret = glob(default_config_file, flags, nullptr, &globbuf); if (nret == GLOB_NOMATCH || globbuf.gl_pathc == 0 || globbuf.gl_pathv == 0 || nret != 0) { globfree(&globbuf); return; } for (int n = 0; n < (int)globbuf.gl_pathc; n++) { if (access(globbuf.gl_pathv[n], R_OK) != -1) { cb(globbuf.gl_pathv[n]); } } globfree(&globbuf); } /////////////////////////////////////////////////////////////////////////////// }