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