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