xref: /src/contrib/kyua/cli/main.cpp (revision b392a90ba4e5ea07d8a88a834fd102191d1967bf)
108334c51SBrooks Davis // Copyright 2010 The Kyua Authors.
208334c51SBrooks Davis // All rights reserved.
308334c51SBrooks Davis //
408334c51SBrooks Davis // Redistribution and use in source and binary forms, with or without
508334c51SBrooks Davis // modification, are permitted provided that the following conditions are
608334c51SBrooks Davis // met:
708334c51SBrooks Davis //
808334c51SBrooks Davis // * Redistributions of source code must retain the above copyright
908334c51SBrooks Davis //   notice, this list of conditions and the following disclaimer.
1008334c51SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
1108334c51SBrooks Davis //   notice, this list of conditions and the following disclaimer in the
1208334c51SBrooks Davis //   documentation and/or other materials provided with the distribution.
1308334c51SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
1408334c51SBrooks Davis //   may be used to endorse or promote products derived from this software
1508334c51SBrooks Davis //   without specific prior written permission.
1608334c51SBrooks Davis //
1708334c51SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1808334c51SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1908334c51SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2008334c51SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2108334c51SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2208334c51SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2308334c51SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2408334c51SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2508334c51SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2608334c51SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2708334c51SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2808334c51SBrooks Davis 
2908334c51SBrooks Davis #include "cli/main.hpp"
3008334c51SBrooks Davis 
3108334c51SBrooks Davis #if defined(HAVE_CONFIG_H)
3208334c51SBrooks Davis #   include "config.h"
3308334c51SBrooks Davis #endif
3408334c51SBrooks Davis 
3508334c51SBrooks Davis extern "C" {
3608334c51SBrooks Davis #include <signal.h>
3708334c51SBrooks Davis #include <unistd.h>
3808334c51SBrooks Davis }
3908334c51SBrooks Davis 
4008334c51SBrooks Davis #include <cstdlib>
4108334c51SBrooks Davis #include <iostream>
4208334c51SBrooks Davis #include <string>
4308334c51SBrooks Davis #include <utility>
4408334c51SBrooks Davis 
4508334c51SBrooks Davis #include "cli/cmd_about.hpp"
4608334c51SBrooks Davis #include "cli/cmd_config.hpp"
4708334c51SBrooks Davis #include "cli/cmd_db_exec.hpp"
4808334c51SBrooks Davis #include "cli/cmd_db_migrate.hpp"
4908334c51SBrooks Davis #include "cli/cmd_debug.hpp"
5008334c51SBrooks Davis #include "cli/cmd_help.hpp"
5108334c51SBrooks Davis #include "cli/cmd_list.hpp"
5208334c51SBrooks Davis #include "cli/cmd_report.hpp"
5308334c51SBrooks Davis #include "cli/cmd_report_html.hpp"
5408334c51SBrooks Davis #include "cli/cmd_report_junit.hpp"
5508334c51SBrooks Davis #include "cli/cmd_test.hpp"
5608334c51SBrooks Davis #include "cli/common.ipp"
5708334c51SBrooks Davis #include "cli/config.hpp"
5808334c51SBrooks Davis #include "engine/atf.hpp"
5908334c51SBrooks Davis #include "engine/plain.hpp"
6008334c51SBrooks Davis #include "engine/scheduler.hpp"
6108334c51SBrooks Davis #include "engine/tap.hpp"
6208334c51SBrooks Davis #include "store/exceptions.hpp"
6308334c51SBrooks Davis #include "utils/cmdline/commands_map.ipp"
6408334c51SBrooks Davis #include "utils/cmdline/exceptions.hpp"
6508334c51SBrooks Davis #include "utils/cmdline/globals.hpp"
6608334c51SBrooks Davis #include "utils/cmdline/options.hpp"
6708334c51SBrooks Davis #include "utils/cmdline/parser.ipp"
6808334c51SBrooks Davis #include "utils/cmdline/ui.hpp"
6908334c51SBrooks Davis #include "utils/config/tree.ipp"
7008334c51SBrooks Davis #include "utils/env.hpp"
7108334c51SBrooks Davis #include "utils/format/macros.hpp"
7208334c51SBrooks Davis #include "utils/fs/operations.hpp"
7308334c51SBrooks Davis #include "utils/fs/path.hpp"
7408334c51SBrooks Davis #include "utils/logging/macros.hpp"
7508334c51SBrooks Davis #include "utils/logging/operations.hpp"
7608334c51SBrooks Davis #include "utils/optional.ipp"
7708334c51SBrooks Davis #include "utils/sanity.hpp"
7808334c51SBrooks Davis #include "utils/signals/exceptions.hpp"
7908334c51SBrooks Davis 
8008334c51SBrooks Davis namespace cmdline = utils::cmdline;
8108334c51SBrooks Davis namespace config = utils::config;
8208334c51SBrooks Davis namespace fs = utils::fs;
8308334c51SBrooks Davis namespace logging = utils::logging;
8408334c51SBrooks Davis namespace signals = utils::signals;
8508334c51SBrooks Davis namespace scheduler = engine::scheduler;
8608334c51SBrooks Davis 
8708334c51SBrooks Davis using utils::none;
8808334c51SBrooks Davis using utils::optional;
8908334c51SBrooks Davis 
9008334c51SBrooks Davis 
9108334c51SBrooks Davis namespace {
9208334c51SBrooks Davis 
9308334c51SBrooks Davis 
9408334c51SBrooks Davis /// Registers all valid scheduler interfaces.
9508334c51SBrooks Davis ///
9608334c51SBrooks Davis /// This is part of Kyua's setup but it is a bit strange to find it here.  I am
9708334c51SBrooks Davis /// not sure what a better location would be though, so for now this is good
9808334c51SBrooks Davis /// enough.
9908334c51SBrooks Davis static void
register_scheduler_interfaces(void)10008334c51SBrooks Davis register_scheduler_interfaces(void)
10108334c51SBrooks Davis {
10208334c51SBrooks Davis     scheduler::register_interface(
10308334c51SBrooks Davis         "atf", std::shared_ptr< scheduler::interface >(
10408334c51SBrooks Davis             new engine::atf_interface()));
10508334c51SBrooks Davis     scheduler::register_interface(
10608334c51SBrooks Davis         "plain", std::shared_ptr< scheduler::interface >(
10708334c51SBrooks Davis             new engine::plain_interface()));
10808334c51SBrooks Davis     scheduler::register_interface(
10908334c51SBrooks Davis         "tap", std::shared_ptr< scheduler::interface >(
11008334c51SBrooks Davis             new engine::tap_interface()));
11108334c51SBrooks Davis }
11208334c51SBrooks Davis 
11308334c51SBrooks Davis 
11408334c51SBrooks Davis /// Executes the given subcommand with proper usage_error reporting.
11508334c51SBrooks Davis ///
11608334c51SBrooks Davis /// \param ui Object to interact with the I/O of the program.
11708334c51SBrooks Davis /// \param command The subcommand to execute.
11808334c51SBrooks Davis /// \param args The part of the command line passed to the subcommand.  The
11908334c51SBrooks Davis ///     first item of this collection must match the command name.
12008334c51SBrooks Davis /// \param user_config The runtime configuration to pass to the subcommand.
12108334c51SBrooks Davis ///
12208334c51SBrooks Davis /// \return The exit code of the command.  Typically 0 on success, some other
12308334c51SBrooks Davis /// integer otherwise.
12408334c51SBrooks Davis ///
12508334c51SBrooks Davis /// \throw cmdline::usage_error If the user input to the subcommand is invalid.
12608334c51SBrooks Davis ///     This error does not encode the command name within it, so this function
12708334c51SBrooks Davis ///     extends the message in the error to specify which subcommand was
12808334c51SBrooks Davis ///     affected.
12908334c51SBrooks Davis /// \throw std::exception This propagates any uncaught exception.  Such
13008334c51SBrooks Davis ///     exceptions are bugs, but we let them propagate so that the runtime will
13108334c51SBrooks Davis ///     abort and dump core.
13208334c51SBrooks Davis static int
run_subcommand(cmdline::ui * ui,cli::cli_command * command,const cmdline::args_vector & args,const config::tree & user_config)13308334c51SBrooks Davis run_subcommand(cmdline::ui* ui, cli::cli_command* command,
13408334c51SBrooks Davis                const cmdline::args_vector& args,
13508334c51SBrooks Davis                const config::tree& user_config)
13608334c51SBrooks Davis {
13708334c51SBrooks Davis     try {
13808334c51SBrooks Davis         PRE(command->name() == args[0]);
13908334c51SBrooks Davis         return command->main(ui, args, user_config);
14008334c51SBrooks Davis     } catch (const cmdline::usage_error& e) {
14108334c51SBrooks Davis         throw std::pair< std::string, cmdline::usage_error >(
14208334c51SBrooks Davis             command->name(), e);
14308334c51SBrooks Davis     }
14408334c51SBrooks Davis }
14508334c51SBrooks Davis 
14608334c51SBrooks Davis 
14708334c51SBrooks Davis /// Exception-safe version of main.
14808334c51SBrooks Davis ///
14908334c51SBrooks Davis /// This function provides the real meat of the entry point of the program.  It
15008334c51SBrooks Davis /// is allowed to throw some known exceptions which are parsed by the caller.
15108334c51SBrooks Davis /// Doing so keeps this function simpler and allow tests to actually validate
15208334c51SBrooks Davis /// that the errors reported are accurate.
15308334c51SBrooks Davis ///
15408334c51SBrooks Davis /// \return The exit code of the program.  Should be EXIT_SUCCESS on success and
15508334c51SBrooks Davis /// EXIT_FAILURE on failure.  The caller extends this to additional integers for
15608334c51SBrooks Davis /// errors reported through exceptions.
15708334c51SBrooks Davis ///
15808334c51SBrooks Davis /// \param ui Object to interact with the I/O of the program.
15908334c51SBrooks Davis /// \param argc The number of arguments passed on the command line.
16008334c51SBrooks Davis /// \param argv NULL-terminated array containing the command line arguments.
16108334c51SBrooks Davis /// \param mock_command An extra command provided for testing purposes; should
16208334c51SBrooks Davis ///     just be NULL other than for tests.
16308334c51SBrooks Davis ///
16408334c51SBrooks Davis /// \throw cmdline::usage_error If the user ran the program with invalid
16508334c51SBrooks Davis ///     arguments.
16608334c51SBrooks Davis /// \throw std::exception This propagates any uncaught exception.  Such
16708334c51SBrooks Davis ///     exceptions are bugs, but we let them propagate so that the runtime will
16808334c51SBrooks Davis ///     abort and dump core.
16908334c51SBrooks Davis static int
safe_main(cmdline::ui * ui,int argc,const char * const argv[],cli::cli_command_ptr mock_command)17008334c51SBrooks Davis safe_main(cmdline::ui* ui, int argc, const char* const argv[],
17108334c51SBrooks Davis           cli::cli_command_ptr mock_command)
17208334c51SBrooks Davis {
17308334c51SBrooks Davis     cmdline::options_vector options;
17408334c51SBrooks Davis     options.push_back(&cli::config_option);
17508334c51SBrooks Davis     options.push_back(&cli::variable_option);
17608334c51SBrooks Davis     const cmdline::string_option loglevel_option(
17708334c51SBrooks Davis         "loglevel", "Level of the messages to log", "level", "info");
17808334c51SBrooks Davis     options.push_back(&loglevel_option);
17908334c51SBrooks Davis     const cmdline::path_option logfile_option(
18008334c51SBrooks Davis         "logfile", "Path to the log file", "file",
18108334c51SBrooks Davis         cli::detail::default_log_name().c_str());
18208334c51SBrooks Davis     options.push_back(&logfile_option);
18308334c51SBrooks Davis 
18408334c51SBrooks Davis     cmdline::commands_map< cli::cli_command > commands;
18508334c51SBrooks Davis 
18608334c51SBrooks Davis     commands.insert(new cli::cmd_about());
18708334c51SBrooks Davis     commands.insert(new cli::cmd_config());
18808334c51SBrooks Davis     commands.insert(new cli::cmd_db_exec());
18908334c51SBrooks Davis     commands.insert(new cli::cmd_db_migrate());
19008334c51SBrooks Davis     commands.insert(new cli::cmd_help(&options, &commands));
19108334c51SBrooks Davis 
19208334c51SBrooks Davis     commands.insert(new cli::cmd_debug(), "Workspace");
19308334c51SBrooks Davis     commands.insert(new cli::cmd_list(), "Workspace");
19408334c51SBrooks Davis     commands.insert(new cli::cmd_test(), "Workspace");
19508334c51SBrooks Davis 
19608334c51SBrooks Davis     commands.insert(new cli::cmd_report(), "Reporting");
19708334c51SBrooks Davis     commands.insert(new cli::cmd_report_html(), "Reporting");
19808334c51SBrooks Davis     commands.insert(new cli::cmd_report_junit(), "Reporting");
19908334c51SBrooks Davis 
20008334c51SBrooks Davis     if (mock_command.get() != NULL)
201b392a90bSJohn Baldwin         commands.insert(std::move(mock_command));
20208334c51SBrooks Davis 
20308334c51SBrooks Davis     const cmdline::parsed_cmdline cmdline = cmdline::parse(argc, argv, options);
20408334c51SBrooks Davis 
20508334c51SBrooks Davis     const fs::path logfile(cmdline.get_option< cmdline::path_option >(
20608334c51SBrooks Davis         "logfile"));
20708334c51SBrooks Davis     fs::mkdir_p(logfile.branch_path(), 0755);
20808334c51SBrooks Davis     LD(F("Log file is %s") % logfile);
20908334c51SBrooks Davis     utils::install_crash_handlers(logfile.str());
21008334c51SBrooks Davis     try {
21108334c51SBrooks Davis         logging::set_persistency(cmdline.get_option< cmdline::string_option >(
21208334c51SBrooks Davis             "loglevel"), logfile);
21308334c51SBrooks Davis     } catch (const std::range_error& e) {
21408334c51SBrooks Davis         throw cmdline::usage_error(e.what());
21508334c51SBrooks Davis     }
21608334c51SBrooks Davis 
21708334c51SBrooks Davis     if (cmdline.arguments().empty())
21808334c51SBrooks Davis         throw cmdline::usage_error("No command provided");
21908334c51SBrooks Davis     const std::string cmdname = cmdline.arguments()[0];
22008334c51SBrooks Davis 
22108334c51SBrooks Davis     const config::tree user_config = cli::load_config(cmdline,
22208334c51SBrooks Davis                                                       cmdname != "help");
22308334c51SBrooks Davis 
22408334c51SBrooks Davis     cli::cli_command* command = commands.find(cmdname);
22508334c51SBrooks Davis     if (command == NULL)
22608334c51SBrooks Davis         throw cmdline::usage_error(F("Unknown command '%s'") % cmdname);
22708334c51SBrooks Davis     register_scheduler_interfaces();
22808334c51SBrooks Davis     return run_subcommand(ui, command, cmdline.arguments(), user_config);
22908334c51SBrooks Davis }
23008334c51SBrooks Davis 
23108334c51SBrooks Davis 
23208334c51SBrooks Davis }  // anonymous namespace
23308334c51SBrooks Davis 
23408334c51SBrooks Davis 
23508334c51SBrooks Davis /// Gets the name of the default log file.
23608334c51SBrooks Davis ///
23708334c51SBrooks Davis /// \return The path to the log file.
23808334c51SBrooks Davis fs::path
default_log_name(void)23908334c51SBrooks Davis cli::detail::default_log_name(void)
24008334c51SBrooks Davis {
24108334c51SBrooks Davis     // Update doc/troubleshooting.texi if you change this algorithm.
24208334c51SBrooks Davis     const optional< std::string > home(utils::getenv("HOME"));
24308334c51SBrooks Davis     if (home) {
24408334c51SBrooks Davis         return logging::generate_log_name(fs::path(home.get()) / ".kyua" /
24508334c51SBrooks Davis                                           "logs", cmdline::progname());
24608334c51SBrooks Davis     } else {
24708334c51SBrooks Davis         const optional< std::string > tmpdir(utils::getenv("TMPDIR"));
24808334c51SBrooks Davis         if (tmpdir) {
24908334c51SBrooks Davis             return logging::generate_log_name(fs::path(tmpdir.get()),
25008334c51SBrooks Davis                                               cmdline::progname());
25108334c51SBrooks Davis         } else {
25208334c51SBrooks Davis             return logging::generate_log_name(fs::path("/tmp"),
25308334c51SBrooks Davis                                               cmdline::progname());
25408334c51SBrooks Davis         }
25508334c51SBrooks Davis     }
25608334c51SBrooks Davis }
25708334c51SBrooks Davis 
25808334c51SBrooks Davis 
25908334c51SBrooks Davis /// Testable entry point, with catch-all exception handlers.
26008334c51SBrooks Davis ///
26108334c51SBrooks Davis /// This entry point does not perform any initialization of global state; it is
26208334c51SBrooks Davis /// provided to allow unit-testing of the utility's entry point.
26308334c51SBrooks Davis ///
26408334c51SBrooks Davis /// \param ui Object to interact with the I/O of the program.
26508334c51SBrooks Davis /// \param argc The number of arguments passed on the command line.
26608334c51SBrooks Davis /// \param argv NULL-terminated array containing the command line arguments.
26708334c51SBrooks Davis /// \param mock_command An extra command provided for testing purposes; should
26808334c51SBrooks Davis ///     just be NULL other than for tests.
26908334c51SBrooks Davis ///
27008334c51SBrooks Davis /// \return 0 on success, some other integer on error.
27108334c51SBrooks Davis ///
27208334c51SBrooks Davis /// \throw std::exception This propagates any uncaught exception.  Such
27308334c51SBrooks Davis ///     exceptions are bugs, but we let them propagate so that the runtime will
27408334c51SBrooks Davis ///     abort and dump core.
27508334c51SBrooks Davis int
main(cmdline::ui * ui,const int argc,const char * const * const argv,cli_command_ptr mock_command)27608334c51SBrooks Davis cli::main(cmdline::ui* ui, const int argc, const char* const* const argv,
27708334c51SBrooks Davis           cli_command_ptr mock_command)
27808334c51SBrooks Davis {
27908334c51SBrooks Davis     try {
280b392a90bSJohn Baldwin         const int exit_code = safe_main(ui, argc, argv, std::move(mock_command));
28108334c51SBrooks Davis 
28208334c51SBrooks Davis         // Codes above 1 are reserved to report conditions captured as
28308334c51SBrooks Davis         // exceptions below.
28408334c51SBrooks Davis         INV(exit_code == EXIT_SUCCESS || exit_code == EXIT_FAILURE);
28508334c51SBrooks Davis 
28608334c51SBrooks Davis         return exit_code;
28708334c51SBrooks Davis     } catch (const signals::interrupted_error& e) {
28808334c51SBrooks Davis         cmdline::print_error(ui, F("%s.") % e.what());
28908334c51SBrooks Davis         // Re-deliver the interruption signal to self so that we terminate with
29008334c51SBrooks Davis         // the right status.  At this point we should NOT have any custom signal
29108334c51SBrooks Davis         // handlers in place.
29208334c51SBrooks Davis         ::kill(getpid(), e.signo());
29308334c51SBrooks Davis         LD("Interrupt signal re-delivery did not terminate program");
29408334c51SBrooks Davis         // If we reach this, something went wrong because we did not exit as
29508334c51SBrooks Davis         // intended.  Return an internal error instead.  (Would be nicer to
29608334c51SBrooks Davis         // abort in principle, but it wouldn't be a nice experience if it ever
29708334c51SBrooks Davis         // happened.)
29808334c51SBrooks Davis         return 2;
29908334c51SBrooks Davis     } catch (const std::pair< std::string, cmdline::usage_error >& e) {
30008334c51SBrooks Davis         const std::string message = F("Usage error for command %s: %s.") %
30108334c51SBrooks Davis             e.first % e.second.what();
30208334c51SBrooks Davis         LE(message);
30308334c51SBrooks Davis         ui->err(message);
30408334c51SBrooks Davis         ui->err(F("Type '%s help %s' for usage information.") %
30508334c51SBrooks Davis                 cmdline::progname() % e.first);
30608334c51SBrooks Davis         return 3;
30708334c51SBrooks Davis     } catch (const cmdline::usage_error& e) {
30808334c51SBrooks Davis         const std::string message = F("Usage error: %s.") % e.what();
30908334c51SBrooks Davis         LE(message);
31008334c51SBrooks Davis         ui->err(message);
31108334c51SBrooks Davis         ui->err(F("Type '%s help' for usage information.") %
31208334c51SBrooks Davis                 cmdline::progname());
31308334c51SBrooks Davis         return 3;
31408334c51SBrooks Davis     } catch (const store::old_schema_error& e) {
31508334c51SBrooks Davis         const std::string message = F("The database has schema version %s, "
31608334c51SBrooks Davis                                       "which is too old; please use db-migrate "
31708334c51SBrooks Davis                                       "to upgrade it.") % e.old_version();
31808334c51SBrooks Davis         cmdline::print_error(ui, message);
31908334c51SBrooks Davis         return 2;
32008334c51SBrooks Davis     } catch (const std::runtime_error& e) {
32108334c51SBrooks Davis         cmdline::print_error(ui, F("%s.") % e.what());
32208334c51SBrooks Davis         return 2;
32308334c51SBrooks Davis     }
32408334c51SBrooks Davis }
32508334c51SBrooks Davis 
32608334c51SBrooks Davis 
32708334c51SBrooks Davis /// Delegate for ::main().
32808334c51SBrooks Davis ///
32908334c51SBrooks Davis /// This function is supposed to be called directly from the top-level ::main()
33008334c51SBrooks Davis /// function.  It takes care of initializing internal libraries and then calls
33108334c51SBrooks Davis /// main(ui, argc, argv).
33208334c51SBrooks Davis ///
33308334c51SBrooks Davis /// \pre This function can only be called once.
33408334c51SBrooks Davis ///
33508334c51SBrooks Davis /// \throw std::exception This propagates any uncaught exception.  Such
33608334c51SBrooks Davis ///     exceptions are bugs, but we let them propagate so that the runtime will
33708334c51SBrooks Davis ///     abort and dump core.
33808334c51SBrooks Davis int
main(const int argc,const char * const * const argv)33908334c51SBrooks Davis cli::main(const int argc, const char* const* const argv)
34008334c51SBrooks Davis {
34108334c51SBrooks Davis     logging::set_inmemory();
34208334c51SBrooks Davis 
34308334c51SBrooks Davis     LI(F("%s %s") % PACKAGE % VERSION);
34408334c51SBrooks Davis 
34508334c51SBrooks Davis     std::string plain_args;
34608334c51SBrooks Davis     for (const char* const* arg = argv; *arg != NULL; arg++)
34708334c51SBrooks Davis         plain_args += F(" %s") % *arg;
34808334c51SBrooks Davis     LI(F("Command line:%s") % plain_args);
34908334c51SBrooks Davis 
35008334c51SBrooks Davis     cmdline::init(argv[0]);
35108334c51SBrooks Davis     cmdline::ui ui;
35208334c51SBrooks Davis 
35308334c51SBrooks Davis     const int exit_code = main(&ui, argc, argv);
35408334c51SBrooks Davis     LI(F("Clean exit with code %s") % exit_code);
35508334c51SBrooks Davis     return exit_code;
35608334c51SBrooks Davis }
357