PageRenderTime 212ms CodeModel.GetById 61ms app.highlight 105ms RepoModel.GetById 25ms app.codeStats 1ms

/mordor/config.cpp

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