LCOV - code coverage report
Current view: top level - source/ps/XML - Xeromyces.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 86 102 84.3 %
Date: 2023-01-19 00:18:29 Functions: 10 12 83.3 %

          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 <vector>
      21             : #include <set>
      22             : #include <map>
      23             : #include <mutex>
      24             : #include <stack>
      25             : #include <algorithm>
      26             : 
      27             : #include "maths/MD5.h"
      28             : #include "ps/CacheLoader.h"
      29             : #include "ps/CLogger.h"
      30             : #include "ps/Filesystem.h"
      31             : 
      32             : #include "RelaxNG.h"
      33             : #include "Xeromyces.h"
      34             : 
      35             : #include <libxml/parser.h>
      36             : 
      37             : static std::mutex g_ValidatorCacheLock;
      38           1 : static std::map<const std::string, RelaxNGValidator> g_ValidatorCache;
      39             : static bool g_XeromycesStarted = false;
      40             : 
      41           5 : static void errorHandler(void* UNUSED(userData), xmlErrorPtr error)
      42             : {
      43             :     // Strip a trailing newline
      44          10 :     std::string message = error->message;
      45           5 :     if (message.length() > 0 && message[message.length()-1] == '\n')
      46           5 :         message.erase(message.length()-1);
      47             : 
      48           5 :     LOGERROR("CXeromyces: Parse %s: %s:%d: %s",
      49             :         error->level == XML_ERR_WARNING ? "warning" : "error",
      50             :         error->file, error->line, message);
      51             :     // TODO: The (non-fatal) warnings and errors don't get stored in the XMB,
      52             :     // so the caching is less transparent than it should be
      53           5 : }
      54             : 
      55          90 : void CXeromyces::Startup()
      56             : {
      57          90 :     ENSURE(!g_XeromycesStarted);
      58          90 :     xmlInitParser();
      59          90 :     xmlSetStructuredErrorFunc(NULL, &errorHandler);
      60         180 :     std::lock_guard<std::mutex> lock(g_ValidatorCacheLock);
      61          90 :     g_ValidatorCache.insert(std::make_pair(std::string(), RelaxNGValidator()));
      62          90 :     g_XeromycesStarted = true;
      63          90 : }
      64             : 
      65          90 : void CXeromyces::Terminate()
      66             : {
      67          90 :     ENSURE(g_XeromycesStarted);
      68          90 :     g_XeromycesStarted = false;
      69          90 :     ClearSchemaCache();
      70         180 :     std::lock_guard<std::mutex> lock(g_ValidatorCacheLock);
      71          90 :     g_ValidatorCache.clear();
      72          90 :     xmlSetStructuredErrorFunc(NULL, NULL);
      73          90 :     xmlCleanupParser();
      74          90 : }
      75             : 
      76          18 : bool CXeromyces::AddValidator(const PIVFS& vfs, const std::string& name, const VfsPath& grammarPath)
      77             : {
      78          18 :     ENSURE(g_XeromycesStarted);
      79             : 
      80          36 :     RelaxNGValidator validator;
      81          18 :     if (!validator.LoadGrammarFile(vfs, grammarPath))
      82             :     {
      83           6 :         LOGERROR("CXeromyces: failed adding validator for '%s'", grammarPath.string8());
      84           6 :         return false;
      85             :     }
      86             :     {
      87          24 :         std::lock_guard<std::mutex> lock(g_ValidatorCacheLock);
      88          12 :         std::map<const std::string, RelaxNGValidator>::iterator it = g_ValidatorCache.find(name);
      89          12 :         if (it != g_ValidatorCache.end())
      90           0 :             g_ValidatorCache.erase(it);
      91          12 :         g_ValidatorCache.insert(std::make_pair(name, validator));
      92             :     }
      93          12 :     return true;
      94             : }
      95             : 
      96           0 : bool CXeromyces::ValidateEncoded(const std::string& name, const std::string& filename, const std::string& document)
      97             : {
      98           0 :     std::lock_guard<std::mutex> lock(g_ValidatorCacheLock);
      99           0 :     return GetValidator(name).ValidateEncoded(filename, document);
     100             : }
     101             : 
     102             : /**
     103             :  * NOTE: Callers MUST acquire the g_ValidatorCacheLock before calling this.
     104             :  */
     105         531 : RelaxNGValidator& CXeromyces::GetValidator(const std::string& name)
     106             : {
     107         531 :     if (g_ValidatorCache.find(name) == g_ValidatorCache.end())
     108           0 :         return g_ValidatorCache.find("")->second;
     109         531 :     return g_ValidatorCache.find(name)->second;
     110             : }
     111             : 
     112         430 : PSRETURN CXeromyces::Load(const PIVFS& vfs, const VfsPath& filename, const std::string& validatorName /* = "" */)
     113             : {
     114         430 :     ENSURE(g_XeromycesStarted);
     115             : 
     116         860 :     CCacheLoader cacheLoader(vfs, L".xmb");
     117             : 
     118         430 :     MD5 validatorGrammarHash;
     119             :     {
     120         860 :         std::lock_guard<std::mutex> lock(g_ValidatorCacheLock);
     121         430 :         validatorGrammarHash = GetValidator(validatorName).GetGrammarHash();
     122             :     }
     123         860 :     VfsPath xmbPath;
     124         430 :     Status ret = cacheLoader.TryLoadingCached(filename, validatorGrammarHash, XMBStorage::XMBVersion, xmbPath);
     125             : 
     126         430 :     if (ret == INFO::OK)
     127             :     {
     128             :         // Found a cached XMB - load it
     129         314 :         if (m_Data.ReadFromFile(vfs, xmbPath) && Initialise(m_Data))
     130         314 :             return PSRETURN_OK;
     131             :         // If this fails then we'll continue and (re)create the loose cache -
     132             :         // this failure legitimately happens due to partially-written XMB files or XMB version upgrades.
     133             :         // NB: at this point xmbPath may point to an archived file, we want to write a loose cached version.
     134           0 :         xmbPath = cacheLoader.LooseCachePath(filename, validatorGrammarHash, XMBStorage::XMBVersion);
     135             :     }
     136         116 :     else if (ret == INFO::SKIPPED)
     137             :     {
     138             :         // No cached version was found - we'll need to create it
     139             :     }
     140             :     else
     141             :     {
     142          69 :         ENSURE(ret < 0);
     143             : 
     144             :         // No source file or archive cache was found, so we can't load the
     145             :         // XML file at all
     146          69 :         LOGERROR("CCacheLoader failed to find archived or source file for: \"%s\"", filename.string8());
     147          69 :         return PSRETURN_Xeromyces_XMLOpenFailed;
     148             :     }
     149             : 
     150             :     // XMB isn't up to date with the XML, so rebuild it
     151          47 :     return ConvertFile(vfs, filename, xmbPath, validatorName);
     152             : }
     153             : 
     154           0 : bool CXeromyces::GenerateCachedXMB(const PIVFS& vfs, const VfsPath& sourcePath, VfsPath& archiveCachePath, const std::string& validatorName /* = "" */)
     155             : {
     156           0 :     CCacheLoader cacheLoader(vfs, L".xmb");
     157             : 
     158           0 :     archiveCachePath = cacheLoader.ArchiveCachePath(sourcePath);
     159             : 
     160           0 :     return (ConvertFile(vfs, sourcePath, VfsPath("cache") / archiveCachePath, validatorName) == PSRETURN_OK);
     161             : }
     162             : 
     163          47 : PSRETURN CXeromyces::ConvertFile(const PIVFS& vfs, const VfsPath& filename, const VfsPath& xmbPath, const std::string& validatorName)
     164             : {
     165          94 :     CVFSFile input;
     166          47 :     if (input.Load(vfs, filename))
     167             :     {
     168           0 :         LOGERROR("CXeromyces: Failed to open XML file %s", filename.string8());
     169           0 :         return PSRETURN_Xeromyces_XMLOpenFailed;
     170             :     }
     171             : 
     172          94 :     xmlDocPtr doc = xmlReadMemory((const char*)input.GetBuffer(), input.GetBufferSize(), CStrW(filename.string()).ToUTF8().c_str(), NULL,
     173          47 :         XML_PARSE_NONET|XML_PARSE_NOCDATA);
     174          47 :     if (!doc)
     175             :     {
     176           1 :         LOGERROR("CXeromyces: Failed to parse XML file %s", filename.string8());
     177           1 :         return PSRETURN_Xeromyces_XMLParseError;
     178             :     }
     179             : 
     180             :     {
     181          92 :         std::lock_guard<std::mutex> lock(g_ValidatorCacheLock);
     182          46 :         RelaxNGValidator& validator = GetValidator(validatorName);
     183          46 :         if (validator.CanValidate() && !validator.ValidateEncoded(doc))
     184             :         {
     185           0 :             LOGERROR("CXeromyces: failed to validate XML file %s", filename.string8());
     186           0 :             return PSRETURN_Xeromyces_XMLValidationFailed;
     187             :         }
     188             :     }
     189             : 
     190          46 :     m_Data.LoadXMLDoc(doc);
     191          46 :     xmlFreeDoc(doc);
     192             : 
     193             :     // Save the file to disk, so it can be loaded quickly next time.
     194             :     // Don't save if invalid, because we want the syntax error every program start.
     195          46 :     vfs->CreateFile(xmbPath, m_Data.m_Buffer, m_Data.m_Size);
     196             : 
     197             :     // Set up the XMBData
     198          46 :     const bool ok = Initialise(m_Data);
     199          46 :     ENSURE(ok);
     200             : 
     201          46 :     return PSRETURN_OK;
     202             : }
     203             : 
     204          56 : PSRETURN CXeromyces::LoadString(const char* xml, const std::string& validatorName /* = "" */)
     205             : {
     206          56 :     ENSURE(g_XeromycesStarted);
     207             : 
     208          56 :     xmlDocPtr doc = xmlReadMemory(xml, (int)strlen(xml), "(no file)", NULL, XML_PARSE_NONET|XML_PARSE_NOCDATA);
     209          56 :     if (!doc)
     210             :     {
     211           1 :         LOGERROR("CXeromyces: Failed to parse XML string");
     212           1 :         return PSRETURN_Xeromyces_XMLParseError;
     213             :     }
     214             : 
     215             :     {
     216         110 :         std::lock_guard<std::mutex> lock(g_ValidatorCacheLock);
     217          55 :         RelaxNGValidator& validator = GetValidator(validatorName);
     218          55 :         if (validator.CanValidate() && !validator.ValidateEncoded(doc))
     219             :         {
     220           0 :             LOGERROR("CXeromyces: failed to validate XML string");
     221           0 :             return PSRETURN_Xeromyces_XMLValidationFailed;
     222             :         }
     223             :     }
     224             : 
     225          55 :     m_Data.LoadXMLDoc(doc);
     226          55 :     xmlFreeDoc(doc);
     227             : 
     228             :     // Set up the XMBData
     229          55 :     const bool ok = Initialise(m_Data);
     230          55 :     ENSURE(ok);
     231             : 
     232          55 :     return PSRETURN_OK;
     233           3 : }

Generated by: LCOV version 1.13