xref: /minix3/external/bsd/kyua-cli/dist/cli/main.cpp (revision 0b98e8aad89f2bd4ba80b523d73cf29e9dd82ce1)
1 // Copyright 2010 Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 //   notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 //   notice, this list of conditions and the following disclaimer in the
12 //   documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 //   may be used to endorse or promote products derived from this software
15 //   without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #include "cli/main.hpp"
30 
31 #if defined(HAVE_CONFIG_H)
32 #   include "config.h"
33 #endif
34 
35 extern "C" {
36 #include <signal.h>
37 #include <unistd.h>
38 }
39 
40 #include <cstdlib>
41 #include <iostream>
42 #include <string>
43 #include <utility>
44 
45 #include "cli/cmd_about.hpp"
46 #include "cli/cmd_config.hpp"
47 #include "cli/cmd_db_exec.hpp"
48 #include "cli/cmd_db_migrate.hpp"
49 #include "cli/cmd_debug.hpp"
50 #include "cli/cmd_help.hpp"
51 #include "cli/cmd_list.hpp"
52 #include "cli/cmd_report.hpp"
53 #include "cli/cmd_report_html.hpp"
54 #include "cli/cmd_report_tap.hpp"
55 #include "cli/cmd_test.hpp"
56 #include "cli/common.ipp"
57 #include "cli/config.hpp"
58 #include "store/exceptions.hpp"
59 #include "utils/cmdline/commands_map.ipp"
60 #include "utils/cmdline/exceptions.hpp"
61 #include "utils/cmdline/globals.hpp"
62 #include "utils/cmdline/options.hpp"
63 #include "utils/cmdline/parser.ipp"
64 #include "utils/cmdline/ui.hpp"
65 #include "utils/config/tree.ipp"
66 #include "utils/env.hpp"
67 #include "utils/format/macros.hpp"
68 #include "utils/fs/operations.hpp"
69 #include "utils/fs/path.hpp"
70 #include "utils/logging/macros.hpp"
71 #include "utils/logging/operations.hpp"
72 #include "utils/optional.ipp"
73 #include "utils/sanity.hpp"
74 #include "utils/signals/exceptions.hpp"
75 
76 namespace cmdline = utils::cmdline;
77 namespace config = utils::config;
78 namespace fs = utils::fs;
79 namespace logging = utils::logging;
80 namespace signals = utils::signals;
81 
82 using utils::none;
83 using utils::optional;
84 
85 
86 namespace {
87 
88 
89 /// Executes the given subcommand with proper usage_error reporting.
90 ///
91 /// \param ui Object to interact with the I/O of the program.
92 /// \param command The subcommand to execute.
93 /// \param args The part of the command line passed to the subcommand.  The
94 ///     first item of this collection must match the command name.
95 /// \param user_config The runtime configuration to pass to the subcommand.
96 ///
97 /// \return The exit code of the command.  Typically 0 on success, some other
98 /// integer otherwise.
99 ///
100 /// \throw cmdline::usage_error If the user input to the subcommand is invalid.
101 ///     This error does not encode the command name within it, so this function
102 ///     extends the message in the error to specify which subcommand was
103 ///     affected.
104 /// \throw std::exception This propagates any uncaught exception.  Such
105 ///     exceptions are bugs, but we let them propagate so that the runtime will
106 ///     abort and dump core.
107 static int
108 run_subcommand(cmdline::ui* ui, cli::cli_command* command,
109                const cmdline::args_vector& args,
110                const config::tree& user_config)
111 {
112     try {
113         PRE(command->name() == args[0]);
114         return command->main(ui, args, user_config);
115     } catch (const cmdline::usage_error& e) {
116         throw std::pair< std::string, cmdline::usage_error >(
117             command->name(), e);
118     }
119 }
120 
121 
122 /// Exception-safe version of main.
123 ///
124 /// This function provides the real meat of the entry point of the program.  It
125 /// is allowed to throw some known exceptions which are parsed by the caller.
126 /// Doing so keeps this function simpler and allow tests to actually validate
127 /// that the errors reported are accurate.
128 ///
129 /// \return The exit code of the program.  Should be EXIT_SUCCESS on success and
130 /// EXIT_FAILURE on failure.  The caller extends this to additional integers for
131 /// errors reported through exceptions.
132 ///
133 /// \param ui Object to interact with the I/O of the program.
134 /// \param argc The number of arguments passed on the command line.
135 /// \param argv NULL-terminated array containing the command line arguments.
136 /// \param mock_command An extra command provided for testing purposes; should
137 ///     just be NULL other than for tests.
138 ///
139 /// \throw cmdline::usage_error If the user ran the program with invalid
140 ///     arguments.
141 /// \throw std::exception This propagates any uncaught exception.  Such
142 ///     exceptions are bugs, but we let them propagate so that the runtime will
143 ///     abort and dump core.
144 static int
145 safe_main(cmdline::ui* ui, int argc, const char* const argv[],
146           cli::cli_command_ptr mock_command)
147 {
148     cmdline::options_vector options;
149     options.push_back(&cli::config_option);
150     options.push_back(&cli::variable_option);
151     const cmdline::string_option loglevel_option(
152         "loglevel", "Level of the messages to log", "level", "info");
153     options.push_back(&loglevel_option);
154     const cmdline::path_option logfile_option(
155         "logfile", "Path to the log file", "file",
156         cli::detail::default_log_name().c_str());
157     options.push_back(&logfile_option);
158 
159     cmdline::commands_map< cli::cli_command > commands;
160 
161     commands.insert(new cli::cmd_about());
162     commands.insert(new cli::cmd_config());
163     commands.insert(new cli::cmd_db_exec());
164     commands.insert(new cli::cmd_db_migrate());
165     commands.insert(new cli::cmd_help(&options, &commands));
166 
167     commands.insert(new cli::cmd_debug(), "Workspace");
168     commands.insert(new cli::cmd_list(), "Workspace");
169     commands.insert(new cli::cmd_test(), "Workspace");
170 
171     commands.insert(new cli::cmd_report(), "Reporting");
172     commands.insert(new cli::cmd_report_html(), "Reporting");
173     commands.insert(new cli::cmd_report_tap(), "Reporting");
174 
175     if (mock_command.get() != NULL)
176         commands.insert(mock_command);
177 
178     const cmdline::parsed_cmdline cmdline = cmdline::parse(argc, argv, options);
179 
180     const fs::path logfile(cmdline.get_option< cmdline::path_option >(
181         "logfile"));
182     fs::mkdir_p(logfile.branch_path(), 0755);
183     LD(F("Log file is %s") % logfile);
184     utils::install_crash_handlers(logfile.str());
185     try {
186         logging::set_persistency(cmdline.get_option< cmdline::string_option >(
187             "loglevel"), logfile);
188     } catch (const std::range_error& e) {
189         throw cmdline::usage_error(e.what());
190     }
191 
192     if (cmdline.arguments().empty())
193         throw cmdline::usage_error("No command provided");
194     const std::string cmdname = cmdline.arguments()[0];
195 
196     const config::tree user_config = cli::load_config(cmdline,
197                                                       cmdname != "help");
198 
199     cli::cli_command* command = commands.find(cmdname);
200     if (command == NULL)
201         throw cmdline::usage_error(F("Unknown command '%s'") % cmdname);
202     return run_subcommand(ui, command, cmdline.arguments(), user_config);
203 }
204 
205 
206 }  // anonymous namespace
207 
208 
209 /// Gets the name of the default log file.
210 ///
211 /// \return The path to the log file.
212 fs::path
213 cli::detail::default_log_name(void)
214 {
215     // Update doc/troubleshooting.texi if you change this algorithm.
216     const optional< std::string > home(utils::getenv("HOME"));
217     if (home) {
218         return logging::generate_log_name(fs::path(home.get()) / ".kyua" /
219                                           "logs", cmdline::progname());
220     } else {
221         const optional< std::string > tmpdir(utils::getenv("TMPDIR"));
222         if (tmpdir) {
223             return logging::generate_log_name(fs::path(tmpdir.get()),
224                                               cmdline::progname());
225         } else {
226             return logging::generate_log_name(fs::path("/tmp"),
227                                               cmdline::progname());
228         }
229     }
230 }
231 
232 
233 /// Testable entry point, with catch-all exception handlers.
234 ///
235 /// This entry point does not perform any initialization of global state; it is
236 /// provided to allow unit-testing of the utility's entry point.
237 ///
238 /// \param ui Object to interact with the I/O of the program.
239 /// \param argc The number of arguments passed on the command line.
240 /// \param argv NULL-terminated array containing the command line arguments.
241 /// \param mock_command An extra command provided for testing purposes; should
242 ///     just be NULL other than for tests.
243 ///
244 /// \return 0 on success, some other integer on error.
245 ///
246 /// \throw std::exception This propagates any uncaught exception.  Such
247 ///     exceptions are bugs, but we let them propagate so that the runtime will
248 ///     abort and dump core.
249 int
250 cli::main(cmdline::ui* ui, const int argc, const char* const* const argv,
251           cli_command_ptr mock_command)
252 {
253     try {
254         const int exit_code = safe_main(ui, argc, argv, mock_command);
255 
256         // Codes above 1 are reserved to report conditions captured as
257         // exceptions below.
258         INV(exit_code == EXIT_SUCCESS || exit_code == EXIT_FAILURE);
259 
260         return exit_code;
261     } catch (const signals::interrupted_error& e) {
262         cmdline::print_error(ui, e.what());
263         // Re-deliver the interruption signal to self so that we terminate with
264         // the right status.  At this point we should NOT have any custom signal
265         // handlers in place.
266         ::kill(getpid(), e.signo());
267         LD("Interrupt signal re-delivery did not terminate program");
268         // If we reach this, something went wrong because we did not exit as
269         // intended.  Return an internal error instead.  (Would be nicer to
270         // abort in principle, but it wouldn't be a nice experience if it ever
271         // happened.)
272         return 2;
273     } catch (const std::pair< std::string, cmdline::usage_error >& e) {
274         const std::string message = F("Usage error for command %s: %s.") %
275             e.first % e.second.what();
276         LE(message);
277         ui->err(message);
278         ui->err(F("Type '%s help %s' for usage information.") %
279                 cmdline::progname() % e.first);
280         return 3;
281     } catch (const cmdline::usage_error& e) {
282         const std::string message = F("Usage error: %s.") % e.what();
283         LE(message);
284         ui->err(message);
285         ui->err(F("Type '%s help' for usage information.") %
286                 cmdline::progname());
287         return 3;
288     } catch (const store::old_schema_error& e) {
289         const std::string message = F("The database has schema version %s, "
290                                       "which is too old; please use db-migrate "
291                                       "to upgrade it") % e.old_version();
292         cmdline::print_error(ui, message);
293         return 2;
294     } catch (const std::runtime_error& e) {
295         cmdline::print_error(ui, e.what());
296         return 2;
297     }
298 }
299 
300 
301 /// Delegate for ::main().
302 ///
303 /// This function is supposed to be called directly from the top-level ::main()
304 /// function.  It takes care of initializing internal libraries and then calls
305 /// main(ui, argc, argv).
306 ///
307 /// \pre This function can only be called once.
308 ///
309 /// \throw std::exception This propagates any uncaught exception.  Such
310 ///     exceptions are bugs, but we let them propagate so that the runtime will
311 ///     abort and dump core.
312 int
313 cli::main(const int argc, const char* const* const argv)
314 {
315     logging::set_inmemory();
316 
317     LI(F("%s %s") % PACKAGE % VERSION);
318 
319     std::string plain_args;
320     for (const char* const* arg = argv; *arg != NULL; arg++)
321         plain_args += F(" %s") % *arg;
322     LI(F("Command line:%s") % plain_args);
323 
324     cmdline::init(argv[0]);
325     cmdline::ui ui;
326 
327     const int exit_code = main(&ui, argc, argv);
328     LI(F("Clean exit with code %s") % exit_code);
329     return exit_code;
330 }
331