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