Line data Source code
1 : // Copyright (c) 2006-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/advgetopt
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : /** \file
21 : * \brief Advanced getopt data access implementation.
22 : *
23 : * The advgetopt class has many function used to access the data in the
24 : * class. These functions are gathered here.
25 : */
26 :
27 : // self
28 : //
29 : #include "advgetopt/advgetopt.h"
30 :
31 : #include "advgetopt/conf_file.h"
32 :
33 :
34 : // cppthread
35 : //
36 : #include <cppthread/log.h>
37 :
38 : // boost
39 : //
40 : #include <boost/algorithm/string/join.hpp>
41 : #include <boost/algorithm/string/replace.hpp>
42 :
43 :
44 : // last include
45 : //
46 : #include <snapdev/poison.h>
47 :
48 :
49 : namespace advgetopt
50 : {
51 :
52 :
53 :
54 :
55 : /** \brief Generate a list of configuration filenames.
56 : *
57 : * This function goes through the list of filenames and directories and
58 : * generates a complete list of all the configuration files that the
59 : * system will load when you call the parse_configuration_files()
60 : * function.
61 : *
62 : * Set the flag \p exists to true if you only want the name of files
63 : * that currently exists.
64 : *
65 : * The \p writable file means that we only want files under the
66 : * \<project-name>.d folder and the user configuration folder.
67 : *
68 : * \note
69 : * The argc/argv and environment variable parameters are used whenever the
70 : * function is called early and we can't call is_defined(). These are
71 : * ignored otherwise.
72 : *
73 : * \param[in] exists Remove files that do not exist from the list.
74 : * \param[in] writable Only return files we consider writable.
75 : * \param[in] argc The number of arguments in argv.
76 : * \param[in] argv The arguments passed to the finish_parsing() function or
77 : * nullptr.
78 : * \param[in] environment_variable The environment variable or an empty string.
79 : *
80 : * \return The list of configuration filenames.
81 : */
82 364 : string_list_t getopt::get_configuration_filenames(
83 : bool exists
84 : , bool writable
85 : , int argc
86 : , char * argv[]) const
87 : {
88 728 : string_list_t result;
89 :
90 364 : if(f_options_environment.f_configuration_files != nullptr)
91 : {
92 : // load options from configuration files specified as is by caller
93 : //
94 455 : for(char const * const * configuration_files(f_options_environment.f_configuration_files)
95 455 : ; *configuration_files != nullptr
96 : ; ++configuration_files)
97 : {
98 352 : char const * filename(*configuration_files);
99 352 : if(*filename != '\0')
100 : {
101 704 : std::string const user_filename(handle_user_directory(filename));
102 352 : if(user_filename == filename)
103 : {
104 345 : if(!writable)
105 : {
106 203 : result.push_back(user_filename);
107 : }
108 :
109 345 : string_list_t const with_project_name(insert_group_name(
110 : user_filename
111 345 : , f_options_environment.f_group_name
112 1035 : , f_options_environment.f_project_name));
113 345 : if(!with_project_name.empty())
114 : {
115 690 : result.insert(
116 690 : result.end()
117 : , with_project_name.begin()
118 1380 : , with_project_name.end());
119 : }
120 : }
121 : else
122 : {
123 7 : result.push_back(user_filename);
124 : }
125 : }
126 : }
127 : }
128 :
129 364 : if(f_options_environment.f_configuration_filename != nullptr)
130 : {
131 322 : string_list_t directories;
132 161 : if(has_flag(GETOPT_ENVIRONMENT_FLAG_SYSTEM_PARAMETERS))
133 : {
134 13 : if(f_parsed)
135 : {
136 : // WARNING: at this point the command line and environment
137 : // variable may not be parsed in full if at all
138 : //
139 : //if(has_flag(SYSTEM_OPTION_CONFIGURATION_FILENAMES))
140 3 : if(is_defined("config-dir"))
141 : {
142 2 : size_t const max(size("config-dir"));
143 6 : for(size_t idx(0); idx < max; ++idx)
144 : {
145 4 : directories.push_back(get_string("config-dir", idx));
146 : }
147 : }
148 : }
149 : else
150 : {
151 : // we've got to do some manual parsing (argh!)
152 : //
153 10 : directories = find_config_dir(argc, argv);
154 10 : if(directories.empty())
155 : {
156 12 : string_list_t args(split_environment(f_environment_variable));
157 :
158 12 : std::vector<char *> sub_argv;
159 6 : sub_argv.resize(args.size() + 2);
160 6 : sub_argv[0] = const_cast<char *>(f_program_fullname.c_str());
161 17 : for(size_t idx(0); idx < args.size(); ++idx)
162 : {
163 11 : sub_argv[idx + 1] = const_cast<char *>(args[idx].c_str());
164 : }
165 6 : sub_argv[args.size() + 1] = nullptr;
166 :
167 6 : directories = find_config_dir(sub_argv.size() - 1, sub_argv.data());
168 : }
169 : }
170 : }
171 :
172 161 : if(f_options_environment.f_configuration_directories != nullptr)
173 : {
174 260 : for(char const * const * configuration_directories(f_options_environment.f_configuration_directories)
175 260 : ; *configuration_directories != nullptr
176 : ; ++configuration_directories)
177 : {
178 204 : directories.push_back(*configuration_directories);
179 : }
180 : }
181 :
182 322 : std::string const filename(f_options_environment.f_configuration_filename);
183 :
184 376 : for(auto directory : directories)
185 : {
186 215 : if(!directory.empty())
187 : {
188 430 : std::string const full_filename(directory + ("/" + filename));
189 430 : std::string const user_filename(handle_user_directory(full_filename));
190 215 : if(user_filename == full_filename)
191 : {
192 168 : if(!writable)
193 : {
194 108 : result.push_back(user_filename);
195 : }
196 :
197 336 : string_list_t const with_project_name(insert_group_name(user_filename, f_options_environment.f_group_name, f_options_environment.f_project_name));
198 168 : if(!with_project_name.empty())
199 : {
200 336 : result.insert(
201 336 : result.end()
202 : , with_project_name.begin()
203 672 : , with_project_name.end());
204 : }
205 : }
206 : else
207 : {
208 47 : result.push_back(user_filename);
209 : }
210 : }
211 : }
212 : }
213 :
214 364 : if(!exists)
215 : {
216 316 : return result;
217 : }
218 :
219 96 : string_list_t existing_files;
220 48 : int const mode(R_OK | (writable ? W_OK : 0));
221 404 : for(auto r : result)
222 : {
223 356 : if(access(r.c_str(), mode) == 0)
224 : {
225 26 : existing_files.push_back(r);
226 : }
227 : }
228 48 : return existing_files;
229 : }
230 :
231 :
232 : /** \brief Search for the "--config-dir" option in a set of arguments.
233 : *
234 : * This function searches the given list of \p argv arguments for the
235 : * "--config-dir".
236 : *
237 : * This is done that way because we prematurely need that information
238 : * in order to properly search for the configuration file. This is because
239 : * the "--config-dir" is not yet defined when we attempt to read the
240 : * user specific configuration file.
241 : *
242 : * \param[in] argc The number of arguments.
243 : * \param[in] argv The list of arguments to be searched.
244 : */
245 16 : string_list_t getopt::find_config_dir(
246 : int argc
247 : , char * argv[])
248 : {
249 16 : if(argv == nullptr)
250 : {
251 2 : return string_list_t();
252 : }
253 :
254 28 : string_list_t result;
255 56 : for(int idx(1); idx < argc; ++idx)
256 : {
257 42 : if(strcmp(argv[idx], "--config-dir") == 0)
258 : {
259 10 : for(++idx; idx < argc; ++idx)
260 : {
261 7 : if(argv[idx][0] == '-')
262 : {
263 2 : --idx;
264 2 : break;
265 : }
266 5 : result.push_back(argv[idx]);
267 : }
268 : }
269 37 : else if(strncmp(argv[idx], "--config-dir=", 13) == 0)
270 : {
271 2 : result.push_back(argv[idx] + 13);
272 : }
273 : }
274 :
275 14 : return result;
276 : }
277 :
278 :
279 : /** \brief This function checks for arguments in configuration files.
280 : *
281 : * Each configuration file is checked one after another. Each file that is
282 : * defined is loaded and each line is viewed as an option. If valid, it is
283 : * added to the resulting getopt list of options.
284 : *
285 : * Note that it is an error to define a command in a configuration file. If
286 : * that happens, an error occurs and the process stops. Technically this is
287 : * defined with the GETOPT_FLAG_CONFIGURATION_FILE flag in your opt table.
288 : *
289 : * The list of files is checked from beginning to end. So if a later file
290 : * changes an option of an earlier file, it is the one effective.
291 : *
292 : * The configuration file loader supports a project name as defined in the
293 : * get_project_name() function. It allows for a sub-directory to
294 : * be inserted between the path and the basename of the configuration
295 : * file. This allows for a file to be search in an extra sub-directory
296 : * so one can avoid changing the original definitions and only use
297 : * configuration files in the sub-directory. The path looks like this
298 : * when a project name is specified:
299 : *
300 : * \code
301 : * <path>/<project name>.d/<basename>
302 : * \endcode
303 : *
304 : * Notice that we add a ".d" as usual in other projects under Linux.
305 : *
306 : * \exception getopt_exception_invalid
307 : * This function generates the getopt_exception_invalid exception whenever
308 : * something invalid is found in the list of options passed as the \p opts
309 : * parameter.
310 : *
311 : * \exception getopt_exception_default
312 : * The function detects whether two options are marked as the default
313 : * option (the one receiving parameters that are not used by another command
314 : * or match a command.) This exception is raised when such is detected.
315 : *
316 : * \param[in] argc The number of arguments in argv.
317 : * \param[in] argv The arguments passed to the finish_parsing() function.
318 : *
319 : * \sa process_configuration_file()
320 : * \sa get_configuration_filenames()
321 : * \sa finish_parsing()
322 : */
323 270 : void getopt::parse_configuration_files(int argc, char * argv[])
324 : {
325 540 : string_list_t const filenames(get_configuration_filenames(false, false, argc, argv));
326 :
327 494 : for(auto f : filenames)
328 : {
329 224 : process_configuration_file(f);
330 224 : f_parsed = false;
331 : }
332 :
333 270 : f_parsed = true;
334 270 : }
335 :
336 :
337 : /** \brief Parse one specific configuration file and process the results.
338 : *
339 : * This function reads one specific configuration file using a conf_file
340 : * object and then goes through the resulting arguments and add them to
341 : * the options of this getopt object.
342 : *
343 : * The options found in the configuration file must match an option by
344 : * its long name. In a configuration file, it is not allowed to have an
345 : * option which name is only one character.
346 : *
347 : * \note
348 : * If the filename points to a file which can't be read or does not exist,
349 : * then nothing happens and the function returns without an error.
350 : *
351 : * \todo
352 : * Extend the support by having the various flags that the conf_file
353 : * class supports appear in the list of configuration filenames.
354 : *
355 : * \param[in] filename The name of the configuration file to check out.
356 : *
357 : * \sa parse_configuration_files()
358 : */
359 235 : void getopt::process_configuration_file(std::string const & filename)
360 : {
361 235 : option_info::set_configuration_filename(filename);
362 :
363 469 : conf_file_setup conf_setup(filename);
364 235 : if(!conf_setup.is_valid())
365 : {
366 : // a non-existant file is considered valid now so this should never
367 : // happen; later we may use the flag if we find errors in the file
368 : //
369 : return; // LCOV_EXCL_LINE
370 : }
371 469 : conf_file::pointer_t conf(conf_file::get_conf_file(conf_setup));
372 :
373 469 : conf_file::sections_t sections(conf->get_sections());
374 :
375 : // is there a variable section?
376 : //
377 235 : if(f_options_environment.f_section_variables_name != nullptr)
378 : {
379 17 : conf->section_to_variables(
380 : f_options_environment.f_section_variables_name
381 : , f_variables);
382 : }
383 :
384 235 : if(!sections.empty())
385 : {
386 11 : std::string const name(CONFIGURATION_SECTIONS);
387 11 : option_info::pointer_t configuration_sections(get_option(name));
388 6 : if(configuration_sections == nullptr)
389 : {
390 3 : configuration_sections = std::make_shared<option_info>(name);
391 3 : configuration_sections->add_flag(
392 : GETOPT_FLAG_MULTIPLE
393 : | GETOPT_FLAG_CONFIGURATION_FILE
394 : );
395 3 : f_options_by_name[configuration_sections->get_name()] = configuration_sections;
396 : }
397 3 : else if(!configuration_sections->has_flag(GETOPT_FLAG_MULTIPLE))
398 : {
399 2 : cppthread::log << cppthread::log_level_t::error
400 1 : << "option \""
401 1 : << name
402 1 : << "\" must have GETOPT_FLAG_MULTIPLE set."
403 2 : << cppthread::end;
404 1 : return;
405 : }
406 16 : for(auto s : sections)
407 : {
408 11 : if(!configuration_sections->has_value(s))
409 : {
410 7 : configuration_sections->add_value(s, option_source_t::SOURCE_CONFIGURATION);
411 : }
412 : }
413 : }
414 :
415 325 : for(auto const & param : conf->get_parameters())
416 : {
417 : // in configuration files we only allow long arguments
418 : //
419 178 : option_info::pointer_t opt(get_option(param.first));
420 91 : if(opt == nullptr)
421 : {
422 13 : if(!has_flag(GETOPT_ENVIRONMENT_FLAG_DYNAMIC_PARAMETERS)
423 5 : || param.first.length() == 1)
424 : {
425 6 : cppthread::log << cppthread::log_level_t::error
426 3 : << "unknown option \""
427 6 : << boost::replace_all_copy(param.first, "-", "_")
428 3 : << "\" found in configuration file \""
429 3 : << filename
430 3 : << "\" on line "
431 6 : << param.second.get_line()
432 3 : << "."
433 12 : << cppthread::end;
434 3 : continue;
435 : }
436 : else
437 : {
438 : // add a new parameter dynamically
439 : //
440 2 : opt = std::make_shared<option_info>(param.first);
441 2 : opt->set_variables(f_variables);
442 :
443 2 : opt->set_flags(GETOPT_FLAG_CONFIGURATION_FILE | GETOPT_FLAG_DYNAMIC);
444 :
445 : // consider the first definition as the default
446 : // (which is likely in our environment)
447 : //
448 2 : opt->set_default(param.second);
449 :
450 2 : f_options_by_name[opt->get_name()] = opt;
451 : }
452 : }
453 : else
454 : {
455 87 : if(!opt->has_flag(GETOPT_FLAG_CONFIGURATION_FILE))
456 : {
457 : // in configuration files we are expected to use '_' so
458 : // print an error with such
459 : //
460 2 : cppthread::log << cppthread::log_level_t::error
461 1 : << "option \""
462 2 : << boost::replace_all_copy(param.first, "-", "_")
463 1 : << "\" is not supported in configuration files (found in \""
464 1 : << filename
465 1 : << "\")."
466 3 : << cppthread::end;
467 1 : continue;
468 : }
469 : }
470 :
471 87 : if(opt != nullptr)
472 : {
473 87 : add_option_from_string(
474 : opt
475 : , param.second
476 : , filename
477 : , option_source_t::SOURCE_CONFIGURATION);
478 : }
479 : }
480 :
481 234 : f_parsed = true;
482 : }
483 :
484 :
485 :
486 :
487 :
488 :
489 6 : } // namespace advgetopt
490 : // vim: ts=4 sw=4 et
|