//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany /// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Dr. Frank Celler //////////////////////////////////////////////////////////////////////////////// #include "ApplicationServer.h" #include #ifdef TRI_HAVE_POSIX_PWD_GRP #include #include #endif #include "ApplicationServer/ApplicationFeature.h" #include "Basics/ConditionLocker.h" #include "Basics/conversions.h" #include "Basics/files.h" #include "Basics/FileUtils.h" #include "Logger/Logger.h" #include "Basics/RandomGenerator.h" #include "Basics/StringUtils.h" #include "Basics/tri-strings.h" using namespace arangodb::basics; using namespace arangodb::rest; static std::string DeprecatedParameter; //////////////////////////////////////////////////////////////////////////////// /// @brief Hidden Options //////////////////////////////////////////////////////////////////////////////// namespace { std::string const OPTIONS_HIDDEN = "Hidden Options"; } //////////////////////////////////////////////////////////////////////////////// /// @brief Command Line Options //////////////////////////////////////////////////////////////////////////////// namespace { std::string const OPTIONS_CMDLINE = "General Options"; } ApplicationServer::ApplicationServer(std::string const& name, std::string const& title, std::string const& version) : _options(), _description(), _descriptionFile(), _arguments(), _features(), _exitOnParentDeath(false), _watchParent(0), _stopping(0), _name(name), _title(title), _version(version), _configFile(), _userConfigFile(), _systemConfigFile(), _uid(), _numericUid(0), _gid(), _numericGid(0), _logApplicationName("arangod"), _logBuffered(true), _logFacility(""), _logFile("+"), _logTty("+"), _logRequestsFile(""), _logPrefix(), _logThreadId(false), _logLineNumber(false), _logLocalTime(false), _logContentFilter(), #ifdef _WIN32 _randomGenerator(5), #else _randomGenerator(3), #endif _finishedCondition() { _logLevel.push_back("info"); } ApplicationServer::~ApplicationServer() { Random::shutdown(); for (auto& it : _features) { delete it; } } //////////////////////////////////////////////////////////////////////////////// /// @brief adds a new feature //////////////////////////////////////////////////////////////////////////////// void ApplicationServer::addFeature(ApplicationFeature* feature) { _features.push_back(feature); } //////////////////////////////////////////////////////////////////////////////// /// @brief sets the name of the system config file //////////////////////////////////////////////////////////////////////////////// void ApplicationServer::setSystemConfigFile(std::string const& name) { _systemConfigFile = name; } //////////////////////////////////////////////////////////////////////////////// /// @brief sets the name of the user config file //////////////////////////////////////////////////////////////////////////////// void ApplicationServer::setUserConfigFile(std::string const& name) { _userConfigFile = name; } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the name of the application //////////////////////////////////////////////////////////////////////////////// std::string const& ApplicationServer::getName() const { return _name; } //////////////////////////////////////////////////////////////////////////////// /// @brief sets up the logging //////////////////////////////////////////////////////////////////////////////// void ApplicationServer::setupLogging(bool threaded, bool daemon, bool backgrounded) { Logger::shutdown(false); Logger::initialize(threaded && _logBuffered); if (_options.has("log.buffered")) { _logThreadId = true; } if (_options.has("log.thread")) { _logThreadId = true; } if (_options.has("log.line-number")) { _logLineNumber = true; } if (_options.has("log.use-local-time")) { _logLocalTime = true; } Logger::setUseLocalTime(_logLocalTime); Logger::setShowLineNumber(_logLineNumber); Logger::setOutputPrefix(_logPrefix); Logger::setShowThreadIdentifier(_logThreadId); std::vector levels; std::vector outputs; // map deprecated option "log.requests-files" to "log.output" if (!_logRequestsFile.empty()) { std::string const& filename = _logRequestsFile; std::string definition; if (filename == "+" || filename == "-") { definition = filename; } else if (daemon) { definition = "file://" + filename + ".daemon"; } else { definition = "file://" + filename; } levels.push_back("requests=info"); outputs.push_back("requests=" + definition); } // map deprecated option "log.facility" to "log.output" #ifdef ARANGODB_ENABLE_SYSLOG if (!_logFacility.empty()) { outputs.push_back("syslog://" + _logFacility + "/" + _logApplicationName); } #endif // map deprecated option "log.performance" to "log.level" if (_options.has("log.performance")) { levels.push_back("requests=trace"); } // map "log.file" to "log.output" if (!_logFile.empty()) { std::string const& filename = _logFile; std::string definition; if (filename == "+" || filename == "-") { definition = filename; } else if (daemon) { definition = "file://" + filename + ".daemon"; } else { definition = "file://" + filename; } outputs.push_back(definition); } // additional log file in case of tty bool ttyLogger = false; if (!backgrounded && isatty(STDIN_FILENO) != 0 && !_logTty.empty()) { bool ttyOut = (_logTty == "+" || _logTty == "-"); if (!ttyOut) { LOG(ERR) << "'log.tty' must either be '+' or '-', ignoring value '" << _logTty << "'"; } else { bool regularOut = false; for (auto const& definition : _logOutput) { regularOut = regularOut || definition == "+" || definition == "-"; } for (auto const& definition : outputs) { regularOut = regularOut || definition == "+" || definition == "-"; } if (!regularOut) { outputs.push_back(_logTty); ttyLogger = true; } } } // create all output definitions levels.insert(levels.end(), _logLevel.begin(), _logLevel.end()); outputs.insert(outputs.end(), _logOutput.begin(), _logOutput.end()); Logger::setLogLevel(levels); std::unordered_set filenames; for (auto const& definition : outputs) { Logger::addAppender(definition, !ttyLogger, _logContentFilter, filenames); } } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the command line options //////////////////////////////////////////////////////////////////////////////// ProgramOptions& ApplicationServer::programOptions() { return _options; } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the command line arguments //////////////////////////////////////////////////////////////////////////////// std::vector ApplicationServer::programArguments() { return _arguments; } //////////////////////////////////////////////////////////////////////////////// /// @brief parses the arguments with empty options description //////////////////////////////////////////////////////////////////////////////// bool ApplicationServer::parse(int argc, char* argv[]) { std::map none; return parse(argc, argv, none); } //////////////////////////////////////////////////////////////////////////////// /// @brief parses the arguments //////////////////////////////////////////////////////////////////////////////// bool ApplicationServer::parse( int argc, char* argv[], std::map opts) { // ............................................................................. // setup the options // ............................................................................. setupOptions(opts); for (std::vector::iterator i = _features.begin(); i != _features.end(); ++i) { (*i)->setupOptions(opts); } // construct options description for (std::map::iterator i = opts.begin(); i != opts.end(); ++i) { std::string name = i->first; ProgramOptionsDescription sectionDescription = i->second; sectionDescription.setName(name); // and add to the global options _description(sectionDescription, name == OPTIONS_HIDDEN); if (name != OPTIONS_CMDLINE) { _descriptionFile(sectionDescription, name == OPTIONS_HIDDEN); } } _description.arguments(&_arguments); // ............................................................................. // parse command line // ............................................................................. bool ok = _options.parse(_description, argc, argv); if (!ok) { LOG(ERR) << "cannot parse command line: " << _options.lastError(); return false; } // check for version request if (_options.has("version")) { std::cout << _version << std::endl; TRI_EXIT_FUNCTION(EXIT_SUCCESS, nullptr); } // check for help std::set help = _options.needHelp("help"); if (!help.empty()) { // output help, but do not yet exit (we'll exit a little later so we can // also // check the specified configuration for errors) std::cout << argv[0] << " " << _title << std::endl << std::endl << _description.usage(help) << std::endl; } Logger::setLogLevel(_logLevel); // ............................................................................. // check configuration file // ............................................................................. ok = readConfigurationFile(); if (!ok) { return false; } // ............................................................................. // parse phase 1 // ............................................................................. for (std::vector::iterator i = _features.begin(); i != _features.end(); ++i) { ok = (*i)->afterOptionParsing(_options); if (!ok) { return false; } } // exit here if --help was specified. // this allows us to use --help to run a configuration file check, too, and // report errors to the user if (!help.empty()) { TRI_EXIT_FUNCTION(EXIT_SUCCESS, nullptr); } // ............................................................................. // UID and GID // ............................................................................. extractPrivileges(); dropPrivilegesPermanently(); // ............................................................................. // setup logging // ............................................................................. setupLogging(false, false, false); // ............................................................................. // select random generate // ............................................................................. try { switch (_randomGenerator) { case 1: { Random::selectVersion(Random::RAND_MERSENNE); break; } case 2: { Random::selectVersion(Random::RAND_RANDOM); break; } case 3: { Random::selectVersion(Random::RAND_URANDOM); break; } case 4: { Random::selectVersion(Random::RAND_COMBINED); break; } case 5: { Random::selectVersion(Random::RAND_WIN32); break; } default: { break; } } } catch (...) { LOG(FATAL) << "cannot select random generator, giving up"; FATAL_ERROR_EXIT(); } return true; } //////////////////////////////////////////////////////////////////////////////// /// @brief prepares the server, step one //////////////////////////////////////////////////////////////////////////////// void ApplicationServer::prepare() { // prepare all features - why reverse? // reason: features might depend on each other. by creating them in reverse // order and shutting them // down in forward order, we ensure that the features created last are // destroyed first, i.e.: LIFO for (std::vector::reverse_iterator i = _features.rbegin(); i != _features.rend(); ++i) { ApplicationFeature* feature = *i; LOG(DEBUG) << "preparing server feature '" << feature->getName() << "'"; bool ok = feature->prepare(); if (!ok) { LOG(FATAL) << "failed to prepare server feature '" << feature->getName() << "'"; FATAL_ERROR_EXIT(); } LOG(TRACE) << "prepared server feature '" << feature->getName() << "'"; } } //////////////////////////////////////////////////////////////////////////////// /// @brief prepares the server, step two //////////////////////////////////////////////////////////////////////////////// void ApplicationServer::prepare2() { // prepare all features for (std::vector::reverse_iterator i = _features.rbegin(); i != _features.rend(); ++i) { ApplicationFeature* feature = *i; LOG(DEBUG) << "preparing(2) server feature '" << feature->getName() << "'"; bool ok = feature->prepare2(); if (!ok) { LOG(FATAL) << "failed to prepare(2) server feature '" << feature->getName() << "'"; FATAL_ERROR_EXIT(); } LOG(TRACE) << "prepared(2) server feature '" << feature->getName() << "'"; } } //////////////////////////////////////////////////////////////////////////////// /// @brief starts the server //////////////////////////////////////////////////////////////////////////////// void ApplicationServer::start() { #ifdef TRI_HAVE_POSIX_THREADS sigset_t all; sigfillset(&all); pthread_sigmask(SIG_SETMASK, &all, 0); #endif // start all startable features for (std::vector::iterator i = _features.begin(); i != _features.end(); ++i) { ApplicationFeature* feature = *i; bool ok = feature->start(); if (!ok) { LOG(FATAL) << "failed to start server feature '" << feature->getName() << "'"; FATAL_ERROR_EXIT(); } LOG(DEBUG) << "started server feature '" << feature->getName() << "'"; } // now open all features for (std::vector::reverse_iterator i = _features.rbegin(); i != _features.rend(); ++i) { ApplicationFeature* feature = *i; LOG(DEBUG) << "opening server feature '" << feature->getName() << "'"; bool ok = feature->open(); if (!ok) { LOG(FATAL) << "failed to open server feature '" << feature->getName() << "'"; FATAL_ERROR_EXIT(); } LOG(TRACE) << "opened server feature '" << feature->getName() << "'"; } } //////////////////////////////////////////////////////////////////////////////// /// @brief waits for shutdown //////////////////////////////////////////////////////////////////////////////// void ApplicationServer::wait() { // wait until we receive a stop signal while (_stopping == 0) { // check the parent and wait for a second if (!checkParent()) { break; } CONDITION_LOCKER(locker, _finishedCondition); locker.wait((uint64_t)(1000 * 1000)); } } //////////////////////////////////////////////////////////////////////////////// /// @brief begins shutdown sequence //////////////////////////////////////////////////////////////////////////////// void ApplicationServer::beginShutdown() { if (_stopping != 0) { return; } _stopping = 1; } //////////////////////////////////////////////////////////////////////////////// /// @brief stops everything //////////////////////////////////////////////////////////////////////////////// void ApplicationServer::stop() { beginShutdown(); CONDITION_LOCKER(locker, _finishedCondition); locker.signal(); // close all features for (std::vector::iterator i = _features.begin(); i != _features.end(); ++i) { ApplicationFeature* feature = *i; feature->close(); LOG(TRACE) << "closed server feature '" << feature->getName() << "'"; } // stop all features for (std::vector::reverse_iterator i = _features.rbegin(); i != _features.rend(); ++i) { ApplicationFeature* feature = *i; LOG(DEBUG) << "shutting down server feature '" << feature->getName() << "'"; feature->stop(); LOG(TRACE) << "shut down server feature '" << feature->getName() << "'"; } } //////////////////////////////////////////////////////////////////////////////// /// @brief extract the privileges to use //////////////////////////////////////////////////////////////////////////////// void ApplicationServer::extractPrivileges() { #ifdef ARANGODB_HAVE_SETGID if (_gid.empty()) { _numericGid = getgid(); } else { int gidNumber = TRI_Int32String(_gid.c_str()); if (TRI_errno() == TRI_ERROR_NO_ERROR && gidNumber >= 0) { #ifdef ARANGODB_HAVE_GETGRGID group* g = getgrgid(gidNumber); if (g == 0) { LOG(FATAL) << "unknown numeric gid '" << _gid << "'"; FATAL_ERROR_EXIT(); } #endif } else { #ifdef ARANGODB_HAVE_GETGRNAM std::string name = _gid; group* g = getgrnam(name.c_str()); if (g != 0) { gidNumber = g->gr_gid; } else { LOG(FATAL) << "cannot convert groupname '" << _gid << "' to numeric gid"; FATAL_ERROR_EXIT(); } #else LOG(FATAL) << "cannot convert groupname '" << _gid << "' to numeric gid"; FATAL_ERROR_EXIT(); #endif } _numericGid = gidNumber; } #endif #ifdef ARANGODB_HAVE_SETUID if (_uid.empty()) { _numericUid = getuid(); } else { int uidNumber = TRI_Int32String(_uid.c_str()); if (TRI_errno() == TRI_ERROR_NO_ERROR) { #ifdef ARANGODB_HAVE_GETPWUID passwd* p = getpwuid(uidNumber); if (p == 0) { LOG(FATAL) << "unknown numeric uid '" << _uid << "'"; FATAL_ERROR_EXIT(); } #endif } else { #ifdef ARANGODB_HAVE_GETPWNAM std::string name = _uid; passwd* p = getpwnam(name.c_str()); if (p != 0) { uidNumber = p->pw_uid; } else { LOG(FATAL) << "cannot convert username '" << _uid << "' to numeric uid"; FATAL_ERROR_EXIT(); } #else LOG(FATAL) << "cannot convert username '" << _uid << "' to numeric uid"; FATAL_ERROR_EXIT(); #endif } _numericUid = uidNumber; } #endif } //////////////////////////////////////////////////////////////////////////////// /// @brief drops the privileges permanently //////////////////////////////////////////////////////////////////////////////// void ApplicationServer::dropPrivilegesPermanently() { // clear all supplementary groups #if defined(ARANGODB_HAVE_INITGROUPS) && defined(ARANGODB_HAVE_SETGID) && \ defined(ARANGODB_HAVE_SETUID) if (!_gid.empty() && !_uid.empty()) { struct passwd* pwent = getpwuid(_numericUid); if (pwent != nullptr) { initgroups(pwent->pw_name, _numericGid); } } #endif // first GID #ifdef ARANGODB_HAVE_SETGID if (!_gid.empty()) { LOG(DEBUG) << "permanently changing the gid to " << _numericGid; int res = setgid(_numericGid); if (res != 0) { LOG(FATAL) << "cannot set gid " << _numericGid << ": " << strerror(errno); FATAL_ERROR_EXIT(); } } #endif // then UID (because we are dropping) #ifdef ARANGODB_HAVE_SETUID if (!_uid.empty()) { LOG(DEBUG) << "permanently changing the uid to " << _numericUid; int res = setuid(_numericUid); if (res != 0) { LOG(FATAL) << "cannot set uid '" << _uid << "': " << strerror(errno); FATAL_ERROR_EXIT(); } } #endif } void ApplicationServer::setupOptions( std::map& options) { // ............................................................................. // command line options // ............................................................................. options["General Options:help-default"]("version,v", "print version string and exit")( "help,h", "produce a usage message and exit")( "configuration,c", &_configFile, "read configuration file"); #if defined(ARANGODB_HAVE_SETUID) || defined(ARANGODB_HAVE_SETGID) options["General Options:help-admin"] #ifdef ARANGODB_HAVE_GETPPID ("exit-on-parent-death", &_exitOnParentDeath, "exit if parent dies") #endif ("watch-process", &_watchParent, "exit if process with given PID dies"); #endif // ............................................................................. // logger options // ............................................................................. // clang-format off options["Logging Options:help-default:help-log"] ("log.file", &_logFile, "log to file") ("log.level,l", &_logLevel, "log level") ("log.output,o", &_logOutput, "log output") ; options["Logging Options:help-log"] ("log.application", &_logApplicationName, "application name for syslog") ("log.content-filter", &_logContentFilter, "only log message containing the specified string (case-sensitive)") ("log.line-number", "always log file and line number") ("log.prefix", &_logPrefix, "prefix log") ("log.thread", "log the thread identifier") ("log.use-local-time", "use local dates and times in log messages") ("log.tty", &_logTty, "additional log file if started on tty") ; options["Hidden Options"] ("log.buffered", &_logBuffered, "buffer the log output") ("log", &_logLevel, "log level") #ifdef ARANGODB_HAVE_SETUID ("uid", &_uid, "switch to user-id after reading config files") #endif #ifdef ARANGODB_HAVE_SETGID ("gid", &_gid, "switch to group-id after reading config files") #endif ; options["Hidden Options"] ("log.performance", "log performance indicators (deprecated)") ("log.requests-file", &_logRequestsFile, "log requests to file (deprecated)") ("log.facility", &_logFacility, "facility name for syslog (deprecated)") ; // clang-format on // ............................................................................. // application server options // ............................................................................. options["Server Options:help-admin"]( "random.generator", &_randomGenerator, "1 = mersenne, 2 = random, 3 = urandom, 4 = combined") #ifdef ARANGODB_HAVE_SETUID ("server.uid", &_uid, "switch to user-id after reading config files") #endif #ifdef ARANGODB_HAVE_SETGID ("server.gid", &_gid, "switch to group-id after reading config files") #endif ; } //////////////////////////////////////////////////////////////////////////////// /// @brief checks if the parent is still alive //////////////////////////////////////////////////////////////////////////////// bool ApplicationServer::checkParent() { // check our parent, if it died given up #ifdef ARANGODB_HAVE_GETPPID if (_exitOnParentDeath && getppid() == 1) { LOG(INFO) << "parent has died"; return false; } #endif // unfortunately even though windows has , there is no // kill method defined. Notice that the kill below is not to terminate // the process. #ifdef TRI_HAVE_SIGNAL_H if (_watchParent != 0) { #ifdef TRI_HAVE_POSIX int res = kill(_watchParent, 0); #else int res = -1; #endif if (res != 0) { LOG(INFO) << "parent " << _watchParent << " has died"; return false; } } #endif return true; } //////////////////////////////////////////////////////////////////////////////// /// @brief reads the configuration files //////////////////////////////////////////////////////////////////////////////// bool ApplicationServer::readConfigurationFile() { // something has been specified on the command line regarding configuration // file if (!_configFile.empty()) { // do not use init files if (StringUtils::tolower(_configFile) == std::string("none")) { LOG(INFO) << "using no init file at all"; return true; } LOG(DEBUG) << "using init file '" << _configFile << "'"; bool ok = _options.parse(_descriptionFile, _configFile); // Observe that this is treated as an error - the configuration file exists // but for some reason can not be parsed. Best to report an error. if (!ok) { LOG(ERR) << "cannot parse config file '" << _configFile << "': " << _options.lastError(); } return ok; } else { LOG(DEBUG) << "no init file has been specified"; } // nothing has been specified on the command line regarding the user's // configuration file if (!_userConfigFile.empty()) { // ......................................................................... // first attempt to obtain a default configuration file from the users home // directory // ......................................................................... // ......................................................................... // Under windows there is no 'HOME' directory as such so getenv("HOME") // may return nullptr -- which it does under windows // A safer approach below // ......................................................................... std::string homeDir = FileUtils::homeDirectory(); if (!homeDir.empty()) { if (homeDir[homeDir.size() - 1] != TRI_DIR_SEPARATOR_CHAR) { homeDir += TRI_DIR_SEPARATOR_CHAR + _userConfigFile; } else { homeDir += _userConfigFile; } // check and see if file exists if (FileUtils::exists(homeDir)) { LOG(DEBUG) << "using user init file '" << homeDir << "'"; bool ok = _options.parse(_descriptionFile, homeDir); // Observe that this is treated as an error - the configuration file // exists // but for some reason can not be parsed. Best to report an error. if (!ok) { LOG(ERR) << "cannot parse config file '" << homeDir << "': " << _options.lastError(); } return ok; } else { LOG(DEBUG) << "no user init file '" << homeDir << "' found"; } } else { LOG(DEBUG) << "no home directory found"; } } // try the configuration file in the system directory - if there is one if (!_systemConfigFile.empty()) { // Please note that the system directory changes depending on // where the user installed the application server. char* d = TRI_LocateConfigDirectory(); if (d != 0) { std::string sysDir = std::string(d); if ((sysDir.length() > 0) && (sysDir[sysDir.length() - 1] != TRI_DIR_SEPARATOR_CHAR)) { sysDir += TRI_DIR_SEPARATOR_CHAR; } sysDir += _systemConfigFile; std::string localSysDir = sysDir + ".local"; TRI_FreeString(TRI_CORE_MEM_ZONE, d); // check and see if a local override file exists if (FileUtils::exists(localSysDir)) { LOG(DEBUG) << "using init override file '" << localSysDir << "'"; bool ok = _options.parse(_descriptionFile, localSysDir); // Observe that this is treated as an error - the configuration file // exists // but for some reason can not be parsed. Best to report an error. if (!ok) { LOG(ERR) << "cannot parse config file '" << localSysDir << "': " << _options.lastError(); return ok; } } else { LOG(DEBUG) << "no system init override file '" << localSysDir << "' found"; } // check and see if file exists if (FileUtils::exists(sysDir)) { LOG(DEBUG) << "using init file '" << sysDir << "'"; bool ok = _options.parse(_descriptionFile, sysDir); // Observe that this is treated as an error - the configuration file // exists // but for some reason can not be parsed. Best to report an error. if (!ok) { LOG(ERR) << "cannot parse config file '" << sysDir << "': " << _options.lastError(); } return ok; } else { LOG(INFO) << "no system init file '" << sysDir << "' found"; } } else { LOG(DEBUG) << "no system init file specified"; } } return true; }