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