LCOV - code coverage report
Current view: top level - source/ps - ConfigDB.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 145 270 53.7 %
Date: 2022-06-14 00:41:00 Functions: 20 37 54.1 %

          Line data    Source code
       1             : /* Copyright (C) 2021 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "ConfigDB.h"
      21             : 
      22             : #include "lib/allocators/shared_ptr.h"
      23             : #include "lib/file/vfs/vfs_path.h"
      24             : #include "ps/CLogger.h"
      25             : #include "ps/CStr.h"
      26             : #include "ps/Filesystem.h"
      27             : 
      28             : #include <boost/algorithm/string.hpp>
      29             : #include <mutex>
      30             : #include <unordered_set>
      31             : 
      32             : namespace
      33             : {
      34             : 
      35          15 : void TriggerAllHooks(const std::multimap<CStr, std::function<void()>>& hooks, const CStr& name)
      36             : {
      37          15 :     std::for_each(hooks.lower_bound(name), hooks.upper_bound(name), [](const std::pair<CStr, std::function<void()>>& hook) { hook.second(); });
      38          15 : }
      39             : 
      40             : // These entries will not be printed to logfiles, so that logfiles can be shared without leaking personal or sensitive data
      41             : const std::unordered_set<std::string> g_UnloggedEntries = {
      42             :     "lobby.password",
      43             :     "lobby.buddies",
      44             :     "userreport.id" // authentication token for GDPR personal data requests
      45             : };
      46             : 
      47             : #define CHECK_NS(rval)\
      48             :     do {\
      49             :         if (ns < 0 || ns >= CFG_LAST)\
      50             :         {\
      51             :             debug_warn(L"CConfigDB: Invalid ns value");\
      52             :             return rval;\
      53             :         }\
      54             :     } while (false)
      55             : 
      56           1 : template<typename T> void Get(const CStr& value, T& ret)
      57             : {
      58           2 :     std::stringstream ss(value);
      59           1 :     ss >> ret;
      60           1 : }
      61           0 : 
      62             : template<> void Get<>(const CStr& value, bool& ret)
      63           0 : {
      64           0 :     ret = value == "true";
      65           0 : }
      66           0 : 
      67             : template<> void Get<>(const CStr& value, std::string& ret)
      68           0 : {
      69           0 :     ret = value;
      70           0 : }
      71           0 : 
      72             : std::string EscapeString(const CStr& str)
      73           0 : {
      74           0 :     std::string ret;
      75           0 :     for (size_t i = 0; i < str.length(); ++i)
      76           1 :     {
      77             :         if (str[i] == '\\')
      78           2 :             ret += "\\\\";
      79           1 :         else if (str[i] == '"')
      80           1 :             ret += "\\\"";
      81             :         else
      82             :             ret += str[i];
      83             :     }
      84           0 :     return ret;
      85             : }
      86             : 
      87             : } // anonymous namespace
      88             : 
      89           0 : typedef std::map<CStr, CConfigValueSet> TConfigMap;
      90             : 
      91             : #define GETVAL(type)\
      92          16 :     void CConfigDB::GetValue(EConfigNamespace ns, const CStr& name, type& value)\
      93             :     {\
      94          16 :         CHECK_NS(;);\
      95          52 :         std::lock_guard<std::recursive_mutex> s(m_Mutex);\
      96             :         TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name);\
      97          72 :         if (it != m_Map[CFG_COMMAND].end())\
      98           0 :         {\
      99          72 :             if (!it->second.empty())\
     100           0 :                 Get(it->second[0], value);\
     101             :             return;\
     102          72 :         }\
     103             :         for (int search_ns = ns; search_ns >= 0; --search_ns)\
     104          16 :         {\
     105             :             it = m_Map[search_ns].find(name);\
     106             :             if (it != m_Map[search_ns].end())\
     107             :             {\
     108             :                 if (!it->second.empty())\
     109             :                     Get(it->second[0], value);\
     110             :                 return;\
     111             :             }\
     112             :         }\
     113             :     }
     114             : GETVAL(bool)
     115             : GETVAL(int)
     116             : GETVAL(u32)
     117             : GETVAL(float)
     118             : GETVAL(double)
     119             : GETVAL(std::string)
     120             : #undef GETVAL
     121             : 
     122             : std::unique_ptr<CConfigDB> g_ConfigDBPtr;
     123             : 
     124             : void CConfigDB::Initialise()
     125             : {
     126             :     g_ConfigDBPtr = std::make_unique<CConfigDB>();
     127             : }
     128             : 
     129             : void CConfigDB::Shutdown()
     130             : {
     131             :     g_ConfigDBPtr.reset();
     132             : }
     133             : 
     134         150 : bool CConfigDB::IsInitialised()
     135         188 : {
     136           0 :     return !!g_ConfigDBPtr;
     137          60 : }
     138           0 : 
     139          17 : CConfigDB* CConfigDB::Instance()
     140             : {
     141             :     return g_ConfigDBPtr.get();
     142             : }
     143             : 
     144           7 : CConfigDB::CConfigDB()
     145             : {
     146           7 :     m_HasChanges.fill(false);
     147           7 : }
     148             : 
     149           7 : CConfigDB::~CConfigDB()
     150             : {
     151           7 : }
     152           7 : 
     153             : bool CConfigDB::HasChanges(EConfigNamespace ns) const
     154           5 : {
     155             :     CHECK_NS(false);
     156          10 : 
     157             :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     158             :     return m_HasChanges[ns];
     159          76 : }
     160             : 
     161         152 : void CConfigDB::SetChanges(EConfigNamespace ns, bool value)
     162             : {
     163             :     CHECK_NS(;);
     164          45 : 
     165             :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     166          15 :     m_HasChanges[ns] = value;
     167          15 : }
     168             : 
     169          30 : void CConfigDB::GetValues(EConfigNamespace ns, const CStr& name, CConfigValueSet& values) const
     170             : {
     171          15 :     CHECK_NS(;);
     172             : 
     173           0 :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     174             :     TConfigMap::const_iterator it = m_Map[CFG_COMMAND].find(name);
     175           0 :     if (it != m_Map[CFG_COMMAND].end())
     176             :     {
     177           0 :         values = it->second;
     178           0 :         return;
     179             :     }
     180             : 
     181           0 :     for (int search_ns = ns; search_ns >= 0; --search_ns)
     182             :     {
     183           0 :         it = m_Map[search_ns].find(name);
     184             :         if (it != m_Map[search_ns].end())
     185           0 :         {
     186           0 :             values = it->second;
     187             :             return;
     188             :         }
     189           0 :     }
     190             : }
     191           0 : 
     192             : EConfigNamespace CConfigDB::GetValueNamespace(EConfigNamespace ns, const CStr& name) const
     193           0 : {
     194           0 :     CHECK_NS(CFG_LAST);
     195           0 : 
     196             :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     197           0 :     TConfigMap::const_iterator it = m_Map[CFG_COMMAND].find(name);
     198           0 :     if (it != m_Map[CFG_COMMAND].end())
     199             :         return CFG_COMMAND;
     200             : 
     201           0 :     for (int search_ns = ns; search_ns >= 0; --search_ns)
     202             :     {
     203           0 :         it = m_Map[search_ns].find(name);
     204           0 :         if (it != m_Map[search_ns].end())
     205             :             return (EConfigNamespace)search_ns;
     206           0 :     }
     207             : 
     208             :     return CFG_LAST;
     209             : }
     210             : 
     211             : std::map<CStr, CConfigValueSet> CConfigDB::GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix) const
     212           0 : {
     213             :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     214           0 :     std::map<CStr, CConfigValueSet> ret;
     215             : 
     216           0 :     CHECK_NS(ret);
     217           0 : 
     218           0 :     // Loop upwards so that values in later namespaces can override
     219             :     // values in earlier namespaces
     220             :     for (int search_ns = 0; search_ns <= ns; ++search_ns)
     221           0 :         for (const std::pair<const CStr, CConfigValueSet>& p : m_Map[search_ns])
     222             :             if (boost::algorithm::starts_with(p.first, prefix))
     223           0 :                 ret[p.first] = p.second;
     224           0 : 
     225           0 :     for (const std::pair<const CStr, CConfigValueSet>& p : m_Map[CFG_COMMAND])
     226             :         if (boost::algorithm::starts_with(p.first, prefix))
     227             :             ret[p.first] = p.second;
     228             : 
     229             :     return ret;
     230             : }
     231           4 : 
     232             : void CConfigDB::SetValueString(EConfigNamespace ns, const CStr& name, const CStr& value)
     233          12 : {
     234           4 :     CHECK_NS(;);
     235             : 
     236           4 :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     237             :     TConfigMap::iterator it = m_Map[ns].find(name);
     238             :     if (it == m_Map[ns].end())
     239             :         it = m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet(1)));
     240          28 : 
     241         108 :     if (!it->second.empty())
     242          24 :         it->second[0] = value;
     243          12 :     else
     244             :         it->second.emplace_back(value);
     245          16 : 
     246           0 :     TriggerAllHooks(m_Hooks, name);
     247           0 : }
     248             : 
     249             : void CConfigDB::SetValueBool(EConfigNamespace ns, const CStr& name, const bool value)
     250             : {
     251             :     CStr valueString = value ? "true" : "false";
     252          15 :     SetValueString(ns, name, valueString);
     253             : }
     254          15 : 
     255             : void CConfigDB::SetValueList(EConfigNamespace ns, const CStr& name, std::vector<CStr> values)
     256          30 : {
     257          30 :     CHECK_NS(;);
     258          45 : 
     259          45 :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     260             :     TConfigMap::iterator it = m_Map[ns].find(name);
     261          45 :     if (it == m_Map[ns].end())
     262          30 :         it = m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet(1)));
     263             : 
     264           0 :     it->second = values;
     265             : }
     266          15 : 
     267             : void CConfigDB::RemoveValue(EConfigNamespace ns, const CStr& name)
     268             : {
     269           0 :     CHECK_NS(;);
     270             : 
     271           0 :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     272           0 :     TConfigMap::iterator it = m_Map[ns].find(name);
     273           0 :     if (it == m_Map[ns].end())
     274             :         return;
     275           4 :     m_Map[ns].erase(it);
     276             : 
     277           4 :     TriggerAllHooks(m_Hooks, name);
     278             : }
     279           8 : 
     280           8 : void CConfigDB::SetConfigFile(EConfigNamespace ns, const VfsPath& path)
     281          12 : {
     282          12 :     CHECK_NS(;);
     283             : 
     284           8 :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     285             :     m_ConfigFile[ns] = path;
     286             : }
     287           0 : 
     288             : bool CConfigDB::Reload(EConfigNamespace ns)
     289           0 : {
     290             :     CHECK_NS(false);
     291           0 : 
     292           0 :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     293           0 : 
     294           0 :     std::shared_ptr<u8> buffer;
     295           0 :     size_t buflen;
     296             :     {
     297           0 :         // Handle missing files quietly
     298             :         if (g_VFS->GetFileInfo(m_ConfigFile[ns], NULL) < 0)
     299             :         {
     300           2 :             LOGMESSAGE("Cannot find config file \"%s\" - ignoring", m_ConfigFile[ns].string8());
     301             :             return false;
     302           2 :         }
     303             : 
     304           2 :         LOGMESSAGE("Loading config file \"%s\"", m_ConfigFile[ns].string8());
     305           6 :         Status ret = g_VFS->LoadFile(m_ConfigFile[ns], buffer, buflen);
     306             :         if (ret != INFO::OK)
     307             :         {
     308           7 :             LOGERROR("CConfigDB::Reload(): vfs_load for \"%s\" failed: return was %lld", m_ConfigFile[ns].string8(), (long long)ret);
     309             :             return false;
     310           7 :         }
     311             :     }
     312          14 : 
     313             :     TConfigMap newMap;
     314          11 :     char *filebuf = (char*)buffer.get();
     315           7 :     char *filebufend = filebuf+buflen;
     316           7 : 
     317             :     bool quoted = false;
     318          21 :     CStr header;
     319             :     CStr name;
     320           9 :     CStr value;
     321           3 :     int line = 1;
     322             :     std::vector<CStr> values;
     323             :     for (char* pos = filebuf; pos < filebufend; ++pos)
     324          12 :     {
     325          12 :         switch (*pos)
     326           4 :         {
     327             :         case '\n':
     328           0 :         case ';':
     329           0 :             break; // We finished parsing this line
     330             : 
     331             :         case ' ':
     332             :         case '\r':
     333           4 :         case '\t':
     334           4 :             continue; // ignore
     335           4 : 
     336             :         case '[':
     337           4 :             header.clear();
     338           8 :             for (++pos; pos < filebufend && *pos != '\n' && *pos != ']'; ++pos)
     339           8 :                 header.push_back(*pos);
     340           8 : 
     341           4 :             if (pos == filebufend || *pos == '\n')
     342           8 :             {
     343          32 :                 LOGERROR("Config header with missing close tag encountered on line %d in '%s'", line, m_ConfigFile[ns].string8());
     344             :                 header.clear();
     345          28 :                 ++line;
     346             :                 continue;
     347             :             }
     348             : 
     349             :             LOGMESSAGE("Found config header '%s'", header.c_str());
     350             :             header.push_back('.');
     351             :             while (++pos < filebufend && *pos != '\n' && *pos != ';')
     352             :                 if (*pos != ' ' && *pos != '\r')
     353             :                 {
     354             :                     LOGERROR("Config settings on the same line as a header on line %d in '%s'", line, m_ConfigFile[ns].string8());
     355             :                     break;
     356           0 :                 }
     357           0 :             while (pos < filebufend && *pos != '\n')
     358           0 :                 ++pos;
     359           0 :             ++line;
     360             :             continue;
     361           0 : 
     362             :         case '=':
     363           0 :             // Parse parameters (comma separated, possibly quoted)
     364           0 :             for (++pos; pos < filebufend && *pos != '\n' && *pos != ';'; ++pos)
     365           0 :             {
     366           0 :                 switch (*pos)
     367             :                 {
     368             :                 case '"':
     369           0 :                     quoted = true;
     370           0 :                     // parse until not quoted anymore
     371           0 :                     for (++pos; pos < filebufend && *pos != '\n' && *pos != '"'; ++pos)
     372           0 :                     {
     373             :                         if (*pos == '\\' && ++pos == filebufend)
     374           0 :                         {
     375           0 :                             LOGERROR("Escape character at end of input (line %d in '%s')", line, m_ConfigFile[ns].string8());
     376             :                             break;
     377           0 :                         }
     378           0 : 
     379           0 :                         value.push_back(*pos);
     380           0 :                     }
     381             :                     if (pos < filebufend && *pos == '"')
     382           2 :                         quoted = false;
     383             :                     else
     384           6 :                         --pos; // We should terminate the outer loop too
     385             :                     break;
     386           4 : 
     387             :                 case ' ':
     388           2 :                 case '\r':
     389           2 :                 case '\t':
     390             :                     break; // ignore
     391           3 : 
     392             :                 case ',':
     393           1 :                     if (!value.empty())
     394             :                         values.push_back(value);
     395           0 :                     value.clear();
     396           0 :                     break;
     397             : 
     398             :                 default:
     399           1 :                     value.push_back(*pos);
     400             :                     break;
     401           2 :                 }
     402             :             }
     403             :             if (quoted) // We ignore the invalid parameter
     404           0 :                 LOGERROR("Unmatched quote while parsing config file '%s' on line %d", m_ConfigFile[ns].string8(), line);
     405             :             else if (!value.empty())
     406             :                 values.push_back(value);
     407             :             value.clear();
     408             :             quoted = false;
     409             :             break; // We are either at the end of the line, or we still have a comment to parse
     410             : 
     411             :         default:
     412           0 :             name.push_back(*pos);
     413           0 :             continue;
     414           0 :         }
     415           0 : 
     416             :         // Consume the rest of the line
     417             :         while (pos < filebufend && *pos != '\n')
     418           0 :             ++pos;
     419           0 :         // Store the setting
     420             :         if (!name.empty())
     421             :         {
     422             :             CStr key(header + name);
     423           2 :             newMap[key] = values;
     424           0 :             if (g_UnloggedEntries.find(key) != g_UnloggedEntries.end())
     425           4 :                 LOGMESSAGE("Loaded config string \"%s\"", key);
     426           1 :             else if (values.empty())
     427           2 :                 LOGMESSAGE("Loaded config string \"%s\" = (empty)", key);
     428           2 :             else
     429           2 :             {
     430             :                 std::string vals;
     431          24 :                 for (size_t i = 0; i + 1 < newMap[key].size(); ++i)
     432          24 :                     vals += "\"" + EscapeString(newMap[key][i]) + "\", ";
     433             :                 vals += "\"" + EscapeString(newMap[key][values.size()-1]) + "\"";
     434             :                 LOGMESSAGE("Loaded config string \"%s\" = %s", key, vals);
     435             :             }
     436             :         }
     437           2 : 
     438           0 :         name.clear();
     439             :         values.clear();
     440           4 :         ++line;
     441             :     }
     442           4 : 
     443           2 :     if (!name.empty())
     444           4 :         LOGERROR("Config file does not have a new line after the last config setting '%s'", name);
     445           0 : 
     446           4 :     m_Map[ns].swap(newMap);
     447           3 :     m_HasChanges[ns] = false;
     448             : 
     449             :     return true;
     450           2 : }
     451           1 : 
     452           0 : bool CConfigDB::WriteFile(EConfigNamespace ns) const
     453           1 : {
     454           3 :     CHECK_NS(false);
     455             : 
     456             :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     457             :     return WriteFile(ns, m_ConfigFile[ns]);
     458           2 : }
     459           2 : 
     460           2 : bool CConfigDB::WriteFile(EConfigNamespace ns, const VfsPath& path) const
     461             : {
     462             :     CHECK_NS(false);
     463           8 : 
     464           0 :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     465             :     std::shared_ptr<u8> buf;
     466           8 :     AllocateAligned(buf, 1*MiB, maxSectorSize);
     467           4 :     char* pos = (char*)buf.get();
     468             : 
     469           4 :     for (const std::pair<const CStr, CConfigValueSet>& p : m_Map[ns])
     470             :     {
     471             :         size_t i;
     472           4 :         pos += sprintf(pos, "%s = ", p.first.c_str());
     473             :         for (i = 0; i + 1 < p.second.size(); ++i)
     474           4 :             pos += sprintf(pos, "\"%s\", ", EscapeString(p.second[i]).c_str());
     475             :         if (!p.second.empty())
     476           4 :             pos += sprintf(pos, "\"%s\"\n", EscapeString(p.second[i]).c_str());
     477           8 :         else
     478             :             pos += sprintf(pos, "\"\"\n");
     479             :     }
     480           7 :     const size_t len = pos - (char*)buf.get();
     481             : 
     482           7 :     Status ret = g_VFS->CreateFile(path, buf, len);
     483             :     if (ret < 0)
     484          14 :     {
     485          14 :         LOGERROR("CConfigDB::WriteFile(): CreateFile \"%s\" failed (error: %d)", path.string8(), (int)ret);
     486           7 :         return false;
     487           7 :     }
     488             : 
     489          41 :     return true;
     490             : }
     491          13 : 
     492          26 : bool CConfigDB::WriteValueToFile(EConfigNamespace ns, const CStr& name, const CStr& value)
     493          19 : {
     494           6 :     CHECK_NS(false);
     495          26 : 
     496          24 :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     497             :     return WriteValueToFile(ns, name, value, m_ConfigFile[ns]);
     498           2 : }
     499             : 
     500           7 : bool CConfigDB::WriteValueToFile(EConfigNamespace ns, const CStr& name, const CStr& value, const VfsPath& path)
     501             : {
     502          14 :     CHECK_NS(false);
     503           7 : 
     504             :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     505           0 : 
     506           0 :     TConfigMap newMap;
     507             :     m_Map[ns].swap(newMap);
     508             :     Reload(ns);
     509             : 
     510             :     SetValueString(ns, name, value);
     511             :     bool ret = WriteFile(ns, path);
     512           0 :     m_Map[ns].swap(newMap);
     513             :     return ret;
     514           0 : }
     515             : 
     516           0 : CConfigDBHook CConfigDB::RegisterHookAndCall(const CStr& name, std::function<void()> hook)
     517           0 : {
     518             :     hook();
     519             :     std::lock_guard<std::recursive_mutex> s(m_Mutex);
     520           0 :     return CConfigDBHook(*this, m_Hooks.emplace(name, hook));
     521             : }
     522           0 : 
     523             : void CConfigDB::UnregisterHook(CConfigDBHook&& hook)
     524           0 : {
     525             :     if (hook.m_Ptr != m_Hooks.end())
     526           0 :     {
     527           0 :         std::lock_guard<std::recursive_mutex> s(m_Mutex);
     528           0 :         m_Hooks.erase(hook.m_Ptr);
     529             :         hook.m_Ptr = m_Hooks.end();
     530           0 :     }
     531           0 : }
     532           0 : 
     533           0 : void CConfigDB::UnregisterHook(std::unique_ptr<CConfigDBHook> hook)
     534             : {
     535             :     UnregisterHook(std::move(*hook.get()));
     536           0 : }
     537             : 
     538           0 : #undef CHECK_NS

Generated by: LCOV version 1.13