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 344 : string_list_t getopt::get_configuration_filenames(
85 : bool exists
86 : , bool writable
87 : , int argc
88 : , char * argv[]) const
89 : {
90 688 : string_list_t result;
91 :
92 344 : if(f_options_environment.f_configuration_files != nullptr)
93 : {
94 : // load options from configuration files specified as is by caller
95 : //
96 453 : for(char const * const * configuration_files(f_options_environment.f_configuration_files)
97 453 : ; *configuration_files != nullptr
98 : ; ++configuration_files)
99 : {
100 351 : char const * filename(*configuration_files);
101 351 : if(*filename != '\0')
102 : {
103 702 : std::string const user_filename(handle_user_directory(filename));
104 351 : if(user_filename == filename)
105 : {
106 344 : if(!writable)
107 : {
108 202 : result.push_back(user_filename);
109 : }
110 :
111 344 : string_list_t const with_project_name(insert_group_name(
112 : user_filename
113 344 : , f_options_environment.f_group_name
114 1032 : , f_options_environment.f_project_name));
115 344 : if(!with_project_name.empty())
116 : {
117 688 : result.insert(
118 688 : result.end()
119 : , with_project_name.begin()
120 1376 : , with_project_name.end());
121 : }
122 : }
123 : else
124 : {
125 7 : result.push_back(user_filename);
126 : }
127 : }
128 : }
129 : }
130 :
131 344 : 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 344 : if(!exists)
217 : {
218 296 : 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 250 : void getopt::parse_configuration_files(int argc, char * argv[])
326 : {
327 500 : string_list_t const filenames(get_configuration_filenames(false, false, argc, argv));
328 :
329 508 : for(auto f : filenames)
330 : {
331 258 : process_configuration_file(f);
332 258 : f_parsed = false;
333 : }
334 :
335 250 : f_parsed = true;
336 250 : }
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 267 : void getopt::process_configuration_file(std::string const & filename)
362 : {
363 267 : option_info::set_configuration_filename(filename);
364 :
365 533 : conf_file_setup conf_setup(filename);
366 267 : 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 533 : conf_file::pointer_t conf(conf_file::get_conf_file(conf_setup));
374 :
375 533 : conf_file::sections_t sections(conf->get_sections());
376 :
377 : // is there a variable section?
378 : //
379 267 : if(f_options_environment.f_section_variables_name != nullptr)
380 : {
381 0 : conf->section_to_variables(
382 : f_options_environment.f_section_variables_name
383 : , f_variables);
384 : }
385 :
386 267 : if(!sections.empty())
387 : {
388 9 : std::string const name(CONFIGURATION_SECTIONS);
389 9 : option_info::pointer_t configuration_sections(get_option(name));
390 5 : if(configuration_sections == nullptr)
391 : {
392 2 : configuration_sections = std::make_shared<option_info>(name);
393 2 : configuration_sections->add_flag(
394 : GETOPT_FLAG_MULTIPLE
395 : | GETOPT_FLAG_CONFIGURATION_FILE
396 : );
397 2 : 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 13 : for(auto s : sections)
409 : {
410 9 : if(!configuration_sections->has_value(s))
411 : {
412 5 : configuration_sections->add_value(s, option_source_t::SOURCE_CONFIGURATION);
413 : }
414 : }
415 : }
416 :
417 384 : for(auto const & param : conf->get_parameters())
418 : {
419 : // in configuration files we only allow long arguments
420 : //
421 232 : option_info::pointer_t opt(get_option(param.first));
422 118 : 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 : << "\"."
433 9 : << 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 114 : 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 114 : if(opt != nullptr)
472 : {
473 114 : add_option_from_string(
474 : opt
475 : , param.second
476 : , filename
477 : , option_source_t::SOURCE_CONFIGURATION);
478 : }
479 : }
480 :
481 266 : f_parsed = true;
482 : }
483 :
484 :
485 :
486 :
487 :
488 :
489 6 : } // namespace advgetopt
490 : // vim: ts=4 sw=4 et
|