Submit
Path:
~
/
/
opt
/
cpanel
/
ea-ruby27
/
src
/
passenger-release-6.0.23
/
src
/
agent
/
Watchdog
/
File Content:
WatchdogMain.cpp
/* * Phusion Passenger - https://www.phusionpassenger.com/ * Copyright (c) 2010-2018 Phusion Holding B.V. * * "Passenger", "Phusion Passenger" and "Union Station" are registered * trademarks of Phusion Holding B.V. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ // Include ev++.h early to avoid macro clash on EV_ERROR. #include <ev++.h> #include <oxt/thread.hpp> #include <oxt/system_calls.hpp> #include <boost/function.hpp> #include <boost/bind/bind.hpp> #include <boost/make_shared.hpp> #include <boost/foreach.hpp> #include <boost/enable_shared_from_this.hpp> #include <string> #include <utility> #include <vector> #include <algorithm> #if !defined(sun) && !defined(__sun) #define HAVE_FLOCK #endif #ifdef __linux__ #include <sys/prctl.h> #endif #include <sys/select.h> #include <sys/types.h> #include <sys/time.h> #ifdef HAVE_FLOCK #include <sys/file.h> #endif #include <sys/resource.h> #include <unistd.h> #include <pwd.h> #include <grp.h> #include <fcntl.h> #include <cstdio> #include <cstdlib> #include <cstring> #include <cerrno> #include <jsoncpp/json.h> #include <Shared/Fundamentals/Initialization.h> #include <Shared/ApiServerUtils.h> #include <Core/OptionParser.h> #include <Watchdog/Config.h> #include <Watchdog/ApiServer.h> #include <JsonTools/Autocast.h> #include <Constants.h> #include <InstanceDirectory.h> #include <FileDescriptor.h> #include <FileTools/PathManip.h> #include <FileTools/PathSecurityCheck.h> #include <SystemTools/UserDatabase.h> #include <SystemTools/ContainerHelpers.h> #include <RandomGenerator.h> #include <BackgroundEventLoop.h> #include <LoggingKit/LoggingKit.h> #include <MainFunctions.h> #include <Exceptions.h> #include <StaticString.h> #include <Hooks.h> #include <IOTools/IOUtils.h> #include <IOTools/MessageIO.h> #include <Utils.h> #include <Utils/Timer.h> #include <Utils/ScopeGuard.h> #include <StrIntTools/StrIntUtils.h> #include <Utils/OptionParsing.h> #include <Utils/VariantMap.h> using namespace std; using namespace boost; using namespace oxt; using namespace Passenger; using namespace Passenger::Agent::Fundamentals; enum OomFileType { OOM_ADJ, OOM_SCORE_ADJ }; class InstanceDirToucher; class AgentWatcher; /***** Working objects *****/ namespace Passenger { namespace Watchdog { struct WorkingObjects { RandomGenerator randomGenerator; EventFd errorEvent; EventFd exitEvent; uid_t defaultUid; gid_t defaultGid; InstanceDirectoryPtr instanceDir; int startupReportFile; int lockFile; vector<string> cleanupPidfiles; bool pidsCleanedUp; bool pidFileCleanedUp; string corePidFile; string fdPassingPassword; Json::Value extraConfigToPassToSubAgents; Json::Value controllerAddresses; Json::Value coreApiServerAddresses; Json::Value coreApiServerAuthorizations; Json::Value watchdogApiServerAddresses; Json::Value watchdogApiServerAuthorizations; int apiServerFds[SERVER_KIT_MAX_SERVER_ENDPOINTS]; BackgroundEventLoop *bgloop; ServerKit::Context *serverKitContext; ServerKit::Schema serverKitSchema; ApiServer::ApiServer *apiServer; WorkingObjects() : errorEvent(__FILE__, __LINE__, "WorkingObjects: errorEvent"), exitEvent(__FILE__, __LINE__, "WorkingObjects: exitEvent"), startupReportFile(-1), pidsCleanedUp(false), pidFileCleanedUp(false), extraConfigToPassToSubAgents(Json::objectValue), controllerAddresses(Json::arrayValue), coreApiServerAddresses(Json::arrayValue), coreApiServerAuthorizations(Json::arrayValue), watchdogApiServerAddresses(Json::arrayValue), watchdogApiServerAuthorizations(Json::arrayValue), bgloop(NULL), serverKitContext(NULL), apiServer(NULL) { for (unsigned int i = 0; i < SERVER_KIT_MAX_SERVER_ENDPOINTS; i++) { apiServerFds[i] = -1; } } }; typedef boost::shared_ptr<WorkingObjects> WorkingObjectsPtr; } // namespace Watchdog } // namespace Passenger using namespace Passenger::Watchdog; static WrapperRegistry::Registry *watchdogWrapperRegistry; static Schema *watchdogSchema; static ConfigKit::Store *watchdogConfig; static WorkingObjects *workingObjects; static void cleanup(const WorkingObjectsPtr &wo); #include "AgentWatcher.cpp" #include "InstanceDirToucher.cpp" #include "CoreWatcher.cpp" /***** Functions *****/ #if !BOOST_OS_MACOS struct WatchdogOomAdjustResult { struct Message { LoggingKit::Level level; string text; }; string oldScore; // LoggingKit has not been initialized yet when setOomScoreNeverKill() // is called, so we store the messages here and print them after // LoggingKit initialization. vector<Message> messages; }; static FILE * openOomAdjFileGetType(const char *mode, OomFileType &type, string &path) { FILE *f = fopen("/proc/self/oom_score_adj", mode); if (f == NULL) { f = fopen("/proc/self/oom_adj", mode); if (f == NULL) { return NULL; } else { type = OOM_ADJ; path = "/proc/self/oom_adj"; return f; } } else { type = OOM_SCORE_ADJ; path = "/proc/self/oom_score_adj"; return f; } } /** * Set the current process's OOM score to "never kill". */ static WatchdogOomAdjustResult setOomScoreNeverKill() { WatchdogOomAdjustResult result; FILE *f; string path; OomFileType type; int e; if (geteuid() != 0) { WatchdogOomAdjustResult::Message msg; msg.level = LoggingKit::DEBUG; msg.text = "Not adjusting Watchdog's OOM score because not running with root privileges"; result.messages.push_back(msg); return result; } f = openOomAdjFileGetType("r", type, path); if (f == NULL) { e = errno; P_ERROR("Error adjusting Watchdog's OOM score: error opening both" " /proc/self/oom_score_adj and /proc/self/oom_adj for reading: " << strerror(e) << " (errno=" << e << ")"); return result; } // mark if this is a legacy score so we won't try to write it as OOM_SCORE_ADJ if (type == OOM_ADJ) { result.oldScore.append("l"); } char buf[1024]; size_t bytesRead; while (true) { bytesRead = fread(buf, 1, sizeof(buf), f); if (bytesRead == 0 && feof(f)) { break; } else if (bytesRead == 0 && ferror(f)) { P_ERROR("Error adjusting Watchdog's OOM score: error reading " << path); fclose(f); result.oldScore.clear(); return result; } else { result.oldScore.append(buf, bytesRead); } } fclose(f); f = fopen(path.c_str(), "w"); if (f == NULL) { e = errno; WatchdogOomAdjustResult::Message msg; msg.level = LoggingKit::ERROR; msg.text = "Error adjusting Watchdog's OOM score: error opening " + path + " for writing: " + strerror(e) + " (errno=" + toString(e) + ")"; result.messages.push_back(msg); result.oldScore.clear(); return result; } if (type == OOM_SCORE_ADJ) { fprintf(f, "-1000\n"); } else { assert(type == OOM_ADJ); fprintf(f, "-17\n"); } e = fflush(f); if (e != 0) { e = errno; WatchdogOomAdjustResult::Message msg; if (autoDetectInContainer()) { msg.level = LoggingKit::INFO; msg.text = "Running in container, so couldn't adjust Watchdog's" " OOM score through " + path; } else { msg.level = LoggingKit::ERROR; msg.text = "Error adjusting Watchdog's OOM score: error writing to " + path + ": " + strerror(e) + " (errno=" + toString(e) + ")"; } result.messages.push_back(msg); } e = fclose(f); if (e == EOF) { e = errno; WatchdogOomAdjustResult::Message msg; msg.level = LoggingKit::ERROR; msg.text = "Error adjusting Watchdog's OOM score: error closing " + path + ": " + strerror(e) + " (errno=" + toString(e) + ")"; result.messages.push_back(msg); } return result; } static void printOomAdjustResultMessages(const WatchdogOomAdjustResult &result) { vector<WatchdogOomAdjustResult::Message>::const_iterator it; for (it = result.messages.begin(); it != result.messages.end(); it++) { P_LOG(LoggingKit::context, it->level, __FILE__, __LINE__, it->text); } } #endif static void terminationHandler(int signo) { ssize_t ret = write(workingObjects->exitEvent.writerFd(), "x", 1); (void) ret; // Don't care about the result. } /** * Wait until the starter process has exited or sent us an exit command, * or until one of the watcher threads encounter an error. If a thread * encountered an error then the error message will be printed. * * Returns whether this watchdog should exit gracefully, which is only the * case if the web server sent us an exit command and no thread encountered * an error. */ static bool waitForStarterProcessOrWatchers(const WorkingObjectsPtr &wo, vector<AgentWatcherPtr> &watchers) { TRACE_POINT(); fd_set fds; int max = -1, ret; char x; wo->bgloop->start("Main event loop", 0); struct sigaction action; action.sa_handler = terminationHandler; action.sa_flags = SA_RESTART; sigemptyset(&action.sa_mask); sigaction(SIGINT, &action, NULL); sigaction(SIGTERM, &action, NULL); FD_ZERO(&fds); if (feedbackFdAvailable()) { FD_SET(FEEDBACK_FD, &fds); max = std::max(max, FEEDBACK_FD); } FD_SET(wo->errorEvent.fd(), &fds); max = std::max(max, wo->errorEvent.fd()); FD_SET(wo->exitEvent.fd(), &fds); max = std::max(max, wo->exitEvent.fd()); UPDATE_TRACE_POINT(); ret = syscalls::select(max + 1, &fds, NULL, NULL, NULL); if (ret == -1) { int e = errno; P_ERROR("select() failed: " << strerror(e)); return false; } action.sa_handler = SIG_DFL; sigaction(SIGINT, &action, NULL); sigaction(SIGTERM, &action, NULL); P_DEBUG("Stopping API server"); wo->bgloop->stop(); for (unsigned int i = 0; i < SERVER_KIT_MAX_SERVER_ENDPOINTS; i++) { if (wo->apiServerFds[i] != -1) { syscalls::close(wo->apiServerFds[i]); } } if (FD_ISSET(wo->errorEvent.fd(), &fds)) { UPDATE_TRACE_POINT(); vector<AgentWatcherPtr>::const_iterator it; string message, backtrace, watcherName; for (it = watchers.begin(); it != watchers.end() && message.empty(); it++) { message = (*it)->getErrorMessage(); backtrace = (*it)->getErrorBacktrace(); watcherName = (*it)->name(); } if (!message.empty() && backtrace.empty()) { P_ERROR("Error in " << watcherName << " watcher:\n " << message); } else if (!message.empty() && !backtrace.empty()) { P_ERROR("Error in " << watcherName << " watcher:\n " << message << "\n" << backtrace); } return false; } else if (FD_ISSET(wo->exitEvent.fd(), &fds)) { return true; } else { UPDATE_TRACE_POINT(); assert(feedbackFdAvailable()); ret = syscalls::read(FEEDBACK_FD, &x, 1); return ret == 1 && x == 'c'; } } static vector<pid_t> readCleanupPids(const WorkingObjectsPtr &wo) { vector<pid_t> result; foreach (string filename, wo->cleanupPidfiles) { FILE *f = fopen(extractBaseName(filename).c_str(), "r"); if (f != NULL) { char buf[33]; size_t ret; ret = fread(buf, 1, 32, f); fclose(f); if (ret > 0) { buf[ret] = '\0'; result.push_back(atoi(buf)); } else { P_WARN("Cannot read cleanup PID file " << extractBaseName(filename).c_str() << " (" << filename << ")"); } } else { P_WARN("Cannot open cleanup PID file " << extractBaseName(filename).c_str() << " (" << filename << ")"); } } return result; } static void killCleanupPids(const vector<pid_t> &cleanupPids) { foreach (pid_t pid, cleanupPids) { P_DEBUG("Sending SIGTERM to cleanup PID " << pid); if(kill(pid, SIGTERM) == -1){ int e = errno; P_WARN("Failed to send SIGTERM to " << pid << ", error: " << e << " " << strerror(e)); } } } static void killCleanupPids(const WorkingObjectsPtr &wo) { if (!wo->pidsCleanedUp) { killCleanupPids(readCleanupPids(wo)); wo->pidsCleanedUp = true; } } static void deletePidFile(const WorkingObjectsPtr &wo) { Json::Value pidFile = watchdogConfig->get("watchdog_pid_file"); if (!pidFile.isNull() && !wo->pidFileCleanedUp && watchdogConfig->get("watchdog_pid_file_autodelete").asBool()) { syscalls::unlink(pidFile.asCString()); wo->pidFileCleanedUp = true; } } static void cleanupAgentsInBackground(const WorkingObjectsPtr &wo, vector<AgentWatcherPtr> &watchers, char *argv[]) { boost::this_thread::disable_interruption di; boost::this_thread::disable_syscall_interruption dsi; pid_t pid; int e; pid = fork(); if (pid == 0) { // Child try { vector<AgentWatcherPtr>::const_iterator it; Timer<SystemTime::GRAN_10MSEC> timer(false); fd_set fds, fds2; int max, agentProcessesDone; unsigned long long deadline = 30000; // miliseconds #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(sun) // Change process title. strcpy(argv[0], "PassengerWatchdog (cleaning up...)"); #endif P_DEBUG("Sending SIGTERM to all agent processes"); for (it = watchers.begin(); it != watchers.end(); it++) { (*it)->signalShutdown(); } max = 0; FD_ZERO(&fds); for (it = watchers.begin(); it != watchers.end(); it++) { FD_SET((*it)->getFeedbackFd(), &fds); if ((*it)->getFeedbackFd() > max) { max = (*it)->getFeedbackFd(); } } P_DEBUG("Waiting until all agent processes have exited..."); timer.start(); agentProcessesDone = 0; while (agentProcessesDone != -1 && agentProcessesDone < (int) watchers.size() && timer.elapsed() < deadline) { struct timeval timeout; #ifdef FD_COPY FD_COPY(&fds, &fds2); #else FD_ZERO(&fds2); for (it = watchers.begin(); it != watchers.end(); it++) { FD_SET((*it)->getFeedbackFd(), &fds2); } #endif timeout.tv_sec = 0; timeout.tv_usec = 10000; agentProcessesDone = syscalls::select(max + 1, &fds2, NULL, NULL, &timeout); if (agentProcessesDone > 0 && timer.elapsed() < deadline) { usleep(10000); } } if (agentProcessesDone == -1 || timer.elapsed() >= deadline) { // An error occurred or we've waited long enough. Kill all the // processes. P_WARN("Some " PROGRAM_NAME " agent processes did not exit " << "in time, forcefully shutting down all."); } else { P_DEBUG("All " PROGRAM_NAME " agent processes have exited. Forcing all subprocesses to shut down."); } P_DEBUG("Sending SIGKILL to all agent processes"); for (it = watchers.begin(); it != watchers.end(); it++) { (*it)->forceShutdown(); } cleanup(wo); _exit(0); } catch (const std::exception &e) { P_CRITICAL("An exception occurred during cleaning up: " << e.what()); _exit(1); } catch (...) { P_CRITICAL("An unknown exception occurred during cleaning up"); _exit(1); } } else if (pid == -1) { // Error e = errno; throw SystemException("fork() failed", e); } else { // Parent // Let child process handle cleanup. wo->instanceDir->detach(); } } static void forceAllAgentsShutdown(const WorkingObjectsPtr &wo, vector<AgentWatcherPtr> &watchers) { vector<AgentWatcherPtr>::iterator it; P_DEBUG("Sending SIGTERM to all agent processes"); for (it = watchers.begin(); it != watchers.end(); it++) { (*it)->signalShutdown(); } usleep(1000000); P_DEBUG("Sending SIGKILL to all agent processes"); for (it = watchers.begin(); it != watchers.end(); it++) { (*it)->forceShutdown(); } } static void runHookScriptAndThrowOnError(const char *name) { TRACE_POINT(); HookScriptOptions options; options.name = name; options.spec = watchdogConfig->get(string("hook_") + name).asString(); options.agentConfig = watchdogConfig->inspectEffectiveValues(); if (!runHookScripts(options)) { throw RuntimeException(string("Hook script ") + name + " failed"); } } static void usage() { printf("Usage: " AGENT_EXE " watchdog <OPTIONS...>\n"); printf("Runs the " PROGRAM_NAME " watchdog.\n\n"); printf("The watchdog runs and supervises various " PROGRAM_NAME " agent processes,\n"); printf("at this moment only the core (there was also a process called UstRouter but\n"); printf("it no longer exists). Arguments marked with \"[A]\", e.g.\n"); printf("--passenger-root and --log-level, are automatically passed to all supervised\n"); printf("agents, unless you explicitly override them by passing extra arguments to a\n"); printf("supervised agent specifically. You can pass arguments to a supervised agent by\n"); printf("wrapping those arguments between --BC/--EC and --BU/--EU.\n"); printf("\n"); printf(" Example 1: pass some arguments to the core.\n\n"); printf(" " SHORT_PROGRAM_NAME " watchdog --passenger-root /opt/passenger \\\n"); printf(" --BC --listen tcp://127.0.0.1:4000 /webapps/foo\n"); printf("\n"); printf(" Example 2: pass some arguments to the core, and some others to the\n"); printf(" UstRouter. The watchdog itself and the core will use logging\n"); printf(" level 3, while the UstRouter will use logging level 1.\n\n"); printf(" " SHORT_PROGRAM_NAME " watchdog --passenger-root /opt/passenger \\\n"); printf(" --BC --listen tcp://127.0.0.1:4000 /webapps/foo --EC \\\n"); printf(" --BU --log-level 1 --EU \\\n"); printf(" --log-level 3\n"); printf("\n"); printf("Required options:\n"); printf(" --passenger-root PATH The location to the " PROGRAM_NAME " source\n"); printf(" directory [A]\n"); printf("\n"); printf("Argument passing options (optional):\n"); printf(" --BC, --begin-core-args Signals the beginning of arguments to pass to the\n"); printf(" Passenger core\n"); printf(" --EC, --end-core-args Signals the end of arguments to pass to the\n"); printf(" Passenger core\n"); printf(" --BU, --begin-ust-router-args\n"); printf(" Signals the beginning of arguments to pass to the\n"); printf(" UstRouter\n"); printf(" --EU, --end-ust-router-args\n"); printf(" Signals the end of arguments to pass to the\n"); printf(" UstRouter\n"); printf("\n"); printf("Other options (optional):\n"); printf(" --api-listen ADDRESS Listen on the given address for API commands.\n"); printf(" The address must be formatted as tcp://IP:PORT for\n"); printf(" TCP sockets, or unix:PATH for Unix domain sockets.\n"); printf(" You can specify this option multiple times (up to\n"); printf(" %u times) to listen on multiple addresses.\n", SERVER_KIT_MAX_SERVER_ENDPOINTS - 1); printf(" --authorize [LEVEL]:USERNAME:PASSWORDFILE\n"); printf(" Enables authentication on the API server, through\n"); printf(" the given API account. LEVEL indicates the\n"); printf(" privilege level (see below). PASSWORDFILE must\n"); printf(" point to a file containing the password\n"); printf("\n"); printf(" --instance-registry-dir Directory to register instance into.\n"); printf(" Default: %s\n", getSystemTempDir()); printf("\n"); printf(" --spawn-dir Directory for spawn handshakes.\n"); printf(" Default: %s\n", getSystemTempDir()); printf("\n"); printf(" --no-user-switching Disables user switching support [A]\n"); printf(" --default-user NAME Default user to start apps as, when user\n"); printf(" switching is enabled. Default: " DEFAULT_WEB_APP_USER "\n"); printf(" --default-group NAME Default group to start apps as, when user\n"); printf(" switching is disabled. Default: the default\n"); printf(" user's primary group\n"); printf("\n"); printf(" --daemonize Daemonize into the background\n"); printf(" --user NAME Lower privilege to the given user\n"); printf(" --pid-file PATH Store the watchdog's PID in the given file. The\n"); printf(" file is deleted on exit\n"); printf(" --no-delete-pid-file Do not delete PID file on exit\n"); printf(" --log-file PATH Log to the given file.\n"); printf(" --log-level LEVEL Logging level. [A] Default: %d\n", DEFAULT_LOG_LEVEL); printf(" --disable-log-prefix Disables prefixing application logs with App PID stdout\n"); printf(" --report-file PATH Upon successful initialization, report instance\n"); printf(" information to the given file, in JSON format\n"); printf(" --cleanup-pidfile PATH Upon shutdown, kill the process specified by\n"); printf(" the given PID file\n"); printf("\n"); printf(" --ctl NAME=VALUE Set custom internal option\n"); printf("\n"); printf(" -h, --help Show this help\n"); printf("\n"); printf("[A] = Automatically passed to supervised agents\n"); printf("\n"); printf("API account privilege levels (ordered from most to least privileges):\n"); printf(" readonly Read-only access\n"); printf(" full Full access (default)\n"); } static void parseOptions(int argc, const char *argv[], ConfigKit::Store &config) { OptionParser p(usage); Json::Value updates(Json::objectValue); int i = 2; while (i < argc) { if (p.isValueFlag(argc, i, argv[i], '\0', "--passenger-root")) { updates["passenger_root"] = argv[i + 1]; i += 2; } else if (p.isFlag(argv[i], '\0', "--BC") || p.isFlag(argv[i], '\0', "--begin-core-args")) { i++; while (i < argc) { if (p.isFlag(argv[i], '\0', "--EC") || p.isFlag(argv[i], '\0', "--end-core-args")) { i++; break; } else if (p.isFlag(argv[i], '\0', "--BU") || p.isFlag(argv[i], '\0', "--begin-ust-router-args")) { break; } else { Json::Value coreUpdates(Json::objectValue); if (!parseCoreOption(argc, argv, i, coreUpdates)) { fprintf(stderr, "ERROR: unrecognized core argument %s. Please " "type '%s core --help' for usage.\n", argv[i], argv[0]); exit(1); } Json::Value::iterator it, end = coreUpdates.end(); for (it = coreUpdates.begin(); it != end; it++) { string translatedName = watchdogSchema->core.translator. reverseTranslateOne(it.name()); updates[translatedName] = *it; } } } } else if (p.isFlag(argv[i], '\0', "--BU") || p.isFlag(argv[i], '\0', "--begin-ust-router-args")) { i++; while (i < argc) { if (p.isFlag(argv[i], '\0', "--EU") || p.isFlag(argv[i], '\0', "--end-ust-router-args")) { i++; break; } else if (p.isFlag(argv[i], '\0', "--BC") || p.isFlag(argv[i], '\0', "--begin-core-args")) { break; } else { fprintf(stderr, "ERROR: unrecognized UstRouter argument %s. Please " "type '%s ust-router --help' for usage.\n", argv[i], argv[0]); exit(1); } } } else if (p.isValueFlag(argc, i, argv[i], '\0', "--api-listen")) { if (getSocketAddressType(argv[i + 1]) != SAT_UNKNOWN) { Json::Value &addresses = updates["watchdog_api_server_addresses"]; if (addresses.size() == SERVER_KIT_MAX_SERVER_ENDPOINTS - 1) { fprintf(stderr, "ERROR: you may specify up to %u --api-listen addresses.\n", SERVER_KIT_MAX_SERVER_ENDPOINTS - 1); exit(1); } addresses.append(argv[i + 1]); i += 2; } else { fprintf(stderr, "ERROR: invalid address format for --api-listen. The address " "must be formatted as tcp://IP:PORT for TCP sockets, or unix:PATH " "for Unix domain sockets.\n"); exit(1); } } else if (p.isValueFlag(argc, i, argv[i], '\0', "--authorize")) { vector<string> args; split(argv[i + 1], ':', args); if (args.size() < 2 || args.size() > 3) { fprintf(stderr, "ERROR: invalid format for --authorize. The syntax " "is \"[LEVEL:]USERNAME:PASSWORDFILE\".\n"); exit(1); } updates["watchdog_api_server_authorizations"].append(argv[i + 1]); i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--instance-registry-dir")) { updates["instance_registry_dir"] = argv[i + 1]; i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--spawn-dir")) { updates["spawn_dir"] = argv[i + 1]; i += 2; } else if (p.isFlag(argv[i], '\0', "--no-user-switching")) { updates["user_switching"] = false; i++; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--default-user")) { updates["default_user"] = argv[i + 1]; i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--default-group")) { updates["default_group"] = argv[i + 1]; i += 2; } else if (p.isFlag(argv[i], '\0', "--daemonize")) { updates["daemonize"] = true; i++; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--user")) { updates["user"] = argv[i + 1]; i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--pid-file")) { updates["watchdog_pid_file"] = argv[i + 1]; i += 2; } else if (p.isFlag(argv[i], '\0', "--no-delete-pid-file")) { updates["watchdog_pid_file_autodelete"] = false; i++; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--log-level")) { updates["log_level"] = argv[i + 1]; i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--report-file")) { updates["startup_report_file"] = argv[i + 1]; i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--cleanup-pidfile")) { updates["pidfiles_to_delete_on_exit"].append(argv[i + 1]); i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--log-file")) { updates["log_target"] = argv[i + 1]; i += 2; } else if (p.isFlag(argv[i], '\0', "--disable-log-prefix")) { updates["disable_log_prefix"] = true; i++; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--ctl")) { const char *value = strchr(argv[i + 1], '='); if (value == NULL) { fprintf(stderr, "ERROR: '%s' is not a valid --ctl parameter. " "It must be in the form of NAME=VALUE.\n", argv[i + 1]); exit(1); } string name(argv[i + 1], value - argv[i + 1]); value++; if (*value == '\0') { fprintf(stderr, "ERROR: '%s' is not a valid --ctl parameter. " "The value must be non-empty.\n", argv[i + 1]); exit(1); } updates[name] = autocastValueToJson(value); i += 2; } else if (p.isFlag(argv[i], 'h', "--help")) { usage(); exit(0); } else { fprintf(stderr, "ERROR: unrecognized argument %s. Please type " "'%s watchdog --help' for usage.\n", argv[i], argv[0]); exit(1); } } if (!updates.empty()) { vector<ConfigKit::Error> errors; if (!config.update(updates, errors)) { P_BUG("Unable to set initial configuration: " << ConfigKit::toString(errors) << "\n" "Raw initial configuration: " << updates.toStyledString()); } } } static void initializeBareEssentials(int argc, char *argv[], WorkingObjectsPtr &wo) { /* * Some Apache installations (like on OS X) redirect stdout to /dev/null, * so that only stderr is redirected to the log file. We therefore * forcefully redirect stdout to stderr so that everything ends up in the * same place. */ dup2(2, 1); /* * Most operating systems overcommit memory. We *know* that this watchdog process * doesn't use much memory; on OS X it uses about 200 KB of private RSS. If the * watchdog is killed by the system Out-Of-Memory Killer or then it's all over: * the system administrator will have to restart the web server for Phusion * Passenger to be usable again. So here we disable Linux's OOM killer * for this watchdog. Note that the OOM score is inherited by child processes * so we need to restore it after each fork(). */ #if !BOOST_OS_MACOS WatchdogOomAdjustResult oomAdjustResult = setOomScoreNeverKill(); #endif watchdogWrapperRegistry = new WrapperRegistry::Registry(); watchdogWrapperRegistry->finalize(); watchdogSchema = new Schema(watchdogWrapperRegistry); watchdogConfig = new ConfigKit::Store(*watchdogSchema); initializeAgent(argc, &argv, SHORT_PROGRAM_NAME " watchdog", *watchdogConfig, watchdogSchema->core.schema.loggingKit.translator, parseOptions, NULL, 2); // Start all sub-agents with this environment variable. setenv("PASSENGER_USE_FEEDBACK_FD", "true", 1); wo = boost::make_shared<WorkingObjects>(); workingObjects = wo.get(); #if !BOOST_OS_MACOS printOomAdjustResultMessages(oomAdjustResult); wo->extraConfigToPassToSubAgents["oom_score"] = oomAdjustResult.oldScore; #endif } static void maybeSetsid() { /* Become the session leader so that Apache can't kill the * watchdog with killpg() during shutdown, so that a * Ctrl-C only affects the web server, and so that * we can kill all of our subprocesses in a single killpg(). * * WatchdogLauncher.h already calls setsid() before exec()ing * the Watchdog, but Flying Passenger does not. */ if (watchdogConfig->get("setsid").asBool()) { setsid(); } } static void redirectStdinToNull() { int fd = open("/dev/null", O_RDONLY); if (fd != -1) { dup2(fd, 0); close(fd); } } static void maybeDaemonize() { pid_t pid; int e; if (watchdogConfig->get("daemonize").asBool()) { pid = fork(); if (pid == 0) { setsid(); redirectStdinToNull(); } else if (pid == -1) { e = errno; throw SystemException("Cannot fork", e); } else { _exit(0); } } } static void createPidFile() { TRACE_POINT(); Json::Value pidFile = watchdogConfig->get("watchdog_pid_file"); if (!pidFile.isNull()) { char pidStr[32]; snprintf(pidStr, sizeof(pidStr), "%lld", (long long) getpid()); int fd = syscalls::open(pidFile.asCString(), O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { int e = errno; throw FileSystemException("Cannot create PID file " + pidFile.asString(), e, pidFile.asString()); } UPDATE_TRACE_POINT(); FdGuard guard(fd, __FILE__, __LINE__); writeExact(fd, pidStr, strlen(pidStr)); } } static void openStartupReportFile(const WorkingObjectsPtr &wo) { TRACE_POINT(); Json::Value path = watchdogConfig->get("startup_report_file"); if (!path.isNull()) { int fd = syscalls::open(path.asCString(), O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd == -1) { int e = errno; throw FileSystemException("Cannot open report file " + path.asString(), e, path.asString()); } P_LOG_FILE_DESCRIPTOR_OPEN4(fd, __FILE__, __LINE__, "WorkingObjects: startupReportFile"); wo->startupReportFile = fd; } } static void chdirToTmpDir() { const Json::Value pidfiles = watchdogConfig->get("pidfiles_to_delete_on_exit"); if (!pidfiles.empty()) { string str = pidfiles[0].asString(); string dir = str.substr(0,str.find_last_of('/')); if (dir != "" && chdir(dir.c_str()) == -1) { throw RuntimeException("Cannot change working directory to " + dir); } } } static void lowerPrivilege() { TRACE_POINT(); string userName = watchdogConfig->get("user").asString(); if (geteuid() == 0 && !userName.empty()) { OsUser osUser; if (!lookupSystemUserByName(userName, osUser)) { throw NonExistentUserException("Operating system user '" + userName + "' does not exist"); } gid_t gid = osUser.pwd.pw_gid; string groupName = lookupSystemGroupnameByGid(gid); if (initgroups(userName.c_str(), gid) != 0) { int e = errno; throw SystemException("Unable to lower " SHORT_PROGRAM_NAME " watchdog's privilege " "to that of user '" + userName + "' and group '" + groupName + "': cannot set supplementary groups", e); } if (setgid(gid) != 0) { int e = errno; throw SystemException("Unable to lower " SHORT_PROGRAM_NAME " watchdog's privilege " "to that of user '" + userName + "' and group '" + groupName + "': cannot set group ID to " + toString(gid), e); } if (setuid(osUser.pwd.pw_uid) != 0) { int e = errno; throw SystemException("Unable to lower " SHORT_PROGRAM_NAME " watchdog's privilege " "to that of user '" + userName + "' and group '" + groupName + "': cannot set user ID to " + toString(osUser.pwd.pw_uid), e); } #ifdef __linux__ // When we change the uid, /proc/self/pid contents don't change owner, // causing us to lose access to our own /proc/self/pid files. // This prctl call changes those files' ownership. // References: // https://stackoverflow.com/questions/8337846/files-ownergroup-doesnt-change-at-location-proc-pid-after-setuid // http://man7.org/linux/man-pages/man5/proc.5.html (search for "dumpable") prctl(PR_SET_DUMPABLE, 1); #endif setenv("USER", osUser.pwd.pw_name, 1); setenv("HOME", osUser.pwd.pw_dir, 1); setenv("UID", toString(gid).c_str(), 1); } } static void lookupDefaultUidGid(uid_t &uid, gid_t &gid) { const string defaultUser = watchdogConfig->get("default_user").asString(); const string defaultGroup = watchdogConfig->get("default_group").asString(); OsUser osUser; if (!lookupSystemUserByName(defaultUser, osUser)) { throw NonExistentUserException("Default user '" + defaultUser + "' does not exist"); } uid = osUser.pwd.pw_uid; OsGroup osGroup; if (!lookupSystemGroupByName(defaultGroup, osGroup)) { throw NonExistentGroupException("Default group '" + defaultGroup + "' does not exist"); } gid = osGroup.grp.gr_gid; } static void warnIfInstanceDirVulnerable(const string &root) { TRACE_POINT(); if (geteuid() != 0) { return; // Passenger is not root, so no escalation. } vector<string> errors, checkErrors; if (isPathProbablySecureForRootUse(root, errors, checkErrors)) { if (!checkErrors.empty()) { string message = "WARNING: unable to perform privilege escalation vulnerability detection:\n"; foreach (string line, checkErrors) { message.append("\n - " + line); } P_WARN(message); } } else { string message = "WARNING: potential privilege escalation vulnerability detected. " \ PROGRAM_NAME " is running as root, and part(s) of the " SHORT_PROGRAM_NAME " instance directory (" + root + ") can be changed by non-root user(s):\n"; foreach (string line, errors) { message.append("\n - " + line); } foreach (string line, checkErrors) { message.append("\n - " + line); } message.append("\n\nPlease either fix up the permissions for the insecure paths, or use" \ " a different location for the instance dir that can only be modified by root."); P_WARN(message); } } static void initializeWorkingObjects(const WorkingObjectsPtr &wo, InstanceDirToucherPtr &instanceDirToucher, uid_t uidBeforeLoweringPrivilege) { TRACE_POINT(); Json::Value doc; Json::Value::iterator it, end; UPDATE_TRACE_POINT(); lookupDefaultUidGid(wo->defaultUid, wo->defaultGid); doc = watchdogConfig->get("pidfiles_to_delete_on_exit"); for (it = doc.begin(); it != doc.end(); it++) { wo->cleanupPidfiles.push_back(it->asString()); } UPDATE_TRACE_POINT(); InstanceDirectory::CreationOptions instanceOptions; instanceOptions.userSwitching = watchdogConfig->get("user_switching").asBool(); instanceOptions.originalUid = uidBeforeLoweringPrivilege; instanceOptions.defaultUid = wo->defaultUid; instanceOptions.defaultGid = wo->defaultGid; instanceOptions.properties["name"] = wo->randomGenerator.generateAsciiString(8); instanceOptions.properties["integration_mode"] = watchdogConfig->get("integration_mode").asString(); instanceOptions.properties["server_software"] = watchdogConfig->get("server_software").asString(); if (watchdogConfig->get("integration_mode").asString() == "standalone") { instanceOptions.properties["standalone_engine"] = watchdogConfig->get("standalone_engine").asString(); } // check if path is safe warnIfInstanceDirVulnerable(watchdogConfig->get("instance_registry_dir").asString()); warnIfInstanceDirVulnerable(watchdogConfig->get("spawn_dir").asString()); wo->instanceDir = boost::make_shared<InstanceDirectory>(instanceOptions, watchdogConfig->get("instance_registry_dir").asString()); wo->extraConfigToPassToSubAgents["instance_dir"] = wo->instanceDir->getPath(); instanceDirToucher = boost::make_shared<InstanceDirToucher>(wo); UPDATE_TRACE_POINT(); string lockFilePath = wo->instanceDir->getPath() + "/lock"; wo->lockFile = syscalls::open(lockFilePath.c_str(), O_RDONLY); if (wo->lockFile == -1) { int e = errno; throw FileSystemException("Cannot open " + lockFilePath + " for reading", e, lockFilePath); } P_LOG_FILE_DESCRIPTOR_OPEN4(wo->lockFile, __FILE__, __LINE__, "WorkingObjects: lock file"); createFile(wo->instanceDir->getPath() + "/watchdog.pid", toString(getpid())); UPDATE_TRACE_POINT(); string readOnlyAdminPassword = wo->randomGenerator.generateAsciiString(24); string fullAdminPassword = wo->randomGenerator.generateAsciiString(24); if (geteuid() == 0 && !watchdogConfig->get("user_switching").asBool()) { createFile(wo->instanceDir->getPath() + "/read_only_admin_password.txt", readOnlyAdminPassword, S_IRUSR, wo->defaultUid, wo->defaultGid); createFile(wo->instanceDir->getPath() + "/full_admin_password.txt", fullAdminPassword, S_IRUSR, wo->defaultUid, wo->defaultGid); } else { createFile(wo->instanceDir->getPath() + "/read_only_admin_password.txt", readOnlyAdminPassword, S_IRUSR | S_IWUSR); createFile(wo->instanceDir->getPath() + "/full_admin_password.txt", fullAdminPassword, S_IRUSR | S_IWUSR); } if (watchdogConfig->get("core_pid_file").isNull()) { wo->corePidFile = wo->instanceDir->getPath() + "/core.pid"; } else { wo->corePidFile = watchdogConfig->get("core_pid_file").asString(); } wo->fdPassingPassword = wo->randomGenerator.generateAsciiString(24); UPDATE_TRACE_POINT(); wo->controllerAddresses.append("unix:" + wo->instanceDir->getPath() + "/agents.s/core"); doc = watchdogConfig->get("controller_addresses"); for (it = doc.begin(); it != doc.end(); it++) { wo->controllerAddresses.append(*it); } wo->coreApiServerAddresses.append("unix:" + wo->instanceDir->getPath() + "/agents.s/core_api"); doc = watchdogConfig->get("core_api_server_addresses"); for (it = doc.begin(); it != doc.end(); it++) { wo->coreApiServerAddresses.append(*it); } UPDATE_TRACE_POINT(); wo->coreApiServerAuthorizations.append( "readonly:ro_admin:" + wo->instanceDir->getPath() + "/read_only_admin_password.txt"); wo->coreApiServerAuthorizations.append( "full:admin:" + wo->instanceDir->getPath() + "/full_admin_password.txt"); doc = watchdogConfig->get("core_api_server_authorizations"); for (it = doc.begin(); it != doc.end(); it++) { wo->coreApiServerAuthorizations.append(*it); } } static void initializeAgentWatchers(const WorkingObjectsPtr &wo, vector<AgentWatcherPtr> &watchers) { TRACE_POINT(); watchers.push_back(boost::make_shared<CoreWatcher>(wo)); } static void makeFileWorldReadableAndWritable(const string &path) { int ret; do { ret = chmod(path.c_str(), parseModeString("u=rw,g=rw,o=rw")); } while (ret == -1 && errno == EINTR); } static void initializeApiServer(const WorkingObjectsPtr &wo) { TRACE_POINT(); Json::Value doc; Json::Value::iterator it; string description; UPDATE_TRACE_POINT(); wo->watchdogApiServerAuthorizations.append( "readonly:ro_admin:" + wo->instanceDir->getPath() + "/read_only_admin_password.txt"); wo->watchdogApiServerAuthorizations.append( "full:admin:" + wo->instanceDir->getPath() + "/full_admin_password.txt"); doc = watchdogConfig->get("watchdog_api_server_authorizations"); for (it = doc.begin(); it != doc.end(); it++) { wo->watchdogApiServerAuthorizations.append(*it); } UPDATE_TRACE_POINT(); wo->watchdogApiServerAddresses.append( "unix:" + wo->instanceDir->getPath() + "/agents.s/watchdog_api"); doc = watchdogConfig->get("watchdog_api_server_addresses"); for (it = doc.begin(); it != doc.end(); it++) { wo->watchdogApiServerAddresses.append(*it); } UPDATE_TRACE_POINT(); for (unsigned int i = 0; i < wo->watchdogApiServerAddresses.size(); i++) { string address = wo->watchdogApiServerAddresses[i].asString(); P_DEBUG("API server will listen on " << address); wo->apiServerFds[i] = createServer(address, 0, true, __FILE__, __LINE__); if (getSocketAddressType(address) == SAT_UNIX) { makeFileWorldReadableAndWritable(parseUnixSocketAddress(address)); } } UPDATE_TRACE_POINT(); Json::Value contextConfig = watchdogConfig->inspectEffectiveValues(); wo->bgloop = new BackgroundEventLoop(true, true); wo->serverKitContext = new ServerKit::Context( watchdogSchema->apiServerKit.schema, contextConfig, watchdogSchema->apiServerKit.translator); wo->serverKitContext->libev = wo->bgloop->safe; wo->serverKitContext->libuv = wo->bgloop->libuv_loop; wo->serverKitContext->initialize(); UPDATE_TRACE_POINT(); Json::Value apiServerConfig = watchdogConfig->inspectEffectiveValues(); apiServerConfig["fd_passing_password"] = wo->fdPassingPassword; apiServerConfig["authorizations"] = wo->watchdogApiServerAuthorizations; wo->apiServer = new ApiServer::ApiServer( wo->serverKitContext, watchdogSchema->apiServer.schema, apiServerConfig, watchdogSchema->apiServer.translator); wo->apiServer->exitEvent = &wo->exitEvent; wo->apiServer->initialize(); for (unsigned int i = 0; i < wo->watchdogApiServerAddresses.size(); i++) { wo->apiServer->listen(wo->apiServerFds[i]); } } static void startAgents(const WorkingObjectsPtr &wo, vector<AgentWatcherPtr> &watchers) { TRACE_POINT(); foreach (AgentWatcherPtr watcher, watchers) { P_DEBUG("Starting agent: " << watcher->name()); try { watcher->start(); } catch (const std::exception &e) { if (feedbackFdAvailable()) { writeArrayMessage(FEEDBACK_FD, "Watchdog startup error", e.what(), NULL); } else { const oxt::tracable_exception *e2 = dynamic_cast<const oxt::tracable_exception *>(&e); if (e2 != NULL) { P_CRITICAL("ERROR: " << e2->what() << "\n" << e2->backtrace()); } else { P_CRITICAL("ERROR: " << e.what()); } } forceAllAgentsShutdown(wo, watchers); cleanup(wo); exit(1); } // Allow other exceptions to propagate and crash the watchdog. } } static void beginWatchingAgents(const WorkingObjectsPtr &wo, vector<AgentWatcherPtr> &watchers) { foreach (AgentWatcherPtr watcher, watchers) { try { watcher->beginWatching(); } catch (const std::exception &e) { writeArrayMessage(FEEDBACK_FD, "Watchdog startup error", e.what(), NULL); forceAllAgentsShutdown(wo, watchers); cleanup(wo); exit(1); } // Allow other exceptions to propagate and crash the watchdog. } } static void reportStartupResult(const WorkingObjectsPtr &wo, const vector<AgentWatcherPtr> &watchers) { TRACE_POINT(); Json::Value report; report["instance_dir"] = wo->instanceDir->getPath(); foreach (AgentWatcherPtr watcher, watchers) { watcher->reportAgentStartupResult(report); } if (feedbackFdAvailable()) { writeArrayMessage(FEEDBACK_FD, "Agents information", NULL); writeScalarMessage(FEEDBACK_FD, report.toStyledString()); } if (wo->startupReportFile != -1) { string str = report.toStyledString(); writeExact(wo->startupReportFile, str.data(), str.size()); close(wo->startupReportFile); P_LOG_FILE_DESCRIPTOR_CLOSE(wo->startupReportFile); wo->startupReportFile = -1; } } static void finalizeInstanceDir(const WorkingObjectsPtr &wo) { TRACE_POINT(); #ifdef HAVE_FLOCK if (flock(wo->lockFile, LOCK_EX) == -1) { int e = errno; throw SystemException("Cannot obtain exclusive lock on the " "instance directory lock file", e); } #endif wo->instanceDir->finalizeCreation(); } static void cleanup(const WorkingObjectsPtr &wo) { TRACE_POINT(); // We need to call destroy() explicitly because of circular references. if (wo->instanceDir != NULL && wo->instanceDir->isOwner()) { wo->instanceDir->destroy(); wo->instanceDir.reset(); } killCleanupPids(wo); deletePidFile(wo); } int watchdogMain(int argc, char *argv[]) { WorkingObjectsPtr wo; initializeBareEssentials(argc, argv, wo); P_NOTICE("Starting " SHORT_PROGRAM_NAME " watchdog..."); InstanceDirToucherPtr instanceDirToucher; vector<AgentWatcherPtr> watchers; uid_t uidBeforeLoweringPrivilege = geteuid(); try { TRACE_POINT(); maybeSetsid(); maybeDaemonize(); createPidFile(); openStartupReportFile(wo); chdirToTmpDir(); lowerPrivilege(); initializeWorkingObjects(wo, instanceDirToucher, uidBeforeLoweringPrivilege); initializeAgentWatchers(wo, watchers); initializeApiServer(wo); UPDATE_TRACE_POINT(); runHookScriptAndThrowOnError("before_watchdog_initialization"); } catch (const std::exception &e) { if (feedbackFdAvailable()) { writeArrayMessage(FEEDBACK_FD, "Watchdog startup error", e.what(), NULL); } else { const oxt::tracable_exception *e2 = dynamic_cast<const oxt::tracable_exception *>(&e); if (e2 != NULL) { P_CRITICAL("ERROR: " << e2->what() << "\n" << e2->backtrace()); } else { P_CRITICAL("ERROR: " << e.what()); } } if (wo != NULL) { cleanup(wo); } return 1; } // Allow other exceptions to propagate and crash the watchdog. try { TRACE_POINT(); startAgents(wo, watchers); beginWatchingAgents(wo, watchers); reportStartupResult(wo, watchers); finalizeInstanceDir(wo); P_INFO("All " PROGRAM_NAME " agents started!"); UPDATE_TRACE_POINT(); runHookScriptAndThrowOnError("after_watchdog_initialization"); UPDATE_TRACE_POINT(); boost::this_thread::disable_interruption di; boost::this_thread::disable_syscall_interruption dsi; bool shouldExitGracefully = waitForStarterProcessOrWatchers(wo, watchers); if (shouldExitGracefully) { /* Fork a child process which cleans up all the agent processes in * the background and exit this watchdog process so that we don't block * the web server. */ P_DEBUG("Web server exited gracefully; gracefully shutting down all agents..."); } else { P_DEBUG("Web server did not exit gracefully, forcing shutdown of all agents..."); } UPDATE_TRACE_POINT(); runHookScriptAndThrowOnError("before_watchdog_shutdown"); UPDATE_TRACE_POINT(); AgentWatcher::stopWatching(watchers); if (shouldExitGracefully) { UPDATE_TRACE_POINT(); cleanupAgentsInBackground(wo, watchers, argv); // Child process will call cleanup() } else { UPDATE_TRACE_POINT(); forceAllAgentsShutdown(wo, watchers); cleanup(wo); } UPDATE_TRACE_POINT(); runHookScriptAndThrowOnError("after_watchdog_shutdown"); return shouldExitGracefully ? 0 : 1; } catch (const tracable_exception &e) { P_CRITICAL("ERROR: " << e.what() << "\n" << e.backtrace()); cleanup(wo); return 1; } }
Submit
FILE
FOLDER
Name
Size
Permission
Action
AgentWatcher.cpp
16752 bytes
0644
ApiServer.h
12757 bytes
0644
Config.h
26837 bytes
0644
CoreWatcher.cpp
3796 bytes
0644
InstanceDirToucher.cpp
5082 bytes
0644
WatchdogMain.cpp
47390 bytes
0644
N4ST4R_ID | Naxtarrr