LCOV - code coverage report
Current view: top level - home/jelte/repos/coverage_bind10/src/bin/auth - auth_config.cc (source / functions) Hit Total Coverage
Test: report.info Lines: 130 132 98.5 %
Date: 2012-05-15 Functions: 25 28 89.3 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 214 335 63.9 %

           Branch data     Line data    Source code
       1                 :            : // Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
       2                 :            : //
       3                 :            : // Permission to use, copy, modify, and/or distribute this software for any
       4                 :            : // purpose with or without fee is hereby granted, provided that the above
       5                 :            : // copyright notice and this permission notice appear in all copies.
       6                 :            : //
       7                 :            : // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
       8                 :            : // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
       9                 :            : // AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
      10                 :            : // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
      11                 :            : // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
      12                 :            : // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
      13                 :            : // PERFORMANCE OF THIS SOFTWARE.
      14                 :            : 
      15                 :            : #include <dns/name.h>
      16                 :            : #include <dns/rrclass.h>
      17                 :            : 
      18                 :            : #include <cc/data.h>
      19                 :            : 
      20                 :            : #include <datasrc/memory_datasrc.h>
      21                 :            : #include <datasrc/zonetable.h>
      22                 :            : #include <datasrc/factory.h>
      23                 :            : 
      24                 :            : #include <auth/auth_srv.h>
      25                 :            : #include <auth/auth_config.h>
      26                 :            : #include <auth/common.h>
      27                 :            : 
      28                 :            : #include <server_common/portconfig.h>
      29                 :            : 
      30                 :            : #include <boost/foreach.hpp>
      31                 :            : #include <boost/shared_ptr.hpp>
      32                 :            : #include <boost/scoped_ptr.hpp>
      33                 :            : 
      34                 :            : #include <set>
      35                 :            : #include <string>
      36                 :            : #include <utility>
      37                 :            : #include <vector>
      38                 :            : 
      39                 :            : using namespace std;
      40                 :            : using namespace isc::dns;
      41                 :            : using namespace isc::data;
      42                 :            : using namespace isc::datasrc;
      43                 :            : using namespace isc::server_common::portconfig;
      44                 :            : 
      45                 :            : namespace {
      46                 :            : // Forward declaration
      47                 :            : AuthConfigParser*
      48                 :            : createAuthConfigParser(AuthSrv& server, const std::string& config_id,
      49                 :            :                        bool internal);
      50                 :            : 
      51                 :            : /// A derived \c AuthConfigParser class for the "datasources" configuration
      52                 :            : /// identifier.
      53         [ +  - ]:        117 : class DatasourcesConfig : public AuthConfigParser {
      54                 :            : public:
      55                 :        117 :     DatasourcesConfig(AuthSrv& server) : server_(server) {}
      56                 :            :     virtual void build(ConstElementPtr config_value);
      57                 :            :     virtual void commit();
      58                 :            : private:
      59                 :            :     AuthSrv& server_;
      60                 :            :     vector<boost::shared_ptr<AuthConfigParser> > datasources_;
      61                 :            :     set<string> configured_sources_;
      62                 :            : };
      63                 :            : 
      64                 :            : /// A derived \c AuthConfigParser for the version value
      65                 :            : /// (which is not used at this moment)
      66                 :          1 : class VersionConfig : public AuthConfigParser {
      67                 :            : public:
      68                 :          2 :     VersionConfig() {}
      69                 :          1 :     virtual void build(ConstElementPtr) {};
      70                 :          1 :     virtual void commit() {};
      71                 :            : };
      72                 :            : 
      73                 :            : void
      74                 :         48 : DatasourcesConfig::build(ConstElementPtr config_value) {
      75   [ +  +  +  - ]:        191 :     BOOST_FOREACH(ConstElementPtr datasrc_elem, config_value->listValue()) {
         [ +  - ][ +  + ]
                 [ +  + ]
      76                 :            :         // The caller is supposed to perform syntax-level checks, but we'll
      77                 :            :         // do minimum level of validation ourselves so that we won't crash due
      78                 :            :         // to a buggy application.
      79 [ +  - ][ +  - ]:         47 :         ConstElementPtr datasrc_type = datasrc_elem->get("type");
      80         [ +  + ]:         47 :         if (!datasrc_type) {
      81 [ +  - ][ +  - ]:          2 :             isc_throw(AuthConfigError, "Missing data source type");
                 [ +  - ]
      82                 :            :         }
      83                 :            : 
      84 [ +  + ][ +  + ]:         91 :         if (configured_sources_.find(datasrc_type->stringValue()) !=
      85                 :         91 :             configured_sources_.end()) {
      86 [ +  - ][ +  - ]:          2 :             isc_throw(AuthConfigError, "Data source type '" <<
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
      87                 :            :                       datasrc_type->stringValue() << "' already configured");
      88                 :            :         }
      89                 :            :         
      90                 :            :         boost::shared_ptr<AuthConfigParser> datasrc_config =
      91                 :            :             boost::shared_ptr<AuthConfigParser>(
      92                 :         44 :                 createAuthConfigParser(server_, string("datasources/") +
      93                 :        132 :                                        datasrc_type->stringValue(),
      94 [ +  - ][ +  - ]:         88 :                                        true));
         [ +  + ][ +  - ]
      95         [ +  + ]:         86 :         datasrc_config->build(datasrc_elem);
      96         [ +  - ]:         32 :         datasources_.push_back(datasrc_config);
      97                 :            : 
      98         [ +  - ]:         64 :         configured_sources_.insert(datasrc_type->stringValue());
      99                 :            :     }
     100                 :         33 : }
     101                 :            : 
     102                 :            : void
     103                 :         32 : DatasourcesConfig::commit() {
     104                 :            :     // XXX a short term workaround: clear all data sources and then reset
     105                 :            :     // to new ones so that we can remove data sources that don't exist in
     106                 :            :     // the new configuration and have been used in the server.
     107                 :            :     // This could be inefficient and requires knowledge about
     108                 :            :     // server implementation details, and isn't scalable wrt the number of
     109                 :            :     // data source types, and should eventually be improved.
     110                 :            :     // Currently memory data source for class IN is the only possibility.
     111         [ +  - ]:         32 :     server_.setInMemoryClient(RRClass::IN(), AuthSrv::InMemoryClientPtr());
     112                 :            : 
     113   [ +  +  +  - ]:        152 :     BOOST_FOREACH(boost::shared_ptr<AuthConfigParser> datasrc_config,
         [ +  - ][ +  + ]
                 [ +  + ]
     114                 :            :                   datasources_) {
     115         [ +  - ]:         30 :         datasrc_config->commit();
     116                 :            :     }
     117                 :         32 : }
     118                 :            : 
     119                 :            : /// A derived \c AuthConfigParser class for the memory type datasource
     120                 :            : /// configuration.  It does not correspond to the configuration syntax;
     121                 :            : /// it's instantiated for internal use.
     122                 :         86 : class MemoryDatasourceConfig : public AuthConfigParser {
     123                 :            : public:
     124                 :            :     MemoryDatasourceConfig(AuthSrv& server) :
     125                 :            :         server_(server),
     126                 :        129 :         rrclass_(0)              // XXX: dummy initial value
     127                 :            :     {}
     128                 :            :     virtual void build(ConstElementPtr config_value);
     129                 :         30 :     virtual void commit() {
     130         [ +  - ]:         30 :         server_.setInMemoryClient(rrclass_, memory_client_);
     131                 :         30 :     }
     132                 :            : private:
     133                 :            :     AuthSrv& server_;
     134                 :            :     RRClass rrclass_;
     135                 :            :     AuthSrv::InMemoryClientPtr memory_client_;
     136                 :            : };
     137                 :            : 
     138                 :            : void
     139                 :         43 : MemoryDatasourceConfig::build(ConstElementPtr config_value) {
     140                 :            :     // XXX: apparently we cannot retrieve the default RR class from the
     141                 :            :     // module spec.  As a temporary workaround we hardcode the default value.
     142         [ +  - ]:         43 :     ConstElementPtr rrclass_elem = config_value->get("class");
     143 [ +  + ][ +  - ]:         43 :     rrclass_ = RRClass(rrclass_elem ? rrclass_elem->stringValue() : "IN");
         [ +  - ][ +  + ]
     144                 :            : 
     145                 :            :     // We'd eventually optimize building zones (in case of reloading) by
     146                 :            :     // selectively loading fresh zones.  Right now we simply check the
     147                 :            :     // RR class is supported by the server implementation.
     148         [ +  + ]:         41 :     server_.getInMemoryClient(rrclass_);
     149 [ +  - ][ +  - ]:         40 :     memory_client_ = AuthSrv::InMemoryClientPtr(new InMemoryClient());
     150                 :            : 
     151 [ +  - ][ +  - ]:         40 :     ConstElementPtr zones_config = config_value->get("zones");
     152         [ +  + ]:         40 :     if (!zones_config) {
     153                 :            :         // XXX: Like the RR class, we cannot retrieve the default value here,
     154                 :            :         // so we assume an empty zone list in this case.
     155                 :         32 :         return;
     156                 :            :     }
     157                 :            : 
     158 [ +  - ][ +  + ]:        178 :     BOOST_FOREACH(ConstElementPtr zone_config, zones_config->listValue()) {
         [ +  - ][ +  - ]
         [ +  + ][ +  + ]
     159 [ +  - ][ +  - ]:         42 :         ConstElementPtr origin = zone_config->get("origin");
     160 [ +  + ][ +  - ]:         84 :         const string origin_txt = origin ? origin->stringValue() : "";
                 [ +  - ]
     161         [ +  + ]:         42 :         if (origin_txt.empty()) {
     162 [ +  - ][ +  - ]:          4 :             isc_throw(AuthConfigError, "Missing zone origin");
                 [ +  - ]
     163                 :            :         }
     164 [ +  - ][ +  - ]:         40 :         ConstElementPtr file = zone_config->get("file");
     165 [ +  + ][ +  - ]:         80 :         const string file_txt = file ? file->stringValue() : "";
                 [ +  - ]
     166         [ +  + ]:         40 :         if (file_txt.empty()) {
     167 [ +  - ][ +  - ]:          4 :             isc_throw(AuthConfigError, "Missing zone file for zone: "
         [ +  - ][ +  - ]
     168                 :            :                       << origin_txt);
     169                 :            :         }
     170                 :            : 
     171                 :            :         // We support the traditional text type and SQLite3 backend.  For the
     172                 :            :         // latter we create a client for the underlying SQLite3 data source,
     173                 :            :         // and build the in-memory zone using an iterator of the underlying
     174                 :            :         // zone.
     175 [ +  - ][ +  - ]:         38 :         ConstElementPtr filetype = zone_config->get("filetype");
     176                 :          3 :         const string filetype_txt = filetype ? filetype->stringValue() :
     177   [ +  +  +  - ]:         79 :             "text";
                 [ +  - ]
     178         [ +  - ]:         38 :         boost::scoped_ptr<DataSourceClientContainer> container;
     179 [ +  - ][ +  + ]:         38 :         if (filetype_txt == "sqlite3") {
     180                 :            :             container.reset(new DataSourceClientContainer(
     181                 :            :                                 "sqlite3",
     182                 :            :                                 Element::fromJSON("{\"database_file\": \"" +
     183 [ +  - ][ +  - ]:          8 :                                                   file_txt + "\"}")));
         [ +  - ][ +  - ]
     184         [ -  + ]:         36 :         } else if (filetype_txt != "text") {
     185 [ #  # ][ #  # ]:          0 :             isc_throw(AuthConfigError, "Invalid filetype for zone "
         [ #  # ][ #  # ]
         [ #  # ][ #  # ]
     186                 :            :                       << origin_txt << ": " << filetype_txt);
     187                 :            :         }
     188                 :            : 
     189                 :            :         // Note: we don't want to have such small try-catch blocks for each
     190                 :            :         // specific error.  We may eventually want to introduce some unified
     191                 :            :         // error handling framework as we have more configuration parameters.
     192                 :            :         // See bug #1627 for the relevant discussion.
     193                 :         38 :         InMemoryZoneFinder* imzf = NULL;
     194                 :            :         try {
     195 [ +  + ][ +  - ]:         38 :             imzf = new InMemoryZoneFinder(rrclass_, Name(origin_txt));
                 [ +  - ]
     196         [ -  + ]:          2 :         } catch (const isc::dns::NameParserException& ex) {
     197 [ -  + ][ -  + ]:          2 :             isc_throw(AuthConfigError, "unable to parse zone's origin: " <<
         [ -  + ][ -  + ]
     198                 :            :                       ex.what());
     199                 :            :         }
     200                 :            : 
     201                 :            :         boost::shared_ptr<InMemoryZoneFinder> zone_finder(imzf);
     202         [ +  - ]:         37 :         const result::Result result = memory_client_->addZone(zone_finder);
     203         [ +  + ]:         37 :         if (result == result::EXIST) {
     204 [ +  - ][ +  - ]:          2 :             isc_throw(AuthConfigError, "zone "<< origin->str()
         [ +  - ][ +  - ]
         [ +  - ][ +  - ]
     205                 :            :                       << " already exists");
     206                 :            :         }
     207                 :            : 
     208                 :            :         /*
     209                 :            :          * TODO: Once we have better reloading of configuration (something
     210                 :            :          * else than throwing everything away and loading it again), we will
     211                 :            :          * need the load method to be split into some kind of build and
     212                 :            :          * commit/abort parts.
     213                 :            :          */
     214 [ +  - ][ +  + ]:         36 :         if (filetype_txt == "text") {
     215         [ +  + ]:         34 :             zone_finder->load(file_txt);
     216                 :            :         } else {
     217                 :            :             zone_finder->load(*container->getInstance().getIterator(
     218 [ +  - ][ +  + ]:          4 :                                   Name(origin_txt)));
                 [ +  - ]
     219                 :            :         }
     220                 :            :     }
     221                 :            : }
     222                 :            : 
     223                 :            : /// A derived \c AuthConfigParser class for the "statistics-internal"
     224                 :            : /// configuration identifier.
     225                 :          4 : class StatisticsIntervalConfig : public AuthConfigParser {
     226                 :            : public:
     227                 :            :     StatisticsIntervalConfig(AuthSrv& server) :
     228                 :          8 :         server_(server), interval_(0)
     229                 :            :     {}
     230                 :          8 :     virtual void build(ConstElementPtr config_value) {
     231                 :          8 :         const int32_t config_interval = config_value->intValue();
     232         [ +  + ]:          6 :         if (config_interval < 0) {
     233 [ +  - ][ +  - ]:          2 :             isc_throw(AuthConfigError, "Negative statistics interval value: "
                 [ +  - ]
     234                 :            :                       << config_interval);
     235                 :            :         }
     236         [ +  + ]:          5 :         if (config_interval > 86400) {
     237 [ +  - ][ +  - ]:          2 :             isc_throw(AuthConfigError, "Statistics interval value "
         [ +  - ][ +  - ]
     238                 :            :                       << config_interval
     239                 :            :                       << " must be equal to or shorter than 86400");
     240                 :            :         }
     241                 :          4 :         interval_ = config_interval;
     242                 :          4 :     }
     243                 :          3 :     virtual void commit() {
     244                 :            :         // setStatisticsTimerInterval() is not 100% exception free.  But
     245                 :            :         // exceptions should happen only in a very rare situation, so we
     246                 :            :         // let them be thrown and subsequently regard them as a fatal error.
     247                 :          3 :         server_.setStatisticsTimerInterval(interval_);
     248                 :          3 :     }
     249                 :            : private:
     250                 :            :     AuthSrv& server_;
     251                 :            :     uint32_t interval_;
     252                 :            : };
     253                 :            : 
     254                 :            : /// A special parser for testing: it throws from commit() despite the
     255                 :            : /// suggested convention of the class interface.
     256                 :          1 : class ThrowerCommitConfig : public AuthConfigParser {
     257                 :            : public:
     258                 :          1 :     virtual void build(ConstElementPtr) {} // ignore param, do nothing
     259                 :          1 :     virtual void commit() {
     260                 :          1 :         throw 10;
     261                 :            :     }
     262                 :            : };
     263                 :            : 
     264                 :            : /**
     265                 :            :  * \brief Configuration parser for listen_on.
     266                 :            :  *
     267                 :            :  * It parses and sets the listening addresses of the server.
     268                 :            :  *
     269                 :            :  * It acts in unusual way. Since actually binding (changing) the sockets
     270                 :            :  * is an operation that is expected to throw often, it shouldn't happen
     271                 :            :  * in commit. Thefere we do it in build. But if the config is not committed
     272                 :            :  * then, we would have it wrong. So we store the old addresses and if
     273                 :            :  * commit is not called before destruction of the object, we return the
     274                 :            :  * old addresses (which is the same kind of dangerous operation, but it is
     275                 :            :  * expected that if we just managed to bind some and had the old ones binded
     276                 :            :  * before, it should work).
     277                 :            :  *
     278                 :            :  * We might do something better in future (like open only the ports that are
     279                 :            :  * extra, put them in in commit and close the old ones), but that's left out
     280                 :            :  * for now.
     281                 :            :  */
     282                 :            : class ListenAddressConfig : public AuthConfigParser {
     283                 :            : public:
     284                 :            :     ListenAddressConfig(AuthSrv& server) :
     285                 :         16 :         server_(server)
     286                 :            :     { }
     287                 :          8 :     ~ ListenAddressConfig() {
     288         [ -  + ]:          8 :         if (rollbackAddresses_.get() != NULL) {
     289         [ #  # ]:          0 :             server_.setListenAddresses(*rollbackAddresses_);
     290                 :            :         }
     291                 :         16 :     }
     292                 :            : private:
     293                 :            :     typedef auto_ptr<AddressList> AddrListPtr;
     294                 :            : public:
     295                 :          8 :     virtual void build(ConstElementPtr config) {
     296         [ +  + ]:         26 :         AddressList newAddresses = parseAddresses(config, "listen_on");
     297 [ +  - ][ +  - ]:          4 :         AddrListPtr old(new AddressList(server_.getListenAddresses()));
                 [ +  - ]
     298         [ +  + ]:          2 :         server_.setListenAddresses(newAddresses);
     299                 :            :         /*
     300                 :            :          * Set the rollback addresses only after successful setting of the
     301                 :            :          * new addresses, so we don't try to rollback if the setup is
     302                 :            :          * unsuccessful (the above can easily throw).
     303                 :            :          */
     304                 :          1 :         rollbackAddresses_ = old;
     305                 :          1 :     }
     306                 :          1 :     virtual void commit() {
     307                 :          1 :         rollbackAddresses_.release();
     308                 :          1 :     }
     309                 :            : private:
     310                 :            :     AuthSrv& server_;
     311                 :            :     /**
     312                 :            :      * This is the old address list, if we expect to roll back. When we commit,
     313                 :            :      * this is set to NULL.
     314                 :            :      */
     315                 :            :     AddrListPtr rollbackAddresses_;
     316                 :            : };
     317                 :            : 
     318                 :            : // This is a generalized version of create function that can create
     319                 :            : // an AuthConfigParser object for "internal" use.
     320                 :            : AuthConfigParser*
     321                 :         99 : createAuthConfigParser(AuthSrv& server, const std::string& config_id,
     322                 :            :                        bool internal)
     323                 :            : {
     324                 :            :     // For the initial implementation we use a naive if-else blocks for
     325                 :            :     // simplicity.  In future we'll probably generalize it using map-like
     326                 :            :     // data structure, and may even provide external register interface so
     327                 :            :     // that it can be dynamically customized.
     328         [ +  + ]:         99 :     if (config_id == "datasources") {
     329                 :         39 :         return (new DatasourcesConfig(server));
     330         [ +  + ]:         60 :     } else if (config_id == "statistics-interval") {
     331                 :          4 :         return (new StatisticsIntervalConfig(server));
     332 [ +  + ][ +  + ]:         56 :     } else if (internal && config_id == "datasources/memory") {
                 [ +  + ]
     333                 :         43 :         return (new MemoryDatasourceConfig(server));
     334         [ +  + ]:         13 :     } else if (config_id == "listen_on") {
     335                 :          8 :         return (new ListenAddressConfig(server));
     336         [ +  + ]:          5 :     } else if (config_id == "_commit_throw") {
     337                 :            :         // This is for testing purpose only and should not appear in the
     338                 :            :         // actual configuration syntax.  While this could crash the caller
     339                 :            :         // as a result, the server implementation is expected to perform
     340                 :            :         // syntax level validation and should be safe in practice.  In future,
     341                 :            :         // we may introduce dynamic registration of configuration parsers,
     342                 :            :         // and then this test can be done in a cleaner and safer way.
     343                 :          1 :         return (new ThrowerCommitConfig());
     344         [ +  + ]:          4 :     } else if (config_id == "version") {
     345                 :            :         // Currently, the version identifier is ignored, but it should
     346                 :            :         // later be used to mark backwards incompatible changes in the
     347                 :            :         // config data
     348                 :          1 :         return (new VersionConfig());
     349                 :            :     } else {
     350 [ +  - ][ +  - ]:        102 :         isc_throw(AuthConfigError, "Unknown configuration identifier: " <<
                 [ +  - ]
     351                 :            :                   config_id);
     352                 :            :     }
     353                 :            : }
     354                 :            : } // end of unnamed namespace
     355                 :            : 
     356                 :            : AuthConfigParser*
     357                 :         55 : createAuthConfigParser(AuthSrv& server, const std::string& config_id) {
     358                 :         55 :     return (createAuthConfigParser(server, config_id, false));
     359                 :            : }
     360                 :            : 
     361                 :            : void
     362                 :         39 : configureAuthServer(AuthSrv& server, ConstElementPtr config_set) {
     363         [ +  + ]:         39 :     if (!config_set) {
     364 [ +  - ][ +  - ]:          2 :         isc_throw(AuthConfigError,
     365                 :            :                   "Null pointer is passed to configuration parser");
     366                 :            :     }
     367                 :            : 
     368                 :            :     typedef boost::shared_ptr<AuthConfigParser> ParserPtr;
     369                 :         38 :     vector<ParserPtr> parsers;
     370                 :            :     typedef pair<string, ConstElementPtr> ConfigPair;
     371                 :            :     try {
     372 [ +  + ][ +  + ]:        143 :         BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
         [ +  - ][ +  - ]
         [ +  - ][ +  + ]
                 [ +  + ]
     373                 :            :             // We should eventually integrate the sqlite3 DB configuration to
     374                 :            :             // this framework, but to minimize diff we begin with skipping that
     375                 :            :             // part.
     376 [ +  - ][ +  + ]:         38 :             if (config_pair.first == "database_file") {
     377                 :          5 :                 continue;
     378                 :            :             }
     379                 :            : 
     380                 :            :             ParserPtr parser(createAuthConfigParser(server,
     381 [ +  + ][ +  - ]:         33 :                                                     config_pair.first));
     382         [ +  + ]:         64 :             parser->build(config_pair.second);
     383         [ +  - ]:         24 :             parsers.push_back(parser);
     384                 :            :         }
     385                 :          2 :     } catch (const AuthConfigError& ex) {
     386                 :          1 :         throw;                  // simply rethrowing it
     387      [ -  +  + ]:         19 :     } catch (const isc::Exception& ex) {
     388 [ -  + ][ -  + ]:         18 :         isc_throw(AuthConfigError, "Server configuration failed: " <<
         [ -  + ][ -  + ]
     389                 :            :                   ex.what());
     390                 :            :     }
     391                 :            : 
     392                 :            :     try {
     393 [ +  + ][ +  - ]:        117 :         BOOST_FOREACH(ParserPtr parser, parsers) {
         [ +  - ][ +  + ]
                 [ +  + ]
     394         [ +  + ]:         23 :             parser->commit();
     395                 :            :         }
     396                 :          2 :     } catch (...) {
     397                 :            :         throw FatalError("Unrecoverable error: "
     398         [ -  + ]:          2 :                          "a configuration parser threw in commit");
     399                 :            :     }
     400                 :         28 : }

Generated by: LCOV version 1.9