2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/config.h"
22 #include <boost/algorithm/string.hpp>
23 #include <boost/algorithm/string/erase.hpp>
24 #include <boost/algorithm/string/predicate.hpp>
25 #include <boost/filesystem.hpp>
27 #include "hphp/runtime/base/ini-setting.h"
28 #include "hphp/runtime/base/array-iterator.h"
29 #include "hphp/util/logger.h"
32 ///////////////////////////////////////////////////////////////////////////////
35 Config::IniName(const Hdf
& config
, bool /*prepend_hhvm*/ /* = true */) {
36 return Config::IniName(config
.getFullPath());
39 std::string
Config::IniName(const std::string
& config
,
40 bool prepend_hhvm
/* = true */) {
46 for (auto& c
: config
) {
47 // This is the first or last character
48 if (idx
== 0 || idx
== config
.length() - 1) {
50 } else if (!isalpha(c
)) {
51 // Any . or _ or numeral is just output with no special behavior
54 if (isupper(c
) && isupper(config
[idx
- 1 ]) && islower(config
[idx
+ 1])) {
55 // Handle something like "SSLPort", and c = "P", which will then put
56 // the underscore between the "L" and "P"
59 } else if (islower(c
) && isupper(config
[idx
+ 1])) {
60 // Handle something like "PathDebug", and c = "h", which will then put
61 // the underscore between the "h" and "D"
65 // Otherwise we just output as lower
72 // The HHIRLICM runtime option is all capitals, so separation
73 // cannot be determined. Special case it.
74 boost::replace_first(out
, "hhirlicm", "hhir_licm");
75 // The HHVM ini option becomes the standard PHP option.
76 boost::replace_first(out
,
77 "hhvm.server.upload.max_file_uploads",
79 // Make sure IPv6 or IPv4 are handled correctly
80 boost::replace_first(out
, "_i_pv", "_ipv");
81 boost::replace_first(out
, ".i_pv", ".ipv");
82 // urls are special too. Let's not have "ur_ls"
83 boost::replace_first(out
, "_ur_ls", "_urls");
84 boost::replace_first(out
, ".ur_ls", ".urls");
85 // No use of Eval in our ini strings
86 boost::replace_first(out
, ".eval.", ".");
87 boost::replace_first(out
, ".my_sql.", ".mysql.");
88 boost::replace_first(out
, ".enable_hip_hop_syntax", ".force_hh");
90 // Fix "XDebug" turning into "x_debug".
91 boost::replace_first(out
, "hhvm.debugger.x_debug_", "xdebug.");
96 void Config::ParseIniString(const std::string
&iniStr
, IniSettingMap
&ini
,
97 const bool constants_only
/* = false */ ) {
98 Config::SetParsedIni(ini
, iniStr
, "", constants_only
, true);
101 void Config::ParseHdfString(const std::string
&hdfStr
, Hdf
&hdf
) {
102 hdf
.fromString(hdfStr
.c_str());
105 void Config::ParseConfigFile(const std::string
&filename
, IniSettingMap
&ini
,
106 Hdf
&hdf
, const bool is_system
/* = true */) {
107 // We don't allow a filename of just ".ini"
108 if (boost::ends_with(filename
, ".ini") && filename
.length() > 4) {
109 Config::ParseIniFile(filename
, ini
, false, is_system
);
111 // For now, assume anything else is an hdf file
112 // TODO(#5151773): Have a non-invasive warning if HDF file does not end
114 Config::ParseHdfFile(filename
, hdf
);
118 void Config::ParseIniFile(const std::string
&filename
,
119 const bool is_system
/* = true */) {
120 IniSettingMap ini
= IniSettingMap();
121 Config::ParseIniFile(filename
, ini
, false, is_system
);
124 void Config::ParseIniFile(const std::string
&filename
, IniSettingMap
&ini
,
125 const bool constants_only
/* = false */,
126 const bool is_system
/* = true */ ) {
127 std::ifstream
ifs(filename
);
128 std::string
str((std::istreambuf_iterator
<char>(ifs
)),
129 std::istreambuf_iterator
<char>());
130 std::string with_includes
;
131 Config::ReplaceIncludesWithIni(filename
, str
, with_includes
);
132 Config::SetParsedIni(ini
, with_includes
, filename
, constants_only
,
136 void Config::ReplaceIncludesWithIni(const std::string
& original_ini_filename
,
137 const std::string
& iniStr
,
138 std::string
& with_includes
) {
139 std::istringstream
iss(iniStr
);
141 while (std::getline(iss
, line
)) {
144 // ##includefoo barbaz"myconfig.ini" how weird is that
145 // Anything that is not a syntactically correct #include "file" after
146 // this pre-processing, will be treated as an ini comment and processed
147 // as such in the ini parser
148 auto pos
= line
.find_first_not_of(" ");
149 if (pos
== std::string::npos
||
150 line
.compare(pos
, strlen("#include"), "#include") != 0) {
151 // treat as normal ini line, including comment that doesn't start with
153 with_includes
+= line
+ "\n";
156 pos
+= strlen("#include");
157 auto start
= line
.find_first_not_of(" ", pos
);
158 auto end
= line
.find_last_not_of(" ");
159 if ((start
== std::string::npos
|| line
[start
] != '"') ||
160 (end
== start
|| line
[end
] != '"')) {
161 with_includes
+= line
+ "\n"; // treat as normal comment
164 std::string file
= line
.substr(start
+ 1, end
- start
- 1);
165 const std::string logger_file
= file
;
166 boost::filesystem::path
p(file
);
167 if (!p
.is_absolute()) {
168 boost::filesystem::path
opath(original_ini_filename
);
169 p
= opath
.parent_path()/p
;
171 if (boost::filesystem::exists(p
)) {
172 std::ifstream
ifs(p
.string());
173 const std::string
contents((std::istreambuf_iterator
<char>(ifs
)),
174 std::istreambuf_iterator
<char>());
175 Config::ReplaceIncludesWithIni(p
.string(), contents
, with_includes
);
177 Logger::Warning("ini include file %s not found", logger_file
.c_str());
182 void Config::ParseHdfFile(const std::string
&filename
, Hdf
&hdf
) {
183 hdf
.append(filename
);
186 void Config::SetParsedIni(IniSettingMap
&ini
, const std::string confStr
,
187 const std::string
&filename
, bool constants_only
,
189 // if we are setting constants, we must be setting system settings
190 if (constants_only
) {
193 auto parsed_ini
= IniSetting::FromStringAsMap(confStr
, filename
);
194 for (ArrayIter
iter(parsed_ini
.toArray()); iter
; ++iter
) {
195 // most likely a string, but just make sure that we are dealing
196 // with something that can be converted to a string
197 assertx(iter
.first().isScalar());
198 ini
.set(iter
.first().toString(), iter
.second());
199 if (constants_only
) {
200 IniSetting::FillInConstant(iter
.first().toString().toCppString(),
202 } else if (is_system
) {
203 IniSetting::SetSystem(iter
.first().toString().toCppString(),
209 // This method must return a char* which is owned by the IniSettingMap
210 // to avoid issues with the lifetime of the char*
211 const char* Config::Get(const IniSettingMap
&ini
, const Hdf
& config
,
212 const std::string
& name
/* = "" */,
213 const char *defValue
/* = nullptr */,
214 const bool prepend_hhvm
/* = true */) {
215 auto ini_name
= IniName(name
, prepend_hhvm
);
216 Hdf hdf
= name
!= "" ? config
[name
] : config
;
217 auto value
= ini_iterate(ini
, ini_name
);
218 if (value
.isString()) {
219 // See generic Get##METHOD below for why we are doing this
220 // Note that value is a string, so value.toString() is not
222 const char* ini_ret
= value
.toString().data();
223 const char* hdf_ret
= hdf
.configGet(ini_ret
);
224 if (hdf_ret
!= ini_ret
) {
226 IniSetting::SetSystem(ini_name
, ini_ret
);
230 return hdf
.configGet(defValue
);
233 template<class T
> static T
variant_init(T v
) {
236 static int64_t variant_init(uint32_t v
) {
240 #define CONFIG_BODY(T, METHOD) \
241 T Config::Get##METHOD(const IniSetting::Map &ini, const Hdf& config, \
242 const std::string &name /* = "" */, \
243 const T defValue /* = 0ish */, \
244 const bool prepend_hhvm /* = true */) { \
245 auto ini_name = IniName(name, prepend_hhvm); \
246 /* If we don't pass a name, then we just use the raw config as-is. */ \
247 /* This could happen when we are at a known leaf of a config node. */ \
248 Hdf hdf = name != "" ? config[name] : config; \
249 auto value = ini_iterate(ini, ini_name); \
250 if (value.isString()) { \
251 T ini_ret, hdf_ret; \
252 ini_on_update(value.toString(), ini_ret); \
253 /* I don't care what the ini_ret was if it isn't equal to what */ \
254 /* is returned back from from an HDF get call, which it will be */ \
255 /* if the call just passes back ini_ret because either they are */ \
256 /* the same or the hdf option associated with this name does */ \
257 /* not exist.... REMEMBER HDF WINS OVER INI UNTIL WE WIPE HDF */ \
258 hdf_ret = hdf.configGet##METHOD(ini_ret); \
259 if (hdf_ret != ini_ret) { \
261 IniSetting::SetSystem(ini_name, variant_init(ini_ret)); \
265 /* If there is a value associated with this setting in the hdf config */ \
266 /* then return it; otherwise the defValue will be returned as it is */ \
267 /* assigned to the return value for this call when nothing exists */ \
268 return hdf.configGet##METHOD(defValue); \
270 void Config::Bind(T& loc, const IniSetting::Map &ini, const Hdf& config, \
271 const std::string& name /* = "" */, \
272 const T defValue /* = 0ish */, \
273 const bool prepend_hhvm /* = true */) { \
274 loc = Get##METHOD(ini, config, name, defValue, prepend_hhvm); \
275 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_SYSTEM, \
276 IniName(name, prepend_hhvm), &loc); \
279 CONFIG_BODY(bool, Bool
)
280 CONFIG_BODY(char, Byte
)
281 CONFIG_BODY(unsigned char, UByte
)
282 CONFIG_BODY(int16_t, Int16
)
283 CONFIG_BODY(uint16_t, UInt16
)
284 CONFIG_BODY(int32_t, Int32
)
285 CONFIG_BODY(uint32_t, UInt32
)
286 CONFIG_BODY(int64_t, Int64
)
287 CONFIG_BODY(uint64_t, UInt64
)
288 CONFIG_BODY(double, Double
)
289 CONFIG_BODY(std::string
, String
)
291 #define CONTAINER_CONFIG_BODY(T, METHOD) \
292 T Config::Get##METHOD(const IniSetting::Map& ini, const Hdf& config, \
293 const std::string& name /* = "" */, \
294 const T& defValue /* = T() */, \
295 const bool prepend_hhvm /* = true */) { \
296 auto ini_name = IniName(name, prepend_hhvm); \
297 Hdf hdf = name != "" ? config[name] : config; \
298 T ini_ret, hdf_ret; \
299 auto value = ini_iterate(ini, ini_name); \
300 if (value.isArray() || value.isObject()) { \
301 ini_on_update(value.toVariant(), ini_ret); \
302 /** Make sure that even if we have an ini value, that if we also **/ \
303 /** have an hdf value, that it maintains its edge as beating out **/ \
305 if (hdf.exists() && !hdf.isEmpty()) { \
306 hdf.configGet(hdf_ret); \
307 if (hdf_ret != ini_ret) { \
309 IniSetting::SetSystem(ini_name, ini_get(ini_ret)); \
314 if (hdf.exists() && !hdf.isEmpty()) { \
315 hdf.configGet(hdf_ret); \
320 void Config::Bind(T& loc, const IniSetting::Map& ini, const Hdf& config, \
321 const std::string& name /* = "" */, \
322 const T& defValue /* = T() */, \
323 const bool prepend_hhvm /* = true */) { \
324 loc = Get##METHOD(ini, config, name, defValue, prepend_hhvm); \
325 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_SYSTEM, \
326 IniName(name, prepend_hhvm), &loc); \
329 CONTAINER_CONFIG_BODY(ConfigVector
, Vector
)
330 CONTAINER_CONFIG_BODY(ConfigMap
, Map
)
331 CONTAINER_CONFIG_BODY(ConfigMapC
, MapC
)
332 CONTAINER_CONFIG_BODY(ConfigSet
, Set
)
333 CONTAINER_CONFIG_BODY(ConfigSetC
, SetC
)
334 CONTAINER_CONFIG_BODY(ConfigFlatSet
, FlatSet
)
335 CONTAINER_CONFIG_BODY(ConfigIMap
, IMap
)
337 static HackStrictOption
GetHackStrictOption(const IniSettingMap
& ini
,
339 const std::string
& name
/* = "" */,
342 auto val
= Config::GetString(ini
, config
, name
);
347 return HackStrictOption::WARN
;
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
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
&,
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
, "");
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());