LCOV - code coverage report
Current view: top level - advgetopt - conf_file.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 540 540 100.0 %
Date: 2021-08-20 21:57:12 Functions: 34 34 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * License:
       3             :  *    Copyright (c) 2006-2021  Made to Order Software Corp.  All Rights Reserved
       4             :  *
       5             :  *    https://snapwebsites.org/project/advgetopt
       6             :  *    contact@m2osw.com
       7             :  *
       8             :  *    This program is free software; you can redistribute it and/or modify
       9             :  *    it under the terms of the GNU General Public License as published by
      10             :  *    the Free Software Foundation; either version 2 of the License, or
      11             :  *    (at your option) any later version.
      12             :  *
      13             :  *    This program is distributed in the hope that it will be useful,
      14             :  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
      15             :  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16             :  *    GNU General Public License for more details.
      17             :  *
      18             :  *    You should have received a copy of the GNU General Public License along
      19             :  *    with this program; if not, write to the Free Software Foundation, Inc.,
      20             :  *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
      21             :  *
      22             :  * Authors:
      23             :  *    Alexis Wilke   alexis@m2osw.com
      24             :  *    Doug Barbieri  doug@m2osw.com
      25             :  */
      26             : 
      27             : 
      28             : /** \file
      29             :  * \brief Implementation of the option_info class.
      30             :  *
      31             :  * This is the implementation of the class used to load and save
      32             :  * configuration files.
      33             :  */
      34             : 
      35             : // self
      36             : //
      37             : #include    "advgetopt/conf_file.h"
      38             : 
      39             : 
      40             : // advgetopt lib
      41             : //
      42             : #include    "advgetopt/exception.h"
      43             : #include    "advgetopt/utils.h"
      44             : 
      45             : 
      46             : // snapdev lib
      47             : //
      48             : #include    <snapdev/safe_variable.h>
      49             : #include    <snapdev/tokenize_string.h>
      50             : 
      51             : 
      52             : // cppthread lib
      53             : //
      54             : #include    <cppthread/guard.h>
      55             : #include    <cppthread/log.h>
      56             : #include    <cppthread/mutex.h>
      57             : 
      58             : 
      59             : // boost lib
      60             : //
      61             : #include    <boost/algorithm/string/join.hpp>
      62             : #include    <boost/algorithm/string/replace.hpp>
      63             : 
      64             : 
      65             : // C++ lib
      66             : //
      67             : #include    <algorithm>
      68             : #include    <fstream>
      69             : 
      70             : 
      71             : // C lib
      72             : //
      73             : #include    <sys/stat.h>
      74             : 
      75             : 
      76             : // last include
      77             : //
      78             : #include    <snapdev/poison.h>
      79             : 
      80             : 
      81             : 
      82             : namespace advgetopt
      83             : {
      84             : 
      85             : 
      86             : // from utils.cpp
      87             : //
      88             : // (it's here because we do not want to make cppthread public in
      89             : // out header files--we could have an advgetopt_private.h, though)
      90             : //
      91             : cppthread::mutex &  get_global_mutex();
      92             : 
      93             : 
      94             : 
      95             : /** \brief Private conf_file data.
      96             :  *
      97             :  * The conf_file has a few globals used to cache configuration files.
      98             :  * Since it has to work in a multi-thread environment, we also have
      99             :  * a mutex.
     100             :  */
     101             : namespace
     102             : {
     103             : 
     104             : 
     105             : 
     106             : /** \brief A map of configuration files.
     107             :  *
     108             :  * This typedef defines a type used to hold all the configuration files
     109             :  * that were loaded so far.
     110             :  *
     111             :  * The map is indexed by a string representing the full path to the
     112             :  * configuration file.
     113             :  *
     114             :  * The value is a shared pointer to configuration file. Since we may
     115             :  * share that data between multiple users, it made sense to force you
     116             :  * to use a configuration file smart pointer. Note, though, that we
     117             :  * never destroy the pointer until we quit (i.e. you cannot force a
     118             :  * re-load of the configuration file. Changes that happen in memory
     119             :  * are visible to all users, but changes to the actual configuration
     120             :  * file are complete invisible to use.)
     121             :  */
     122             : typedef std::map<std::string, conf_file::pointer_t>     conf_file_map_t;
     123             : 
     124             : 
     125             : /** \brief The configuration files.
     126             :  *
     127             :  * This global defines a list of configuration files indexed by
     128             :  * filename (full path, but not the URL, just a path.)
     129             :  *
     130             :  * Whenever a configuration file is being retrieved with the
     131             :  * conf_file::get_conf_file() function, it is first searched
     132             :  * in this map. If it exists in the map, that version gets
     133             :  * used (if the URL of the two setups match one to one.)
     134             :  * If there is no such file in the map, then a new one is
     135             :  * created by loading the corresponding file.
     136             :  */
     137           2 : conf_file_map_t     g_conf_files = conf_file_map_t();
     138             : 
     139             : 
     140             : } // no name namespace
     141             : 
     142             : 
     143             : 
     144             : 
     145             : 
     146             : /** \brief Initialize the file setup object.
     147             :  *
     148             :  * This constructor initializes the setup object which can later be used
     149             :  * to search for an existing conf_file or creating a new conf_file.
     150             :  *
     151             :  * The setup holds the various parameters used to know how to load a
     152             :  * configuration file in memory. The parameters include
     153             :  *
     154             :  * \li \p filename -- the name of the file to read as a configuration file.
     155             :  * \li \p line_continuation -- how lines in the files are being read; in
     156             :  * most cases a line in a text file ends when a newline character (`\\n`)
     157             :  * is found; this parameter allows for lines that span (continue) on
     158             :  * multiple text lines. Only one type of continuation or no continue
     159             :  * (a.k.a. "single line") can be used per file.
     160             :  * \li \p assignment_operator -- the character(s) accepted between the
     161             :  * name of a variable and its value; by default this is the equal sign
     162             :  * (`=`). Multiple operators can be accepted.
     163             :  * \li \p comment -- how comments are introduced when supported. Multiple
     164             :  * introducers can be accepted within one file. By default we accept the
     165             :  * Unix Shell (`#`) and INI file (`;`) comment introducers.
     166             :  * \li \p section_operator -- the set of characters accepted as section
     167             :  * separator. By default we accept the INI file syntax (the `[section]`
     168             :  * syntax.)
     169             :  *
     170             :  * \note
     171             :  * If the filename represent an existing file, then the name is going to
     172             :  * get canonicalized before it gets saved in the structure. Otherwise it
     173             :  * gets saved as is.
     174             :  *
     175             :  * \param[in] filename  A valid filename.
     176             :  * \param[in] line_continue  One of the line_continuation_t values.
     177             :  * \param[in] assignment_operator  A set of assignment operator flags.
     178             :  * \param[in] comment  A set of comment flags.
     179             :  * \param[in] section_operator  A set of section operator flags.
     180             :  */
     181       28530 : conf_file_setup::conf_file_setup(
     182             :           std::string const & filename
     183             :         , line_continuation_t line_continuation
     184             :         , assignment_operator_t assignment_operator
     185             :         , comment_t comment
     186       28530 :         , section_operator_t section_operator)
     187             :     : f_original_filename(filename)
     188             :     , f_line_continuation(line_continuation)
     189       28530 :     , f_assignment_operator(assignment_operator == 0
     190       28530 :                 ? ASSIGNMENT_OPERATOR_EQUAL
     191             :                 : assignment_operator)
     192             :     , f_comment(comment)
     193       57061 :     , f_section_operator(section_operator)
     194             : {
     195       28530 :     if(filename.empty())
     196             :     {
     197           1 :         throw getopt_invalid("trying to load a configuration file using an empty filename.");
     198             :     }
     199             : 
     200       57058 :     std::unique_ptr<char, decltype(&::free)> fn(realpath(filename.c_str(), nullptr), &::free);
     201       28529 :     if(fn != nullptr)
     202             :     {
     203       28032 :         f_filename = fn.get();
     204             :     }
     205             :     else
     206             :     {
     207         497 :         f_filename = filename;
     208             :     }
     209       28529 : }
     210             : 
     211             : 
     212             : /** \brief Check whether the setup is considered valid.
     213             :  *
     214             :  * This function is used to check whether the conf_file_setup is valid or
     215             :  * not. It is valid when everything is in order, which at this point means
     216             :  * the filename is not empty.
     217             :  *
     218             :  * All the other parameters are always viewed as being valid.
     219             :  *
     220             :  * \warning
     221             :  * The is_valid() always returns true at this time. We always save the
     222             :  * filename. I'm not totally sure why I wanted to not have a way to get
     223             :  * a valid configuration file by viewing a non-existing file as the same
     224             :  * as an empty file. Now that's what happens.
     225             :  *
     226             :  * \return true if the conf_file_setup is considered valid.
     227             :  */
     228       25903 : bool conf_file_setup::is_valid() const
     229             : {
     230       25903 :     return !f_filename.empty();
     231             : }
     232             : 
     233             : 
     234             : /** \brief Get the original filename.
     235             :  *
     236             :  * When creating a new conf_file_setup, you have to specify a filename.
     237             :  * This function returns that string exactly, without canonicalization.
     238             :  *
     239             :  * \return The filename as specified at the time of construction.
     240             :  *
     241             :  * \sa get_filename()
     242             :  */
     243       25226 : std::string const & conf_file_setup::get_original_filename() const
     244             : {
     245       25226 :     return f_original_filename;
     246             : }
     247             : 
     248             : 
     249             : /** \brief Get the filename.
     250             :  *
     251             :  * When creating a new conf_file_setup, you have to specify a filename.
     252             :  * This function returns that filename after it was canonicalized by
     253             :  * the constructor.
     254             :  *
     255             :  * The canonicalization process computes the full path to the real
     256             :  * file. If such does not exist then no filename is defined, so this
     257             :  * function may return an empty string.
     258             :  *
     259             :  * \return The filename or an empty string if the realpath() could not
     260             :  *         be calculated.
     261             :  *
     262             :  * \sa get_original_filename()
     263             :  */
     264       29252 : std::string const & conf_file_setup::get_filename() const
     265             : {
     266       29252 :     return f_filename;
     267             : }
     268             : 
     269             : 
     270             : /** \brief Get the line continuation setting.
     271             :  *
     272             :  * This function returns the line continuation for this setup.
     273             :  *
     274             :  * This parameter is not a set of flags. We only support one type of
     275             :  * line continuation per file. Many continuations could be contradictory
     276             :  * if used simultaneously.
     277             :  *
     278             :  * The continuation setting is one of the following:
     279             :  *
     280             :  * \li line_continuation_t::single_line -- no continuation support; any
     281             :  * definition must be on one single line.
     282             :  * \li line_continuation_t::rfc_822 -- like email/HTTP, whitespace at
     283             :  * the start of the next line means that the current line continues there;
     284             :  * those whitespaces get removed from the value so if you want a space
     285             :  * between two lines, make sure to finish the current line with a space.
     286             :  * \li line_continuation_t::msdos -- `&` at end of the line.
     287             :  * \li line_continuation_t::unix -- `\` at end of the line.
     288             :  * \li line_continuation_t::fortran -- `&` at the start of the next line;
     289             :  * there cannot be any spaces, the `&` has to be the very first character.
     290             :  * \li line_continuation_t::semicolon -- `;` ends the _line_; when reading
     291             :  * a line with this continuation mode, the reader stops only when it finds
     292             :  * the `;` or EOF (also if a comment is found.)
     293             :  *
     294             :  * \return a line continuation mode.
     295             :  */
     296       26260 : line_continuation_t conf_file_setup::get_line_continuation() const
     297             : {
     298       26260 :     return f_line_continuation;
     299             : }
     300             : 
     301             : 
     302             : /** \brief Get the accepted assignment operators.
     303             :  *
     304             :  * This function returns the set of flags describing the list of
     305             :  * accepted operators one can use to do assignments.
     306             :  *
     307             :  * Right now we support the follow:
     308             :  *
     309             :  * \li ASSIGNMENT_OPERATOR_EQUAL -- the equal (`=`) character, like in
     310             :  * most Unix configuration files and shell scripts.
     311             :  * \li ASSIGNMENT_OPERATOR_COLON -- the colon (`:`) character, like in
     312             :  * email and HTTP headers.
     313             :  * \li ASSIGNMENT_OPERATOR_SPACE -- the space (` `) character; this is
     314             :  * less used, but many Unix configuration files still use this scheme.
     315             :  *
     316             :  * \todo
     317             :  * Add support for additional operators such as:
     318             :  * \todo
     319             :  * \li `+=` -- append data
     320             :  * \li `?=` -- set to this value if not yet set
     321             :  *
     322             :  * \return The set of accepted assignment operators.
     323             :  *
     324             :  * \sa is_assignment_operator()
     325             :  */
     326     1144810 : assignment_operator_t conf_file_setup::get_assignment_operator() const
     327             : {
     328     1144810 :     return f_assignment_operator;
     329             : }
     330             : 
     331             : 
     332             : /** Get the comment flags.
     333             :  *
     334             :  * This function returns the comment flags. These describe which type
     335             :  * of comments are supported in this configuration file.
     336             :  *
     337             :  * Currently we support:
     338             :  *
     339             :  * \li COMMENT_INI -- INI file like comments, these are introduced with
     340             :  * a semi-colon (`;`) and end with a newline.
     341             :  * \li COMMENT_SHELL -- Unix shell like comments, these are introduced
     342             :  * with a hash (`#`) and end with a newline.
     343             :  * \li COMMENT_CPP -- C++ like comments, these are introduced with two
     344             :  * slashes (`//`) and end with a newline.
     345             :  *
     346             :  * Right now we only support line comments. Configuration entries cannot
     347             :  * include comments. A comment character can be preceeded by spaces and
     348             :  * tabs.
     349             :  *
     350             :  * Line continuation is taken in account with comments. So the following
     351             :  * when the line continuation is set to Unix is one long comment:
     352             :  *
     353             :  * \code
     354             :  *   # line continuation works with comments \
     355             :  *   just like with any other line... because the \
     356             :  *   continuation character and the newline characters \
     357             :  *   just get removed before the get_line() function \
     358             :  *   returns...
     359             :  * \endcode
     360             :  *
     361             :  * \return The comment flags.
     362             :  *
     363             :  * \sa is_comment()
     364             :  */
     365       26189 : comment_t conf_file_setup::get_comment() const
     366             : {
     367       26189 :     return f_comment;
     368             : }
     369             : 
     370             : 
     371             : /** \brief Get the accepted section operators.
     372             :  *
     373             :  * This function returns the flags representing which of the
     374             :  * section operators are accepted.
     375             :  *
     376             :  * We currently support the following types of sections:
     377             :  *
     378             :  * \li SECTION_OPERATOR_NONE -- no sections are accepted.
     379             :  * \li SECTION_OPERATOR_C -- the period (`.`) is viewed as a section/name
     380             :  * separator as when you access a variable member in a structure.
     381             :  * \li SECTION_OPERATOR_CPP -- the scope operator (`::`) is viewed as a
     382             :  * section/name separator; if used at the very beginning, it is viewed
     383             :  * as "global scope" and whatever other section is currently active is
     384             :  * ignored.
     385             :  * \li SECTION_OPERATOR_BLOCK -- the configuration files can include
     386             :  * opening (`{`) and closing (`}`) curvly brackets to group parameters
     387             :  * together; a name must preceed the opening bracket, it represents
     388             :  * the section name.
     389             :  * \li SECTION_OPERATOR_INI_FILE -- like in the MS-DOS .ini files, the
     390             :  * configuration file can include square brackets to mark sections; this
     391             :  * method limits the number of section names to one level.
     392             :  *
     393             :  * \bug
     394             :  * The INI file support does not verify that a section name does not
     395             :  * itself include more sub-sections. For example, the following would
     396             :  * be three section names:
     397             :  * \bug
     398             :  * \code
     399             :  * [a::b::c]
     400             :  * var=123
     401             :  * \endcode
     402             :  * \bug
     403             :  * So in effect, the variable named `var` ends up in section `a`,
     404             :  * sub-section `b`, and sub-sub-section `c` (or section `a::b::c`.)
     405             :  * Before saving the results in the parameters, all section operators
     406             :  * get transformed to the C++ scope (`::`) operator, which is why that
     407             :  * operator used in any name ends up looking like a section separator.
     408             :  */
     409       44754 : section_operator_t conf_file_setup::get_section_operator() const
     410             : {
     411       44754 :     return f_section_operator;
     412             : }
     413             : 
     414             : 
     415             : /** \brief Transform the setup in a URL.
     416             :  *
     417             :  * This function transforms the configuration file setup in a unique URL.
     418             :  * This URL allows us to verify that two setup are the same so when
     419             :  * attempting to reload the same configuration file, we can make sure
     420             :  * you are attempting to do so with the same URL.
     421             :  *
     422             :  * This is because trying to read the same file with, for example, line
     423             :  * continuation set to Unix the first time and then set to MS-DOS the
     424             :  * second time would not load the same thing is either line continuation
     425             :  * was used.
     426             :  *
     427             :  * \todo
     428             :  * We should look into have a set_config_url() or have a constructor
     429             :  * which accepts a URL.
     430             :  *
     431             :  * \return The URL representing this setup.
     432             :  */
     433       42048 : std::string conf_file_setup::get_config_url() const
     434             : {
     435       42048 :     if(f_url.empty())
     436             :     {
     437       57134 :         std::stringstream ss;
     438             : 
     439             :         ss << "file://"
     440       85701 :            << (f_filename.empty()
     441       57134 :                     ? "/<empty>"
     442       28567 :                     : f_filename);
     443             : 
     444       57134 :         std::vector<std::string> params;
     445       28567 :         if(f_line_continuation != line_continuation_t::line_continuation_unix)
     446             :         {
     447       46274 :             std::string name;
     448       23137 :             switch(f_line_continuation)
     449             :             {
     450        4223 :             case line_continuation_t::line_continuation_single_line:
     451        4223 :                 name = "single-line";
     452        4223 :                 break;
     453             : 
     454        4727 :             case line_continuation_t::line_continuation_rfc_822:
     455        4727 :                 name = "rfc-822";
     456        4727 :                 break;
     457             : 
     458        4727 :             case line_continuation_t::line_continuation_msdos:
     459        4727 :                 name = "msdos";
     460        4727 :                 break;
     461             : 
     462             :             // we should not ever receive this one since we don't enter
     463             :             // this block when the value is "unix"
     464             :             //
     465             :             //case line_continuation_t::line_continuation_unix:
     466             :             //    name = "unix";
     467             :             //    break;
     468             : 
     469        4728 :             case line_continuation_t::line_continuation_fortran:
     470        4728 :                 name = "fortran";
     471        4728 :                 break;
     472             : 
     473        4727 :             case line_continuation_t::line_continuation_semicolon:
     474        4727 :                 name = "semi-colon";
     475        4727 :                 break;
     476             : 
     477           5 :             default:
     478           5 :                 throw getopt_logic_error("unexpected line continuation.");
     479             : 
     480             :             }
     481       23132 :             params.push_back("line-continuation=" + name);
     482             :         }
     483             : 
     484       28562 :         if(f_assignment_operator != ASSIGNMENT_OPERATOR_EQUAL)
     485             :         {
     486       42326 :             std::vector<std::string> assignments;
     487       21163 :             if((f_assignment_operator & ASSIGNMENT_OPERATOR_EQUAL) != 0)
     488             :             {
     489       10577 :                 assignments.push_back("equal");
     490             :             }
     491       21163 :             if((f_assignment_operator & ASSIGNMENT_OPERATOR_COLON) != 0)
     492             :             {
     493       14111 :                 assignments.push_back("colon");
     494             :             }
     495       21163 :             if((f_assignment_operator & ASSIGNMENT_OPERATOR_SPACE) != 0)
     496             :             {
     497       14104 :                 assignments.push_back("space");
     498             :             }
     499       21163 :             if(!assignments.empty())
     500             :             {
     501       21163 :                 params.push_back("assignment-operator=" + boost::algorithm::join(assignments, ","));
     502             :             }
     503             :         }
     504             : 
     505             :         if(f_comment != COMMENT_INI | COMMENT_SHELL)
     506             :         {
     507       57124 :             std::vector<std::string> comment;
     508       28562 :             if((f_comment & COMMENT_INI) != 0)
     509             :             {
     510       12823 :                 comment.push_back("ini");
     511             :             }
     512       28562 :             if((f_comment & COMMENT_SHELL) != 0)
     513             :             {
     514       12366 :                 comment.push_back("shell");
     515             :             }
     516       28562 :             if((f_comment & COMMENT_CPP) != 0)
     517             :             {
     518       12379 :                 comment.push_back("cpp");
     519             :             }
     520       28562 :             if(comment.empty())
     521             :             {
     522        3816 :                 params.push_back("comment=none");
     523             :             }
     524             :             else
     525             :             {
     526       24746 :                 params.push_back("comment=" + boost::algorithm::join(comment, ","));
     527             :             }
     528             :         }
     529             : 
     530       28562 :         if(f_section_operator != SECTION_OPERATOR_INI_FILE)
     531             :         {
     532       53116 :             std::vector<std::string> section_operator;
     533       26558 :             if((f_section_operator & SECTION_OPERATOR_C) != 0)
     534             :             {
     535       13005 :                 section_operator.push_back("c");
     536             :             }
     537       26558 :             if((f_section_operator & SECTION_OPERATOR_CPP) != 0)
     538             :             {
     539       12996 :                 section_operator.push_back("cpp");
     540             :             }
     541       26558 :             if((f_section_operator & SECTION_OPERATOR_BLOCK) != 0)
     542             :             {
     543       12991 :                 section_operator.push_back("block");
     544             :             }
     545       26558 :             if((f_section_operator & SECTION_OPERATOR_INI_FILE) != 0)
     546             :             {
     547       11432 :                 section_operator.push_back("ini-file");
     548             :             }
     549       26558 :             if(!section_operator.empty())
     550             :             {
     551       24444 :                 params.push_back("section-operator=" + boost::algorithm::join(section_operator, ","));
     552             :             }
     553             :         }
     554             : 
     555       57124 :         std::string const query_string(boost::algorithm::join(params, "&"));
     556       28562 :         if(!query_string.empty())
     557             :         {
     558             :             ss << '?'
     559       28562 :                << query_string;
     560             :         }
     561             : 
     562       28562 :         f_url = ss.str();
     563             :     }
     564             : 
     565       42043 :     return f_url;
     566             : }
     567             : 
     568             : 
     569             : 
     570             : 
     571             : /** \brief Create and read a conf_file.
     572             :  *
     573             :  * This function creates a new conf_file object unless one with the same
     574             :  * filename already exists.
     575             :  *
     576             :  * If the configuration file was already loaded, then that pointer gets
     577             :  * returned instead of reloading the file. There is currently no API to
     578             :  * allow for the removal because another thread or function may have
     579             :  * the existing pointer cached and we want all instances of a configuration
     580             :  * file to be the same (i.e. if you update the value of a parameter then
     581             :  * that new value should be visible by all the users of that configuration
     582             :  * file.) Therefore, you can think of a configuration file as a global
     583             :  * variable.
     584             :  *
     585             :  * \note
     586             :  * Any number of call this function to load a given file always returns
     587             :  * exactly the same pointer.
     588             :  *
     589             :  * \todo
     590             :  * With the communicator, we will at some point implement a class
     591             :  * used to detect that a file changed, allowing us to get a signal
     592             :  * and reload the file as required. This get_conf_file() function
     593             :  * will greatly benefit from such since that way we can automatically
     594             :  * reload the configuration file. In other words, process A could
     595             :  * make a change, then process B reloads and sees the change that
     596             :  * process A made. Such an implementation will require a proper
     597             :  * locking mechanism of the configuration files while modifications
     598             :  * are being performed.
     599             :  *
     600             :  * \param[in] setup  The settings to be used in this configuration file reader.
     601             :  *
     602             :  * \return A pointer to the configuration file data.
     603             :  */
     604        3323 : conf_file::pointer_t conf_file::get_conf_file(conf_file_setup const & setup)
     605             : {
     606        6646 :     cppthread::guard lock(get_global_mutex());
     607             : 
     608        3323 :     auto it(g_conf_files.find(setup.get_filename()));
     609        3323 :     if(it != g_conf_files.end())
     610             :     {
     611        3023 :         if(it->second->get_setup().get_config_url() != setup.get_config_url())
     612             :         {
     613             :             throw getopt_logic_error("trying to load configuration file \""
     614        5250 :                                        + setup.get_config_url()
     615        7875 :                                        + "\" but an existing configuration file with the same name was loaded with URL: \""
     616       10500 :                                        + it->second->get_setup().get_config_url()
     617        7875 :                                        + "\".");
     618             :         }
     619         398 :         return it->second;
     620             :     }
     621             : 
     622             :     // TODO: look into not blocking forever?
     623             :     //
     624         600 :     conf_file::pointer_t cf(new conf_file(setup));
     625         300 :     g_conf_files[setup.get_filename()] = cf;
     626         300 :     return cf;
     627             : }
     628             : 
     629             : 
     630             : /** \brief Save the configuration file.
     631             :  *
     632             :  * This function saves the current data from this configuration file to
     633             :  * the file. It overwrites the existing file.
     634             :  *
     635             :  * Note that when you load the configuration, you may get data from
     636             :  * many different configuration files. This very file will only
     637             :  * include the data that was loaded from this file, though, and whatever
     638             :  * modifications you made.
     639             :  *
     640             :  * If the conf is not marked as modified, the function returns immediately
     641             :  * with true.
     642             :  *
     643             :  * \param[in] create_backup  Whether to create a backup or not.
     644             :  *
     645             :  * \return true if the save worked as expected.
     646             :  */
     647           2 : bool conf_file::save_configuration(bool create_backup)
     648             : {
     649           2 :     if(f_modified)
     650             :     {
     651             :         // create backup?
     652             :         //
     653           1 :         if(create_backup)
     654             :         {
     655             :             // TODO: offer means to set the backup extension
     656             :             //
     657           2 :             std::string const backup_filename(f_setup.get_filename() + ".bak");
     658             : 
     659           2 :             if(unlink(backup_filename.c_str()) != 0
     660           1 :             && errno != ENOENT)
     661             :             {
     662             :                 f_errno = errno;   // LCOV_EXCL_LINE
     663             :                 return false;      // LCOV_EXCL_LINE
     664             :             }
     665             : 
     666           1 :             if(rename(f_setup.get_filename().c_str(), backup_filename.c_str()) != 0)
     667             :             {
     668             :                 f_errno = errno;   // LCOV_EXCL_LINE
     669             :                 return false;      // LCOV_EXCL_LINE
     670             :             }
     671             :         }
     672             : 
     673             :         // save parameters to file
     674             :         //
     675           2 :         std::ofstream conf;
     676           1 :         conf.open(f_setup.get_filename().c_str());
     677           1 :         if(!conf.is_open())
     678             :         {
     679             :             f_errno = errno;   // LCOV_EXCL_LINE
     680             :             return false;      // LCOV_EXCL_LINE
     681             :         }
     682             : 
     683           1 :         time_t const now(time(nullptr));
     684           1 :         tm t;
     685           1 :         gmtime_r(&now, &t);
     686           1 :         char str_date[16];
     687           1 :         strftime(str_date, sizeof(str_date), "%Y/%m/%d", &t);
     688           1 :         char str_time[16];
     689           1 :         strftime(str_time, sizeof(str_time), "%H:%M:%S", &t);
     690             : 
     691             :         // header warning with date & time
     692             :         //
     693           1 :         conf << "# This file was auto-generated by snap_config.cpp on " << str_date << " at " << str_time << "." << std::endl
     694           1 :              << "# Making modifications here is likely safe unless the tool handling this" << std::endl
     695           1 :              << "# configuration file is actively working on it while you do the edits." << std::endl;
     696           4 :         for(auto p : f_parameters)
     697             :         {
     698           3 :             conf << p.first << "=";
     699             : 
     700             :             // prevent saving \r and \n characters as is when part of the
     701             :             // value; also double \ otherwise reading those back would fail
     702             :             //
     703           6 :             std::string value(p.second);
     704           3 :             boost::replace_all(value, "\\", "\\\\");
     705           3 :             boost::replace_all(value, "\r", "\\r");
     706           3 :             boost::replace_all(value, "\n", "\\n");
     707           3 :             boost::replace_all(value, "\t", "\\t");
     708           3 :             conf << value << std::endl;
     709             : 
     710           3 :             if(!conf)
     711             :             {
     712             :                 return false;   // LCOV_EXCL_LINE
     713             :             }
     714             :         }
     715             : 
     716             :         // it all worked, it's considered saved now
     717             :         //
     718           1 :         f_modified = false;
     719             :     }
     720             : 
     721           2 :     return true;
     722             : }
     723             : 
     724             : 
     725             : /** \brief Initialize and read a configuration file.
     726             :  *
     727             :  * This constructor initializes this conf_file object and then reads the
     728             :  * corresponding configuration file.
     729             :  *
     730             :  * Note that you have to use the create_conf_file() function for you
     731             :  * to be able to create a configuration file. It is done that way became
     732             :  * a file can be read only once. Once loaded, it gets cached until your
     733             :  * application quits.
     734             :  *
     735             :  * \param[in] setup  The configuration file setup.
     736             :  */
     737         300 : conf_file::conf_file(conf_file_setup const & setup)
     738         300 :     : f_setup(setup)
     739             : {
     740         300 :     read_configuration();
     741         300 : }
     742             : 
     743             : 
     744             : /** \brief Get the configuration file setup.
     745             :  *
     746             :  * This function returns a copy of the setup used to load this
     747             :  * configuration file.
     748             :  *
     749             :  * \note
     750             :  * This function has no mutex protection because the setup can't
     751             :  * change so there is no multi-thread protection necessary (the
     752             :  * fact that you hold a shared pointer to the conf_file object
     753             :  * is enough protection in this case.)
     754             :  *
     755             :  * \return A reference to this configuration file setup.
     756             :  */
     757        5796 : conf_file_setup const & conf_file::get_setup() const
     758             : {
     759        5796 :     return f_setup;
     760             : }
     761             : 
     762             : 
     763             : /** \brief Add a callback to detect when changes happen.
     764             :  *
     765             :  * This function is used to attach a callback to this configuration file.
     766             :  * This is useful if you'd like to know when a change happen to a parameter
     767             :  * in this configuration file.
     768             :  *
     769             :  * The callbacks get called when:
     770             :  *
     771             :  * \li The set_parameter() is called and the parameter gets created.
     772             :  * \li The set_parameter() is called and the parameter gets updated.
     773             :  * \li The erase_parameter() is called and the parameter gets erased.
     774             :  *
     775             :  * You can cancel your callback by calling the remove_callback() function
     776             :  * with the identifier returned by this function.
     777             :  *
     778             :  * To attach another object to your callback, you can either create
     779             :  * a callback which is attached to your object and a function
     780             :  * member or use std::bind() to attach the object to the function
     781             :  * call.
     782             :  *
     783             :  * If you specifcy a \p parameter_name, the callback is called only if the
     784             :  * parameter has that specific name.
     785             :  *
     786             :  * \param[in] c  The new callback std::function.
     787             :  * \param[in] parameter_name  The parameter name or an empty string.
     788             :  *
     789             :  * \return The callback identifier (useful if you want to be able to remove it).
     790             :  */
     791           2 : conf_file::callback_id_t conf_file::add_callback(
     792             :           callback_t const & c
     793             :         , std::string const & parameter_name)
     794             : {
     795           4 :     cppthread::guard lock(get_global_mutex());
     796             : 
     797           2 :     ++f_next_callback_id;
     798           2 :     f_callbacks.emplace_back(f_next_callback_id, c, parameter_name);
     799           4 :     return f_next_callback_id;
     800             : }
     801             : 
     802             : 
     803             : /** \brief Remove a callback.
     804             :  *
     805             :  * This function is the opposite of the add_callback(). It removes a callback
     806             :  * that you previously added. This is useful if you are interested in hearing
     807             :  * about the changing values when set a first time but are not interested at
     808             :  * all about future changes.
     809             :  *
     810             :  * \param[in] id  The id returned by the add_callback() function.
     811             :  */
     812           2 : void conf_file::remove_callback(callback_id_t id)
     813             : {
     814           4 :     cppthread::guard lock(get_global_mutex());
     815             : 
     816           2 :     auto it(std::find_if(
     817             :               f_callbacks.begin()
     818             :             , f_callbacks.end()
     819           1 :             , [id](auto e)
     820           1 :             {
     821           1 :                 return e.f_id == id;
     822           3 :             }));
     823           2 :     if(it != f_callbacks.end())
     824             :     {
     825           1 :         f_callbacks.erase(it);
     826             :     }
     827           2 : }
     828             : 
     829             : 
     830             : /** \brief Call whenever the value changed so we can handle callbacks.
     831             :  *
     832             :  * This function is called on a change of the internal values.
     833             :  *
     834             :  * The function is used to call the callbacks that were added to this
     835             :  * option_info object. The function first copies the existing list of
     836             :  * callbacks so you can safely update the list from within a callback.
     837             :  *
     838             :  * \warning
     839             :  * Destroying your advgetopt::getopt option is not safe while a callback
     840             :  * is running.
     841             :  */
     842          12 : void conf_file::value_changed(
     843             :           callback_action_t action
     844             :         , std::string const & parameter_name
     845             :         , std::string const & value)
     846             : {
     847          24 :     callback_vector_t callbacks;
     848          12 :     callbacks.reserve(f_callbacks.size());
     849             : 
     850             :     {
     851          24 :         cppthread::guard lock(get_global_mutex());
     852          12 :         callbacks = f_callbacks;
     853             :     }
     854             : 
     855          18 :     for(auto & e : callbacks)
     856             :     {
     857          12 :         if(e.f_parameter_name.empty()
     858           6 :         || e.f_parameter_name == parameter_name)
     859             :         {
     860           6 :             e.f_callback(shared_from_this(), action, parameter_name, value);
     861             :         }
     862             :     }
     863          12 : }
     864             : 
     865             : 
     866             : /** \brief Get the error number opening/reading the configuration file.
     867             :  *
     868             :  * The class registers the errno value whenever an I/O error happens
     869             :  * while handling the configuration file. In most cases the function
     870             :  * is expected to return 0.
     871             :  *
     872             :  * The ENOENT error should not happen since the setup is going to be
     873             :  * marked as invalid when a configuration file does not exist and
     874             :  * you should not end up creation a conf_file object when that
     875             :  * happens. However, it is expected when you want to make some
     876             :  * changes to a few parameters and save them back to file (i.e. 
     877             :  * the very first time there will be no file under the writable
     878             :  * configuration folder.)
     879             :  *
     880             :  * \return The last errno detected while accessing the configuration file.
     881             :  */
     882         152 : int conf_file::get_errno() const
     883             : {
     884         304 :     cppthread::guard lock(get_global_mutex());
     885             : 
     886         304 :     return f_errno;
     887             : }
     888             : 
     889             : 
     890             : /** \brief Get a list of sections.
     891             :  *
     892             :  * This function returns a copy of the list of sections defined in
     893             :  * this configuration file. In most cases, you should not need this
     894             :  * function since you are expected to know what parameters may be
     895             :  * defined. There are times though when it can be very practical.
     896             :  * For example, the options_config.cpp makes use of it since each
     897             :  * section is a parameter which we do not know the name of until
     898             :  * we have access to this array of sections.
     899             :  *
     900             :  * \note
     901             :  * We return a list because in a multithread environment another thread
     902             :  * may decide to make changes to the list of parameters which has the
     903             :  * side effect of eventually adding a section.
     904             :  *
     905             :  * \return A copy of the list of sections.
     906             :  */
     907         692 : conf_file::sections_t conf_file::get_sections() const
     908             : {
     909        1384 :     cppthread::guard lock(get_global_mutex());
     910             : 
     911        1384 :     return f_sections;
     912             : }
     913             : 
     914             : 
     915             : /** \brief Get a list of parameters.
     916             :  *
     917             :  * This function returns a copy of the list of parameters defined in
     918             :  * this configuration file.
     919             :  *
     920             :  * \note
     921             :  * We return a list because in a multithread environment another thread
     922             :  * may decide to make changes to the list of parameters (including
     923             :  * erasing a parameter.)
     924             :  *
     925             :  * \return A copy of the list of parameters.
     926             :  */
     927         375 : conf_file::parameters_t conf_file::get_parameters() const
     928             : {
     929         750 :     cppthread::guard lock(get_global_mutex());
     930             : 
     931         750 :     return f_parameters;
     932             : }
     933             : 
     934             : 
     935             : /** \brief Check whether a parameter is defined.
     936             :  *
     937             :  * This function checks for the existance of a parameter. It is a good
     938             :  * idea to first check for the existance of a parameter since the
     939             :  * get_parameter() function may otherwise return an empty string and
     940             :  * you cannot know whether that empty string means that the parameter
     941             :  * was not defined or it was set to the empty string.
     942             :  *
     943             :  * \param[in] name  The name of the parameter to check.
     944             :  *
     945             :  * \return true if the parameter is defined, false otherwise.
     946             :  *
     947             :  * \sa get_parameter()
     948             :  * \sa set_parameter()
     949             :  */
     950         628 : bool conf_file::has_parameter(std::string name) const
     951             : {
     952         628 :     std::replace(name.begin(), name.end(), '_', '-');
     953             : 
     954        1256 :     cppthread::guard lock(get_global_mutex());
     955             : 
     956         628 :     auto it(f_parameters.find(name));
     957        1256 :     return it != f_parameters.end();
     958             : }
     959             : 
     960             : 
     961             : /** \brief Get the named parameter.
     962             :  *
     963             :  * This function searches for the specified parameter. If that parameter
     964             :  * exists, then its value is returned. Note that the value of a parameter
     965             :  * may be the empty string.
     966             :  *
     967             :  * If the parameter does not exist, the function returns the empty string.
     968             :  * To distinguish between an undefined parameter and a parameter set to
     969             :  * the empty string, use the has_parameter() function.
     970             :  *
     971             :  * \param[in] name  The name of the parameter to retrieve.
     972             :  *
     973             :  * \return The current value of the parameter or an empty string.
     974             :  *
     975             :  * \sa has_parameter()
     976             :  * \sa set_parameter()
     977             :  */
     978         616 : std::string conf_file::get_parameter(std::string name) const
     979             : {
     980         616 :     std::replace(name.begin(), name.end(), '_', '-');
     981             : 
     982        1232 :     cppthread::guard lock(get_global_mutex());
     983             : 
     984         616 :     auto it(f_parameters.find(name));
     985         616 :     if(it != f_parameters.end())
     986             :     {
     987         473 :         return it->second;
     988             :     }
     989         143 :     return std::string();
     990             : }
     991             : 
     992             : 
     993             : /** \brief Set a parameter.
     994             :  *
     995             :  * This function sets a parameter to the specified value.
     996             :  *
     997             :  * The name of the value includes the \p section names and the \p name
     998             :  * parameter concatenated with a C++ scopre operator (::) in between
     999             :  * (unless \p section is the empty string in which case no scope operator
    1000             :  * gets added.)
    1001             :  *
    1002             :  * When the \p name parameter starts with a scope parameter, the \p section
    1003             :  * parameter is ignored. This allows one to ignore the current section
    1004             :  * (i.e. the last '[...]' or any '\<name> { ... }').
    1005             :  *
    1006             :  * The \p section parameter is a list of section names separated by
    1007             :  * the C++ scope operator (::).
    1008             :  *
    1009             :  * The \p name parameter may include C (.) and/or C++ (::) section
    1010             :  * separators when the configuration file supports those. Internally,
    1011             :  * those get moved to the \p section parameter. That allows us to
    1012             :  * verify that the number of sections is valid.
    1013             :  *
    1014             :  * This function may be called any number of time. The last value is
    1015             :  * the one kept. While reading the configuration file, though, a warning
    1016             :  * is generated when a parameter gets overwritten since this is often the
    1017             :  * source of a problem.
    1018             :  *
    1019             :  * In the following configuration file:
    1020             :  *
    1021             :  * \code
    1022             :  *     var=name
    1023             :  *     var=twice
    1024             :  * \endcode
    1025             :  *
    1026             :  * The variable named `var` will be set to `twice` on return and a warning
    1027             :  * will have been generated warning about the fact that the variable was
    1028             :  * modified while reading the configuration file.
    1029             :  *
    1030             :  * The full name of the parameter (i.e. section + name) cannot include any
    1031             :  * of the following characters:
    1032             :  *
    1033             :  * \li control characters (any character between 0x00 and 0x1F)
    1034             :  * \li a space (0x20)
    1035             :  * \li a backslash (`\`)
    1036             :  * \li quotation (`"` and `'`)
    1037             :  * \li comment (';', '#', '/')
    1038             :  * \li assignment ('=', ':', '?', '+')
    1039             :  *
    1040             :  * \note
    1041             :  * The \p section and \p name parameters have any underscore (`_`)
    1042             :  * replaced with dashes (`-`) before getting used. The very first
    1043             :  * character can be a dash. This allows you to therefore create
    1044             :  * parameters which cannot appear in a configuration file, an
    1045             :  * environment variable or on the command line (where parameter are
    1046             :  * not allowed to start with a dash.)
    1047             :  *
    1048             :  * \warning
    1049             :  * It is important to note that when a \p name includes a C++ scope
    1050             :  * operator, the final parameter name looks like it includes a section
    1051             :  * name (i.e. the name "a::b", when the C++ section flag is not set,
    1052             :  * is accepted as is; so the final parameter name is going to be "a::b"
    1053             :  * and therefore it will include what looks like a section name.)
    1054             :  * There should not be any concern about this small \em glitch though
    1055             :  * since you do not have to accept any such parameter.
    1056             :  *
    1057             :  * \param[in] section  The list of section or an empty string.
    1058             :  * \param[in] name  The name of the parameter.
    1059             :  * \param[in] value  The value of the parameter.
    1060             :  */
    1061         672 : bool conf_file::set_parameter(std::string section, std::string name, std::string const & value)
    1062             : {
    1063             :     // use the tokenize_string() function because we do not want to support
    1064             :     // quoted strings in this list of sections which our split_string()
    1065             :     // does automatically
    1066             :     //
    1067        1344 :     string_list_t section_list;
    1068             : 
    1069         672 :     std::replace(section.begin(), section.end(), '_', '-');
    1070         672 :     std::replace(name.begin(), name.end(), '_', '-');
    1071             : 
    1072         672 :     char const * n(name.c_str());
    1073             : 
    1074             :     // global scope? if so ignore the section parameter
    1075             :     //
    1076        1344 :     if((f_setup.get_section_operator() & SECTION_OPERATOR_CPP) != 0
    1077          32 :     && n[0] == ':'
    1078         674 :     && n[1] == ':')
    1079             :     {
    1080           2 :         do
    1081             :         {
    1082           4 :             ++n;
    1083             :         }
    1084           4 :         while(*n == ':');
    1085             :     }
    1086             :     else
    1087             :     {
    1088        1340 :         snap::tokenize_string(section_list
    1089             :                             , section
    1090             :                             , "::"
    1091             :                             , true
    1092        1340 :                             , std::string()
    1093             :                             , &snap::string_predicate<string_list_t>);
    1094             :     }
    1095             : 
    1096         672 :     char const * s(n);
    1097        7974 :     while(*n != '\0')
    1098             :     {
    1099        7306 :         if((f_setup.get_section_operator() & SECTION_OPERATOR_C) != 0
    1100        3653 :         && *n == '.')
    1101             :         {
    1102          32 :             if(s == n)
    1103             :             {
    1104           2 :                 cppthread::log << cppthread::log_level_t::error
    1105           1 :                                << "option name \""
    1106           1 :                                << name
    1107           1 :                                << "\" cannot start with a period (.)."
    1108           2 :                                << cppthread::end;
    1109           1 :                 return false;
    1110             :             }
    1111          31 :             section_list.push_back(std::string(s, n - s));
    1112           8 :             do
    1113             :             {
    1114          39 :                 ++n;
    1115             :             }
    1116          39 :             while(*n == '.');
    1117          31 :             s = n;
    1118             :         }
    1119        7242 :         else if((f_setup.get_section_operator() & SECTION_OPERATOR_CPP) != 0
    1120          66 :              && n[0] == ':'
    1121        3633 :              && n[1] == ':')
    1122             :         {
    1123          12 :             if(s == n)
    1124             :             {
    1125           2 :                 cppthread::log << cppthread::log_level_t::error
    1126           1 :                                << "option name \""
    1127           1 :                                << name
    1128           1 :                                << "\" cannot start with a scope operator (::)."
    1129           2 :                                << cppthread::end;
    1130           1 :                 return false;
    1131             :             }
    1132          11 :             section_list.push_back(std::string(s, n - s));
    1133          11 :             do
    1134             :             {
    1135          22 :                 ++n;
    1136             :             }
    1137          22 :             while(*n == ':');
    1138          11 :             s = n;
    1139             :         }
    1140             :         else
    1141             :         {
    1142        3609 :             ++n;
    1143             :         }
    1144             :     }
    1145         670 :     if(s == n)
    1146             :     {
    1147           4 :         cppthread::log << cppthread::log_level_t::error
    1148           2 :                        << "option name \""
    1149           2 :                        << name
    1150           2 :                        << "\" cannot end with a section operator or be empty."
    1151           4 :                        << cppthread::end;
    1152           2 :         return false;
    1153             :     }
    1154        1336 :     std::string param_name(s, n - s);
    1155             : 
    1156        1336 :     std::string const section_name(boost::algorithm::join(section_list, "::"));
    1157             : 
    1158        1336 :     if(f_setup.get_section_operator() == SECTION_OPERATOR_NONE
    1159         668 :     && !section_list.empty())
    1160             :     {
    1161           2 :         cppthread::log << cppthread::log_level_t::error
    1162           1 :                        << "option name \""
    1163           1 :                        << name
    1164           1 :                        << "\" cannot be added to section \""
    1165           1 :                        << section_name
    1166           1 :                        << "\" because there is no section support for this configuration file."
    1167           2 :                        << cppthread::end;
    1168           1 :         return false;
    1169             :     }
    1170        1334 :     if((f_setup.get_section_operator() & SECTION_OPERATOR_ONE_SECTION) != 0
    1171         667 :     && section_list.size() > 1)
    1172             :     {
    1173          10 :         cppthread::log << cppthread::log_level_t::error
    1174           5 :                        << "option name \""
    1175           5 :                        << name
    1176           5 :                        << "\" cannot be added to section \""
    1177           5 :                        << section_name
    1178           5 :                        << "\" because this configuration only accepts one section level."
    1179          10 :                        << cppthread::end;
    1180           5 :         return false;
    1181             :     }
    1182             : 
    1183         662 :     section_list.push_back(param_name);
    1184        1324 :     std::string const full_name(boost::algorithm::join(section_list, "::"));
    1185             : 
    1186             :     // verify that each section name only includes characters we accept
    1187             :     // for a parameter name
    1188             :     //
    1189             :     // WARNING: we do not test with full_name because it includes ':'
    1190             :     //
    1191        1371 :     for(auto sn : section_list)
    1192             :     {
    1193        4522 :         for(char const * f(sn.c_str()); *f != '\0'; ++f)
    1194             :         {
    1195        3813 :             switch(*f)
    1196             :             {
    1197         109 :             case '\001':    // forbid controls
    1198             :             case '\002':
    1199             :             case '\003':
    1200             :             case '\004':
    1201             :             case '\005':
    1202             :             case '\006':
    1203             :             case '\007':
    1204             :             case '\010':
    1205             :             case '\011':
    1206             :             case '\012':
    1207             :             case '\013':
    1208             :             case '\014':
    1209             :             case '\015':
    1210             :             case '\016':
    1211             :             case '\017':
    1212             :             case '\020':
    1213             :             case '\021':
    1214             :             case '\022':
    1215             :             case '\023':
    1216             :             case '\024':
    1217             :             case '\025':
    1218             :             case '\026':
    1219             :             case '\027':
    1220             :             case '\030':
    1221             :             case '\031':
    1222             :             case '\032':
    1223             :             case '\033':
    1224             :             case '\034':
    1225             :             case '\035':
    1226             :             case '\036':
    1227             :             case '\037':
    1228             :             case ' ':       // forbid spaces
    1229             :             case '\'':      // forbid all quotes
    1230             :             case '"':       // forbid all quotes
    1231             :             case ';':       // forbid all comment operators
    1232             :             case '#':       // forbid all comment operators
    1233             :             case '/':       // forbid all comment operators
    1234             :             case '=':       // forbid all assignment operators
    1235             :             case ':':       // forbid all assignment operators
    1236             :             case '?':       // forbid all assignment operators (for later)
    1237             :             case '+':       // forbid all assignment operators (for later)
    1238             :             case '\\':      // forbid backslashes
    1239         218 :                 cppthread::log << cppthread::log_level_t::error
    1240         109 :                                << "parameter \""
    1241         109 :                                << full_name
    1242         109 :                                << "\" on line "
    1243         109 :                                << f_line
    1244         109 :                                << " in configuration file \""
    1245         109 :                                << f_setup.get_filename()
    1246         109 :                                << "\" includes a character not acceptable for a section or parameter name (controls, space, quotes, and \";#/=:?+\\\")."
    1247         218 :                                << cppthread::end;
    1248         109 :                 return false;
    1249             : 
    1250             :             }
    1251             :         }
    1252             :     }
    1253             : 
    1254        1106 :     cppthread::guard lock(get_global_mutex());
    1255             : 
    1256             :     // add the section to the list of sections
    1257             :     //
    1258             :     // TODO: should we have a list of all the parent sections? Someone can
    1259             :     //       write "a::b::c::d = 123" and we currently only get section
    1260             :     //       "a::b::c", no section "a" and no section "a::b".
    1261             :     //
    1262         553 :     if(!section_name.empty())
    1263             :     {
    1264         138 :         f_sections.insert(section_name);
    1265             :     }
    1266             : 
    1267         553 :     callback_action_t action(callback_action_t::created);
    1268         553 :     auto it(f_parameters.find(full_name));
    1269         553 :     if(it == f_parameters.end())
    1270             :     {
    1271         542 :         f_parameters[full_name] = value;
    1272             :     }
    1273             :     else
    1274             :     {
    1275          11 :         if(f_reading)
    1276             :         {
    1277             :             // this is just a warning; it can be neat to know about such
    1278             :             // problems and fix them early
    1279             :             //
    1280           4 :             cppthread::log << cppthread::log_level_t::warning
    1281           2 :                            << "parameter \""
    1282           2 :                            << full_name
    1283           2 :                            << "\" on line "
    1284           2 :                            << f_line
    1285           2 :                            << " in configuration file \""
    1286           2 :                            << f_setup.get_filename()
    1287           2 :                            << "\" was found twice in the same configuration file."
    1288           4 :                            << cppthread::end;
    1289             :         }
    1290             : 
    1291          11 :         it->second = value;
    1292             : 
    1293          11 :         action = callback_action_t::updated;
    1294             :     }
    1295             : 
    1296         553 :     if(!f_reading)
    1297             :     {
    1298          11 :         f_modified = true;
    1299             : 
    1300          11 :         value_changed(action, full_name, value);
    1301             :     }
    1302             : 
    1303         553 :     return true;
    1304             : }
    1305             : 
    1306             : 
    1307             : /** \brief Erase the named parameter from this configuration file.
    1308             :  *
    1309             :  * This function can be used to remove the specified parameter from
    1310             :  * this configuration file.
    1311             :  *
    1312             :  * If that parameter is not defined in the file, then nothing happens.
    1313             :  *
    1314             :  * \param[in] name  The name of the parameter to remove.
    1315             :  *
    1316             :  * \return true if the parameter was removed, false if it did not exist.
    1317             :  */
    1318           2 : bool conf_file::erase_parameter(std::string name)
    1319             : {
    1320           2 :     std::replace(name.begin(), name.end(), '_', '-');
    1321             : 
    1322           2 :     auto it(f_parameters.find(name));
    1323           2 :     if(it == f_parameters.end())
    1324             :     {
    1325           1 :         return false;
    1326             :     }
    1327             : 
    1328           1 :     f_parameters.erase(it);
    1329             : 
    1330           1 :     if(!f_reading)
    1331             :     {
    1332           1 :         f_modified = true;
    1333             : 
    1334           1 :         value_changed(callback_action_t::erased, name, std::string());
    1335             :     }
    1336             : 
    1337           1 :     return true;
    1338             : }
    1339             : 
    1340             : 
    1341             : /** \brief Check whether this configuration file was modified.
    1342             :  *
    1343             :  * This function returns the value of the f_modified flag which is true
    1344             :  * if any value was createed, updated, or erased from the configuration
    1345             :  * file since after it was loaded.
    1346             :  *
    1347             :  * This tells you whether you should call the save() function, assuming
    1348             :  * you want to keep such changes.
    1349             :  *
    1350             :  * \return true if changes were made to this file parameters.
    1351             :  */
    1352          10 : bool conf_file::was_modified() const
    1353             : {
    1354          10 :     return f_modified;
    1355             : }
    1356             : 
    1357             : 
    1358             : /** \brief Read one characte from the input stream.
    1359             :  *
    1360             :  * This function reads one character from the input stream and returns it
    1361             :  * as an `int`.
    1362             :  *
    1363             :  * If there is an ungotten character (i.e. ungetc() was called) then that
    1364             :  * character is returned.
    1365             :  *
    1366             :  * When the end of the file is reached, this function returns -1.
    1367             :  *
    1368             :  * \note
    1369             :  * This function is oblivious of UTF-8. It should not matter since any
    1370             :  * Unicode character would anyway be treated as is.
    1371             :  *
    1372             :  * \param[in,out] in  The input stream.
    1373             :  *
    1374             :  * \return The character read or -1 when EOF is reached.
    1375             :  */
    1376       13225 : int conf_file::getc(std::ifstream & in)
    1377             : {
    1378       13225 :     if(f_unget_char != '\0')
    1379             :     {
    1380          34 :         int const r(f_unget_char);
    1381          34 :         f_unget_char = '\0';
    1382          34 :         return r;
    1383             :     }
    1384             : 
    1385       13191 :     char c;
    1386       13191 :     in.get(c);
    1387             : 
    1388       13191 :     if(!in)
    1389             :     {
    1390         196 :         return EOF;
    1391             :     }
    1392             : 
    1393       12995 :     return static_cast<std::uint8_t>(c);
    1394             : }
    1395             : 
    1396             : 
    1397             : /** \brief Restore one character.
    1398             :  *
    1399             :  * This function is used whenever we read one additional character to
    1400             :  * know whether a certain character followed another. For example, we
    1401             :  * check for a `'\\n'` whenever we find a `'\\r'`. However, if the
    1402             :  * character right after the `'\\r'` is not a `'\\n'` we call this
    1403             :  * ungetc() function so next time we can re-read that same character.
    1404             :  *
    1405             :  * \note
    1406             :  * You can call ungetc() only once between calls to getc(). The
    1407             :  * current buffer is just one single character. Right now our
    1408             :  * parser doesn't need more than that.
    1409             :  *
    1410             :  * \param[in] c  The character to restore.
    1411             :  */
    1412          34 : void conf_file::ungetc(int c)
    1413             : {
    1414          34 :     if(f_unget_char != '\0')
    1415             :     {
    1416             :         throw getopt_logic_error("conf_file::ungetc() called when the f_unget_char variable member is not '\\0'."); // LCOV_EXCL_LINE
    1417             :     }
    1418          34 :     f_unget_char = c;
    1419          34 : }
    1420             : 
    1421             : 
    1422             : /** \brief Get one line.
    1423             :  *
    1424             :  * This function reads one line. The function takes the line continuation
    1425             :  * setup in account. So for example a line that ends with a backslash
    1426             :  * continues on the next line when the line continuation is setup to Unix.
    1427             :  *
    1428             :  * Note that by default comments are also continued. So a backslash in
    1429             :  * Unix mode continues a comment on the next line.
    1430             :  *
    1431             :  * There is a special case with the semicolon continuation setup. When
    1432             :  * the line starts as a comment, it will end on the first standalone
    1433             :  * newline (i.e. a comment does not need to end with a semi-colon.)
    1434             :  *
    1435             :  * \param[in,out] in  The input stream.
    1436             :  * \param[out] line  Where the line gets saved.
    1437             :  *
    1438             :  * \return true if a line was read, false on EOF.
    1439             :  */
    1440        1058 : bool conf_file::get_line(std::ifstream & in, std::string & line)
    1441             : {
    1442        1058 :     line.clear();
    1443             : 
    1444             :     for(;;)
    1445             :     {
    1446       13137 :         int c(getc(in));
    1447       13137 :         if(c == EOF)
    1448             :         {
    1449         195 :             return false;
    1450             :         }
    1451       12942 :         if(c == ';'
    1452       12942 :         && f_setup.get_line_continuation() == line_continuation_t::line_continuation_semicolon)
    1453             :         {
    1454           1 :             return true;
    1455             :         }
    1456             : 
    1457       13003 :         while(c == '\n' || c == '\r')
    1458             :         {
    1459             :             // count the "\r\n" sequence as one line
    1460             :             //
    1461         892 :             if(c == '\r')
    1462             :             {
    1463          21 :                 c = getc(in);
    1464          21 :                 if(c != '\n')
    1465             :                 {
    1466           3 :                     ungetc(c);
    1467             :                 }
    1468          21 :                 c = '\n';
    1469             :             }
    1470             : 
    1471         892 :             ++f_line;
    1472         892 :             switch(f_setup.get_line_continuation())
    1473             :             {
    1474          76 :             case line_continuation_t::line_continuation_single_line:
    1475             :                 // continuation support
    1476          76 :                 return true;
    1477             : 
    1478          17 :             case line_continuation_t::line_continuation_rfc_822:
    1479          17 :                 c = getc(in);
    1480          17 :                 if(!iswspace(c))
    1481             :                 {
    1482          15 :                     ungetc(c);
    1483          15 :                     return true;
    1484             :                 }
    1485           2 :                 do
    1486             :                 {
    1487           4 :                     c = getc(in);
    1488             :                 }
    1489           4 :                 while(iswspace(c));
    1490           2 :                 break;
    1491             : 
    1492          17 :             case line_continuation_t::line_continuation_msdos:
    1493          34 :                 if(line.empty()
    1494          17 :                 || line.back() != '&')
    1495             :                 {
    1496          16 :                     return true;
    1497             :                 }
    1498           1 :                 line.pop_back();
    1499           1 :                 c = getc(in);
    1500           1 :                 break;
    1501             : 
    1502         748 :             case line_continuation_t::line_continuation_unix:
    1503        1496 :                 if(line.empty()
    1504         748 :                 || line.back() != '\\')
    1505             :                 {
    1506         737 :                     return true;
    1507             :                 }
    1508          11 :                 line.pop_back();
    1509          11 :                 c = getc(in);
    1510          11 :                 break;
    1511             : 
    1512          17 :             case line_continuation_t::line_continuation_fortran:
    1513          17 :                 c = getc(in);
    1514          17 :                 if(c != '&')
    1515             :                 {
    1516          16 :                     ungetc(c);
    1517          16 :                     return true;
    1518             :                 }
    1519           1 :                 c = getc(in);
    1520           1 :                 break;
    1521             : 
    1522          17 :             case line_continuation_t::line_continuation_semicolon:
    1523             :                 // if we have a comment, we want to return immediately;
    1524             :                 // at this time, the comments are not multi-line so
    1525             :                 // the call can return true only if we were reading the
    1526             :                 // very first line
    1527             :                 //
    1528          17 :                 if(is_comment(line.c_str()))
    1529             :                 {
    1530           1 :                     return true;
    1531             :                 }
    1532             :                 // the semicolon is checked earlier, just keep the newline
    1533             :                 // in this case (but not at the start)
    1534             :                 //
    1535          16 :                 if(!line.empty() || c != '\n')
    1536             :                 {
    1537          15 :                     line += c;
    1538             :                 }
    1539          16 :                 c = getc(in);
    1540          16 :                 break;
    1541             : 
    1542             :             }
    1543             :         }
    1544             : 
    1545             :         // we just read the last line
    1546       12080 :         if(c == EOF)
    1547             :         {
    1548           1 :             return true;
    1549             :         }
    1550             : 
    1551       12079 :         line += c;
    1552       12079 :     }
    1553             : }
    1554             : 
    1555             : 
    1556             : /** \brief Read a configuration file.
    1557             :  *
    1558             :  * This function reads a configuration file and saves all the parameters it
    1559             :  * finds in a map which can later be checked against an option table for
    1560             :  * validation.
    1561             :  *
    1562             :  * \todo
    1563             :  * Add support for quotes in configuration files as parameters are otherwise
    1564             :  * saved as a separated list of parameters losing the number of spaces between
    1565             :  * each entry.
    1566             :  */
    1567         300 : void conf_file::read_configuration()
    1568             : {
    1569         495 :     snap::safe_variable<decltype(f_reading)> safe_reading(f_reading, true);
    1570             : 
    1571         495 :     std::ifstream conf(f_setup.get_filename());
    1572         300 :     if(!conf)
    1573             :     {
    1574         105 :         f_errno = errno;
    1575         105 :         return;
    1576             :     }
    1577             : 
    1578         390 :     std::string current_section;
    1579         390 :     std::vector<std::string> sections;
    1580         390 :     std::string str;
    1581         195 :     f_line = 0;
    1582        1921 :     while(get_line(conf, str))
    1583             :     {
    1584         863 :         char const * s(str.c_str());
    1585         959 :         while(iswspace(*s))
    1586             :         {
    1587          48 :             ++s;
    1588             :         }
    1589        1726 :         if(*s == '\0'
    1590         863 :         || is_comment(s))
    1591             :         {
    1592             :             // skip empty lines and comments
    1593         148 :             continue;
    1594             :         }
    1595        1435 :         if((f_setup.get_section_operator() & SECTION_OPERATOR_BLOCK) != 0
    1596         715 :         && *s == '}')
    1597             :         {
    1598           5 :             current_section = sections.back();
    1599           5 :             sections.pop_back();
    1600           5 :             continue;
    1601             :         }
    1602         710 :         char const * str_name(s);
    1603         710 :         char const * e(nullptr);
    1604        8552 :         while(!is_assignment_operator(*s)
    1605        4013 :            && ((f_setup.get_section_operator() & SECTION_OPERATOR_BLOCK) == 0 || (*s != '{' && *s != '}'))
    1606        4013 :            && ((f_setup.get_section_operator() & SECTION_OPERATOR_INI_FILE) == 0 || *s != ']')
    1607        3975 :            && *s != '\0'
    1608        8591 :            && !iswspace(*s))
    1609             :         {
    1610        3921 :             ++s;
    1611             :         }
    1612         710 :         if(iswspace(*s))
    1613             :         {
    1614          42 :             e = s;
    1615         294 :             while(iswspace(*s))
    1616             :             {
    1617         126 :                 ++s;
    1618             :             }
    1619          87 :             if(*s != '\0'
    1620          42 :             && !is_assignment_operator(*s)
    1621          12 :             && (f_setup.get_assignment_operator() & ASSIGNMENT_OPERATOR_SPACE) == 0
    1622          51 :             && ((f_setup.get_section_operator() & SECTION_OPERATOR_BLOCK) == 0 || (*s != '{' && *s != '}')))
    1623             :             {
    1624           6 :                 cppthread::log << cppthread::log_level_t::error
    1625           3 :                                << "option name from \""
    1626           3 :                                << str
    1627           3 :                                << "\" on line "
    1628           3 :                                << f_line
    1629           3 :                                << " in configuration file \""
    1630           3 :                                << f_setup.get_filename()
    1631           3 :                                << "\" cannot include a space, missing assignment operator?"
    1632           6 :                                << cppthread::end;
    1633           3 :                 continue;
    1634             :             }
    1635             :         }
    1636         707 :         if(e == nullptr)
    1637             :         {
    1638         668 :             e = s;
    1639             :         }
    1640         708 :         if(e - str_name == 0)
    1641             :         {
    1642           2 :             cppthread::log << cppthread::log_level_t::error
    1643           1 :                            << "no option name in \""
    1644           1 :                            << str
    1645           1 :                            << "\" on line "
    1646           1 :                            << f_line
    1647           1 :                            << " from configuration file \""
    1648           1 :                            << f_setup.get_filename()
    1649           1 :                            << "\", missing name before the assignment operator?"
    1650           2 :                            << cppthread::end;
    1651           1 :             continue;
    1652             :         }
    1653        1408 :         std::string name(str_name, e - str_name);
    1654         706 :         std::replace(name.begin(), name.end(), '_', '-');
    1655         708 :         if(name[0] == '-')
    1656             :         {
    1657           4 :             cppthread::log << cppthread::log_level_t::error
    1658           2 :                            << "option names in configuration files cannot start with a dash or an underscore in \""
    1659           2 :                            << str
    1660           2 :                            << "\" on line "
    1661           2 :                            << f_line
    1662           2 :                            << " from configuration file \""
    1663           2 :                            << f_setup.get_filename()
    1664           2 :                            << "\"."
    1665           4 :                            << cppthread::end;
    1666           2 :             continue;
    1667             :         }
    1668        1408 :         if((f_setup.get_section_operator() & SECTION_OPERATOR_INI_FILE) != 0
    1669         215 :         && name.length() >= 1
    1670         215 :         && name[0] == '['
    1671         742 :         && *s == ']')
    1672             :         {
    1673          38 :             ++s;
    1674          39 :             if(!sections.empty())
    1675             :             {
    1676           2 :                 cppthread::log << cppthread::log_level_t::error
    1677           1 :                                << "`[...]` sections can't be used within a `section { ... }` on line "
    1678           1 :                                << f_line
    1679           1 :                                << " from configuration file \""
    1680           1 :                                << f_setup.get_filename()
    1681           1 :                                << "\"."
    1682           2 :                                << cppthread::end;
    1683           1 :                 continue;
    1684             :             }
    1685          41 :             while(iswspace(*s))
    1686             :             {
    1687           2 :                 ++s;
    1688             :             }
    1689          75 :             if(*s != '\0'
    1690          37 :             && !is_comment(s))
    1691             :             {
    1692           2 :                 cppthread::log << cppthread::log_level_t::error
    1693           1 :                                << "section names in configuration files cannot be followed by anything other than spaces in \""
    1694           1 :                                << str
    1695           1 :                                << "\" on line "
    1696           1 :                                << f_line
    1697           1 :                                << " from configuration file \""
    1698           1 :                                << f_setup.get_filename()
    1699           1 :                                << "\"."
    1700           2 :                                << cppthread::end;
    1701           1 :                 continue;
    1702             :             }
    1703          36 :             if(name.length() == 1)
    1704             :             {
    1705             :                 // "[]" removes the section
    1706             :                 //
    1707           1 :                 current_section.clear();
    1708             :             }
    1709             :             else
    1710             :             {
    1711          35 :                 current_section = name.substr(1);
    1712          35 :                 current_section += "::";
    1713             :             }
    1714             :         }
    1715        1332 :         else if((f_setup.get_section_operator() & SECTION_OPERATOR_BLOCK) != 0
    1716         666 :              && *s == '{')
    1717             :         {
    1718           6 :             sections.push_back(current_section);
    1719           6 :             current_section += name;
    1720           6 :             current_section += "::";
    1721             :         }
    1722             :         else
    1723             :         {
    1724         660 :             if(is_assignment_operator(*s))
    1725             :             {
    1726         642 :                 ++s;
    1727             :             }
    1728         722 :             while(iswspace(*s))
    1729             :             {
    1730          31 :                 ++s;
    1731             :             }
    1732         688 :             for(e = str.c_str() + str.length(); e > s; --e)
    1733             :             {
    1734         672 :                 if(!iswspace(e[-1]))
    1735             :                 {
    1736         644 :                     break;
    1737             :                 }
    1738             :             }
    1739         660 :             size_t const len(e - s);
    1740        1320 :             std::string value(s, len);
    1741         660 :             boost::replace_all(value, "\\\\", "\\");
    1742         660 :             boost::replace_all(value, "\\r", "\r");
    1743         660 :             boost::replace_all(value, "\\n", "\n");
    1744         660 :             boost::replace_all(value, "\\t", "\t");
    1745         660 :             set_parameter(current_section, name, unquote(value));
    1746             :         }
    1747             :     }
    1748         195 :     if(!conf.eof())
    1749             :     {
    1750             :         f_errno = errno;                                            // LCOV_EXCL_LINE
    1751             :         cppthread::log << cppthread::log_level_t::error             // LCOV_EXCL_LINE
    1752             :                        << "an error occurred while reading line "   // LCOV_EXCL_LINE
    1753             :                        << f_line                                    // LCOV_EXCL_LINE
    1754             :                        << " of configuration file \""               // LCOV_EXCL_LINE
    1755             :                        << f_setup.get_filename()                    // LCOV_EXCL_LINE
    1756             :                        << "\"."                                     // LCOV_EXCL_LINE
    1757             :                        << cppthread::end;                           // LCOV_EXCL_LINE
    1758             :     }
    1759         195 :     if(!sections.empty())
    1760             :     {
    1761           2 :         cppthread::log << cppthread::log_level_t::error
    1762           1 :                        << "unterminated `section { ... }`, the `}` is missing in configuration file \""
    1763           1 :                        << f_setup.get_filename()
    1764           1 :                        << "\"."
    1765           2 :                        << cppthread::end;
    1766             :     }
    1767             : }
    1768             : 
    1769             : 
    1770             : /** \brief Check whether `c` is an assignment operator.
    1771             :  *
    1772             :  * This function checks the \p c parameter to know whether it matches
    1773             :  * one of the character allowed as an assignment character.
    1774             :  *
    1775             :  * \param[in] c  The character to be checked.
    1776             :  *
    1777             :  * \return true if c is considered to represent an assignment character.
    1778             :  */
    1779     1119445 : bool conf_file::is_assignment_operator(int c) const
    1780             : {
    1781     1119445 :     assignment_operator_t const assignment_operator(f_setup.get_assignment_operator());
    1782     2238757 :     return ((assignment_operator & ASSIGNMENT_OPERATOR_EQUAL) != 0 && c == '=')
    1783     1118179 :         || ((assignment_operator & ASSIGNMENT_OPERATOR_COLON) != 0 && c == ':')
    1784     2237602 :         || ((assignment_operator & ASSIGNMENT_OPERATOR_SPACE) != 0 && std::iswspace(c));
    1785             : }
    1786             : 
    1787             : 
    1788             : /** \brief Check whether the string starts with a comment introducer.
    1789             :  *
    1790             :  * This function checks whether the \p s string starts with a comment.
    1791             :  *
    1792             :  * We support different types of comment introducers. This function
    1793             :  * checks the flags as defined in the constructor and returns true
    1794             :  * if the type of character introducer defines a comment.
    1795             :  *
    1796             :  * We currently support:
    1797             :  *
    1798             :  * \li .ini file comments, introduced by a semi-colon (;)
    1799             :  *
    1800             :  * \li Shell file comments, introduced by a hash character (#)
    1801             :  *
    1802             :  * \li C++ comment, introduced by two slashes (//)
    1803             :  *
    1804             :  * \param[in] s  The string to check for a comment.
    1805             :  *
    1806             :  * \return `true` if the string represents a comment.
    1807             :  */
    1808         836 : bool conf_file::is_comment(char const * s) const
    1809             : {
    1810         836 :     comment_t const comment(f_setup.get_comment());
    1811         836 :     if((comment & COMMENT_INI) != 0
    1812         242 :     && *s == ';')
    1813             :     {
    1814           5 :         return true;
    1815             :     }
    1816             : 
    1817         831 :     if((comment & COMMENT_SHELL) != 0
    1818         500 :     && *s == '#')
    1819             :     {
    1820          94 :         return true;
    1821             :     }
    1822             : 
    1823         737 :     if((comment & COMMENT_CPP) != 0
    1824          10 :     && s[0] == '/'
    1825           5 :     && s[1] == '/')
    1826             :     {
    1827           5 :         return true;
    1828             :     }
    1829             : 
    1830         732 :     return false;
    1831             : }
    1832             : 
    1833             : 
    1834             : /** \brief Returns true if \p c is considered to be a whitespace.
    1835             :  *
    1836             :  * Our iswspace() function is equivalent to the std::iswspace() function
    1837             :  * except that `'\\r'` and `'\\n'` are never viewed as white spaces.
    1838             :  *
    1839             :  * \return true if c is considered to be a white space character.
    1840             :  */
    1841     1121284 : bool iswspace(int c)
    1842             : {
    1843             :     return c != '\n'
    1844     1121278 :         && c != '\r'
    1845     2242561 :         && std::iswspace(c);
    1846             : }
    1847             : 
    1848             : 
    1849           6 : }   // namespace advgetopt
    1850             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13