Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : */
8 :
9 : #include <cmdline.hpp>
10 :
11 : #include <backendparser.hpp>
12 : #include <kdb.hpp>
13 : #include <kdbconfig.h>
14 : #include <keysetget.hpp>
15 : #include <keysetio.hpp>
16 :
17 : #include <cstdio>
18 : #include <iostream>
19 : #include <set>
20 : #include <vector>
21 :
22 : #include <getopt.h>
23 :
24 : #include <command.hpp>
25 : #include <external.hpp>
26 :
27 : #ifndef _WIN32
28 : #include <sys/types.h>
29 : #include <unistd.h>
30 : #endif
31 :
32 :
33 : using namespace std;
34 :
35 3580 : Cmdline::Cmdline (int argc, char ** argv, Command * command)
36 3580 : : synopsis (command->getSynopsis ()), helpText (), invalidOpt (false),
37 :
38 : /*XXX: Step 2: initialise your option here.*/
39 : debug (), force (), load (), humanReadable (), help (), interactive (), minDepth (0), maxDepth (numeric_limits<int>::max ()),
40 : noNewline (), test (), recursive (), resolver (KDB_RESOLVER), strategy ("preserve"), verbose (), quiet (), version (), withoutElektra (),
41 : inputFile (""), null (), first (true), second (true), third (true), withRecommends (false), all (), format (KDB_STORAGE), plugins (""),
42 : globalPlugins ("spec"), pluginsConfig (""), color ("auto"), ns (""), editor (), bookmarks (), profile ("current"),
43 :
44 96660 : executable (), commandName ()
45 : {
46 : extern int optind;
47 : extern char * optarg;
48 :
49 : int opt;
50 :
51 : size_t optionPos;
52 :
53 10740 : helpText += command->getShortHelpText ();
54 7160 : helpText += "\n";
55 10740 : helpText += command->getLongHelpText ();
56 7160 : helpText += "\n";
57 :
58 7160 : string allOptions = command->getShortOptions ();
59 3580 : allOptions += "HVCpvd";
60 :
61 : // Make sure to use the unsorted allOptions for getopt to preserve argument chars : and ::
62 10740 : std::set<string::value_type> unique_sorted_chars (allOptions.begin (), allOptions.end ());
63 25060 : string acceptedOptions (unique_sorted_chars.begin (), unique_sorted_chars.end ());
64 :
65 7160 : vector<option> long_options;
66 : /*XXX: Step 3: give it a long name.*/
67 3580 : if (acceptedOptions.find ('a') != string::npos)
68 : {
69 296 : option o = { "all", no_argument, nullptr, 'a' };
70 296 : long_options.push_back (o);
71 592 : helpText += "-a --all Consider all of the keys.\n";
72 : }
73 3580 : if (acceptedOptions.find ('d') != string::npos)
74 : {
75 3580 : option o = { "debug", no_argument, nullptr, 'd' };
76 3580 : long_options.push_back (o);
77 7160 : helpText += "-d --debug Give debug information or ask debug questions (in interactive mode).\n";
78 : }
79 3580 : if (acceptedOptions.find ('f') != string::npos)
80 : {
81 741 : option o = { "force", no_argument, nullptr, 'f' };
82 741 : long_options.push_back (o);
83 1482 : helpText += "-f --force Force the action to be done.\n";
84 : }
85 3580 : if (acceptedOptions.find ('l') != string::npos)
86 : {
87 429 : option o = { "load", no_argument, nullptr, 'f' };
88 429 : long_options.push_back (o);
89 858 : helpText += "-l --load Load plugin even if system/elektra is available.\n";
90 : }
91 3580 : if (acceptedOptions.find ('h') != string::npos)
92 : {
93 0 : option o = { "human-readable", no_argument, nullptr, 'h' };
94 0 : long_options.push_back (o);
95 0 : helpText += "-h --human-readable Print numbers in an human readable way.\n";
96 : }
97 3580 : if (acceptedOptions.find ('H') != string::npos)
98 : {
99 3580 : option o = { "help", no_argument, nullptr, 'H' };
100 3580 : long_options.push_back (o);
101 7160 : helpText += "-H --help Show the man page.\n";
102 : }
103 3580 : if (acceptedOptions.find ('i') != string::npos)
104 : {
105 308 : option o = { "interactive", no_argument, nullptr, 'i' };
106 308 : long_options.push_back (o);
107 616 : helpText += "-i --interactive Ask the user interactively.\n";
108 : }
109 3580 : optionPos = acceptedOptions.find ('m');
110 3580 : if (optionPos != string::npos)
111 : {
112 72 : acceptedOptions.insert (optionPos + 1, ":");
113 72 : option o = { "min-depth", required_argument, nullptr, 'm' };
114 72 : long_options.push_back (o);
115 144 : helpText += "-m --min-depth <min> Specify the minimum depth (default 0).\n";
116 : }
117 3580 : optionPos = acceptedOptions.find ('M');
118 3580 : if (optionPos != string::npos)
119 : {
120 72 : acceptedOptions.insert (optionPos + 1, ":");
121 72 : option o = { "max-depth", required_argument, nullptr, 'M' };
122 72 : long_options.push_back (o);
123 144 : helpText += "-M --max-depth <max> Specify the maximum depth (unlimited by default).\n";
124 : }
125 3580 : if (acceptedOptions.find ('n') != string::npos)
126 : {
127 602 : option o = { "no-newline", no_argument, nullptr, 'n' };
128 602 : long_options.push_back (o);
129 1204 : helpText += "-n --no-newline Suppress the newline at the end of the output.\n";
130 : }
131 3580 : if (acceptedOptions.find ('t') != string::npos)
132 : {
133 0 : option o = { "test", no_argument, nullptr, 't' };
134 0 : long_options.push_back (o);
135 0 : helpText += "-t --test Test.\n";
136 : }
137 3580 : if (acceptedOptions.find ('r') != string::npos)
138 : {
139 337 : option o = { "recursive", no_argument, nullptr, 'r' };
140 337 : long_options.push_back (o);
141 674 : helpText += "-r --recursive Work in a recursive mode.\n";
142 : }
143 3580 : optionPos = acceptedOptions.find ('R');
144 3580 : if (optionPos != string::npos)
145 : {
146 318 : acceptedOptions.insert (optionPos + 1, ":");
147 318 : option o = { "resolver", required_argument, nullptr, 'R' };
148 318 : long_options.push_back (o);
149 636 : helpText += "-R --resolver <name> Specify the resolver plugin to use.\n";
150 : }
151 3580 : optionPos = acceptedOptions.find ('p');
152 3580 : if (optionPos != string::npos)
153 : {
154 3580 : acceptedOptions.insert (optionPos + 1, ":");
155 3580 : option o = { "profile", required_argument, nullptr, 'p' };
156 3580 : long_options.push_back (o);
157 7160 : helpText += "-p --profile <name> Use a different profile for kdb configuration.\n";
158 : }
159 3580 : optionPos = acceptedOptions.find ('s');
160 3580 : if (optionPos != string::npos)
161 : {
162 394 : acceptedOptions.insert (optionPos + 1, ":");
163 394 : option o = { "strategy", required_argument, nullptr, 's' };
164 394 : long_options.push_back (o);
165 788 : helpText += "-s --strategy <name> Specify the strategy to resolve conflicts.\n";
166 : }
167 3580 : if (acceptedOptions.find ('v') != string::npos)
168 : {
169 3580 : option o = { "verbose", no_argument, nullptr, 'v' };
170 3580 : long_options.push_back (o);
171 7160 : helpText += "-v --verbose Explain what is happening.\n";
172 : }
173 3580 : if (acceptedOptions.find ('q') != string::npos)
174 : {
175 755 : option o = { "quiet", no_argument, nullptr, 'q' };
176 755 : long_options.push_back (o);
177 1510 : helpText += "-q --quiet Only print error messages.\n";
178 : }
179 3580 : if (acceptedOptions.find ('V') != string::npos)
180 : {
181 3580 : option o = { "version", no_argument, nullptr, 'V' };
182 3580 : long_options.push_back (o);
183 7160 : helpText += "-V --version Print version info.\n";
184 : }
185 3580 : if (acceptedOptions.find ('E') != string::npos)
186 : {
187 1190 : option o = { "without-elektra", no_argument, nullptr, 'E' };
188 1190 : long_options.push_back (o);
189 2380 : helpText += "-E --without-elektra Omit the `/elektra` directory.\n";
190 : }
191 3580 : optionPos = acceptedOptions.find ('F');
192 3580 : if (optionPos != string::npos)
193 : {
194 9 : acceptedOptions.insert (optionPos + 1, ":");
195 9 : option o = { "input-file", required_argument, nullptr, 'F' };
196 9 : long_options.push_back (o);
197 18 : helpText += "-F --input-file <plugin>=<file> Load the given file with the given plugin instead of using the KDB.\n";
198 : }
199 3580 : optionPos = acceptedOptions.find ('e');
200 3580 : if (optionPos != string::npos)
201 : {
202 0 : acceptedOptions.insert (optionPos + 1, ":");
203 0 : option o = { "editor", required_argument, 0, 'e' };
204 0 : long_options.push_back (o);
205 0 : helpText += "-e --editor <editor> Which external editor to use.\n";
206 : }
207 3580 : if (acceptedOptions.find ('W') != string::npos)
208 : {
209 323 : option o = { "with-recommends", no_argument, nullptr, 'W' };
210 323 : long_options.push_back (o);
211 646 : helpText += "-W --with-recommends Add recommended plugins.\n";
212 : }
213 3580 : if (acceptedOptions.find ('0') != string::npos)
214 : {
215 397 : option o = { "null", no_argument, nullptr, '0' };
216 397 : long_options.push_back (o);
217 794 : helpText += "-0 --null Use binary 0 termination.\n";
218 : }
219 3580 : if (acceptedOptions.find ('1') != string::npos)
220 : {
221 307 : option o = { "first", no_argument, nullptr, '1' };
222 307 : long_options.push_back (o);
223 614 : helpText += "-1 --first Suppress the first column.\n";
224 : }
225 3580 : if (acceptedOptions.find ('2') != string::npos)
226 : {
227 307 : option o = { "second", no_argument, nullptr, '2' };
228 307 : long_options.push_back (o);
229 614 : helpText += "-2 --second Suppress the second column.\n";
230 : }
231 3580 : if (acceptedOptions.find ('3') != string::npos)
232 : {
233 307 : option o = { "third", no_argument, nullptr, '3' };
234 307 : long_options.push_back (o);
235 614 : helpText += "-3 --third Suppress the third column.\n";
236 : }
237 3580 : optionPos = acceptedOptions.find ('N');
238 3580 : if (acceptedOptions.find ('N') != string::npos)
239 : {
240 715 : acceptedOptions.insert (optionPos + 1, ":");
241 715 : option o = { "namespace", required_argument, nullptr, 'N' };
242 715 : long_options.push_back (o);
243 1430 : helpText += "-N --namespace <ns> Specify the namespace to use for cascading keys.\n";
244 : }
245 3580 : optionPos = acceptedOptions.find ('c');
246 3580 : if (optionPos != string::npos)
247 : {
248 1716 : acceptedOptions.insert (optionPos + 1, ":");
249 1716 : option o = { "plugins-config", required_argument, nullptr, 'c' };
250 1716 : long_options.push_back (o);
251 3432 : helpText += "-c --plugins-config <c> Add a plugin configuration.\n";
252 : }
253 3580 : optionPos = acceptedOptions.find ('C');
254 3580 : if (optionPos != string::npos)
255 : {
256 3580 : acceptedOptions.insert (optionPos + 1, ":");
257 3580 : option o = { "color", required_argument, nullptr, 'C' };
258 3580 : long_options.push_back (o);
259 7160 : helpText += "-C --color <when> Print never/auto(default)/always colored output.\n";
260 : }
261 :
262 3580 : int index = 0;
263 3580 : option o = { nullptr, 0, nullptr, 0 };
264 3580 : long_options.push_back (o);
265 :
266 7160 : executable = argv[0];
267 7160 : commandName = argv[1];
268 :
269 3580 : opterr = 0;
270 :
271 8776 : while ((opt = getopt_long (argc, argv, acceptedOptions.c_str (), &long_options[0], &index)) != EOF)
272 : {
273 808 : switch (opt)
274 : {
275 : case 'p':
276 0 : profile = optarg;
277 : break;
278 : default: // ignore everything else for now
279 : break;
280 : }
281 : }
282 :
283 :
284 7160 : if (profile != "%")
285 : {
286 : try
287 : {
288 : using namespace kdb;
289 : /*XXX: Step 4: use default from KDB, if available.*/
290 7160 : KDB kdb;
291 7160 : KeySet conf;
292 :
293 14320 : for (int i = 0; i <= 2; ++i)
294 : {
295 21480 : std::string dirname;
296 10740 : switch (i)
297 : {
298 : // prefer later dirnames (will override)
299 : case 0:
300 10740 : dirname = "/sw/kdb/" + profile + "/";
301 3580 : break; // legacy
302 : case 1:
303 : dirname = "/sw/elektra/kdb/#0/%/";
304 : break; // no profile
305 : case 2:
306 10740 : dirname = "/sw/elektra/kdb/#0/" + profile + "/";
307 3580 : break; // current profile
308 : }
309 :
310 10740 : kdb.get (conf, dirname);
311 :
312 42960 : Key k = conf.lookup (dirname + "resolver");
313 10740 : if (k) resolver = k.get<string> ();
314 :
315 53700 : k = conf.lookup (dirname + "format");
316 10740 : if (k) format = k.get<string> ();
317 :
318 53700 : k = conf.lookup (dirname + "plugins");
319 10740 : if (k) plugins = k.get<string> ();
320 :
321 53700 : k = conf.lookup (dirname + "plugins/global");
322 10740 : if (k) globalPlugins = k.get<string> ();
323 :
324 53700 : k = conf.lookup (dirname + "namespace");
325 10740 : if (k) ns = k.get<string> ();
326 :
327 53700 : k = conf.lookup (dirname + "verbose");
328 10740 : if (k) verbose = k.get<bool> ();
329 :
330 53700 : k = conf.lookup (dirname + "quiet");
331 10740 : if (k) quiet = k.get<bool> ();
332 :
333 53700 : k = conf.lookup (dirname + "editor");
334 10740 : if (k) editor = k.get<string> ();
335 :
336 53700 : k = conf.lookup (dirname + "recommends");
337 10740 : if (k) withRecommends = k.get<bool> ();
338 :
339 42960 : map nks = conf.get<map> (dirname + "bookmarks");
340 32220 : bookmarks.insert (nks.begin (), nks.end ());
341 :
342 53700 : k = conf.lookup (dirname + "color");
343 10740 : if (k) color = k.get<std::string> ();
344 : }
345 : }
346 0 : catch (kdb::KDBException const & ce)
347 : {
348 0 : std::cerr << "Sorry, I could not fetch my own configuration:\n" << ce.what () << std::endl;
349 : }
350 : }
351 :
352 : // reinit
353 3580 : index = 0;
354 3580 : optind = 1;
355 :
356 3580 : if (!dynamic_cast<ExternalCommand *> (command))
357 : {
358 : // do not print to stderr for external commands,
359 : // we do not know which options they have and
360 : // otherwise maybe wrong "invalid/unrecognized option"
361 : // are reported to stderr.
362 3502 : opterr = 1;
363 : }
364 :
365 8776 : while ((opt = getopt_long (argc, argv, acceptedOptions.c_str (), &long_options[0], &index)) != EOF)
366 : {
367 808 : switch (opt)
368 : {
369 : /*XXX: Step 5: and now process the option.*/
370 : case 'a':
371 0 : all = true;
372 0 : break;
373 : case 'C':
374 0 : color = optarg;
375 0 : if (color != "never" && color != "auto" && color != "always")
376 : {
377 0 : std::cerr << argv[0] << ": -C --color needs never, auto, or always as argument\n";
378 0 : invalidOpt = true;
379 : }
380 : break;
381 : case 'd':
382 2 : debug = true;
383 2 : break;
384 : case 'e':
385 0 : editor = optarg;
386 : break;
387 : case 'f':
388 6 : force = true;
389 6 : break;
390 : case 'F':
391 0 : inputFile = optarg;
392 : break;
393 : case 'h':
394 0 : humanReadable = true;
395 0 : break;
396 : case 'l':
397 0 : load = true;
398 0 : break;
399 : case 'H':
400 0 : help = true;
401 0 : break;
402 : case 'i':
403 0 : interactive = true;
404 0 : break;
405 : case 'm':
406 : try
407 : {
408 10 : minDepth = stoi (optarg);
409 : }
410 0 : catch (std::invalid_argument const & ia)
411 : {
412 0 : std::cerr << argv[0] << ": -m --min-depth needs a valid number as argument\n";
413 0 : invalidOpt = true;
414 : }
415 : break;
416 : case 'M':
417 : try
418 : {
419 50 : maxDepth = stoi (optarg);
420 : }
421 0 : catch (std::invalid_argument const & ia)
422 : {
423 0 : std::cerr << argv[0] << ": -M --max-depth needs a valid number as argument\n";
424 0 : invalidOpt = true;
425 : }
426 :
427 10 : if (maxDepth == -1)
428 : {
429 0 : maxDepth = numeric_limits<int>::max ();
430 : }
431 : break;
432 : case 'n':
433 139 : noNewline = true;
434 139 : break;
435 : case 't':
436 0 : test = true;
437 0 : break;
438 : case 'r':
439 254 : recursive = true;
440 254 : break;
441 : case 'p':
442 : break; // already handled above
443 : case 'R':
444 67 : resolver = optarg;
445 : break;
446 : case 's':
447 0 : strategy = optarg;
448 : break;
449 : case 'v':
450 8 : verbose = true;
451 8 : break;
452 : case 'q':
453 0 : quiet = true;
454 0 : break;
455 : case 'V':
456 0 : version = true;
457 0 : break;
458 : case 'E':
459 136 : withoutElektra = true;
460 136 : break;
461 : case 'W':
462 2 : withRecommends = true;
463 2 : break;
464 : case '0':
465 0 : null = true;
466 0 : break;
467 : case '1':
468 0 : first = false;
469 0 : break;
470 : case '2':
471 0 : second = false;
472 0 : break;
473 : case '3':
474 0 : third = false;
475 0 : break;
476 : case 'N':
477 175 : ns = optarg;
478 : break;
479 : case 'c':
480 5 : pluginsConfig = optarg;
481 : break;
482 :
483 : default:
484 2 : invalidOpt = true;
485 2 : break;
486 : }
487 : }
488 :
489 3580 : if (quiet && verbose)
490 : {
491 0 : std::cout << "Both quiet and verbose is active: will suppress default messages, but print verbose messages" << std::endl;
492 : }
493 :
494 7160 : if (ns.empty ())
495 : {
496 : #ifndef _WIN32
497 3405 : if (getuid () == 0 || geteuid () == 0)
498 : {
499 0 : ns = "system";
500 : }
501 : else
502 : {
503 3405 : ns = "user";
504 : }
505 : #else
506 : ns = "user";
507 : #endif
508 : }
509 :
510 3580 : optind++; // skip the command name
511 15646 : while (optind < argc)
512 : {
513 30165 : arguments.push_back (argv[optind++]);
514 : }
515 :
516 : // init colors
517 3580 : hasStdColor (color);
518 3580 : hasErrorColor (color);
519 3580 : }
520 :
521 1613 : kdb::KeySet Cmdline::getPluginsConfig (string basepath) const
522 : {
523 1613 : return kdb::tools::parsePluginArguments (pluginsConfig, basepath);
524 : }
525 :
526 : /**
527 : * @brief create a key from argument number pos
528 : *
529 : * @param pos the position in cl.arguments that tells us the name of the key to create
530 : *
531 : * @throw invalid_argument if the argument is not a valid keyname
532 : *
533 : * @return a newly created key from the name found in cl.arguments[pos]
534 : */
535 2881 : kdb::Key Cmdline::createKey (int pos, bool allowCascading) const
536 : {
537 11524 : std::string name = arguments[pos];
538 : // std::cerr << "Using " << name << std::endl;
539 : // for (auto const & n : bookmarks) std::cout << "nks: " << n.second << std::endl;
540 2881 : if (name.empty ())
541 : {
542 14 : throw invalid_argument ("<empty string> is not a valid keyname. Please enter a valid one.");
543 : }
544 :
545 5734 : kdb::Key root (name, KEY_END);
546 :
547 2867 : if (name[0] == '+')
548 : {
549 0 : kdb::Key bookmark = resolveBookmark (name);
550 0 : if (!bookmark.isValid ())
551 : {
552 0 : throw invalid_argument ("cannot find bookmark " + bookmark.getFullName ());
553 : }
554 0 : root = bookmark;
555 : }
556 :
557 2867 : if (!root.isValid ())
558 : {
559 0 : throw invalid_argument (name + " is not a valid keyname" + "\n\n" +
560 : "For absolute keys (starting without '/'), please note that only one of the predefined namespaces "
561 0 : "can be used (see 'man elektra-namespaces').\n" +
562 0 : "Please also ensure that the path is separated by a '/'.\n" +
563 0 : "An example for a valid absolute key is user/a/key, and for a valid cascading key /a/key.");
564 : }
565 :
566 2895 : if (!allowCascading && root.isCascading ())
567 : {
568 0 : throw invalid_argument ("The key '" + root.getName () +
569 0 : "'is a cascading keyname, which is not supported. Please choose a namespace.");
570 : }
571 :
572 2867 : return root;
573 : }
574 :
575 : /**
576 : * @brief resolve the bookmark with the given name
577 : *
578 : * @param bookmark the name of the bookmark to resolve
579 : *
580 : * @return a key to the resolved bookmark, or an invalid key if no bookmark with the given name exists
581 : */
582 0 : kdb::Key Cmdline::resolveBookmark (std::string name) const
583 : {
584 0 : if (!name.empty () && name[0] == '+')
585 : {
586 0 : size_t found = name.find ('/');
587 0 : std::string bookmark;
588 0 : std::string restKey;
589 0 : if (found != std::string::npos)
590 : {
591 0 : bookmark = name.substr (1, found - 1);
592 0 : restKey = name.substr (found, name.length () - found);
593 : }
594 : else
595 : {
596 0 : bookmark = name.substr (1, name.length () - 1);
597 : }
598 0 : auto realKeyIt = bookmarks.find (bookmark);
599 0 : std::string realKey;
600 0 : if (realKeyIt != bookmarks.end ())
601 : {
602 0 : realKey = realKeyIt->second;
603 0 : name = realKey + "/" + restKey;
604 0 : if (verbose)
605 : {
606 0 : std::cout << "using bookmark " << bookmark << " which is: " << realKey << "-" << restKey << std::endl;
607 : }
608 0 : return kdb::Key (name, KEY_END);
609 : }
610 : }
611 : return kdb::Key ();
612 : }
613 :
614 17 : std::ostream & operator<< (std::ostream & os, Cmdline & cl)
615 : {
616 17 : if (cl.invalidOpt)
617 : {
618 2 : os << "Sorry, I could not process the given options (see errors above)\n" << endl;
619 : }
620 :
621 119 : os << "Usage: " << cl.executable << " " << cl.commandName << " " << cl.synopsis;
622 51 : os << "\n\n" << cl.helpText;
623 17 : return os;
624 7164 : }
|