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