/* * Phusion Passenger - https://www.phusionpassenger.com/ * Copyright (c) 2010-2014 Phusion * * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. * * 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 #include #include #include #include #include #include #include #include #include #include #if !defined(sun) && !defined(__sun) #define HAVE_FLOCK #endif #include #include #include #ifdef HAVE_FLOCK #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace boost; using namespace oxt; using namespace Passenger; enum OomFileType { OOM_ADJ, OOM_SCORE_ADJ }; #define REQUEST_SOCKET_PASSWORD_SIZE 64 class InstanceDirToucher; class AgentWatcher; /***** Working objects *****/ namespace Passenger { namespace WatchdogAgent { struct WorkingObjects { RandomGenerator randomGenerator; EventFd errorEvent; EventFd exitEvent; ResourceLocatorPtr resourceLocator; uid_t defaultUid; gid_t defaultGid; InstanceDirectoryPtr instanceDir; int reportFile; int lockFile; vector cleanupPidfiles; bool pidsCleanedUp; bool pidFileCleanedUp; vector adminAuthorizations; int adminServerFds[SERVER_KIT_MAX_SERVER_ENDPOINTS]; BackgroundEventLoop *bgloop; ServerKit::Context *serverKitContext; AdminServer *adminServer; WorkingObjects() : reportFile(-1), pidsCleanedUp(false), pidFileCleanedUp(false), bgloop(NULL), serverKitContext(NULL), adminServer(NULL) { for (unsigned int i = 0; i < SERVER_KIT_MAX_SERVER_ENDPOINTS; i++) { adminServerFds[i] = -1; } } }; typedef boost::shared_ptr WorkingObjectsPtr; } // namespace WatchdogAgent } // namespace Passenger using namespace Passenger::WatchdogAgent; static VariantMap *agentsOptions; static WorkingObjects *workingObjects; static string oldOomScore; static void setOomScore(const StaticString &score); static void cleanup(const WorkingObjectsPtr &wo); #include "AgentWatcher.cpp" #include "InstanceDirToucher.cpp" #include "HelperAgentWatcher.cpp" #include "LoggingAgentWatcher.cpp" /***** Functions *****/ static FILE * openOomAdjFile(const char *mode, OomFileType &type) { 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; return f; } } else { type = OOM_SCORE_ADJ; return f; } } /** * Linux-only way to change OOM killer configuration for * current process. Requires root privileges, which we * should have. */ static void setOomScore(const StaticString &score) { if (score.empty()) { return; } FILE *f; OomFileType type; f = openOomAdjFile("w", type); if (f != NULL) { size_t ret = fwrite(score.data(), 1, score.size(), f); // We can't do anything about failures, so ignore compiler // warnings about not doing anything with the result. (void) ret; fclose(f); } } /** * Set the current process's OOM score to "never kill". */ static string setOomScoreNeverKill() { string oldScore; FILE *f; OomFileType type; f = openOomAdjFile("r", type); if (f == NULL) { return ""; } 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)) { fclose(f); return ""; } else { oldScore.append(buf, bytesRead); } } fclose(f); f = openOomAdjFile("w", type); if (f == NULL) { return ""; } if (type == OOM_SCORE_ADJ) { fprintf(f, "-1000\n"); } else { assert(type == OOM_ADJ); fprintf(f, "-17\n"); } fclose(f); return oldScore; } 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 &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 admin server"); wo->bgloop->stop(); for (unsigned int i = 0; i < SERVER_KIT_MAX_SERVER_ENDPOINTS; i++) { if (wo->adminServerFds[i] != -1) { syscalls::close(wo->adminServerFds[i]); } } if (FD_ISSET(wo->errorEvent.fd(), &fds)) { UPDATE_TRACE_POINT(); vector::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 readCleanupPids(const WorkingObjectsPtr &wo) { vector result; foreach (string filename, wo->cleanupPidfiles) { FILE *f = fopen(filename.c_str(), "r"); if (f != NULL) { char buf[33]; size_t ret; ret = fread(buf, 1, 32, f); if (ret > 0) { buf[ret] = '\0'; result.push_back(atoi(buf)); } else { P_WARN("Cannot read cleanup PID file " << filename); } } else { P_WARN("Cannot open cleanup PID file " << filename); } } return result; } static void killCleanupPids(const vector &cleanupPids) { foreach (pid_t pid, cleanupPids) { P_DEBUG("Sending SIGTERM to cleanup PID " << pid); kill(pid, SIGTERM); } } static void killCleanupPids(const WorkingObjectsPtr &wo) { if (!wo->pidsCleanedUp) { killCleanupPids(readCleanupPids(wo)); wo->pidsCleanedUp = true; } } static void deletePidFile(const WorkingObjectsPtr &wo) { string pidFile = agentsOptions->get("pid_file", false); if (!pidFile.empty() && !wo->pidFileCleanedUp && agentsOptions->getBool("delete_pid_file")) { syscalls::unlink(pidFile.c_str()); wo->pidFileCleanedUp = true; } } static void cleanupAgentsInBackground(const WorkingObjectsPtr &wo, vector &watchers, char *argv[]) { this_thread::disable_interruption di; this_thread::disable_syscall_interruption dsi; pid_t pid; int e; pid = fork(); if (pid == 0) { // Child try { vector::const_iterator it; Timer 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 &watchers) { vector::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 string inferDefaultGroup(const string &defaultUser) { struct passwd *userEntry = getpwnam(defaultUser.c_str()); if (userEntry == NULL) { throw ConfigurationException( string("The user that PassengerDefaultUser refers to, '") + defaultUser + "', does not exist."); } return getGroupName(userEntry->pw_gid); } static void runHookScriptAndThrowOnError(const char *name) { TRACE_POINT(); HookScriptOptions options; options.name = name; options.spec = agentsOptions->get(string("hook_") + name, false); options.agentsOptions = agentsOptions; if (!runHookScripts(options)) { throw RuntimeException(string("Hook script ") + name + " failed"); } } static void usage() { printf("Usage: " AGENT_EXE " watchdog \n"); printf("Runs the " PROGRAM_NAME " watchdog.\n\n"); printf("The watchdog runs and supervises various " PROGRAM_NAME " agent processes,\n"); printf("namely the HTTP server and logging server. 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 --BS/--ES and --BL/--EL.\n"); printf("\n"); printf(" Example 1: pass some arguments to the HTTP server.\n\n"); printf(" " AGENT_EXE " watchdog --passenger-root /opt/passenger \\\n"); printf(" --BS --listen tcp://127.0.0.1:4000 /webapps/foo\n"); printf("\n"); printf(" Example 2: pass some arguments to the HTTP server, and some others to the\n"); printf(" logging server. The watchdog itself and the HTTP server will use logging\n"); printf(" level 3, while the logging server will use logging level 1.\n\n"); printf(" " AGENT_EXE " watchdog --passenger-root /opt/passenger \\\n"); printf(" --BS --listen tcp://127.0.0.1:4000 /webapps/foo --ES \\\n"); printf(" --BL --log-level 1 --EL \\\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(" --BS, --begin-server-args Signals the beginning of arguments to pass to the\n"); printf(" HTTP server\n"); printf(" --ES, --end-server-args Signals the end of arguments to pass to the HTTP\n"); printf(" server\n"); printf(" --BL, --begin-logger-args Signals the beginning of arguments to pass to the\n"); printf(" logging server\n"); printf(" --EL, --end-logger-args Signals the end of arguments to pass to the\n"); printf(" logging server\n"); printf("\n"); printf("Other options (optional):\n"); printf(" --admin-listen ADDRESS\n"); printf(" Listen on the given address for admin 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 admin server, through\n"); printf(" the given admin 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(" --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(" --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("Admin 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[], VariantMap &options) { OptionParser p(usage); int i = 2; while (i < argc) { if (p.isValueFlag(argc, i, argv[i], '\0', "--passenger-root")) { options.set("passenger_root", argv[i + 1]); i += 2; } else if (p.isFlag(argv[i], '\0', "--BS") || p.isFlag(argv[i], '\0', "--begin-server-args")) { i++; while (i < argc) { if (p.isFlag(argv[i], '\0', "--ES") || p.isFlag(argv[i], '\0', "--end-server-args")) { i++; break; } else if (p.isFlag(argv[i], '\0', "--BL") || p.isFlag(argv[i], '\0', "--begin-logger-args")) { break; } else if (!parseServerOption(argc, argv, i, options)) { fprintf(stderr, "ERROR: unrecognized HTTP server argument %s. Please " "type '%s server --help' for usage.\n", argv[i], argv[0]); exit(1); } } } else if (p.isFlag(argv[i], '\0', "--BL") || p.isFlag(argv[i], '\0', "--begin-logger-args")) { i++; while (i < argc) { if (p.isFlag(argv[i], '\0', "--EL") || p.isFlag(argv[i], '\0', "--end-logger-args")) { i++; break; } else if (p.isFlag(argv[i], '\0', "--BS") || p.isFlag(argv[i], '\0', "--begin-server-args")) { break; } else if (!parseLoggingAgentOption(argc, argv, i, options)) { fprintf(stderr, "ERROR: unrecognized logging agent argument %s. Please " "type '%s logger --help' for usage.\n", argv[i], argv[0]); exit(1); } } } else if (p.isValueFlag(argc, i, argv[i], '\0', "--admin-listen")) { if (getSocketAddressType(argv[i + 1]) != SAT_UNKNOWN) { vector addresses = options.getStrSet("watchdog_admin_addresses", false); if (addresses.size() == SERVER_KIT_MAX_SERVER_ENDPOINTS - 1) { fprintf(stderr, "ERROR: you may specify up to %u --admin-listen addresses.\n", SERVER_KIT_MAX_SERVER_ENDPOINTS - 1); exit(1); } addresses.push_back(argv[i + 1]); options.setStrSet("watchdog_admin_addresses", addresses); i += 2; } else { fprintf(stderr, "ERROR: invalid address format for --admin-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 args; vector authorizations = options.getStrSet("watchdog_authorizations", false); 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); } authorizations.push_back(argv[i + 1]); options.setStrSet("watchdog_authorizations", authorizations); i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--instance-registry-dir")) { options.set("instance_registry_dir", argv[i + 1]); i += 2; } else if (p.isFlag(argv[i], '\0', "--no-user-switching")) { options.setBool("user_switching", false); i++; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--default-user")) { options.set("default_user", argv[i + 1]); i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--default-group")) { options.set("default_group", argv[i + 1]); i += 2; } else if (p.isFlag(argv[i], '\0', "--daemonize")) { options.setBool("daemonize", true); i++; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--user")) { options.set("user", argv[i + 1]); i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--pid-file")) { options.set("pid_file", argv[i + 1]); i += 2; } else if (p.isFlag(argv[i], '\0', "--no-delete-pid-file")) { options.setBool("delete_pid_file", false); i++; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--log-level")) { options.setInt("log_level", atoi(argv[i + 1])); i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--report-file")) { options.set("report_file", argv[i + 1]); i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--cleanup-pidfile")) { vector pidfiles = options.getStrSet("cleanup_pidfiles", false); pidfiles.push_back(argv[i + 1]); options.setStrSet("cleanup_pidfiles", pidfiles); i += 2; } else if (p.isValueFlag(argc, i, argv[i], '\0', "--log-file")) { options.set("debug_log_file", argv[i + 1]); i += 2; } 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); } options.set(name, 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); } } } 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(). */ oldOomScore = setOomScoreNeverKill(); agentsOptions = new VariantMap(); *agentsOptions = initializeAgent(argc, &argv, AGENT_EXE " watchdog", parseOptions, NULL, 2); // Start all sub-agents with this environment variable. setenv("PASSENGER_USE_FEEDBACK_FD", "true", 1); wo = boost::make_shared(); workingObjects = wo.get(); } static void setAgentsOptionsDefaults() { VariantMap &options = *agentsOptions; options.setDefault("instance_registry_dir", getSystemTempDir()); options.setDefaultBool("user_switching", true); options.setDefault("default_user", DEFAULT_WEB_APP_USER); if (!options.has("default_group")) { options.set("default_group", inferDefaultGroup(options.get("default_user"))); } options.setDefault("server_software", SERVER_TOKEN_NAME "/" PASSENGER_VERSION); options.setDefaultStrSet("cleanup_pidfiles", vector()); options.setDefault("data_buffer_dir", getSystemTempDir()); options.setDefaultBool("delete_pid_file", true); } static void sanityCheckOptions() { VariantMap &options = *agentsOptions; if (!options.has("passenger_root")) { fprintf(stderr, "ERROR: please set the --passenger-root argument.\n"); exit(1); } } 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(). * * AgentsStarter.h already calls setsid() before exec()ing * the Watchdog, but Flying Passenger does not. */ if (agentsOptions->getBool("setsid", false)) { 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 (agentsOptions->getBool("daemonize", false)) { 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(); string pidFile = agentsOptions->get("pid_file", false); if (!pidFile.empty()) { char pidStr[32]; snprintf(pidStr, sizeof(pidStr), "%lld", (long long) getpid()); int fd = syscalls::open(pidFile.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { int e = errno; throw FileSystemException("Cannot create PID file " + pidFile, e, pidFile); } UPDATE_TRACE_POINT(); writeExact(fd, pidStr, strlen(pidStr)); syscalls::close(fd); } } static void openReportFile(const WorkingObjectsPtr &wo) { TRACE_POINT(); string reportFile = agentsOptions->get("report_file", false); if (!reportFile.empty()) { int fd = syscalls::open(reportFile.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd == -1) { int e = errno; throw FileSystemException("Cannot open report file " + reportFile, e, reportFile); } wo->reportFile = fd; } } static void lowerPrivilege() { TRACE_POINT(); const VariantMap &options = *agentsOptions; string userName = options.get("user", false); if (geteuid() == 0 && !userName.empty()) { struct passwd *pwUser = getpwnam(userName.c_str()); if (pwUser == NULL) { throw RuntimeException("Cannot lookup user information for user " + userName); } gid_t gid = pwUser->pw_gid; string groupName = getGroupName(pwUser->pw_gid); if (initgroups(userName.c_str(), gid) != 0) { int e = errno; throw SystemException("Unable to lower " AGENT_EXE " 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 " AGENT_EXE " watchdog's privilege " "to that of user '" + userName + "' and group '" + groupName + "': cannot set group ID to " + toString(gid), e); } if (setuid(pwUser->pw_uid) != 0) { int e = errno; throw SystemException("Unable to lower " AGENT_EXE " watchdog's privilege " "to that of user '" + userName + "' and group '" + groupName + "': cannot set user ID to " + toString(pwUser->pw_uid), e); } setenv("USER", pwUser->pw_name, 1); setenv("HOME", pwUser->pw_dir, 1); setenv("UID", toString(gid).c_str(), 1); } } static void lookupDefaultUidGid(uid_t &uid, gid_t &gid) { VariantMap &options = *agentsOptions; const string &defaultUser = options.get("default_user"); const string &defaultGroup = options.get("default_group"); struct passwd *userEntry; userEntry = getpwnam(defaultUser.c_str()); if (userEntry == NULL) { throw NonExistentUserException("Default user '" + defaultUser + "' does not exist."); } uid = userEntry->pw_uid; gid = lookupGid(defaultGroup); if (gid == (gid_t) -1) { throw NonExistentGroupException("Default group '" + defaultGroup + "' does not exist."); } } static void initializeWorkingObjects(const WorkingObjectsPtr &wo, InstanceDirToucherPtr &instanceDirToucher) { TRACE_POINT(); VariantMap &options = *agentsOptions; vector strset; options.set("instance_registry_dir", absolutizePath(options.get("instance_registry_dir"))); if (options.get("server_software").find(SERVER_TOKEN_NAME) == string::npos && options.get("server_software").find(FLYING_PASSENGER_NAME) == string::npos) { options.set("server_software", options.get("server_software") + (" " SERVER_TOKEN_NAME "/" PASSENGER_VERSION)); } wo->resourceLocator = boost::make_shared(agentsOptions->get("passenger_root")); UPDATE_TRACE_POINT(); lookupDefaultUidGid(wo->defaultUid, wo->defaultGid); wo->cleanupPidfiles = options.getStrSet("cleanup_pidfiles", false); UPDATE_TRACE_POINT(); InstanceDirectory::CreationOptions instanceOptions; instanceOptions.userSwitching = options.getBool("user_switching"); instanceOptions.defaultUid = wo->defaultUid; instanceOptions.defaultGid = wo->defaultGid; instanceOptions.properties["name"] = wo->randomGenerator.generateAsciiString(8); instanceOptions.properties["server_software"] = options.get("server_software"); wo->instanceDir = boost::make_shared(instanceOptions, options.get("instance_registry_dir")); options.set("instance_dir", wo->instanceDir->getPath()); instanceDirToucher = boost::make_shared(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); } 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 && !options.getBool("user_switching")) { 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); } options.setDefault("server_pid_file", wo->instanceDir->getPath() + "/server.pid"); UPDATE_TRACE_POINT(); strset = options.getStrSet("server_addresses", false); strset.insert(strset.begin(), "unix:" + wo->instanceDir->getPath() + "/agents.s/server"); options.setStrSet("server_addresses", strset); options.setDefault("server_password", wo->randomGenerator.generateAsciiString(24)); strset = options.getStrSet("server_admin_addresses", false); strset.insert(strset.begin(), "unix:" + wo->instanceDir->getPath() + "/agents.s/server_admin"); options.setStrSet("server_admin_addresses", strset); UPDATE_TRACE_POINT(); options.setDefault("logging_agent_address", "unix:" + wo->instanceDir->getPath() + "/agents.s/logging"); options.setDefault("logging_agent_password", wo->randomGenerator.generateAsciiString(24)); strset = options.getStrSet("logging_agent_admin_addresses", false); strset.insert(strset.begin(), "unix:" + wo->instanceDir->getPath() + "/agents.s/logging_admin"); options.setStrSet("logging_agent_admin_addresses", strset); UPDATE_TRACE_POINT(); strset = options.getStrSet("logging_agent_authorizations", false); strset.insert(strset.begin(), "readonly:ro_admin:" + wo->instanceDir->getPath() + "/read_only_admin_password.txt"); strset.insert(strset.begin(), "full:admin:" + wo->instanceDir->getPath() + "/full_admin_password.txt"); options.setStrSet("logging_agent_authorizations", strset); strset = options.getStrSet("server_authorizations", false); strset.insert(strset.begin(), "readonly:ro_admin:" + wo->instanceDir->getPath() + "/read_only_admin_password.txt"); strset.insert(strset.begin(), "full:admin:" + wo->instanceDir->getPath() + "/full_admin_password.txt"); options.setStrSet("server_authorizations", strset); } static void initializeAgentWatchers(const WorkingObjectsPtr &wo, vector &watchers) { TRACE_POINT(); watchers.push_back(make_shared(wo)); watchers.push_back(make_shared(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 parseAndAddAdminAuthorization(const WorkingObjectsPtr &wo, const string &description) { TRACE_POINT(); AdminServer::Authorization auth; vector args; split(description, ':', args); if (args.size() == 2) { auth.level = AdminServer::FULL; auth.username = args[0]; auth.password = strip(readAll(args[1])); } else if (args.size() == 3) { auth.level = AdminServer::parseLevel(args[0]); auth.username = args[1]; auth.password = strip(readAll(args[2])); } else { P_BUG("Too many elements in authorization description"); } wo->adminAuthorizations.push_back(auth); } static void initializeAdminServer(const WorkingObjectsPtr &wo) { TRACE_POINT(); VariantMap &options = *agentsOptions; vector authorizations = options.getStrSet("watchdog_authorizations", false); vector adminAddresses = options.getStrSet("watchdog_admin_addresses", false); string description; UPDATE_TRACE_POINT(); authorizations.insert(authorizations.begin(), "readonly:ro_admin:" + wo->instanceDir->getPath() + "/read_only_admin_password.txt"); authorizations.insert(authorizations.begin(), "full:admin:" + wo->instanceDir->getPath() + "/full_admin_password.txt"); options.setStrSet("watchdog_authorizations", authorizations); foreach (description, authorizations) { parseAndAddAdminAuthorization(wo, description); } UPDATE_TRACE_POINT(); adminAddresses.insert(adminAddresses.begin(), "unix:" + wo->instanceDir->getPath() + "/agents.s/watchdog"); options.setStrSet("watchdog_admin_addresses", adminAddresses); UPDATE_TRACE_POINT(); for (unsigned int i = 0; i < adminAddresses.size(); i++) { P_DEBUG("Admin server will listen on " << adminAddresses[i]); wo->adminServerFds[i] = createServer(adminAddresses[i]); if (getSocketAddressType(adminAddresses[i]) == SAT_UNIX) { makeFileWorldReadableAndWritable(parseUnixSocketAddress(adminAddresses[i])); } } UPDATE_TRACE_POINT(); wo->bgloop = new BackgroundEventLoop(true, true); wo->serverKitContext = new ServerKit::Context(wo->bgloop->safe); wo->serverKitContext->defaultFileBufferedChannelConfig.bufferDir = absolutizePath(options.get("data_buffer_dir")); UPDATE_TRACE_POINT(); wo->adminServer = new AdminServer(wo->serverKitContext); wo->adminServer->exitEvent = &wo->exitEvent; wo->adminServer->authorizations = wo->adminAuthorizations; for (unsigned int i = 0; i < adminAddresses.size(); i++) { wo->adminServer->listen(wo->adminServerFds[i]); } } static void startAgents(const WorkingObjectsPtr &wo, vector &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(&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 &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 reportAgentsInformation(const WorkingObjectsPtr &wo, const vector &watchers) { TRACE_POINT(); VariantMap report; report.set("instance_dir", wo->instanceDir->getPath()); foreach (AgentWatcherPtr watcher, watchers) { watcher->reportAgentsInformation(report); } if (feedbackFdAvailable()) { report.writeToFd(FEEDBACK_FD, "Agents information"); } if (wo->reportFile != -1) { Json::Value doc; VariantMap::ConstIterator it; string str; for (it = report.begin(); it != report.end(); it++) { doc[it->first] = it->second; } str = doc.toStyledString(); writeExact(wo->reportFile, str.data(), str.size()); } } 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); setAgentsOptionsDefaults(); sanityCheckOptions(); P_NOTICE("Starting " AGENT_EXE " watchdog..."); P_DEBUG("Watchdog options: " << agentsOptions->inspect()); InstanceDirToucherPtr instanceDirToucher; vector watchers; try { TRACE_POINT(); maybeSetsid(); maybeDaemonize(); createPidFile(); openReportFile(wo); lowerPrivilege(); initializeWorkingObjects(wo, instanceDirToucher); initializeAgentWatchers(wo, watchers); initializeAdminServer(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(&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); reportAgentsInformation(wo, watchers); finalizeInstanceDir(wo); P_INFO("All " PROGRAM_NAME " agents started!"); UPDATE_TRACE_POINT(); runHookScriptAndThrowOnError("after_watchdog_initialization"); UPDATE_TRACE_POINT(); this_thread::disable_interruption di; 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; } }