#include #include #include #include #include #include "args.hh" #include "logger.hh" #include "logger_file.hh" #include "logger_syslog.hh" #include "server.hh" namespace { struct Context { std::shared_ptr logger_; bool verbose_{false}; bool daemon_{false}; std::string host_{"localhost"}; uint16_t port_{12000}; }; bool parse_args(int argc, char** argv, std::shared_ptr logger, Context& context, int& exit_code) { auto args = Args::create(); auto opt_help = args->add_option('h', "help", "display this text and exit"); auto opt_version = args->add_option( 'V', "version", "display version and exit"); auto opt_verbose = args->add_option('v', "verbose", "be verbose"); auto opt_daemon = args->add_option( 'd', "daemon", "fork into the background as a daemon"); auto opt_logfile = args->add_option_with_arg( 'L', "log", "log to FILE instead of default (" "syslog for daemon, stdout otherwise)", "FILE"); auto opt_host = args->add_option_with_arg( 'h', "host", "HOST to listen for requests on (default localhost)", "HOST"); auto opt_port = args->add_option_with_arg( 'p', "port", "PORT to listen for requests on (default 12000)", "PORT"); std::vector extra; if (!args->run(argc, argv, "buildhelper", std::cerr, extra)) { std::cerr << "Try `buildhelper --help` for usage." << std::endl; exit_code = 1; return false; } if (opt_help->is_set()) { std::cout << "Usage `buildhelper [OPTIONS]`.\n" << "Runs a buildhelper instance, implementing the Remote Execution API. Answers requests from bazel and others.\n" << "\n" << "Options:" << std::endl; args->print_descriptions(std::cout, 80); exit_code = 0; return false; } if (opt_version->is_set()) { std::cout << "buildhelper 0.1 written by Joel Klinghed " << std::endl; exit_code = 0; return false; } context.verbose_ = opt_verbose->is_set(); context.daemon_ = opt_daemon->is_set(); if (opt_host->is_set()) { context.host_ = opt_host->arg(); } if (opt_port->is_set()) { auto const& arg = opt_port->arg(); auto [ptr, err] = std::from_chars(arg.data(), arg.data() + arg.size(), context.port_); if (ptr != arg.data() + arg.size() || err != std::errc()) { std::cerr << "Invalid port value: " << arg << std::endl; exit_code = 1; return false; } } if (opt_logfile->is_set()) { if (opt_logfile->arg() == "-") { if (context.daemon_) { std::cerr << "Cannot log to stdout AND fork into background." << std::endl; exit_code = 1; return false; } context.logger_ = logger; } else { context.logger_ = LoggerFile::create( std::filesystem::path(opt_logfile->arg()), logger.get()); } } else { if (context.daemon_) { context.logger_ = LoggerSyslog::create("buildhelper"); } else { context.logger_ = logger; } } return true; } } // namespace int main(int argc, char** argv) { std::shared_ptr logger = Logger::create_stdio(); Context context; int exit_code = 0; if (!parse_args(argc, argv, logger, context, exit_code)) { return exit_code; } auto server = Server::create(); if (!server->setup(logger, context.host_, context.port_)) { return 1; } if (context.daemon_) { auto pid = fork(); if (pid == -1) { logger->err("Unable to fork: %s", strerror(errno)); return 1; } if (pid == 0) { // Child process // Close stdin, stdout and stderr. close(0); close(1); close(2); // Chdir to root, avoid keeping started directory alive chdir("/"); // Create new session setsid(); } else { // Parent process if (context.verbose_) logger->info("Forked as %ld", static_cast(pid)); // Don't run atexit or anything like that, all is owned by child. _exit(0); } } logger.reset(); if (server->run(context.logger_)) return 0; return 1; }