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
|