Use std::filesystem consistently
[hiphop-php.git] / hphp / runtime / base / config.cpp
blobeaa4f4979500b3ad4ee6fe9f0cd6d0c087dcd845
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/config.h"
19 #include <filesystem>
20 #include <fstream>
21 #include <sstream>
23 #include <boost/algorithm/string.hpp>
24 #include <boost/algorithm/string/erase.hpp>
25 #include <boost/algorithm/string/predicate.hpp>
27 #include "hphp/runtime/base/ini-setting.h"
28 #include "hphp/runtime/base/array-iterator.h"
29 #include "hphp/runtime/base/preg.h"
30 #include "hphp/util/logger.h"
32 namespace HPHP {
33 ///////////////////////////////////////////////////////////////////////////////
35 std::string
36 Config::IniName(const Hdf& config, bool /*prepend_hhvm*/ /* = true */) {
37 return Config::IniName(config.getFullPath());
40 std::string Config::IniName(const std::string& config,
41 bool prepend_hhvm /* = true */) {
42 std::string out = "";
43 if (prepend_hhvm) {
44 out += "hhvm.";
46 size_t idx = 0;
47 for (auto& c : config) {
48 // This is the first or last character
49 if (idx == 0 || idx == config.length() - 1) {
50 out += tolower(c);
51 } else if (!isalpha(c)) {
52 // Any . or _ or numeral is just output with no special behavior
53 out += c;
54 } else {
55 if (isupper(c) && isupper(config[idx - 1 ]) && islower(config[idx + 1])) {
56 // Handle something like "SSLPort", and c = "P", which will then put
57 // the underscore between the "L" and "P"
58 out += "_";
59 out += tolower(c);
60 } else if (islower(c) && isupper(config[idx + 1])) {
61 // Handle something like "PathDebug", and c = "h", which will then put
62 // the underscore between the "h" and "D"
63 out += tolower(c);
64 out += "_";
65 } else {
66 // Otherwise we just output as lower
67 out += tolower(c);
70 idx++;
73 // The HHVM ini option becomes the standard PHP option.
74 boost::replace_first(out,
75 "hhvm.server.upload.max_file_uploads",
76 "max_file_uploads");
77 // Make sure IPv6 or IPv4 are handled correctly
78 boost::replace_first(out, "_i_pv", "_ipv");
79 boost::replace_first(out, ".i_pv", ".ipv");
80 // urls are special too. Let's not have "ur_ls"
81 boost::replace_first(out, "_ur_ls", "_urls");
82 boost::replace_first(out, ".ur_ls", ".urls");
83 // No use of Eval in our ini strings
84 boost::replace_first(out, ".eval.", ".");
85 boost::replace_first(out, ".my_sql.", ".mysql.");
87 return out;
90 void Config::ParseIniString(const std::string &iniStr, IniSettingMap &ini,
91 const bool constants_only /* = false */ ) {
92 Config::SetParsedIni(ini, iniStr, "", constants_only, true);
95 void Config::ParseHdfString(const std::string &hdfStr, Hdf &hdf) {
96 hdf.fromString(hdfStr);
99 void Config::ParseConfigFile(const std::string &filename, IniSettingMap &ini,
100 Hdf &hdf, const bool is_system /* = true */) {
101 // We don't allow a filename of just ".ini"
102 if (boost::ends_with(filename, ".ini") && filename.length() > 4) {
103 Config::ParseIniFile(filename, ini, false, is_system);
104 } else {
105 // For now, assume anything else is an hdf file
106 // TODO(#5151773): Have a non-invasive warning if HDF file does not end
107 // .hdf
108 Config::ParseHdfFile(filename, hdf);
112 void Config::ParseIniFile(const std::string &filename,
113 const bool is_system /* = true */) {
114 IniSettingMap ini = IniSettingMap();
115 Config::ParseIniFile(filename, ini, false, is_system);
118 void Config::ParseIniFile(const std::string &filename, IniSettingMap &ini,
119 const bool constants_only /* = false */,
120 const bool is_system /* = true */ ) {
121 std::ifstream ifs(filename);
122 std::string str((std::istreambuf_iterator<char>(ifs)),
123 std::istreambuf_iterator<char>());
124 std::string with_includes;
125 Config::ReplaceIncludesWithIni(filename, str, with_includes);
126 Config::SetParsedIni(ini, with_includes, filename, constants_only,
127 is_system);
130 void Config::ReplaceIncludesWithIni(const std::string& original_ini_filename,
131 const std::string& iniStr,
132 std::string& with_includes) {
133 std::istringstream iss(iniStr);
134 std::string line;
135 while (std::getline(iss, line)) {
136 // Handle cases like
137 // #include ""
138 // ##includefoo barbaz"myconfig.ini" how weird is that
139 // Anything that is not a syntactically correct #include "file" after
140 // this pre-processing, will be treated as an ini comment and processed
141 // as such in the ini parser
142 auto pos = line.find_first_not_of(" ");
143 if (pos == std::string::npos ||
144 line.compare(pos, strlen("#include"), "#include") != 0) {
145 // treat as normal ini line, including comment that doesn't start with
146 // #include
147 with_includes += line + "\n";
148 continue;
150 pos += strlen("#include");
151 auto start = line.find_first_not_of(" ", pos);
152 auto end = line.find_last_not_of(" ");
153 if ((start == std::string::npos || line[start] != '"') ||
154 (end == start || line[end] != '"')) {
155 with_includes += line + "\n"; // treat as normal comment
156 continue;
158 std::string file = line.substr(start + 1, end - start - 1);
159 const std::string logger_file = file;
160 std::filesystem::path p(file);
161 if (!p.is_absolute()) {
162 std::filesystem::path opath(original_ini_filename);
163 p = opath.parent_path()/p;
165 if (std::filesystem::exists(p)) {
166 std::ifstream ifs(p.string());
167 const std::string contents((std::istreambuf_iterator<char>(ifs)),
168 std::istreambuf_iterator<char>());
169 Config::ReplaceIncludesWithIni(p.string(), contents, with_includes);
170 } else {
171 Logger::Warning("ini include file %s not found", logger_file.c_str());
176 void Config::ParseHdfFile(const std::string &filename, Hdf &hdf) {
177 hdf.append(filename);
180 void Config::SetParsedIni(IniSettingMap &ini, const std::string confStr,
181 const std::string &filename, bool constants_only,
182 bool is_system) {
183 // if we are setting constants, we must be setting system settings
184 if (constants_only) {
185 assertx(is_system);
187 auto parsed_ini = IniSetting::FromStringAsMap(confStr, filename);
188 for (ArrayIter iter(parsed_ini.toArray()); iter; ++iter) {
189 // most likely a string, but just make sure that we are dealing
190 // with something that can be converted to a string
191 assertx(iter.first().isScalar());
192 ini.set(iter.first().toString(), iter.second());
193 if (constants_only) {
194 IniSetting::FillInConstant(iter.first().toString().toCppString(),
195 iter.second());
196 } else if (is_system) {
197 IniSetting::SetSystem(iter.first().toString().toCppString(),
198 iter.second());
203 // This method must return a char* which is owned by the IniSettingMap
204 // to avoid issues with the lifetime of the char*
205 const char* Config::Get(const IniSettingMap &ini, const Hdf& config,
206 const std::string& name /* = "" */,
207 const char *defValue /* = nullptr */,
208 const bool prepend_hhvm /* = true */) {
209 auto ini_name = IniName(name, prepend_hhvm);
210 Hdf hdf = name != "" ? config[name] : config;
211 auto value = ini_iterate(ini, ini_name);
212 if (value.isString()) {
213 // See generic Get##METHOD below for why we are doing this
214 // Note that value is a string, so value.toString() is not
215 // a temporary.
216 const char* ini_ret = value.toString().data();
217 const char* hdf_ret = hdf.configGet(ini_ret);
218 if (hdf_ret != ini_ret) {
219 ini_ret = hdf_ret;
220 IniSetting::SetSystem(ini_name, ini_ret);
222 return ini_ret;
224 return hdf.configGet(defValue);
227 template<class T> static T variant_init(T v) {
228 return v;
230 static int64_t variant_init(uint32_t v) {
231 return v;
234 #define CONFIG_BODY(T, METHOD) \
235 T Config::Get##METHOD(const IniSetting::Map &ini, const Hdf& config, \
236 const std::string &name /* = "" */, \
237 const T defValue /* = 0ish */, \
238 const bool prepend_hhvm /* = true */) { \
239 auto ini_name = IniName(name, prepend_hhvm); \
240 /* If we don't pass a name, then we just use the raw config as-is. */ \
241 /* This could happen when we are at a known leaf of a config node. */ \
242 Hdf hdf = name != "" ? config[name] : config; \
243 auto value = ini_iterate(ini, ini_name); \
244 if (value.isString()) { \
245 T ini_ret, hdf_ret; \
246 ini_on_update(value.toString(), ini_ret); \
247 /* I don't care what the ini_ret was if it isn't equal to what */ \
248 /* is returned back from from an HDF get call, which it will be */ \
249 /* if the call just passes back ini_ret because either they are */ \
250 /* the same or the hdf option associated with this name does */ \
251 /* not exist.... REMEMBER HDF WINS OVER INI UNTIL WE WIPE HDF */ \
252 hdf_ret = hdf.configGet##METHOD(ini_ret); \
253 if (hdf_ret != ini_ret) { \
254 ini_ret = hdf_ret; \
255 IniSetting::SetSystem(ini_name, variant_init(ini_ret)); \
257 return ini_ret; \
259 /* If there is a value associated with this setting in the hdf config */ \
260 /* then return it; otherwise the defValue will be returned as it is */ \
261 /* assigned to the return value for this call when nothing exists */ \
262 return hdf.configGet##METHOD(defValue); \
264 void Config::Bind(T& loc, const IniSetting::Map &ini, const Hdf& config, \
265 const std::string& name /* = "" */, \
266 const T defValue /* = 0ish */, \
267 const bool prepend_hhvm /* = true */) { \
268 loc = Get##METHOD(ini, config, name, defValue, prepend_hhvm); \
269 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_SYSTEM, \
270 IniName(name, prepend_hhvm), &loc); \
273 CONFIG_BODY(bool, Bool)
274 CONFIG_BODY(char, Byte)
275 CONFIG_BODY(unsigned char, UByte)
276 CONFIG_BODY(int16_t, Int16)
277 CONFIG_BODY(uint16_t, UInt16)
278 CONFIG_BODY(int32_t, Int32)
279 CONFIG_BODY(uint32_t, UInt32)
280 CONFIG_BODY(int64_t, Int64)
281 CONFIG_BODY(uint64_t, UInt64)
282 CONFIG_BODY(double, Double)
283 CONFIG_BODY(std::string, String)
285 #define CONTAINER_CONFIG_BODY(T, METHOD) \
286 T Config::Get##METHOD(const IniSetting::Map& ini, const Hdf& config, \
287 const std::string& name /* = "" */, \
288 const T& defValue /* = T() */, \
289 const bool prepend_hhvm /* = true */) { \
290 auto ini_name = IniName(name, prepend_hhvm); \
291 Hdf hdf = name != "" ? config[name] : config; \
292 T ini_ret, hdf_ret; \
293 auto value = ini_iterate(ini, ini_name); \
294 if (value.isArray() || value.isObject()) { \
295 ini_on_update(value.toVariant(), ini_ret); \
296 /** Make sure that even if we have an ini value, that if we also **/ \
297 /** have an hdf value, that it maintains its edge as beating out **/ \
298 /** ini **/ \
299 if (hdf.exists() && !hdf.isEmpty()) { \
300 hdf.configGet(hdf_ret); \
301 if (hdf_ret != ini_ret) { \
302 ini_ret = hdf_ret; \
303 IniSetting::SetSystem(ini_name, ini_get(ini_ret)); \
306 return ini_ret; \
308 if (hdf.exists() && !hdf.isEmpty()) { \
309 hdf.configGet(hdf_ret); \
310 return hdf_ret; \
312 return defValue; \
314 void Config::Bind(T& loc, const IniSetting::Map& ini, const Hdf& config, \
315 const std::string& name /* = "" */, \
316 const T& defValue /* = T() */, \
317 const bool prepend_hhvm /* = true */) { \
318 loc = Get##METHOD(ini, config, name, defValue, prepend_hhvm); \
319 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_SYSTEM, \
320 IniName(name, prepend_hhvm), &loc); \
323 CONTAINER_CONFIG_BODY(std::vector<uint32_t>, UInt32Vector)
324 CONTAINER_CONFIG_BODY(std::vector<std::string>, StrVector)
325 namespace { using simap = std::unordered_map<std::string, int>; }
326 CONTAINER_CONFIG_BODY(simap, IntMap)
327 CONTAINER_CONFIG_BODY(ConfigMap, Map)
328 CONTAINER_CONFIG_BODY(ConfigFastMap, FastMap)
329 CONTAINER_CONFIG_BODY(ConfigMapC, MapC)
330 CONTAINER_CONFIG_BODY(ConfigSet, Set)
331 CONTAINER_CONFIG_BODY(ConfigSetC, SetC)
332 CONTAINER_CONFIG_BODY(ConfigFlatSet, FlatSet)
333 CONTAINER_CONFIG_BODY(ConfigIMap, IMap)
334 CONTAINER_CONFIG_BODY(ConfigIFastMap, IFastMap)
335 CONTAINER_CONFIG_BODY(ConfigFastSet, FastSet);
337 static HackStrictOption GetHackStrictOption(const IniSettingMap& ini,
338 const Hdf& config,
339 const std::string& name /* = "" */,
340 HackStrictOption def
342 auto val = Config::GetString(ini, config, name);
343 if (val.empty()) {
344 return def;
346 if (val == "warn") {
347 return HackStrictOption::WARN;
349 bool ret;
350 ini_on_update(val, ret);
351 return ret ? HackStrictOption::ON : HackStrictOption::OFF;
354 void Config::Bind(HackStrictOption& loc, const IniSettingMap& ini,
355 const Hdf& config, const std::string& name /* = "" */,
356 HackStrictOption def) {
357 // Currently this doens't bind to ini_get since it is hard to thread through
358 // an enum
359 loc = GetHackStrictOption(ini, config, name, def);
362 // No `ini` binding yet. Hdf still takes precedence but will be removed
363 // once we have made all options ini-aware. All new settings should
364 // use the ini path of this method (i.e., pass a bogus Hdf or keep it null)
365 void Config::Iterate(std::function<void (const IniSettingMap&,
366 const Hdf&,
367 const std::string&)> cb,
368 const IniSettingMap &ini, const Hdf& config,
369 const std::string &name,
370 const bool prepend_hhvm /* = true */) {
371 Hdf hdf = name.empty() ? config : config[name];
372 if (hdf.exists() && !hdf.isEmpty()) {
373 for (Hdf c = hdf.firstChild(); c.exists(); c = c.next()) {
374 cb(IniSetting::Map::object, c, "");
376 } else {
377 Hdf empty;
378 auto ini_value = name.empty() ? ini :
379 ini_iterate(ini, IniName(name, prepend_hhvm));
380 if (ini_value.isArray()) {
381 for (ArrayIter iter(ini_value.toArray()); iter; ++iter) {
382 cb(iter.second(), empty, iter.first().toString().toCppString());
388 bool Config::matchHdfPattern(const std::string &value,
389 const IniSettingMap& ini, Hdf hdfPattern,
390 const std::string& name,
391 const std::string& suffix) {
392 std::string pattern = Config::GetString(ini, hdfPattern, name, "", false);
393 if (!pattern.empty()) {
394 if (!suffix.empty()) pattern += suffix;
395 Variant ret = preg_match(String(pattern.c_str(), pattern.size(),
396 CopyString),
397 String(value.c_str(), value.size(),
398 CopyString));
399 if (ret.toInt64() <= 0) {
400 return false;
403 return true;