xref: /netbsd-src/external/bsd/kyua-cli/dist/cli/main.cpp (revision 46b85cbbd3d745f264b248ec8702f62b162c7789)
16b3a42afSjmmv // Copyright 2010 Google Inc.
26b3a42afSjmmv // All rights reserved.
36b3a42afSjmmv //
46b3a42afSjmmv // Redistribution and use in source and binary forms, with or without
56b3a42afSjmmv // modification, are permitted provided that the following conditions are
66b3a42afSjmmv // met:
76b3a42afSjmmv //
86b3a42afSjmmv // * Redistributions of source code must retain the above copyright
96b3a42afSjmmv //   notice, this list of conditions and the following disclaimer.
106b3a42afSjmmv // * Redistributions in binary form must reproduce the above copyright
116b3a42afSjmmv //   notice, this list of conditions and the following disclaimer in the
126b3a42afSjmmv //   documentation and/or other materials provided with the distribution.
136b3a42afSjmmv // * Neither the name of Google Inc. nor the names of its contributors
146b3a42afSjmmv //   may be used to endorse or promote products derived from this software
156b3a42afSjmmv //   without specific prior written permission.
166b3a42afSjmmv //
176b3a42afSjmmv // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
186b3a42afSjmmv // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
196b3a42afSjmmv // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
206b3a42afSjmmv // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
216b3a42afSjmmv // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
226b3a42afSjmmv // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
236b3a42afSjmmv // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
246b3a42afSjmmv // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
256b3a42afSjmmv // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
266b3a42afSjmmv // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
276b3a42afSjmmv // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
286b3a42afSjmmv 
296b3a42afSjmmv #include "cli/main.hpp"
306b3a42afSjmmv 
316b3a42afSjmmv #if defined(HAVE_CONFIG_H)
326b3a42afSjmmv #   include "config.h"
336b3a42afSjmmv #endif
346b3a42afSjmmv 
356b3a42afSjmmv extern "C" {
366b3a42afSjmmv #include <signal.h>
376b3a42afSjmmv #include <unistd.h>
386b3a42afSjmmv }
396b3a42afSjmmv 
406b3a42afSjmmv #include <cstdlib>
416b3a42afSjmmv #include <iostream>
426b3a42afSjmmv #include <string>
436b3a42afSjmmv #include <utility>
446b3a42afSjmmv 
456b3a42afSjmmv #include "cli/cmd_about.hpp"
466b3a42afSjmmv #include "cli/cmd_config.hpp"
476b3a42afSjmmv #include "cli/cmd_db_exec.hpp"
486b3a42afSjmmv #include "cli/cmd_db_migrate.hpp"
496b3a42afSjmmv #include "cli/cmd_debug.hpp"
506b3a42afSjmmv #include "cli/cmd_help.hpp"
516b3a42afSjmmv #include "cli/cmd_list.hpp"
526b3a42afSjmmv #include "cli/cmd_report.hpp"
536b3a42afSjmmv #include "cli/cmd_report_html.hpp"
546b3a42afSjmmv #include "cli/cmd_test.hpp"
556b3a42afSjmmv #include "cli/common.ipp"
566b3a42afSjmmv #include "cli/config.hpp"
576b3a42afSjmmv #include "store/exceptions.hpp"
586b3a42afSjmmv #include "utils/cmdline/commands_map.ipp"
596b3a42afSjmmv #include "utils/cmdline/exceptions.hpp"
606b3a42afSjmmv #include "utils/cmdline/globals.hpp"
616b3a42afSjmmv #include "utils/cmdline/options.hpp"
626b3a42afSjmmv #include "utils/cmdline/parser.ipp"
636b3a42afSjmmv #include "utils/cmdline/ui.hpp"
646b3a42afSjmmv #include "utils/config/tree.ipp"
656b3a42afSjmmv #include "utils/env.hpp"
666b3a42afSjmmv #include "utils/format/macros.hpp"
676b3a42afSjmmv #include "utils/fs/operations.hpp"
686b3a42afSjmmv #include "utils/fs/path.hpp"
696b3a42afSjmmv #include "utils/logging/macros.hpp"
706b3a42afSjmmv #include "utils/logging/operations.hpp"
716b3a42afSjmmv #include "utils/optional.ipp"
726b3a42afSjmmv #include "utils/sanity.hpp"
736b3a42afSjmmv #include "utils/signals/exceptions.hpp"
746b3a42afSjmmv 
756b3a42afSjmmv namespace cmdline = utils::cmdline;
766b3a42afSjmmv namespace config = utils::config;
776b3a42afSjmmv namespace fs = utils::fs;
786b3a42afSjmmv namespace logging = utils::logging;
796b3a42afSjmmv namespace signals = utils::signals;
806b3a42afSjmmv 
816b3a42afSjmmv using utils::none;
826b3a42afSjmmv using utils::optional;
836b3a42afSjmmv 
846b3a42afSjmmv 
856b3a42afSjmmv namespace {
866b3a42afSjmmv 
876b3a42afSjmmv 
886b3a42afSjmmv /// Executes the given subcommand with proper usage_error reporting.
896b3a42afSjmmv ///
906b3a42afSjmmv /// \param ui Object to interact with the I/O of the program.
916b3a42afSjmmv /// \param command The subcommand to execute.
926b3a42afSjmmv /// \param args The part of the command line passed to the subcommand.  The
936b3a42afSjmmv ///     first item of this collection must match the command name.
946b3a42afSjmmv /// \param user_config The runtime configuration to pass to the subcommand.
956b3a42afSjmmv ///
966b3a42afSjmmv /// \return The exit code of the command.  Typically 0 on success, some other
976b3a42afSjmmv /// integer otherwise.
986b3a42afSjmmv ///
996b3a42afSjmmv /// \throw cmdline::usage_error If the user input to the subcommand is invalid.
1006b3a42afSjmmv ///     This error does not encode the command name within it, so this function
1016b3a42afSjmmv ///     extends the message in the error to specify which subcommand was
1026b3a42afSjmmv ///     affected.
1036b3a42afSjmmv /// \throw std::exception This propagates any uncaught exception.  Such
1046b3a42afSjmmv ///     exceptions are bugs, but we let them propagate so that the runtime will
1056b3a42afSjmmv ///     abort and dump core.
1066b3a42afSjmmv static int
run_subcommand(cmdline::ui * ui,cli::cli_command * command,const cmdline::args_vector & args,const config::tree & user_config)1076b3a42afSjmmv run_subcommand(cmdline::ui* ui, cli::cli_command* command,
1086b3a42afSjmmv                const cmdline::args_vector& args,
1096b3a42afSjmmv                const config::tree& user_config)
1106b3a42afSjmmv {
1116b3a42afSjmmv     try {
1126b3a42afSjmmv         PRE(command->name() == args[0]);
1136b3a42afSjmmv         return command->main(ui, args, user_config);
1146b3a42afSjmmv     } catch (const cmdline::usage_error& e) {
1156b3a42afSjmmv         throw std::pair< std::string, cmdline::usage_error >(
1166b3a42afSjmmv             command->name(), e);
1176b3a42afSjmmv     }
1186b3a42afSjmmv }
1196b3a42afSjmmv 
1206b3a42afSjmmv 
1216b3a42afSjmmv /// Exception-safe version of main.
1226b3a42afSjmmv ///
1236b3a42afSjmmv /// This function provides the real meat of the entry point of the program.  It
1246b3a42afSjmmv /// is allowed to throw some known exceptions which are parsed by the caller.
1256b3a42afSjmmv /// Doing so keeps this function simpler and allow tests to actually validate
1266b3a42afSjmmv /// that the errors reported are accurate.
1276b3a42afSjmmv ///
1286b3a42afSjmmv /// \return The exit code of the program.  Should be EXIT_SUCCESS on success and
1296b3a42afSjmmv /// EXIT_FAILURE on failure.  The caller extends this to additional integers for
1306b3a42afSjmmv /// errors reported through exceptions.
1316b3a42afSjmmv ///
1326b3a42afSjmmv /// \param ui Object to interact with the I/O of the program.
1336b3a42afSjmmv /// \param argc The number of arguments passed on the command line.
1346b3a42afSjmmv /// \param argv NULL-terminated array containing the command line arguments.
1356b3a42afSjmmv /// \param mock_command An extra command provided for testing purposes; should
1366b3a42afSjmmv ///     just be NULL other than for tests.
1376b3a42afSjmmv ///
1386b3a42afSjmmv /// \throw cmdline::usage_error If the user ran the program with invalid
1396b3a42afSjmmv ///     arguments.
1406b3a42afSjmmv /// \throw std::exception This propagates any uncaught exception.  Such
1416b3a42afSjmmv ///     exceptions are bugs, but we let them propagate so that the runtime will
1426b3a42afSjmmv ///     abort and dump core.
1436b3a42afSjmmv static int
safe_main(cmdline::ui * ui,int argc,const char * const argv[],cli::cli_command_ptr mock_command)1446b3a42afSjmmv safe_main(cmdline::ui* ui, int argc, const char* const argv[],
1456b3a42afSjmmv           cli::cli_command_ptr mock_command)
1466b3a42afSjmmv {
1476b3a42afSjmmv     cmdline::options_vector options;
1486b3a42afSjmmv     options.push_back(&cli::config_option);
1496b3a42afSjmmv     options.push_back(&cli::variable_option);
1506b3a42afSjmmv     const cmdline::string_option loglevel_option(
1516b3a42afSjmmv         "loglevel", "Level of the messages to log", "level", "info");
1526b3a42afSjmmv     options.push_back(&loglevel_option);
1536b3a42afSjmmv     const cmdline::path_option logfile_option(
1546b3a42afSjmmv         "logfile", "Path to the log file", "file",
1556b3a42afSjmmv         cli::detail::default_log_name().c_str());
1566b3a42afSjmmv     options.push_back(&logfile_option);
1576b3a42afSjmmv 
1586b3a42afSjmmv     cmdline::commands_map< cli::cli_command > commands;
1596b3a42afSjmmv 
1606b3a42afSjmmv     commands.insert(new cli::cmd_about());
1616b3a42afSjmmv     commands.insert(new cli::cmd_config());
1626b3a42afSjmmv     commands.insert(new cli::cmd_db_exec());
1636b3a42afSjmmv     commands.insert(new cli::cmd_db_migrate());
1646b3a42afSjmmv     commands.insert(new cli::cmd_help(&options, &commands));
1656b3a42afSjmmv 
1666b3a42afSjmmv     commands.insert(new cli::cmd_debug(), "Workspace");
1676b3a42afSjmmv     commands.insert(new cli::cmd_list(), "Workspace");
1686b3a42afSjmmv     commands.insert(new cli::cmd_test(), "Workspace");
1696b3a42afSjmmv 
1706b3a42afSjmmv     commands.insert(new cli::cmd_report(), "Reporting");
1716b3a42afSjmmv     commands.insert(new cli::cmd_report_html(), "Reporting");
1726b3a42afSjmmv 
1736b3a42afSjmmv     if (mock_command.get() != NULL)
174*46b85cbbSlukem         commands.insert(std::move(mock_command));
1756b3a42afSjmmv 
1766b3a42afSjmmv     const cmdline::parsed_cmdline cmdline = cmdline::parse(argc, argv, options);
1776b3a42afSjmmv 
1786b3a42afSjmmv     const fs::path logfile(cmdline.get_option< cmdline::path_option >(
1796b3a42afSjmmv         "logfile"));
1806b3a42afSjmmv     fs::mkdir_p(logfile.branch_path(), 0755);
1816b3a42afSjmmv     LD(F("Log file is %s") % logfile);
1826b3a42afSjmmv     utils::install_crash_handlers(logfile.str());
1836b3a42afSjmmv     try {
1846b3a42afSjmmv         logging::set_persistency(cmdline.get_option< cmdline::string_option >(
1856b3a42afSjmmv             "loglevel"), logfile);
1866b3a42afSjmmv     } catch (const std::range_error& e) {
1876b3a42afSjmmv         throw cmdline::usage_error(e.what());
1886b3a42afSjmmv     }
1896b3a42afSjmmv 
1906b3a42afSjmmv     if (cmdline.arguments().empty())
1916b3a42afSjmmv         throw cmdline::usage_error("No command provided");
1926b3a42afSjmmv     const std::string cmdname = cmdline.arguments()[0];
1936b3a42afSjmmv 
1946b3a42afSjmmv     const config::tree user_config = cli::load_config(cmdline,
1956b3a42afSjmmv                                                       cmdname != "help");
1966b3a42afSjmmv 
1976b3a42afSjmmv     cli::cli_command* command = commands.find(cmdname);
1986b3a42afSjmmv     if (command == NULL)
1996b3a42afSjmmv         throw cmdline::usage_error(F("Unknown command '%s'") % cmdname);
2006b3a42afSjmmv     return run_subcommand(ui, command, cmdline.arguments(), user_config);
2016b3a42afSjmmv }
2026b3a42afSjmmv 
2036b3a42afSjmmv 
2046b3a42afSjmmv }  // anonymous namespace
2056b3a42afSjmmv 
2066b3a42afSjmmv 
2076b3a42afSjmmv /// Gets the name of the default log file.
2086b3a42afSjmmv ///
2096b3a42afSjmmv /// \return The path to the log file.
2106b3a42afSjmmv fs::path
default_log_name(void)2116b3a42afSjmmv cli::detail::default_log_name(void)
2126b3a42afSjmmv {
2136b3a42afSjmmv     // Update doc/troubleshooting.texi if you change this algorithm.
2146b3a42afSjmmv     const optional< std::string > home(utils::getenv("HOME"));
2156b3a42afSjmmv     if (home) {
2166b3a42afSjmmv         return logging::generate_log_name(fs::path(home.get()) / ".kyua" /
2176b3a42afSjmmv                                           "logs", cmdline::progname());
2186b3a42afSjmmv     } else {
2196b3a42afSjmmv         const optional< std::string > tmpdir(utils::getenv("TMPDIR"));
2206b3a42afSjmmv         if (tmpdir) {
2216b3a42afSjmmv             return logging::generate_log_name(fs::path(tmpdir.get()),
2226b3a42afSjmmv                                               cmdline::progname());
2236b3a42afSjmmv         } else {
2246b3a42afSjmmv             return logging::generate_log_name(fs::path("/tmp"),
2256b3a42afSjmmv                                               cmdline::progname());
2266b3a42afSjmmv         }
2276b3a42afSjmmv     }
2286b3a42afSjmmv }
2296b3a42afSjmmv 
2306b3a42afSjmmv 
2316b3a42afSjmmv /// Testable entry point, with catch-all exception handlers.
2326b3a42afSjmmv ///
2336b3a42afSjmmv /// This entry point does not perform any initialization of global state; it is
2346b3a42afSjmmv /// provided to allow unit-testing of the utility's entry point.
2356b3a42afSjmmv ///
2366b3a42afSjmmv /// \param ui Object to interact with the I/O of the program.
2376b3a42afSjmmv /// \param argc The number of arguments passed on the command line.
2386b3a42afSjmmv /// \param argv NULL-terminated array containing the command line arguments.
2396b3a42afSjmmv /// \param mock_command An extra command provided for testing purposes; should
2406b3a42afSjmmv ///     just be NULL other than for tests.
2416b3a42afSjmmv ///
2426b3a42afSjmmv /// \return 0 on success, some other integer on error.
2436b3a42afSjmmv ///
2446b3a42afSjmmv /// \throw std::exception This propagates any uncaught exception.  Such
2456b3a42afSjmmv ///     exceptions are bugs, but we let them propagate so that the runtime will
2466b3a42afSjmmv ///     abort and dump core.
2476b3a42afSjmmv int
main(cmdline::ui * ui,const int argc,const char * const * const argv,cli_command_ptr mock_command)2486b3a42afSjmmv cli::main(cmdline::ui* ui, const int argc, const char* const* const argv,
2496b3a42afSjmmv           cli_command_ptr mock_command)
2506b3a42afSjmmv {
2516b3a42afSjmmv     try {
252*46b85cbbSlukem         const int exit_code = safe_main(ui, argc, argv, std::move(mock_command));
2536b3a42afSjmmv 
2546b3a42afSjmmv         // Codes above 1 are reserved to report conditions captured as
2556b3a42afSjmmv         // exceptions below.
2566b3a42afSjmmv         INV(exit_code == EXIT_SUCCESS || exit_code == EXIT_FAILURE);
2576b3a42afSjmmv 
2586b3a42afSjmmv         return exit_code;
2596b3a42afSjmmv     } catch (const signals::interrupted_error& e) {
2606b3a42afSjmmv         cmdline::print_error(ui, e.what());
2616b3a42afSjmmv         // Re-deliver the interruption signal to self so that we terminate with
2626b3a42afSjmmv         // the right status.  At this point we should NOT have any custom signal
2636b3a42afSjmmv         // handlers in place.
2646b3a42afSjmmv         ::kill(getpid(), e.signo());
2656b3a42afSjmmv         LD("Interrupt signal re-delivery did not terminate program");
2666b3a42afSjmmv         // If we reach this, something went wrong because we did not exit as
2676b3a42afSjmmv         // intended.  Return an internal error instead.  (Would be nicer to
2686b3a42afSjmmv         // abort in principle, but it wouldn't be a nice experience if it ever
2696b3a42afSjmmv         // happened.)
2706b3a42afSjmmv         return 2;
2716b3a42afSjmmv     } catch (const std::pair< std::string, cmdline::usage_error >& e) {
2726b3a42afSjmmv         const std::string message = F("Usage error for command %s: %s.") %
2736b3a42afSjmmv             e.first % e.second.what();
2746b3a42afSjmmv         LE(message);
2756b3a42afSjmmv         ui->err(message);
2766b3a42afSjmmv         ui->err(F("Type '%s help %s' for usage information.") %
2776b3a42afSjmmv                 cmdline::progname() % e.first);
2786b3a42afSjmmv         return 3;
2796b3a42afSjmmv     } catch (const cmdline::usage_error& e) {
2806b3a42afSjmmv         const std::string message = F("Usage error: %s.") % e.what();
2816b3a42afSjmmv         LE(message);
2826b3a42afSjmmv         ui->err(message);
2836b3a42afSjmmv         ui->err(F("Type '%s help' for usage information.") %
2846b3a42afSjmmv                 cmdline::progname());
2856b3a42afSjmmv         return 3;
2866b3a42afSjmmv     } catch (const store::old_schema_error& e) {
2876b3a42afSjmmv         const std::string message = F("The database has schema version %s, "
2886b3a42afSjmmv                                       "which is too old; please use db-migrate "
2896b3a42afSjmmv                                       "to upgrade it") % e.old_version();
2906b3a42afSjmmv         cmdline::print_error(ui, message);
2916b3a42afSjmmv         return 2;
2926b3a42afSjmmv     } catch (const std::runtime_error& e) {
2936b3a42afSjmmv         cmdline::print_error(ui, e.what());
2946b3a42afSjmmv         return 2;
2956b3a42afSjmmv     }
2966b3a42afSjmmv }
2976b3a42afSjmmv 
2986b3a42afSjmmv 
2996b3a42afSjmmv /// Delegate for ::main().
3006b3a42afSjmmv ///
3016b3a42afSjmmv /// This function is supposed to be called directly from the top-level ::main()
3026b3a42afSjmmv /// function.  It takes care of initializing internal libraries and then calls
3036b3a42afSjmmv /// main(ui, argc, argv).
3046b3a42afSjmmv ///
3056b3a42afSjmmv /// \pre This function can only be called once.
3066b3a42afSjmmv ///
3076b3a42afSjmmv /// \throw std::exception This propagates any uncaught exception.  Such
3086b3a42afSjmmv ///     exceptions are bugs, but we let them propagate so that the runtime will
3096b3a42afSjmmv ///     abort and dump core.
3106b3a42afSjmmv int
main(const int argc,const char * const * const argv)3116b3a42afSjmmv cli::main(const int argc, const char* const* const argv)
3126b3a42afSjmmv {
3136b3a42afSjmmv     logging::set_inmemory();
3146b3a42afSjmmv 
3156b3a42afSjmmv     LI(F("%s %s") % PACKAGE % VERSION);
3166b3a42afSjmmv 
3176b3a42afSjmmv     std::string plain_args;
3186b3a42afSjmmv     for (const char* const* arg = argv; *arg != NULL; arg++)
3196b3a42afSjmmv         plain_args += F(" %s") % *arg;
3206b3a42afSjmmv     LI(F("Command line:%s") % plain_args);
3216b3a42afSjmmv 
3226b3a42afSjmmv     cmdline::init(argv[0]);
3236b3a42afSjmmv     cmdline::ui ui;
3246b3a42afSjmmv 
3256b3a42afSjmmv     const int exit_code = main(&ui, argc, argv);
3266b3a42afSjmmv     LI(F("Clean exit with code %s") % exit_code);
3276b3a42afSjmmv     return exit_code;
3286b3a42afSjmmv }
329