LCOV - code coverage report
Current view: top level - advgetopt - conf_file.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 735 754 97.5 %
Date: 2024-10-05 13:34:54 Functions: 55 57 96.5 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14

Snap C++ | List of projects | List of versions