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