관리-도구
편집 파일: Exceptions.h
/* * Phusion Passenger - https://www.phusionpassenger.com/ * Copyright (c) 2011-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. */ #ifndef _PASSENGER_SPAWNING_KIT_EXCEPTIONS_H_ #define _PASSENGER_SPAWNING_KIT_EXCEPTIONS_H_ #include <oxt/tracable_exception.hpp> #include <string> #include <stdexcept> #include <limits> #include <Constants.h> #include <Exceptions.h> #include <LoggingKit/LoggingKit.h> #include <DataStructures/StringKeyTable.h> #include <ProcessManagement/Spawn.h> #include <SystemTools/SystemMetricsCollector.h> #include <StrIntTools/StrIntUtils.h> #include <Core/SpawningKit/Config.h> #include <Core/SpawningKit/Journey.h> extern "C" { extern char **environ; } namespace Passenger { namespace SpawningKit { using namespace std; using namespace oxt; enum ErrorCategory { INTERNAL_ERROR, FILE_SYSTEM_ERROR, OPERATING_SYSTEM_ERROR, IO_ERROR, TIMEOUT_ERROR, UNKNOWN_ERROR_CATEGORY }; inline StaticString errorCategoryToString(ErrorCategory category); inline ErrorCategory inferErrorCategoryFromAnotherException(const std::exception &e, JourneyStep failedJourneyStep); /** * For an introduction see README.md, section "Error reporting". */ class SpawnException: public oxt::tracable_exception { private: struct EnvDump { pid_t pid; string envvars; string userInfo; string ulimits; EnvDump() : pid(-1) { } }; ErrorCategory category; Journey journey; Config config; string summary; string advancedProblemDetails; string problemDescription; string solutionDescription; string stdoutAndErrData; string id; EnvDump parentProcessEnvDump; EnvDump preloaderEnvDump; EnvDump subprocessEnvDump; string systemMetrics; StringKeyTable<string> annotations; static string createDefaultSummary(ErrorCategory category, const Journey &journey, const StaticString &advancedProblemDetails) { string message; switch (category) { case TIMEOUT_ERROR: // We only return a single error message instead of a customized // one based on the failed step, because the timeout // applies to the entire journey, not just to a specific step. // A timeout at a specific step could be the result of a previous // step taking too much time. // The way to debug a timeout error is by looking at the timings // of each step. switch (journey.getType()) { case START_PRELOADER: message = "A timeout occurred while starting a preloader process"; break; default: message = "A timeout occurred while spawning an application process"; break; } break; default: string categoryPhraseWithIndefiniteArticle = getErrorCategoryPhraseWithIndefiniteArticle( category, true); switch (journey.getType()) { case START_PRELOADER: switch (journey.getFirstFailedStep()) { case SPAWNING_KIT_PREPARATION: message = categoryPhraseWithIndefiniteArticle + " occurred while preparing to start a preloader process"; break; default: message = categoryPhraseWithIndefiniteArticle + " occurred while starting a preloader process"; break; } break; default: switch (journey.getFirstFailedStep()) { case SPAWNING_KIT_PREPARATION: message = categoryPhraseWithIndefiniteArticle + " occurred while preparing to spawn an application process"; break; case SPAWNING_KIT_FORK_SUBPROCESS: message = categoryPhraseWithIndefiniteArticle + " occurred while creating (forking) subprocess"; break; case SPAWNING_KIT_CONNECT_TO_PRELOADER: message = categoryPhraseWithIndefiniteArticle + " occurred while connecting to the preloader process"; break; case SPAWNING_KIT_SEND_COMMAND_TO_PRELOADER: message = categoryPhraseWithIndefiniteArticle + " occurred while sending a command to the preloader process"; break; case SPAWNING_KIT_READ_RESPONSE_FROM_PRELOADER: message = categoryPhraseWithIndefiniteArticle + " occurred while receiving a response from the preloader process"; break; case SPAWNING_KIT_PARSE_RESPONSE_FROM_PRELOADER: message = categoryPhraseWithIndefiniteArticle + " occurred while parsing a response from the preloader process"; break; case SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER: message = categoryPhraseWithIndefiniteArticle + " occurred while processing a response from the preloader process"; break; default: message = categoryPhraseWithIndefiniteArticle + " occurred while spawning an application process"; break; } break; } break; } if (advancedProblemDetails.empty()) { message.append("."); } else { message.append(": "); message.append(advancedProblemDetails); } return message; } static string createDefaultProblemDescription(ErrorCategory category, const Journey &journey, const Config &config, const StaticString &advancedProblemDetails, const StaticString &stdoutAndErrData) { StaticString categoryStringWithIndefiniteArticle = getErrorCategoryPhraseWithIndefiniteArticle(category, false); switch (category) { case INTERNAL_ERROR: case OPERATING_SYSTEM_ERROR: case IO_ERROR: switch (journey.getType()) { case START_PRELOADER: switch (journey.getFirstFailedStep()) { case SPAWNING_KIT_PREPARATION: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application. In doing so, " SHORT_PROGRAM_NAME " had to first start an internal" " helper tool called the \"preloader\". But " SHORT_PROGRAM_NAME " encountered " + categoryStringWithIndefiniteArticle + " while performing this preparation work", category, advancedProblemDetails, stdoutAndErrData); case SPAWNING_KIT_FORK_SUBPROCESS: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application. But " SHORT_PROGRAM_NAME " encountered " + categoryStringWithIndefiniteArticle + " while creating a subprocess", category, advancedProblemDetails, stdoutAndErrData); case SPAWNING_KIT_HANDSHAKE_PERFORM: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application. In doing so, " SHORT_PROGRAM_NAME " first started an internal" " helper tool called the \"preloader\". But " SHORT_PROGRAM_NAME " encountered " + categoryStringWithIndefiniteArticle + " while communicating with" " this tool about its startup", category, advancedProblemDetails, stdoutAndErrData); case SUBPROCESS_BEFORE_FIRST_EXEC: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application. In doing so, " SHORT_PROGRAM_NAME " had to first start an internal" " helper tool called the \"preloader\". But" " the subprocess which was supposed to execute this" " preloader encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); case SUBPROCESS_OS_SHELL: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application. In doing so, " SHORT_PROGRAM_NAME " had to first start an internal" " helper tool called the \"preloader\", which" " in turn had to be started through the operating" " system (OS) shell. But the OS shell encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); case SUBPROCESS_SPAWN_ENV_SETUPPER_BEFORE_SHELL: case SUBPROCESS_SPAWN_ENV_SETUPPER_AFTER_SHELL: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application. In doing so, " SHORT_PROGRAM_NAME " had to first start an internal" " helper tool called the \"preloader\", which" " in turn had to be started through another internal" " tool called the \"SpawnEnvSetupper\". But the" " SpawnEnvSetupper encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); case SUBPROCESS_EXEC_WRAPPER: if (!config.genericApp && config.startsUsingWrapper && config.wrapperSuppliedByThirdParty) { return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application through a " SHORT_PROGRAM_NAME " helper tool called" " the \"wrapper\". This helper tool is not part of " SHORT_PROGRAM_NAME ". But " SHORT_PROGRAM_NAME " was unable to execute that helper tool" " because it encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); } else { return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application through a " SHORT_PROGRAM_NAME "-internal helper tool called" " the \"wrapper\". But " SHORT_PROGRAM_NAME " was unable to execute that helper tool" " because it encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); } case SUBPROCESS_WRAPPER_PREPARATION: if (!config.genericApp && config.startsUsingWrapper && config.wrapperSuppliedByThirdParty) { return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application through a " SHORT_PROGRAM_NAME " helper tool called" " the \"wrapper\"). This tool is not part of " SHORT_PROGRAM_NAME ". But that helper tool encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); } else { return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application through a " SHORT_PROGRAM_NAME "-internal helper tool called" " the \"wrapper\"). But that helper tool encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); } case SUBPROCESS_APP_LOAD_OR_EXEC: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application. But the application" " itself (and not " SHORT_PROGRAM_NAME ") encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); case SUBPROCESS_LISTEN: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application. The application tried " " to setup a socket for accepting connections," " but in doing so it encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); case SUBPROCESS_FINISH: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application, but the application" " encountered " + categoryStringWithIndefiniteArticle + " while finalizing its startup procedure", category, advancedProblemDetails, stdoutAndErrData); default: P_BUG("Unsupported preloader journey step " << toString((int) journey.getFirstFailedStep())); } default: switch (journey.getFirstFailedStep()) { case SPAWNING_KIT_PREPARATION: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application, but " SHORT_PROGRAM_NAME " encountered " + categoryStringWithIndefiniteArticle + " while performing preparation work", category, advancedProblemDetails, stdoutAndErrData); case SPAWNING_KIT_FORK_SUBPROCESS: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application. But " SHORT_PROGRAM_NAME " encountered " + categoryStringWithIndefiniteArticle + " while creating a subprocess", category, advancedProblemDetails, stdoutAndErrData); case SPAWNING_KIT_CONNECT_TO_PRELOADER: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application by communicating with a" " helper process that we call a \"preloader\". However, " SHORT_PROGRAM_NAME " encountered " + categoryStringWithIndefiniteArticle + " while connecting to this helper process", category, advancedProblemDetails, stdoutAndErrData); case SPAWNING_KIT_SEND_COMMAND_TO_PRELOADER: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application by communicating with a" " helper process that we call a \"preloader\". However, " SHORT_PROGRAM_NAME " encountered " + categoryStringWithIndefiniteArticle + " while sending a command to this helper process", category, advancedProblemDetails, stdoutAndErrData); case SPAWNING_KIT_READ_RESPONSE_FROM_PRELOADER: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application by communicating with a" " helper process that we call a \"preloader\". However, " SHORT_PROGRAM_NAME " encountered " + categoryStringWithIndefiniteArticle + " while receiving a response to this helper process", category, advancedProblemDetails, stdoutAndErrData); case SPAWNING_KIT_PARSE_RESPONSE_FROM_PRELOADER: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application by communicating with a" " helper process that we call a \"preloader\". However, " SHORT_PROGRAM_NAME " encountered " + categoryStringWithIndefiniteArticle + " while parsing a response from this helper process", category, advancedProblemDetails, stdoutAndErrData); case SPAWNING_KIT_PROCESS_RESPONSE_FROM_PRELOADER: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application by communicating with a" " helper process that we call a \"preloader\". However, " SHORT_PROGRAM_NAME " encountered " + categoryStringWithIndefiniteArticle + " while processing a response from this helper process", category, advancedProblemDetails, stdoutAndErrData); case SPAWNING_KIT_HANDSHAKE_PERFORM: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application. Everything was looking OK," " but then suddenly " SHORT_PROGRAM_NAME " encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); case SUBPROCESS_BEFORE_FIRST_EXEC: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application. " SHORT_PROGRAM_NAME " launched a subprocess which was supposed to" " execute the application, but instead that" " subprocess encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); case SUBPROCESS_OS_SHELL: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application through the operating" " system (OS) shell. But the OS shell encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); case SUBPROCESS_SPAWN_ENV_SETUPPER_BEFORE_SHELL: case SUBPROCESS_SPAWN_ENV_SETUPPER_AFTER_SHELL: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application through a " SHORT_PROGRAM_NAME "-internal helper tool called the" " SpawnEnvSetupper. But that helper tool encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); case SUBPROCESS_EXEC_WRAPPER: if (!config.genericApp && config.startsUsingWrapper && config.wrapperSuppliedByThirdParty) { return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application through a " SHORT_PROGRAM_NAME " helper tool called" " the \"wrapper\". This helper tool is not part of " SHORT_PROGRAM_NAME ". But " SHORT_PROGRAM_NAME " was unable to execute that helper tool because" " it encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); } else { return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application through a " SHORT_PROGRAM_NAME "-internal helper tool called" " the \"wrapper\". But " SHORT_PROGRAM_NAME " was unable to execute that helper tool because" " it encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); } case SUBPROCESS_WRAPPER_PREPARATION: if (!config.genericApp && config.startsUsingWrapper && config.wrapperSuppliedByThirdParty) { return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application through a " SHORT_PROGRAM_NAME " helper tool called" " the \"wrapper\". This helper tool is not part of " SHORT_PROGRAM_NAME ". But that helper tool encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); } else { return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application through a " SHORT_PROGRAM_NAME "-internal helper tool called" " the \"wrapper\". But that helper tool encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); } case SUBPROCESS_APP_LOAD_OR_EXEC: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application. But the application" " itself (and not " SHORT_PROGRAM_NAME ") encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); case SUBPROCESS_PREPARE_AFTER_FORKING_FROM_PRELOADER: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application through a " SHORT_PROGRAM_NAME "-internal helper tool called" " the \"preloader\". But the preloader encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); case SUBPROCESS_LISTEN: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application. The application tried " " to setup a socket for accepting connections," " but in doing so it encountered " + categoryStringWithIndefiniteArticle, category, advancedProblemDetails, stdoutAndErrData); case SUBPROCESS_FINISH: return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried to" " start the web application, but the application" " encountered " + categoryStringWithIndefiniteArticle + " while finalizing its startup procedure", category, advancedProblemDetails, stdoutAndErrData); default: P_BUG("Unrecognized journey step " << toString((int) journey.getFirstFailedStep())); } } case TIMEOUT_ERROR: // We only return a single error message instead of a customized // one based on the failed step, because the timeout // applies to the entire journey, not just to a specific step. // A timeout at a specific step could be the result of a previous // step taking too much time. // The way to debug a timeout error is by looking at the timings // of each step. return wrapInParaAndMaybeAddErrorMessages( "The " PROGRAM_NAME " application server tried" " to start the web application, but this took too much time," " so " SHORT_PROGRAM_NAME " put a stop to that", TIMEOUT_ERROR, string(), stdoutAndErrData); default: P_BUG("Unrecognized error category " + toString((int) category)); return string(); // Never reached, shut up compiler warning. } } static string createDefaultSolutionDescription(ErrorCategory category, const Journey &journey, const Config &config) { string message; switch (category) { case INTERNAL_ERROR: return "<p class=\"sole-solution\">" "Unfortunately, " SHORT_PROGRAM_NAME " does not know" " how to solve this problem. Please try troubleshooting" " the problem by studying the <strong>error message</strong>" " and the <strong>diagnostics</strong> reports. You can also" " consult <a href=\"" SUPPORT_URL "\">the " SHORT_PROGRAM_NAME " support resources</a> for help.</p>"; case FILE_SYSTEM_ERROR: return "<p class=\"sole-solution\">" "Unfortunately, " SHORT_PROGRAM_NAME " does not know how to" " solve this problem. But it looks like some kind of filesystem error." " This generally means that you need to fix nonexistant" " files/directories or fix filesystem permissions. Please" " try troubleshooting the problem by studying the" " <strong>error message</strong> and the" " <strong>diagnostics</strong> reports.</p>"; case OPERATING_SYSTEM_ERROR: case IO_ERROR: return "<div class=\"multiple-solutions\">" "<h3>Check whether the server is low on resources</h3>" "<p>Maybe the server is currently low on resources. This would" " cause errors to occur. Please study the <em>error" " message</em> and the <em>diagnostics reports</em> to" " verify whether this is the case. Key things to check for:</p>" "<ul>" "<li>Excessive CPU usage</li>" "<li>Memory and swap</li>" "<li>Ulimits</li>" "</ul>" "<p>If the server is indeed low on resources, find a way to" " free up some resources.</p>" "<h3>Check your (filesystem) security settings</h3>" "<p>Maybe security settings are preventing " SHORT_PROGRAM_NAME " from doing the work it needs to do. Please check whether the" " error may be caused by your system's security settings, or" " whether it may be caused by wrong permissions on a file or" " directory.</p>" "<h3>Still no luck?</h3>" "<p>Please try troubleshooting the problem by studying the" " <em>diagnostics</em> reports.</p>" "</div>"; case TIMEOUT_ERROR: message = "<div class=\"multiple-solutions\">" "<h3>Check whether the server is low on resources</h3>" "<p>Maybe the server is currently so low on resources that" " all the work that needed to be done, could not finish within" " the given time limit." " Please inspect the server resource utilization statistics" " in the <em>diagnostics</em> section to verify" " whether server is indeed low on resources.</p>" "<p>If so, then either increase the spawn timeout (currently" " configured at " + toString(config.startTimeoutMsec / 1000) + " sec), or find a way to lower the server's resource" " utilization.</p>"; switch (journey.getFirstFailedStep()) { case SUBPROCESS_OS_SHELL: message.append( "<h3>Check whether your OS shell's startup scripts can" " take a long time or get stuck</h3>" "<p>One of your OS shell's startup scripts may do too much work," " or it may have invoked a command that then got stuck." " Please investigate and debug your OS shell's startup" " scripts.</p>"); break; case SUBPROCESS_APP_LOAD_OR_EXEC: if (config.appType == "nodejs") { message.append( "<h3>Check whether the application calls <code>http.Server.listen()</code></h3>" "<p>" SHORT_PROGRAM_NAME " requires that the application calls" " <code>listen()</code> on an http.Server object. If" " the application never calls this, then " SHORT_PROGRAM_NAME " will think the application is" " stuck. <a href=\"https://www.phusionpassenger.com/" "library/indepth/nodejs/reverse_port_binding.html\">" "Learn more about this problem.</a></p>"); } message.append( "<h3>Check whether the application is stuck during startup</h3>" "<p>The easiest way find out where the application is stuck" "is by inserting print statements into the application's code.</p>"); break; default: break; } message.append("<h3>Still no luck?</h3>" "<p>Please try troubleshooting the problem by studying the" " <em>diagnostics</em> reports.</p>" "</div>"); return message; default: return "(error generating solution description: unknown error category)"; } } static string createDefaultAdvancedProblemDetails(const std::exception &e) { const oxt::tracable_exception *te = dynamic_cast<const oxt::tracable_exception *>(&e); if (te != NULL) { return string(e.what()) + "\n" + te->backtrace(); } else { return e.what(); } } static StaticString getErrorCategoryPhraseWithIndefiniteArticle( ErrorCategory category, bool beginOfSentence) { switch (category) { case INTERNAL_ERROR: if (beginOfSentence) { return P_STATIC_STRING("An internal error"); } else { return P_STATIC_STRING("an internal error"); } case FILE_SYSTEM_ERROR: if (beginOfSentence) { return P_STATIC_STRING("A file system error"); } else { return P_STATIC_STRING("a file system error"); } case OPERATING_SYSTEM_ERROR: if (beginOfSentence) { return P_STATIC_STRING("An operating system error"); } else { return P_STATIC_STRING("an operating system error"); } case IO_ERROR: if (beginOfSentence) { return P_STATIC_STRING("An I/O error"); } else { return P_STATIC_STRING("an I/O error"); } case TIMEOUT_ERROR: if (beginOfSentence) { return P_STATIC_STRING("A timeout error"); } else { return P_STATIC_STRING("a timeout error"); } default: P_BUG("Unsupported error category " + toString((int) category)); return StaticString(); } } static StaticString getErrorCategoryPhraseWithIndefiniteArticle( const std::exception &e, const Journey &journey, bool beginOfSentence) { ErrorCategory category = inferErrorCategoryFromAnotherException( e, journey.getFirstFailedStep()); return getErrorCategoryPhraseWithIndefiniteArticle(category, beginOfSentence); } static string wrapInParaAndMaybeAddErrorMessages(const StaticString &message, ErrorCategory category, const StaticString &advancedProblemDetails, const StaticString &stdoutAndErrData) { string result = "<p>" + message + ".</p>"; if (!advancedProblemDetails.empty()) { if (category == INTERNAL_ERROR || category == FILE_SYSTEM_ERROR) { result.append("<p>Error details:</p>" "<pre>" + escapeHTML(advancedProblemDetails) + "</pre>"); } else if (category == IO_ERROR) { result.append("<p>The error reported by the I/O layer is:</p>" "<pre>" + escapeHTML(advancedProblemDetails) + "</pre>"); } else { P_ASSERT_EQ(category, OPERATING_SYSTEM_ERROR); result.append("<p>The error reported by the operating system is:</p>" "<pre>" + escapeHTML(advancedProblemDetails) + "</pre>"); } } if (!stdoutAndErrData.empty()) { result.append("<p>The stdout/stderr output of the subprocess so far is:</p>" "<pre>" + escapeHTML(stdoutAndErrData) + "</pre>"); } return result; } static string gatherEnvvars() { string result; unsigned int i = 0; while (environ[i] != NULL) { result.append(environ[i]); result.append(1, '\n'); i++; } return result; } static string gatherUlimits() { // On Linux, ulimit is a shell builtin and not a command. const char *command[] = { "/bin/sh", "-c", "ulimit -a", NULL }; try { SubprocessInfo info; SubprocessOutput output; runCommandAndCaptureOutput(command, info, output, std::numeric_limits<size_t>::max()); if (output.data.empty()) { output.data.assign("Error: command 'ulimit -a' failed"); } return output.data; } catch (const SystemException &e) { return P_STATIC_STRING("Error: command 'ulimit -a' failed: ") + e.what(); } } static string gatherUserInfo() { const char *command[] = { "id", "-a", NULL }; try { SubprocessInfo info; SubprocessOutput output; runCommandAndCaptureOutput(command, info, output, std::numeric_limits<size_t>::max()); if (output.data.empty()) { output.data.assign("Error: command 'id -a' failed"); } return output.data; } catch (const SystemException &e) { return P_STATIC_STRING("Error: command 'id -a' failed: ") + e.what(); } } static string gatherSystemMetrics() { SystemMetrics metrics; try { SystemMetricsCollector().collect(metrics); } catch (const RuntimeException &e) { return "Error: cannot parse system metrics: " + StaticString(e.what()); } FastStringStream<> stream; metrics.toDescription(stream); return string(stream.data(), stream.size()); } public: SpawnException(ErrorCategory _category, const Journey &_journey, const Config *_config) : category(_category), journey(_journey), config(*_config) { assert(_journey.getFirstFailedStep() != UNKNOWN_JOURNEY_STEP); config.internStrings(); } SpawnException(const std::exception &originalException, const Journey &_journey, const Config *_config) : category(inferErrorCategoryFromAnotherException( originalException, _journey.getFirstFailedStep())), journey(_journey), config(*_config), summary(createDefaultSummary( category, _journey, originalException.what())), advancedProblemDetails(createDefaultAdvancedProblemDetails(originalException)) { assert(_journey.getFirstFailedStep() != UNKNOWN_JOURNEY_STEP); config.internStrings(); } virtual ~SpawnException() throw() {} virtual const char *what() const throw() { return summary.c_str(); } ErrorCategory getErrorCategory() const { return category; } const Journey &getJourney() const { return journey; } const Config &getConfig() const { return config; } const string &getSummary() const { return summary; } void setSummary(const string &value) { summary = value; } const string &getAdvancedProblemDetails() const { return advancedProblemDetails; } void setAdvancedProblemDetails(const string &value) { advancedProblemDetails = value; } const string &getProblemDescriptionHTML() const { return problemDescription; } void setProblemDescriptionHTML(const string &value) { problemDescription = value; } const string &getSolutionDescriptionHTML() const { return solutionDescription; } void setSolutionDescriptionHTML(const string &value) { solutionDescription = value; } const string &getStdoutAndErrData() const { return stdoutAndErrData; } void setStdoutAndErrData(const string &value) { stdoutAndErrData = value; } const string &getId() const { return id; } void setId(const string &value) { id = value; } SpawnException &finalize() { TRACE_POINT(); if (summary.empty()) { summary = createDefaultSummary(category, journey, advancedProblemDetails); } if (problemDescription.empty()) { problemDescription = createDefaultProblemDescription( category, journey, config, advancedProblemDetails, stdoutAndErrData); } if (solutionDescription.empty()) { solutionDescription = createDefaultSolutionDescription( category, journey, config); } parentProcessEnvDump.pid = getpid(); parentProcessEnvDump.envvars = gatherEnvvars(); parentProcessEnvDump.userInfo = gatherUserInfo(); parentProcessEnvDump.ulimits = gatherUlimits(); systemMetrics = gatherSystemMetrics(); return *this; } const string &getParentProcessEnvvars() const { return parentProcessEnvDump.envvars; } const string &getParentProcessUserInfo() const { return parentProcessEnvDump.userInfo; } const string &getParentProcessUlimits() const { return parentProcessEnvDump.ulimits; } pid_t getPreloaderPid() const { return preloaderEnvDump.pid; } void setPreloaderPid(pid_t pid) { preloaderEnvDump.pid = pid; } const string &getPreloaderEnvvars() const { return preloaderEnvDump.envvars; } void setPreloaderEnvvars(const string &value) { preloaderEnvDump.envvars = value; } const string &getPreloaderUserInfo() const { return preloaderEnvDump.userInfo; } void setPreloaderUserInfo(const string &value) { preloaderEnvDump.userInfo = value; } const string &getPreloaderUlimits() const { return preloaderEnvDump.ulimits; } void setPreloaderUlimits(const string &value) { preloaderEnvDump.ulimits = value; } pid_t getSubprocessPid() const { return subprocessEnvDump.pid; } void setSubprocessPid(pid_t pid) { subprocessEnvDump.pid = pid; } const string &getSubprocessEnvvars() const { return subprocessEnvDump.envvars; } void setSubprocessEnvvars(const string &value) { subprocessEnvDump.envvars = value; } const string &getSubprocessUserInfo() const { return subprocessEnvDump.userInfo; } void setSubprocessUserInfo(const string &value) { subprocessEnvDump.userInfo = value; } const string &getSubprocessUlimits() const { return subprocessEnvDump.ulimits; } void setSubprocessUlimits(const string &value) { subprocessEnvDump.ulimits = value; } const string &getSystemMetrics() const { return systemMetrics; } const StringKeyTable<string> &getAnnotations() const { return annotations; } void setAnnotation(const HashedStaticString &name, const string &value, bool overwrite = true) { annotations.insert(name, value, overwrite); } Json::Value inspectBasicInfoAsJson() const { Json::Value doc; doc["category"] = errorCategoryToString(category).toString(); doc["summary"] = summary; doc["problem_description_html"] = problemDescription; doc["solution_description_html"] = solutionDescription; if (!advancedProblemDetails.empty()) { doc["aux_details"] = advancedProblemDetails; } if (!id.empty()) { doc["id"] = id; } return doc; } Json::Value inspectSystemWideDetailsAsJson() const { Json::Value doc; doc["system_metrics"] = systemMetrics; return doc; } Json::Value inspectParentProcessDetailsAsJson() const { Json::Value doc; doc["backtrace"] = backtrace(); doc["pid"] = (Json::Int) parentProcessEnvDump.pid; doc["envvars"] = getParentProcessEnvvars(); doc["user_info"] = getParentProcessUserInfo(); doc["ulimits"] = getParentProcessUlimits(); return doc; } Json::Value inspectPreloaderProcessDetailsAsJson() const { Json::Value doc, annotations(Json::objectValue); if (getPreloaderPid() != (pid_t) -1) { doc["pid"] = getPreloaderPid(); } doc["envvars"] = getPreloaderEnvvars(); doc["user_info"] = getPreloaderUserInfo(); doc["ulimits"] = getPreloaderUlimits(); StringKeyTable<string>::ConstIterator it(this->annotations); while (*it != NULL) { annotations[it.getKey().toString()] = it.getValue(); it.next(); } doc["annotations"] = annotations; return doc; } Json::Value inspectSubprocessDetailsAsJson() const { Json::Value doc, annotations(Json::objectValue); if (getSubprocessPid() != (pid_t) -1) { doc["pid"] = getSubprocessPid(); } doc["envvars"] = getSubprocessEnvvars(); doc["user_info"] = getSubprocessUserInfo(); doc["ulimits"] = getSubprocessUlimits(); doc["stdout_and_err"] = getStdoutAndErrData(); StringKeyTable<string>::ConstIterator it(this->annotations); while (*it != NULL) { annotations[it.getKey().toString()] = it.getValue(); it.next(); } doc["annotations"] = annotations; return doc; } }; inline StaticString errorCategoryToString(ErrorCategory category) { switch (category) { case INTERNAL_ERROR: return P_STATIC_STRING("INTERNAL_ERROR"); case FILE_SYSTEM_ERROR: return P_STATIC_STRING("FILE_SYSTEM_ERROR"); case OPERATING_SYSTEM_ERROR: return P_STATIC_STRING("OPERATING_SYSTEM_ERROR"); case IO_ERROR: return P_STATIC_STRING("IO_ERROR"); case TIMEOUT_ERROR: return P_STATIC_STRING("TIMEOUT_ERROR"); case UNKNOWN_ERROR_CATEGORY: return P_STATIC_STRING("UNKNOWN_ERROR_CATEGORY"); default: return P_STATIC_STRING("(invalid value)"); } } inline bool _isFileSystemError(const std::exception &e) { if (dynamic_cast<const FileSystemException *>(&e) != NULL) { return true; } const SystemException *sysEx = dynamic_cast<const SystemException *>(&e); if (sysEx != NULL) { return sysEx->code() == ENOENT || sysEx->code() == ENAMETOOLONG || sysEx->code() == EEXIST || sysEx->code() == EACCES; } return false; } inline bool _systemErrorIsActuallyIoError(JourneyStep failedJourneyStep) { return failedJourneyStep == SPAWNING_KIT_CONNECT_TO_PRELOADER || failedJourneyStep == SPAWNING_KIT_SEND_COMMAND_TO_PRELOADER || failedJourneyStep == SPAWNING_KIT_READ_RESPONSE_FROM_PRELOADER; } inline ErrorCategory inferErrorCategoryFromAnotherException(const std::exception &e, JourneyStep failedJourneyStep) { if (dynamic_cast<const SystemException *>(&e) != NULL) { if (_systemErrorIsActuallyIoError(failedJourneyStep)) { return IO_ERROR; } else { return OPERATING_SYSTEM_ERROR; } } else if (_isFileSystemError(e)) { return FILE_SYSTEM_ERROR; } else if (dynamic_cast<const IOException *>(&e) != NULL) { return IO_ERROR; } else if (dynamic_cast<const TimeoutException *>(&e) != NULL) { return TIMEOUT_ERROR; } else { return INTERNAL_ERROR; } } inline ErrorCategory stringToErrorCategory(const StaticString &value) { if (value == P_STATIC_STRING("INTERNAL_ERROR")) { return INTERNAL_ERROR; } else if (value == P_STATIC_STRING("FILE_SYSTEM_ERROR")) { return FILE_SYSTEM_ERROR; } else if (value == P_STATIC_STRING("OPERATING_SYSTEM_ERROR")) { return OPERATING_SYSTEM_ERROR; } else if (value == P_STATIC_STRING("IO_ERROR")) { return IO_ERROR; } else if (value == P_STATIC_STRING("TIMEOUT_ERROR")) { return TIMEOUT_ERROR; } else { return UNKNOWN_ERROR_CATEGORY; } } } // namespace SpawningKit } // namespace Passenger #endif /* _PASSENGER_SPAWNING_KIT_EXCEPTIONS_H_ */