PageRenderTime 53ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/mordor/config.cpp

http://github.com/mozy/mordor
C++ | 479 lines | 422 code | 44 blank | 13 comment | 70 complexity | 7ed18607d3c46f43eaeaff9809a2d192 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. // Copyright (c) 2009 - Mozy, Inc.
  2. #include "mordor/config.h"
  3. #include <algorithm>
  4. #include <boost/regex.hpp>
  5. #include <boost/thread/thread.hpp>
  6. #include "json.h"
  7. #include "scheduler.h"
  8. #include "string.h"
  9. #include "timer.h"
  10. #include "util.h"
  11. #ifdef WINDOWS
  12. #include "iomanager.h"
  13. #else
  14. #ifndef OSX
  15. extern char **environ;
  16. #endif
  17. #endif
  18. #ifdef OSX
  19. #include <crt_externs.h>
  20. #endif
  21. namespace Mordor {
  22. static Logger::ptr g_log = Log::lookup("mordor:config");
  23. bool
  24. isValidConfigVarName(const std::string &name, bool allowDot)
  25. {
  26. static const boost::regex regname("[a-z][a-z0-9]*");
  27. static const boost::regex regnameDot("[a-z][a-z0-9]*(\\.[a-z0-9]+)*");
  28. if (allowDot)
  29. return boost::regex_match(name, regnameDot);
  30. else
  31. return boost::regex_match(name, regname);
  32. }
  33. bool Config::s_locked = false;
  34. void
  35. Config::loadFromCommandLine(int &argc, char *argv[])
  36. {
  37. char **end = argv + argc;
  38. char **arg = argv;
  39. // Skip argv[0] (presumably program name)
  40. ++arg;
  41. while (arg < end) {
  42. // Only look at arguments that begin with --
  43. if (strncmp(*arg, "--", 2u) != 0) {
  44. ++arg;
  45. continue;
  46. }
  47. // Don't process arguments after --
  48. if (strcmp(*arg, "--") == 0)
  49. break;
  50. char *equals = strchr(*arg, '=');
  51. char *val;
  52. // Support either --arg=value or --arg value
  53. if (equals) {
  54. *equals = '\0';
  55. val = equals + 1;
  56. } else {
  57. val = *(arg + 1);
  58. }
  59. ConfigVarBase::ptr var = lookup(*arg + 2);
  60. if (var) {
  61. // Don't use val == *end, we don't want to actually dereference end
  62. if (val == *(arg + 1) && arg + 1 == end)
  63. MORDOR_THROW_EXCEPTION(std::invalid_argument(*arg + 2));
  64. if (!var->fromString(val))
  65. MORDOR_THROW_EXCEPTION(std::invalid_argument(*arg + 2));
  66. // Adjust argv to remove this arg (and its param, if it was a
  67. // separate arg)
  68. int toSkip = 1;
  69. if (val != equals + 1)
  70. ++toSkip;
  71. memmove(arg, arg + toSkip, (end - arg - toSkip) * sizeof(char *));
  72. argc -= toSkip;
  73. end -= toSkip;
  74. } else {
  75. // --arg=value wasn't a ConfigVar, restore the equals
  76. if (equals)
  77. *equals = '=';
  78. ++arg;
  79. }
  80. }
  81. }
  82. void
  83. Config::loadFromEnvironment()
  84. {
  85. #ifdef WINDOWS
  86. wchar_t *enviro = GetEnvironmentStringsW();
  87. if (!enviro)
  88. return;
  89. boost::shared_ptr<wchar_t> environScope(enviro, &FreeEnvironmentStringsW);
  90. for (const wchar_t *env = enviro; *env; env += wcslen(env) + 1) {
  91. const wchar_t *equals = wcschr(env, '=');
  92. if (!equals)
  93. continue;
  94. if (equals == env)
  95. continue;
  96. std::string key(toUtf8(env, equals - env));
  97. std::string value(toUtf8(equals + 1));
  98. #else
  99. #ifdef OSX
  100. char **environ = *_NSGetEnviron();
  101. #endif
  102. if (!environ)
  103. return;
  104. for (const char *env = *environ; *env; env += strlen(env) + 1) {
  105. const char *equals = strchr(env, '=');
  106. if (!equals)
  107. continue;
  108. if (equals == env)
  109. continue;
  110. std::string key(env, equals - env);
  111. std::string value(equals + 1);
  112. #endif
  113. std::transform(key.begin(), key.end(), key.begin(), tolower);
  114. replace(key, '_', '.');
  115. if (!isValidConfigVarName(key))
  116. continue;
  117. ConfigVarBase::ptr var = lookup(key);
  118. if (var)
  119. var->fromString(value);
  120. }
  121. }
  122. namespace {
  123. class JSONVisitor : public boost::static_visitor<>
  124. {
  125. public:
  126. void operator()(const JSON::Object &object)
  127. {
  128. std::string prefix;
  129. if (!m_current.empty())
  130. prefix = m_current + '.';
  131. for (JSON::Object::const_iterator it(object.begin());
  132. it != object.end();
  133. ++it) {
  134. std::string key = it->first;
  135. std::transform(key.begin(), key.end(), key.begin(), tolower);
  136. if (!isValidConfigVarName(key, false))
  137. continue;
  138. m_toCheck.push_back(std::make_pair(prefix + key, &it->second));
  139. }
  140. }
  141. void operator()(const JSON::Array &array) const
  142. {
  143. // Ignore it
  144. }
  145. void operator()(const boost::blank &null) const
  146. {
  147. (*this)(std::string());
  148. }
  149. void operator()(bool b) const
  150. {
  151. setValue(b);
  152. }
  153. void operator()(long long l) const
  154. {
  155. setValue(l);
  156. }
  157. void operator()(double d) const
  158. {
  159. setValue(d);
  160. }
  161. void operator()(const std::string &str) const
  162. {
  163. setValue(str);
  164. }
  165. template <class T> void operator()(const T &t) const
  166. {
  167. (*this)(boost::lexical_cast<std::string>(t));
  168. }
  169. template <class T> void setValue(const T &v) const
  170. {
  171. if (!m_current.empty()) {
  172. ConfigVarBase::ptr var = Config::lookup(m_current);
  173. if (var) {
  174. var->fromString(boost::lexical_cast<std::string>(v));
  175. } else if (isValidConfigVarName(m_current)) {
  176. Config::lookup(m_current, v, "Come from config file!");
  177. }
  178. }
  179. }
  180. std::list<std::pair<std::string, const JSON::Value *> > m_toCheck;
  181. std::string m_current;
  182. };
  183. }
  184. void
  185. Config::loadFromJSON(const JSON::Value &json)
  186. {
  187. JSONVisitor visitor;
  188. visitor.m_toCheck.push_back(std::make_pair(std::string(), &json));
  189. while (!visitor.m_toCheck.empty()) {
  190. std::pair<std::string, const JSON::Value *> current =
  191. visitor.m_toCheck.front();
  192. visitor.m_toCheck.pop_front();
  193. visitor.m_current = current.first;
  194. boost::apply_visitor(visitor, *current.second);
  195. }
  196. }
  197. ConfigVarBase::ptr
  198. Config::lookup(const std::string &name)
  199. {
  200. ConfigVarSet::iterator it = vars().find(name);
  201. if (it != vars().end())
  202. return *it;
  203. return ConfigVarBase::ptr();
  204. }
  205. void
  206. Config::visit(boost::function<void (ConfigVarBase::ptr)> dg)
  207. {
  208. for (ConfigVarSet::const_iterator it = vars().begin();
  209. it != vars().end();
  210. ++it) {
  211. dg(*it);
  212. }
  213. }
  214. #ifdef WINDOWS
  215. static void loadFromRegistry(HKEY hKey)
  216. {
  217. std::string buffer;
  218. std::wstring valueName;
  219. DWORD type;
  220. DWORD index = 0;
  221. DWORD valueNameSize, size;
  222. LSTATUS status = RegQueryInfoKeyW(hKey, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
  223. &valueNameSize, &size, NULL, NULL);
  224. if (status)
  225. MORDOR_THROW_EXCEPTION_FROM_ERROR_API(status, "RegQueryInfoKeyW");
  226. valueName.resize(std::max<DWORD>(valueNameSize + 1, 1u));
  227. buffer.resize(std::max<DWORD>(size, 1u));
  228. while (true) {
  229. valueNameSize = (DWORD)valueName.size();
  230. size = (DWORD)buffer.size();
  231. status = RegEnumValueW(hKey, index++, &valueName[0], &valueNameSize,
  232. NULL, &type, (LPBYTE)&buffer[0], &size);
  233. if (status == ERROR_NO_MORE_ITEMS)
  234. break;
  235. if (status == ERROR_MORE_DATA)
  236. continue;
  237. if (status)
  238. MORDOR_THROW_EXCEPTION_FROM_ERROR_API(status, "RegEnumValueW");
  239. switch (type) {
  240. case REG_DWORD:
  241. if (size != 4)
  242. continue;
  243. break;
  244. case REG_QWORD:
  245. if (size != 8)
  246. continue;
  247. break;
  248. case REG_EXPAND_SZ:
  249. case REG_SZ:
  250. break;
  251. default:
  252. continue;
  253. }
  254. std::string varName = toUtf8(valueName.c_str(), valueNameSize);
  255. ConfigVarBase::ptr var = Config::lookup(varName);
  256. if (var) {
  257. std::string data;
  258. switch (type) {
  259. case REG_DWORD:
  260. data = boost::lexical_cast<std::string>(
  261. *(DWORD *)&buffer[0]);
  262. break;
  263. case REG_QWORD:
  264. data = boost::lexical_cast<std::string>(
  265. *(long long *)&buffer[0]);
  266. break;
  267. case REG_EXPAND_SZ:
  268. case REG_SZ:
  269. if (((wchar_t *)&buffer[0])[size / sizeof(wchar_t) - 1] ==
  270. L'\0')
  271. size -= sizeof(wchar_t);
  272. data = toUtf8((wchar_t *)&buffer[0],
  273. size / sizeof(wchar_t));
  274. break;
  275. }
  276. var->fromString(data);
  277. }
  278. }
  279. }
  280. Config::RegistryMonitor::RegistryMonitor(IOManager &ioManager,
  281. HKEY hKey, const std::wstring &subKey)
  282. : m_ioManager(ioManager),
  283. m_hKey(NULL),
  284. m_hEvent(NULL)
  285. {
  286. LSTATUS status = RegOpenKeyExW(hKey, subKey.c_str(), 0,
  287. KEY_QUERY_VALUE | KEY_NOTIFY, &m_hKey);
  288. if (status)
  289. MORDOR_THROW_EXCEPTION_FROM_ERROR_API(status, "RegOpenKeyExW");
  290. try {
  291. m_hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
  292. if (!m_hEvent)
  293. MORDOR_THROW_EXCEPTION_FROM_LAST_ERROR_API("CreateEventW");
  294. status = RegNotifyChangeKeyValue(m_hKey, FALSE,
  295. REG_NOTIFY_CHANGE_LAST_SET, m_hEvent, TRUE);
  296. if (status)
  297. MORDOR_THROW_EXCEPTION_FROM_ERROR_API(status,
  298. "RegNotifyChangeKeyValue");
  299. } catch (...) {
  300. if (m_hKey)
  301. RegCloseKey(m_hKey);
  302. if (m_hEvent)
  303. CloseHandle(m_hEvent);
  304. throw;
  305. }
  306. }
  307. Config::RegistryMonitor::~RegistryMonitor()
  308. {
  309. m_ioManager.unregisterEvent(m_hEvent);
  310. RegCloseKey(m_hKey);
  311. CloseHandle(m_hEvent);
  312. }
  313. void
  314. Config::RegistryMonitor::onRegistryChange(
  315. boost::weak_ptr<RegistryMonitor> self)
  316. {
  317. try
  318. {
  319. RegistryMonitor::ptr strongSelf = self.lock();
  320. if (strongSelf) {
  321. LSTATUS status = RegNotifyChangeKeyValue(strongSelf->m_hKey, FALSE,
  322. REG_NOTIFY_CHANGE_LAST_SET, strongSelf->m_hEvent, TRUE);
  323. if (status)
  324. MORDOR_THROW_EXCEPTION_FROM_ERROR_API(status,
  325. "RegNotifyChangeKeyValue");
  326. Mordor::loadFromRegistry(strongSelf->m_hKey);
  327. }
  328. }
  329. catch(...)
  330. {
  331. MORDOR_LOG_WARNING(g_log) << "failed to monitor registry: " <<
  332. boost::current_exception_diagnostic_information();
  333. }
  334. }
  335. void
  336. Config::loadFromRegistry(HKEY hKey, const std::string &subKey)
  337. {
  338. loadFromRegistry(hKey, toUtf16(subKey));
  339. }
  340. void
  341. Config::loadFromRegistry(HKEY hKey, const std::wstring &subKey)
  342. {
  343. HKEY localKey;
  344. LSTATUS status = RegOpenKeyExW(hKey, subKey.c_str(), 0, KEY_QUERY_VALUE,
  345. &localKey);
  346. if (status)
  347. MORDOR_THROW_EXCEPTION_FROM_ERROR_API(status, "RegOpenKeyExW");
  348. try {
  349. Mordor::loadFromRegistry(localKey);
  350. } catch (...) {
  351. RegCloseKey(localKey);
  352. throw;
  353. }
  354. RegCloseKey(localKey);
  355. }
  356. Config::RegistryMonitor::ptr
  357. Config::monitorRegistry(IOManager &ioManager, HKEY hKey,
  358. const std::string &subKey)
  359. {
  360. return monitorRegistry(ioManager, hKey, toUtf16(subKey));
  361. }
  362. Config::RegistryMonitor::ptr
  363. Config::monitorRegistry(IOManager &ioManager, HKEY hKey,
  364. const std::wstring &subKey)
  365. {
  366. RegistryMonitor::ptr result(new RegistryMonitor(ioManager, hKey, subKey));
  367. // Have to wait until after the object is constructed to get the weak_ptr
  368. // we need
  369. ioManager.registerEvent(result->m_hEvent,
  370. boost::bind(&RegistryMonitor::onRegistryChange,
  371. boost::weak_ptr<RegistryMonitor>(result)), true);
  372. Mordor::loadFromRegistry(result->m_hKey);
  373. return result;
  374. }
  375. #endif
  376. static bool verifyString(const std::string &string)
  377. {
  378. stringToMicroseconds(string);
  379. return true;
  380. }
  381. static void updateTimer(const std::string &string, Timer *timer)
  382. {
  383. timer->reset(stringToMicroseconds(string), false);
  384. }
  385. Timer::ptr associateTimerWithConfigVar(TimerManager &timerManager,
  386. ConfigVar<std::string>::ptr configVar, boost::function<void ()> dg)
  387. {
  388. unsigned long long initialValue = stringToMicroseconds(configVar->val());
  389. Timer::ptr result = timerManager.registerTimer(initialValue, dg, true);
  390. configVar->beforeChange.connect(&verifyString);
  391. configVar->onChange.connect(
  392. ConfigVar<std::string>::on_change_signal_type::slot_type(
  393. &updateTimer, _1, result.get()).track(result));
  394. return result;
  395. }
  396. static bool verifyThreadCount(int value)
  397. {
  398. return value != 0;
  399. }
  400. static void updateThreadCount(int value, Scheduler &scheduler)
  401. {
  402. if (value < 0)
  403. value = -value * boost::thread::hardware_concurrency();
  404. scheduler.threadCount(value);
  405. }
  406. void associateSchedulerWithConfigVar(Scheduler &scheduler,
  407. ConfigVar<int>::ptr configVar)
  408. {
  409. configVar->beforeChange.connect(&verifyThreadCount);
  410. configVar->onChange.connect(boost::bind(&updateThreadCount, _1,
  411. boost::ref(scheduler)));
  412. updateThreadCount(configVar->val(), scheduler);
  413. }
  414. HijackConfigVar::HijackConfigVar(const std::string &name, const std::string &value)
  415. : m_var(Config::lookup(name))
  416. {
  417. MORDOR_ASSERT(m_var);
  418. m_oldValue = m_var->toString();
  419. // failed to set value
  420. if (!m_var->fromString(value))
  421. m_var.reset();
  422. }
  423. HijackConfigVar::~HijackConfigVar()
  424. {
  425. reset();
  426. }
  427. void
  428. HijackConfigVar::reset()
  429. {
  430. if (m_var) {
  431. m_var->fromString(m_oldValue);
  432. m_var.reset();
  433. }
  434. }
  435. }