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