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: 154 275 56.0 %
Date: 2023-01-19 00:18:29 Functions: 23 41 56.1 %

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

Generated by: LCOV version 1.13