LCOV - code coverage report
Current view: top level - advgetopt - conf_file.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 615 646 95.2 %
Date: 2022-05-26 21:41:34 Functions: 47 52 90.4 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13