Implement support for CLI commands

fixes #7246
This commit is contained in:
Gunnar Beutner 2014-10-06 14:21:18 +02:00
parent a68bfea737
commit a4081f1445
19 changed files with 1060 additions and 457 deletions

View File

@ -53,7 +53,7 @@ check_run () {
} }
check_config () { check_config () {
$DAEMON --validate -u "$DAEMON_USER" -g "$DAEMON_GROUP" -c "$DAEMON_CONFIG" $DAEMON run --validate -u "$DAEMON_USER" -g "$DAEMON_GROUP" -c "$DAEMON_CONFIG"
} }
# #
@ -69,7 +69,7 @@ do_start()
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|| return 1 || return 1
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
-c "$DAEMON_CONFIG" -u "$DAEMON_USER" -g "$DAEMON_GROUP" -d $DAEMON_ARGS \ run -c "$DAEMON_CONFIG" -u "$DAEMON_USER" -g "$DAEMON_GROUP" -d $DAEMON_ARGS \
|| return 2 || return 2
# Add code here, if necessary, that waits for the process to be ready # Add code here, if necessary, that waits for the process to be ready
# to handle requests from services started subsequently which depend # to handle requests from services started subsequently which depend
@ -84,7 +84,7 @@ do_foreground()
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test \ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test \
|| return 1 || return 1
start-stop-daemon --start --pidfile $PIDFILE --exec $DAEMON -- \ start-stop-daemon --start --pidfile $PIDFILE --exec $DAEMON -- \
-c "$DAEMON_CONFIG" -u "$DAEMON_USER" -g "$DAEMON_GROUP" $DAEMON_ARGS \ run -c "$DAEMON_CONFIG" -u "$DAEMON_USER" -g "$DAEMON_GROUP" $DAEMON_ARGS \
|| return 2 || return 2
} }

View File

@ -1,6 +1,7 @@
debian/config/apt.conf etc/icinga2/conf.d/hosts/localhost debian/config/apt.conf etc/icinga2/conf.d/hosts/localhost
debian/tmp/etc/icinga2 debian/tmp/etc/icinga2
debian/tmp/etc/logrotate.d debian/tmp/etc/logrotate.d
debian/tmp/etc/bash_completion.d
tools/syntax/* usr/share/icinga2-common/syntax tools/syntax/* usr/share/icinga2-common/syntax
usr/bin/icinga2-build* usr/bin/icinga2-build*
usr/bin/icinga2-sign-key usr/bin/icinga2-sign-key

View File

@ -68,6 +68,11 @@ if(NOT WIN32)
install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink ../features-available/checker.conf \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_SYSCONFDIR}/icinga2/features-enabled/checker.conf\")") install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink ../features-available/checker.conf \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_SYSCONFDIR}/icinga2/features-enabled/checker.conf\")")
install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink ../features-available/notification.conf \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_SYSCONFDIR}/icinga2/features-enabled/notification.conf\")") install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink ../features-available/notification.conf \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_SYSCONFDIR}/icinga2/features-enabled/notification.conf\")")
install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink ../features-available/mainlog.conf \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_SYSCONFDIR}/icinga2/features-enabled/mainlog.conf\")") install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink ../features-available/mainlog.conf \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_SYSCONFDIR}/icinga2/features-enabled/mainlog.conf\")")
install(
FILES bash_completion.d/icinga2
DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/bash_completion.d
)
else() else()
install( install(
FILES icinga2/features-enabled/checker.conf icinga2/features-enabled/notification.conf FILES icinga2/features-enabled/checker.conf icinga2/features-enabled/notification.conf

View File

@ -0,0 +1,11 @@
_icinga2()
{
local cur opts
opts="${COMP_WORDS[*]}"
cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=($(icinga2 --autocomplete ${COMP_WORDS[*]:1} < /dev/null))
return 0
}
complete -F _icinga2 icinga2

View File

@ -48,7 +48,7 @@ start() {
printf "Starting Icinga 2: " printf "Starting Icinga 2: "
@CMAKE_INSTALL_FULL_SBINDIR@/icinga2-prepare-dirs $SYSCONFIGFILE @CMAKE_INSTALL_FULL_SBINDIR@/icinga2-prepare-dirs $SYSCONFIGFILE
if ! $DAEMON -c $ICINGA2_CONFIG_FILE -d -e $ICINGA2_ERROR_LOG -u $ICINGA2_USER -g $ICINGA2_GROUP > $ICINGA2_STARTUP_LOG 2>&1; then if ! $DAEMON daemon -c $ICINGA2_CONFIG_FILE -d -e $ICINGA2_ERROR_LOG -u $ICINGA2_USER -g $ICINGA2_GROUP > $ICINGA2_STARTUP_LOG 2>&1; then
echo "Error starting Icinga. Check '$ICINGA2_STARTUP_LOG' for details." echo "Error starting Icinga. Check '$ICINGA2_STARTUP_LOG' for details."
exit 1 exit 1
else else
@ -111,7 +111,7 @@ reload() {
checkconfig() { checkconfig() {
printf "Checking configuration: " printf "Checking configuration: "
if ! $DAEMON -c $ICINGA2_CONFIG_FILE -C -u $ICINGA2_USER -g $ICINGA2_GROUP > $ICINGA2_STARTUP_LOG 2>&1; then if ! $DAEMON daemon -c $ICINGA2_CONFIG_FILE -C -u $ICINGA2_USER -g $ICINGA2_GROUP > $ICINGA2_STARTUP_LOG 2>&1; then
if [ "x$1" = "x" ]; then if [ "x$1" = "x" ]; then
cat $ICINGA2_STARTUP_LOG cat $ICINGA2_STARTUP_LOG
echo "Icinga 2 detected configuration errors. Check '$ICINGA2_STARTUP_LOG' for details." echo "Icinga 2 detected configuration errors. Check '$ICINGA2_STARTUP_LOG' for details."

View File

@ -6,7 +6,7 @@ After=syslog.target postgresql.service mariadb.service carbon-cache.service
Type=forking Type=forking
EnvironmentFile=@ICINGA2_SYSCONFIGFILE@ EnvironmentFile=@ICINGA2_SYSCONFIGFILE@
ExecStartPre=@CMAKE_INSTALL_FULL_SBINDIR@/icinga2-prepare-dirs @ICINGA2_SYSCONFIGFILE@ ExecStartPre=@CMAKE_INSTALL_FULL_SBINDIR@/icinga2-prepare-dirs @ICINGA2_SYSCONFIGFILE@
ExecStart=@CMAKE_INSTALL_FULL_SBINDIR@/icinga2 -c ${ICINGA2_CONFIG_FILE} -d -e ${ICINGA2_ERROR_LOG} -u ${ICINGA2_USER} -g ${ICINGA2_GROUP} ExecStart=@CMAKE_INSTALL_FULL_SBINDIR@/icinga2 daemon -c ${ICINGA2_CONFIG_FILE} -d -e ${ICINGA2_ERROR_LOG} -u ${ICINGA2_USER} -g ${ICINGA2_GROUP}
PIDFile=@ICINGA2_RUNDIR@/icinga2/icinga2.pid PIDFile=@ICINGA2_RUNDIR@/icinga2/icinga2.pid
ExecReload=/bin/kill -HUP $MAINPID ExecReload=/bin/kill -HUP $MAINPID

View File

@ -28,249 +28,20 @@
#include "base/convert.hpp" #include "base/convert.hpp"
#include "base/scriptvariable.hpp" #include "base/scriptvariable.hpp"
#include "base/context.hpp" #include "base/context.hpp"
#include "base/clicommand.hpp"
#include "config.h" #include "config.h"
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
#include <boost/tuple/tuple.hpp> #include <boost/tuple/tuple.hpp>
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <iostream>
#ifndef _WIN32
# include <sys/types.h>
# include <pwd.h>
# include <grp.h>
#endif /* _WIN32 */
using namespace icinga; using namespace icinga;
namespace po = boost::program_options; namespace po = boost::program_options;
static po::variables_map g_AppParams;
#ifdef _WIN32 #ifdef _WIN32
SERVICE_STATUS l_SvcStatus; SERVICE_STATUS l_SvcStatus;
SERVICE_STATUS_HANDLE l_SvcStatusHandle; SERVICE_STATUS_HANDLE l_SvcStatusHandle;
#endif /* _WIN32 */ #endif /* _WIN32 */
static String LoadAppType(const String& typeSpec)
{
Log(LogInformation, "icinga-app", "Loading application type: " + typeSpec);
String::SizeType index = typeSpec.FindFirstOf('/');
if (index == String::NPos)
return typeSpec;
String library = typeSpec.SubStr(0, index);
(void) Utility::LoadExtensionLibrary(library);
return typeSpec.SubStr(index + 1);
}
static void IncludeZoneDirRecursive(const String& path)
{
String zoneName = Utility::BaseName(path);
Utility::GlobRecursive(path, "*.conf", boost::bind(&ConfigCompiler::CompileFile, _1, zoneName), GlobFile);
}
static void IncludeNonLocalZone(const String& zonePath)
{
String etcPath = Application::GetZonesDir() + "/" + Utility::BaseName(zonePath);
if (Utility::PathExists(etcPath))
return;
IncludeZoneDirRecursive(zonePath);
}
static bool LoadConfigFiles(const String& appType, const String& objectsFile = String())
{
ConfigCompilerContext::GetInstance()->Reset();
if (g_AppParams.count("config") > 0) {
BOOST_FOREACH(const String& configPath, g_AppParams["config"].as<std::vector<std::string> >()) {
ConfigCompiler::CompileFile(configPath);
}
} else if (!g_AppParams.count("no-config"))
ConfigCompiler::CompileFile(Application::GetSysconfDir() + "/icinga2/icinga2.conf");
/* Load cluster config files - this should probably be in libremote but
* unfortunately moving it there is somewhat non-trivial. */
String zonesEtcDir = Application::GetZonesDir();
if (!zonesEtcDir.IsEmpty() && Utility::PathExists(zonesEtcDir))
Utility::Glob(zonesEtcDir + "/*", &IncludeZoneDirRecursive, GlobDirectory);
String zonesVarDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones";
if (Utility::PathExists(zonesVarDir))
Utility::Glob(zonesVarDir + "/*", &IncludeNonLocalZone, GlobDirectory);
String name, fragment;
BOOST_FOREACH(boost::tie(name, fragment), ConfigFragmentRegistry::GetInstance()->GetItems()) {
ConfigCompiler::CompileText(name, fragment);
}
ConfigItemBuilder::Ptr builder = make_shared<ConfigItemBuilder>();
builder->SetType(appType);
builder->SetName("application");
ConfigItem::Ptr item = builder->Compile();
item->Register();
bool result = ConfigItem::ValidateItems(objectsFile);
int warnings = 0, errors = 0;
BOOST_FOREACH(const ConfigCompilerMessage& message, ConfigCompilerContext::GetInstance()->GetMessages()) {
std::ostringstream locbuf;
ShowCodeFragment(locbuf, message.Location, true);
String location = locbuf.str();
String logmsg;
if (!location.IsEmpty())
logmsg = "Location:\n" + location;
logmsg += String("\nConfig ") + (message.Error ? "error" : "warning") + ": " + message.Text;
if (message.Error) {
Log(LogCritical, "config", logmsg);
errors++;
} else {
Log(LogWarning, "config", logmsg);
warnings++;
}
}
if (warnings > 0 || errors > 0) {
LogSeverity severity;
if (errors == 0)
severity = LogWarning;
else
severity = LogCritical;
Log(severity, "config", Convert::ToString(errors) + " errors, " + Convert::ToString(warnings) + " warnings.");
}
if (!result)
return false;
return true;
}
#ifndef _WIN32
static void SigHupHandler(int)
{
Application::RequestRestart();
}
#endif /* _WIN32 */
static bool Daemonize(void)
{
#ifndef _WIN32
pid_t pid = fork();
if (pid == -1) {
return false;
}
if (pid) {
// systemd requires that the pidfile of the daemon is written before the forking
// process terminates. So wait till either the forked daemon has written a pidfile or died.
int status;
int ret;
pid_t readpid;
do {
Utility::Sleep(0.1);
readpid = Application::ReadPidFile(Application::GetPidPath());
ret = waitpid(pid, &status, WNOHANG);
} while (readpid != pid && ret == 0);
if (ret == pid) {
Log(LogCritical, "icinga-app", "The daemon could not be started. See log output for details.");
exit(EXIT_FAILURE);
} else if (ret == -1) {
std::ostringstream msgbuf;
msgbuf << "waitpid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
Log(LogCritical, "icinga-app", msgbuf.str());
exit(EXIT_FAILURE);
}
exit(0);
}
#endif /* _WIN32 */
return true;
}
static bool SetDaemonIO(const String& stderrFile)
{
#ifndef _WIN32
int fdnull = open("/dev/null", O_RDWR);
if (fdnull >= 0) {
if (fdnull != 0)
dup2(fdnull, 0);
if (fdnull != 1)
dup2(fdnull, 1);
if (fdnull > 1)
close(fdnull);
}
const char *errPath = "/dev/null";
if (!stderrFile.IsEmpty())
errPath = stderrFile.CStr();
int fderr = open(errPath, O_WRONLY | O_APPEND);
if (fderr < 0 && errno == ENOENT)
fderr = open(errPath, O_CREAT | O_WRONLY | O_APPEND, 0600);
if (fderr > 0) {
if (fderr != 2)
dup2(fderr, 2);
if (fderr > 2)
close(fderr);
}
pid_t sid = setsid();
if (sid == -1) {
return false;
}
#endif
return true;
}
/**
* Terminate another process and wait till it has ended
*
* @params target PID of the process to end
*/
static void TerminateAndWaitForEnd(pid_t target)
{
#ifndef _WIN32
// allow 30 seconds timeout
double timeout = Utility::GetTime() + 30;
int ret = kill(target, SIGTERM);
while (Utility::GetTime() < timeout && (ret == 0 || errno != ESRCH)) {
Utility::Sleep(0.1);
ret = kill(target, 0);
}
// timeout and the process still seems to live: kill it
if (ret == 0 || errno != ESRCH)
kill(target, SIGKILL);
#else
// TODO: implement this for Win32
#endif /* _WIN32 */
}
int Main(void) int Main(void)
{ {
int argc = Application::GetArgC(); int argc = Application::GetArgC();
@ -329,46 +100,39 @@ int Main(void)
Application::DeclareZonesDir(Application::GetSysconfDir() + "/icinga2/zones.d"); Application::DeclareZonesDir(Application::GetSysconfDir() + "/icinga2/zones.d");
Application::DeclareApplicationType("icinga/IcingaApplication"); Application::DeclareApplicationType("icinga/IcingaApplication");
po::options_description desc("Supported options"); LogSeverity logLevel = Logger::GetConsoleLogSeverity();
Logger::SetConsoleLogSeverity(LogWarning);
Utility::LoadExtensionLibrary("cli");
po::options_description desc("Global options");
desc.add_options() desc.add_options()
("help", "show this help message") ("help", "show this help message")
("version,V", "show version information") ("version,V", "show version information")
("define,D", po::value<std::vector<std::string> >(), "define a constant")
("library,l", po::value<std::vector<std::string> >(), "load a library") ("library,l", po::value<std::vector<std::string> >(), "load a library")
("include,I", po::value<std::vector<std::string> >(), "add include search directory") ("include,I", po::value<std::vector<std::string> >(), "add include search directory")
("define,D", po::value<std::vector<std::string> >(), "define a constant")
("config,c", po::value<std::vector<std::string> >(), "parse a configuration file")
("no-config,z", "start without a configuration file")
("validate,C", "exit after validating the configuration")
("log-level,x", po::value<std::string>(), "specify the log level for the console log") ("log-level,x", po::value<std::string>(), "specify the log level for the console log")
("errorlog,e", po::value<std::string>(), "log fatal errors to the specified log file (only works in combination with --daemonize)") ("no-stack-rlimit", "used internally, do not specify manually")
#ifndef _WIN32 ("autocomplete", "auto-complete arguments");
("reload-internal", po::value<int>(), "used internally to implement config reload: do not call manually, send SIGHUP instead")
("daemonize,d", "detach from the controlling terminal") String cmdname;
("user,u", po::value<std::string>(), "user to run Icinga as") CLICommand::Ptr command;
("group,g", po::value<std::string>(), "group to run Icinga as") bool autocomplete;
# ifdef RLIMIT_STACK po::variables_map vm;
("no-stack-rlimit", "don't attempt to set RLIMIT_STACK")
# endif /* RLIMIT_STACK */
#else /* _WIN32 */
("scm", "run as a Windows service (must be the first argument if specified)")
("scm-install", "installs Icinga 2 as a Windows service (must be the first argument if specified")
("scm-uninstall", "uninstalls the Icinga 2 Windows service (must be the first argument if specified")
#endif /* _WIN32 */
;
try { try {
po::store(po::parse_command_line(argc, argv, desc), g_AppParams); CLICommand::ParseCommand(argc, argv, desc, vm, cmdname, command, autocomplete);
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
std::ostringstream msgbuf; std::ostringstream msgbuf;
msgbuf << "Error while parsing command-line options: " << ex.what(); msgbuf << "Error while parsing command-line options: " << ex.what();
Log(LogCritical, "icinga-app", msgbuf.str()); Log(LogCritical, "cli_daemon", msgbuf.str());
return EXIT_FAILURE; return EXIT_FAILURE;
} }
po::notify(g_AppParams); if (vm.count("define")) {
BOOST_FOREACH(const String& define, vm["define"].as<std::vector<std::string> >()) {
if (g_AppParams.count("define")) {
BOOST_FOREACH(const String& define, g_AppParams["define"].as<std::vector<std::string> >()) {
String key, value; String key, value;
size_t pos = define.FindFirstOf('='); size_t pos = define.FindFirstOf('=');
if (pos != String::NPos) { if (pos != String::NPos) {
@ -381,214 +145,109 @@ int Main(void)
ScriptVariable::Set(key, value); ScriptVariable::Set(key, value);
} }
} }
Application::DeclareStatePath(Application::GetLocalStateDir() + "/lib/icinga2/icinga2.state"); Application::DeclareStatePath(Application::GetLocalStateDir() + "/lib/icinga2/icinga2.state");
Application::DeclareObjectsPath(Application::GetLocalStateDir() + "/cache/icinga2/icinga2.debug"); Application::DeclareObjectsPath(Application::GetLocalStateDir() + "/cache/icinga2/icinga2.debug");
Application::DeclarePidPath(Application::GetRunDir() + "/icinga2/icinga2.pid"); Application::DeclarePidPath(Application::GetRunDir() + "/icinga2/icinga2.pid");
#ifndef _WIN32 ConfigCompiler::AddIncludeSearchDir(Application::GetIncludeConfDir());
if (g_AppParams.count("group")) {
String group = g_AppParams["group"].as<std::string>();
errno = 0; if (!autocomplete && vm.count("include")) {
struct group *gr = getgrnam(group.CStr()); BOOST_FOREACH(const String& includePath, vm["include"].as<std::vector<std::string> >()) {
ConfigCompiler::AddIncludeSearchDir(includePath);
}
}
Logger::SetConsoleLogSeverity(logLevel);
if (!gr) { if (!autocomplete) {
if (errno == 0) { if (vm.count("log-level")) {
std::ostringstream msgbuf; String severity = vm["log-level"].as<std::string>();
msgbuf << "Invalid group specified: " + group;
Log(LogCritical, "icinga-app", msgbuf.str()); LogSeverity logLevel = LogInformation;
return EXIT_FAILURE; try {
} else { logLevel = Logger::StringToSeverity(severity);
std::ostringstream msgbuf; } catch (std::exception&) {
msgbuf << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; /* use the default */
Log(LogCritical, "icinga-app", msgbuf.str()); Log(LogWarning, "icinga", "Invalid log level set. Using default 'information'.");
return EXIT_FAILURE; }
Logger::SetConsoleLogSeverity(logLevel);
}
if (vm.count("library")) {
BOOST_FOREACH(const String& libraryName, vm["library"].as<std::vector<std::string> >()) {
(void)Utility::LoadExtensionLibrary(libraryName);
} }
} }
if (!g_AppParams.count("reload-internal") && setgroups(0, NULL) < 0) { if (!command || vm.count("help") || vm.count("version")) {
std::ostringstream msgbuf; String appName = Utility::BaseName(Application::GetArgV()[0]);
msgbuf << "setgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
Log(LogCritical, "icinga-app", msgbuf.str()); if (appName.GetLength() > 3 && appName.SubStr(0, 3) == "lt-")
return EXIT_FAILURE; appName = appName.SubStr(3, appName.GetLength() - 3);
}
std::cout << appName << " " << "- The Icinga 2 network monitoring daemon.";
if (setgid(gr->gr_gid) < 0) {
std::ostringstream msgbuf; if (!command || vm.count("help")) {
msgbuf << "setgid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; std::cout << std::endl << std::endl
Log(LogCritical, "icinga-app", msgbuf.str()); << "Usage:" << std::endl
return EXIT_FAILURE; << " " << argv[0] << " ";
}
} if (cmdname.IsEmpty())
std::cout << "<command>";
if (g_AppParams.count("user")) { else
String user = g_AppParams["user"].as<std::string>(); std::cout << cmdname;
errno = 0; std::cout << " [<arguments>]";
struct passwd *pw = getpwnam(user.CStr());
if (command) {
if (!pw) { std::cout << std::endl << std::endl
if (errno == 0) { << command->GetDescription();
std::ostringstream msgbuf; }
msgbuf << "Invalid user specified: " + user;
Log(LogCritical, "icinga-app", msgbuf.str());
return EXIT_FAILURE;
} else {
std::ostringstream msgbuf;
msgbuf << "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
Log(LogCritical, "icinga-app", msgbuf.str());
return EXIT_FAILURE;
} }
}
if (vm.count("version")) {
// also activate the additional groups the configured user is member of std::cout << " (Version: " << Application::GetVersion() << ")";
if (!g_AppParams.count("reload-internal") && initgroups(user.CStr(), pw->pw_gid) < 0) { std::cout << std::endl
std::ostringstream msgbuf; << "Copyright (c) 2012-2014 Icinga Development Team (http://www.icinga.org)" << std::endl
msgbuf << "initgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; << "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl2.html>" << std::endl
Log(LogCritical, "icinga-app", msgbuf.str()); << "This is free software: you are free to change and redistribute it." << std::endl
return EXIT_FAILURE; << "There is NO WARRANTY, to the extent permitted by law.";
} }
if (setuid(pw->pw_uid) < 0) {
std::ostringstream msgbuf;
msgbuf << "setuid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
Log(LogCritical, "icinga-app", msgbuf.str());
return EXIT_FAILURE;
}
}
#endif /* _WIN32 */
if (g_AppParams.count("log-level")) {
String severity = g_AppParams["log-level"].as<std::string>();
LogSeverity logLevel = LogInformation;
try {
logLevel = Logger::StringToSeverity(severity);
} catch (std::exception&) {
/* use the default */
Log(LogWarning, "icinga", "Invalid log level set. Using default 'information'.");
}
Logger::SetConsoleLogSeverity(logLevel);
}
if (g_AppParams.count("help") || g_AppParams.count("version")) {
String appName = Utility::BaseName(argv[0]);
if (appName.GetLength() > 3 && appName.SubStr(0, 3) == "lt-")
appName = appName.SubStr(3, appName.GetLength() - 3);
std::cout << appName << " " << "- The Icinga 2 network monitoring daemon.";
if (g_AppParams.count("version")) {
std::cout << " (Version: " << Application::GetVersion() << ")";
std::cout << std::endl
<< "Copyright (c) 2012-2014 Icinga Development Team (http://www.icinga.org)" << std::endl
<< "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl2.html>" << std::endl
<< "This is free software: you are free to change and redistribute it." << std::endl
<< "There is NO WARRANTY, to the extent permitted by law.";
}
std::cout << std::endl;
if (g_AppParams.count("version")) {
std::cout << std::endl; std::cout << std::endl;
Application::DisplayInfoMessage(true); if (vm.count("version")) {
std::cout << std::endl;
Application::DisplayInfoMessage(true);
return EXIT_SUCCESS;
}
}
if (!command || vm.count("help")) {
if (!command) {
std::cout << std::endl;
CLICommand::ShowCommands(argc, argv, NULL, false);
}
std::cout << std::endl
<< desc << std::endl
<< "Report bugs at <https://dev.icinga.org/>" << std::endl
<< "Icinga home page: <http://www.icinga.org/>" << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
} }
if (g_AppParams.count("help")) { int rc = 1;
std::cout << std::endl
<< desc << std::endl
<< "Report bugs at <https://dev.icinga.org/>" << std::endl
<< "Icinga home page: <http://www.icinga.org/>" << std::endl;
return EXIT_SUCCESS;
}
ScriptVariable::Set("UseVfork", true, false, true); if (autocomplete) {
CLICommand::ShowCommands(argc, argv, &desc, true);
Application::MakeVariablesConstant(); rc = 0;
} else if (command)
Log(LogInformation, "icinga-app", "Icinga application loader (version: " + Application::GetVersion() + ")"); rc = command->Run(vm);
String appType = LoadAppType(Application::GetApplicationType());
if (g_AppParams.count("library")) {
BOOST_FOREACH(const String& libraryName, g_AppParams["library"].as<std::vector<std::string> >()) {
(void)Utility::LoadExtensionLibrary(libraryName);
}
}
ConfigCompiler::AddIncludeSearchDir(Application::GetIncludeConfDir());
if (g_AppParams.count("include")) {
BOOST_FOREACH(const String& includePath, g_AppParams["include"].as<std::vector<std::string> >()) {
ConfigCompiler::AddIncludeSearchDir(includePath);
}
}
if (!g_AppParams.count("validate") && !g_AppParams.count("reload-internal")) {
pid_t runningpid = Application::ReadPidFile(Application::GetPidPath());
if (runningpid > 0) {
Log(LogCritical, "icinga-app", "Another instance of Icinga already running with PID " + Convert::ToString(runningpid));
return EXIT_FAILURE;
}
}
if (!LoadConfigFiles(appType, Application::GetObjectsPath()))
return EXIT_FAILURE;
if (g_AppParams.count("validate")) {
Log(LogInformation, "icinga-app", "Finished validating the configuration file(s).");
return EXIT_SUCCESS;
}
if(g_AppParams.count("reload-internal")) {
int parentpid = g_AppParams["reload-internal"].as<int>();
Log(LogInformation, "icinga-app", "Terminating previous instance of Icinga (PID " + Convert::ToString(parentpid) + ")");
TerminateAndWaitForEnd(parentpid);
Log(LogInformation, "icinga-app", "Previous instance has ended, taking over now.");
}
if (g_AppParams.count("daemonize")) {
if (!g_AppParams.count("reload-internal")) {
// no additional fork neccessary on reload
try {
Daemonize();
} catch (std::exception&) {
Log(LogCritical, "icinga-app", "Daemonize failed. Exiting.");
return EXIT_FAILURE;
}
}
}
// activate config only after daemonization: it starts threads and that is not compatible with fork()
if (!ConfigItem::ActivateItems()) {
Log(LogCritical, "icinga-app", "Error activating configuration.");
return EXIT_FAILURE;
}
if (g_AppParams.count("daemonize")) {
String errorLog;
if (g_AppParams.count("errorlog"))
errorLog = g_AppParams["errorlog"].as<std::string>();
SetDaemonIO(errorLog);
Logger::DisableConsoleLog();
}
#ifndef _WIN32
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = &SigHupHandler;
sigaction(SIGHUP, &sa, NULL);
#endif /* _WIN32 */
int rc = Application::GetInstance()->Run();
#ifndef _DEBUG #ifndef _DEBUG
Application::Exit(rc); Application::Exit(rc);

View File

@ -506,6 +506,7 @@ exit 0
%doc COPYING COPYING.Exceptions README.md NEWS AUTHORS ChangeLog tools/syntax %doc COPYING COPYING.Exceptions README.md NEWS AUTHORS ChangeLog tools/syntax
%attr(0750,%{icinga_user},%{icingacmd_group}) %dir %{_localstatedir}/log/%{name} %attr(0750,%{icinga_user},%{icingacmd_group}) %dir %{_localstatedir}/log/%{name}
%config(noreplace) %{_sysconfdir}/logrotate.d/%{name} %config(noreplace) %{_sysconfdir}/logrotate.d/%{name}
%{_sysconfdir}/bash_completion.d/%{name}
%attr(0750,%{icinga_user},%{icinga_group}) %dir %{_localstatedir}/spool/%{name} %attr(0750,%{icinga_user},%{icinga_group}) %dir %{_localstatedir}/spool/%{name}
%attr(0750,%{icinga_user},%{icinga_group}) %dir %{_localstatedir}/spool/%{name}/perfdata %attr(0750,%{icinga_user},%{icinga_group}) %dir %{_localstatedir}/spool/%{name}/perfdata
%attr(0750,%{icinga_user},%{icinga_group}) %dir %{_localstatedir}/spool/%{name}/tmp %attr(0750,%{icinga_user},%{icinga_group}) %dir %{_localstatedir}/spool/%{name}/tmp

View File

@ -16,6 +16,7 @@
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
add_subdirectory(base) add_subdirectory(base)
add_subdirectory(cli)
add_subdirectory(config) add_subdirectory(config)
add_subdirectory(icinga) add_subdirectory(icinga)
add_subdirectory(db_ido) add_subdirectory(db_ido)

View File

@ -23,7 +23,7 @@ mkclass_target(streamlogger.ti streamlogger.thpp)
mkclass_target(sysloglogger.ti sysloglogger.thpp) mkclass_target(sysloglogger.ti sysloglogger.thpp)
set(base_SOURCES set(base_SOURCES
application.cpp application.thpp array.cpp configerror.cpp context.cpp application.cpp application.thpp array.cpp clicommand.cpp configerror.cpp context.cpp
convert.cpp debuginfo.cpp dictionary.cpp dynamicobject.cpp dynamicobject.thpp dynamictype.cpp convert.cpp debuginfo.cpp dictionary.cpp dynamicobject.cpp dynamicobject.thpp dynamictype.cpp
exception.cpp fifo.cpp filelogger.cpp filelogger.thpp logger.cpp logger.thpp exception.cpp fifo.cpp filelogger.cpp filelogger.thpp logger.cpp logger.thpp
netstring.cpp networkstream.cpp object.cpp objectlock.cpp process.cpp netstring.cpp networkstream.cpp object.cpp objectlock.cpp process.cpp

View File

@ -135,8 +135,10 @@ void Application::InitializeBase(void)
maxfds = 65536; maxfds = 65536;
for (rlim_t i = 3; i < maxfds; i++) { for (rlim_t i = 3; i < maxfds; i++) {
#ifdef _DEBUG
if (close(i) >= 0) if (close(i) >= 0)
std::cerr << "Closed FD " << i << " which we inherited from our parent process." << std::endl; std::cerr << "Closed FD " << i << " which we inherited from our parent process." << std::endl;
#endif /* _DEBUG */
} }
} }
#endif /* _WIN32 */ #endif /* _WIN32 */

211
lib/base/clicommand.cpp Normal file
View File

@ -0,0 +1,211 @@
/******************************************************************************
* Icinga 2 *
* Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software Foundation *
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
#include "base/clicommand.hpp"
#include "base/logger_fwd.hpp"
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/foreach.hpp>
#include <boost/program_options.hpp>
#include <algorithm>
using namespace icinga;
namespace po = boost::program_options;
boost::mutex l_RegistryMutex;
std::map<std::vector<String>, CLICommand::Ptr> l_Registry;
CLICommand::Ptr CLICommand::GetByName(const std::vector<String>& name)
{
boost::mutex::scoped_lock lock(l_RegistryMutex);
std::map<std::vector<String>, CLICommand::Ptr>::const_iterator it = l_Registry.find(name);
if (it == l_Registry.end())
return CLICommand::Ptr();
return it->second;
}
void CLICommand::Register(const std::vector<String>& name, const CLICommand::Ptr& function)
{
boost::mutex::scoped_lock lock(l_RegistryMutex);
l_Registry[name] = function;
}
void CLICommand::Unregister(const std::vector<String>& name)
{
boost::mutex::scoped_lock lock(l_RegistryMutex);
l_Registry.erase(name);
}
RegisterCLICommandHelper::RegisterCLICommandHelper(const String& name, const CLICommand::Ptr& command)
{
std::vector<String> vname;
boost::algorithm::split(vname, name, boost::is_any_of("/"));
CLICommand::Register(vname, command);
}
bool CLICommand::ParseCommand(int argc, char **argv, po::options_description& desc, po::variables_map& vm,
String& cmdname, CLICommand::Ptr& command, bool& autocomplete)
{
boost::mutex::scoped_lock lock(l_RegistryMutex);
typedef std::map<std::vector<String>, CLICommand::Ptr>::value_type CLIKeyValue;
std::vector<String> best_match;
int arg_end = 1;
BOOST_FOREACH(const CLIKeyValue& kv, l_Registry) {
const std::vector<String>& vname = kv.first;
for (int i = 0, k = 1; i < vname.size() && k < argc; i++, k++) {
if (strcmp(argv[k], "--no-stack-rlimit") == 0 || strcmp(argv[k], "--autocomplete") == 0) {
if (strcmp(argv[k], "--autocomplete") == 0) {
autocomplete = true;
return false;
}
i--;
continue;
}
if (vname[i] != argv[k])
break;
if (i >= best_match.size())
best_match.push_back(vname[i]);
if (i == vname.size() - 1) {
cmdname = boost::algorithm::join(vname, " ");
command = kv.second;
arg_end = k;
goto found_command;
}
}
}
found_command:
lock.unlock();
po::options_description ldesc("Command options");
if (command)
command->InitParameters(ldesc);
desc.add(ldesc);
po::store(po::parse_command_line(argc - arg_end, argv + arg_end, desc), vm);
po::notify(vm);
return true;
}
void CLICommand::ShowCommands(int argc, char **argv, po::options_description *desc, bool autocomplete)
{
boost::mutex::scoped_lock lock(l_RegistryMutex);
typedef std::map<std::vector<String>, CLICommand::Ptr>::value_type CLIKeyValue;
std::vector<String> best_match;
int arg_begin = 0;
CLICommand::Ptr command;
BOOST_FOREACH(const CLIKeyValue& kv, l_Registry) {
const std::vector<String>& vname = kv.first;
arg_begin = 0;
for (int i = 0, k = 1; i < vname.size() && k < argc; i++, k++) {
if (strcmp(argv[k], "--no-stack-rlimit") == 0 || strcmp(argv[k], "--autocomplete") == 0) {
i--;
arg_begin++;
continue;
}
if (vname[i] != argv[k])
break;
if (i >= best_match.size()) {
best_match.push_back(vname[i]);
}
if (i == vname.size() - 1) {
command = kv.second;
break;
}
}
}
if (!autocomplete)
std::cout << "Supported commands: " << std::endl;
BOOST_FOREACH(const CLIKeyValue& kv, l_Registry) {
const std::vector<String>& vname = kv.first;
if (vname.size() < best_match.size())
continue;
bool match = true;
for (int i = 0; i < best_match.size(); i++) {
if (vname[i] != best_match[i]) {
match = false;
break;
}
}
if (!match)
continue;
if (autocomplete) {
if (best_match.size() < vname.size()) {
String cname = vname[best_match.size()];
String pname;
if (arg_begin + best_match.size() + 1 < argc)
pname = argv[arg_begin + best_match.size() + 1];
if (cname.Find(pname) == 0)
std::cout << vname[best_match.size()] << " ";
}
} else
std::cout << " * " << boost::algorithm::join(vname, " ") << " (" << kv.second->GetShortDescription() << ")" << std::endl;
}
if (command && autocomplete) {
po::options_description ldesc("Command options");
if (command)
command->InitParameters(ldesc);
desc->add(ldesc);
BOOST_FOREACH(const shared_ptr<po::option_description>& odesc, desc->options()) {
String cname = "--" + odesc->long_name();
String pname = argv[argc - 1];
if (cname.Find(pname) == 0)
std::cout << cname << " ";
}
}
return;
}

74
lib/base/clicommand.hpp Normal file
View File

@ -0,0 +1,74 @@
/******************************************************************************
* Icinga 2 *
* Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software Foundation *
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
#ifndef CLICOMMAND_H
#define CLICOMMAND_H
#include "base/i2-base.hpp"
#include "base/value.hpp"
#include "base/utility.hpp"
#include <vector>
#include <boost/program_options.hpp>
namespace icinga
{
/**
* A CLI command.
*
* @ingroup base
*/
class I2_BASE_API CLICommand : public Object
{
public:
DECLARE_PTR_TYPEDEFS(CLICommand);
virtual String GetDescription(void) const = 0;
virtual String GetShortDescription(void) const = 0;
virtual void InitParameters(boost::program_options::options_description& desc) const = 0;
virtual int Run(const boost::program_options::variables_map& vm) const = 0;
static CLICommand::Ptr GetByName(const std::vector<String>& name);
static void Register(const std::vector<String>& name, const CLICommand::Ptr& command);
static void Unregister(const std::vector<String>& name);
static bool ParseCommand(int argc, char **argv, boost::program_options::options_description& desc,
boost::program_options::variables_map& vm, String& cmdname, CLICommand::Ptr& command, bool& autocomplete);
static void ShowCommands(int argc, char **argv, boost::program_options::options_description *desc, bool autocomplete);
};
/**
* Helper class for registering CLICommand implementation classes.
*
* @ingroup base
*/
class I2_BASE_API RegisterCLICommandHelper
{
public:
RegisterCLICommandHelper(const String& name, const CLICommand::Ptr& command);
};
#define REGISTER_CLICOMMAND(name, klass) \
namespace { namespace UNIQUE_NAME(cli) { \
I2_EXPORT icinga::RegisterCLICommandHelper l_RegisterCLICommand(name, make_shared<klass>()); \
} }
}
#endif /* CLICOMMAND_H */

View File

@ -1001,7 +1001,7 @@ String Utility::GetFQDN(void)
addrinfo *result; addrinfo *result;
int rc = getaddrinfo(hostname.CStr(), NULL, &hints, &result); int rc = getaddrinfo(hostname.CStr(), NULL, &hints, &result);
if (rc < 0) if (rc != 0)
result = NULL; result = NULL;
String canonicalName; String canonicalName;

42
lib/cli/CMakeLists.txt Normal file
View File

@ -0,0 +1,42 @@
# Icinga 2
# Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
set(cli_SOURCES
cainitcommand.cpp daemoncommand.cpp
)
if(ICINGA2_UNITY_BUILD)
mkunity_target(cli cli_SOURCES)
endif()
add_library(cli SHARED ${cli_SOURCES})
target_link_libraries(cli ${Boost_LIBRARIES} base config)
set_target_properties (
cli PROPERTIES
INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/icinga2
FOLDER Lib
)
install(
TARGETS cli
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/icinga2
)

54
lib/cli/cainitcommand.cpp Normal file
View File

@ -0,0 +1,54 @@
/******************************************************************************
* Icinga 2 *
* Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software Foundation *
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
#include "cli/cainitcommand.hpp"
#include "base/logger_fwd.hpp"
#include "base/clicommand.hpp"
using namespace icinga;
REGISTER_CLICOMMAND("ca/init", CAInitCommand);
String CAInitCommand::GetDescription(void) const
{
return "Sets up a new Certificate Authority.";
}
String CAInitCommand::GetShortDescription(void) const
{
return "sets up a new CA";
}
void CAInitCommand::InitParameters(boost::program_options::options_description& desc) const
{
/* Command doesn't support any parameters. */
}
/**
* The entry point for the "ca init" CLI command.
*
* @returns An exit status.
*/
int CAInitCommand::Run(const boost::program_options::variables_map& vm) const
{
Log(LogNotice, "cli", "Test!");
Log(LogInformation, "cli", "Hello World!");
return 0;
}

48
lib/cli/cainitcommand.hpp Normal file
View File

@ -0,0 +1,48 @@
/******************************************************************************
* Icinga 2 *
* Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software Foundation *
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
#ifndef CAINITCOMMAND_H
#define CAINITCOMMAND_H
#include "base/qstring.hpp"
#include "base/clicommand.hpp"
namespace icinga
{
/**
* The "ca init" command.
*
* @ingroup cli
*/
class CAInitCommand : public CLICommand
{
public:
DECLARE_PTR_TYPEDEFS(CAInitCommand);
virtual String GetDescription(void) const;
virtual String GetShortDescription(void) const;
virtual void InitParameters(boost::program_options::options_description& desc) const;
virtual int Run(const boost::program_options::variables_map& vm) const;
};
}
#endif /* CAINITCOMMAND_H */

446
lib/cli/daemoncommand.cpp Normal file
View File

@ -0,0 +1,446 @@
/******************************************************************************
* Icinga 2 *
* Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software Foundation *
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
#include "cli/daemoncommand.hpp"
#include "config/configcompilercontext.hpp"
#include "config/configcompiler.hpp"
#include "config/configitembuilder.hpp"
#include "base/logger_fwd.hpp"
#include "base/clicommand.hpp"
#include "base/application.hpp"
#include "base/logger.hpp"
#include "base/timer.hpp"
#include "base/utility.hpp"
#include "base/exception.hpp"
#include "base/convert.hpp"
#include "base/scriptvariable.hpp"
#include "base/context.hpp"
#include "config.h"
#include <boost/program_options.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/foreach.hpp>
#include <iostream>
#ifndef _WIN32
# include <sys/types.h>
# include <pwd.h>
# include <grp.h>
#endif /* _WIN32 */
using namespace icinga;
namespace po = boost::program_options;
static po::variables_map g_AppParams;
REGISTER_CLICOMMAND("daemon", DaemonCommand);
static String LoadAppType(const String& typeSpec)
{
Log(LogInformation, "cli", "Loading application type: " + typeSpec);
String::SizeType index = typeSpec.FindFirstOf('/');
if (index == String::NPos)
return typeSpec;
String library = typeSpec.SubStr(0, index);
(void) Utility::LoadExtensionLibrary(library);
return typeSpec.SubStr(index + 1);
}
static void IncludeZoneDirRecursive(const String& path)
{
String zoneName = Utility::BaseName(path);
Utility::GlobRecursive(path, "*.conf", boost::bind(&ConfigCompiler::CompileFile, _1, zoneName), GlobFile);
}
static void IncludeNonLocalZone(const String& zonePath)
{
String etcPath = Application::GetZonesDir() + "/" + Utility::BaseName(zonePath);
if (Utility::PathExists(etcPath))
return;
IncludeZoneDirRecursive(zonePath);
}
static bool LoadConfigFiles(const boost::program_options::variables_map& vm, const String& appType, const String& objectsFile = String())
{
ConfigCompilerContext::GetInstance()->Reset();
if (vm.count("config") > 0) {
BOOST_FOREACH(const String& configPath, vm["config"].as<std::vector<std::string> >()) {
ConfigCompiler::CompileFile(configPath);
}
} else if (!vm.count("no-config"))
ConfigCompiler::CompileFile(Application::GetSysconfDir() + "/icinga2/icinga2.conf");
/* Load cluster config files - this should probably be in libremote but
* unfortunately moving it there is somewhat non-trivial. */
String zonesEtcDir = Application::GetZonesDir();
if (!zonesEtcDir.IsEmpty() && Utility::PathExists(zonesEtcDir))
Utility::Glob(zonesEtcDir + "/*", &IncludeZoneDirRecursive, GlobDirectory);
String zonesVarDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones";
if (Utility::PathExists(zonesVarDir))
Utility::Glob(zonesVarDir + "/*", &IncludeNonLocalZone, GlobDirectory);
String name, fragment;
BOOST_FOREACH(boost::tie(name, fragment), ConfigFragmentRegistry::GetInstance()->GetItems()) {
ConfigCompiler::CompileText(name, fragment);
}
ConfigItemBuilder::Ptr builder = make_shared<ConfigItemBuilder>();
builder->SetType(appType);
builder->SetName("application");
ConfigItem::Ptr item = builder->Compile();
item->Register();
bool result = ConfigItem::ValidateItems(objectsFile);
int warnings = 0, errors = 0;
BOOST_FOREACH(const ConfigCompilerMessage& message, ConfigCompilerContext::GetInstance()->GetMessages()) {
std::ostringstream locbuf;
ShowCodeFragment(locbuf, message.Location, true);
String location = locbuf.str();
String logmsg;
if (!location.IsEmpty())
logmsg = "Location:\n" + location;
logmsg += String("\nConfig ") + (message.Error ? "error" : "warning") + ": " + message.Text;
if (message.Error) {
Log(LogCritical, "config", logmsg);
errors++;
} else {
Log(LogWarning, "config", logmsg);
warnings++;
}
}
if (warnings > 0 || errors > 0) {
LogSeverity severity;
if (errors == 0)
severity = LogWarning;
else
severity = LogCritical;
Log(severity, "config", Convert::ToString(errors) + " errors, " + Convert::ToString(warnings) + " warnings.");
}
if (!result)
return false;
return true;
}
#ifndef _WIN32
static void SigHupHandler(int)
{
Application::RequestRestart();
}
#endif /* _WIN32 */
static bool Daemonize(void)
{
#ifndef _WIN32
pid_t pid = fork();
if (pid == -1) {
return false;
}
if (pid) {
// systemd requires that the pidfile of the daemon is written before the forking
// process terminates. So wait till either the forked daemon has written a pidfile or died.
int status;
int ret;
pid_t readpid;
do {
Utility::Sleep(0.1);
readpid = Application::ReadPidFile(Application::GetPidPath());
ret = waitpid(pid, &status, WNOHANG);
} while (readpid != pid && ret == 0);
if (ret == pid) {
Log(LogCritical, "cli", "The daemon could not be started. See log output for details.");
exit(EXIT_FAILURE);
} else if (ret == -1) {
std::ostringstream msgbuf;
msgbuf << "waitpid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
Log(LogCritical, "cli", msgbuf.str());
exit(EXIT_FAILURE);
}
exit(0);
}
#endif /* _WIN32 */
return true;
}
static bool SetDaemonIO(const String& stderrFile)
{
#ifndef _WIN32
int fdnull = open("/dev/null", O_RDWR);
if (fdnull >= 0) {
if (fdnull != 0)
dup2(fdnull, 0);
if (fdnull != 1)
dup2(fdnull, 1);
if (fdnull > 1)
close(fdnull);
}
const char *errPath = "/dev/null";
if (!stderrFile.IsEmpty())
errPath = stderrFile.CStr();
int fderr = open(errPath, O_WRONLY | O_APPEND);
if (fderr < 0 && errno == ENOENT)
fderr = open(errPath, O_CREAT | O_WRONLY | O_APPEND, 0600);
if (fderr > 0) {
if (fderr != 2)
dup2(fderr, 2);
if (fderr > 2)
close(fderr);
}
pid_t sid = setsid();
if (sid == -1) {
return false;
}
#endif
return true;
}
/**
* Terminate another process and wait till it has ended
*
* @params target PID of the process to end
*/
static void TerminateAndWaitForEnd(pid_t target)
{
#ifndef _WIN32
// allow 30 seconds timeout
double timeout = Utility::GetTime() + 30;
int ret = kill(target, SIGTERM);
while (Utility::GetTime() < timeout && (ret == 0 || errno != ESRCH)) {
Utility::Sleep(0.1);
ret = kill(target, 0);
}
// timeout and the process still seems to live: kill it
if (ret == 0 || errno != ESRCH)
kill(target, SIGKILL);
#else
// TODO: implement this for Win32
#endif /* _WIN32 */
}
String DaemonCommand::GetDescription(void) const
{
return "Starts Icinga 2.";
}
String DaemonCommand::GetShortDescription(void) const
{
return "starts Icinga 2";
}
void DaemonCommand::InitParameters(boost::program_options::options_description& desc) const
{
desc.add_options()
("config,c", po::value<std::vector<std::string> >(), "parse a configuration file")
("no-config,z", "start without a configuration file")
("validate,C", "exit after validating the configuration")
("errorlog,e", po::value<std::string>(), "log fatal errors to the specified log file (only works in combination with --daemonize)")
#ifndef _WIN32
("reload-internal", po::value<int>(), "used internally to implement config reload: do not call manually, send SIGHUP instead")
("daemonize,d", "detach from the controlling terminal")
("user,u", po::value<std::string>(), "user to run Icinga as")
("group,g", po::value<std::string>(), "group to run Icinga as")
#endif /* _WIN32 */
;
}
/**
* The entry point for the "daemon" CLI command.
*
* @returns An exit status.
*/
int DaemonCommand::Run(const po::variables_map& vm) const
{
#ifndef _WIN32
if (vm.count("group")) {
String group = vm["group"].as<std::string>();
errno = 0;
struct group *gr = getgrnam(group.CStr());
if (!gr) {
if (errno == 0) {
std::ostringstream msgbuf;
msgbuf << "Invalid group specified: " + group;
Log(LogCritical, "cli", msgbuf.str());
return EXIT_FAILURE;
} else {
std::ostringstream msgbuf;
msgbuf << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
Log(LogCritical, "cli", msgbuf.str());
return EXIT_FAILURE;
}
}
if (!vm.count("reload-internal") && setgroups(0, NULL) < 0) {
std::ostringstream msgbuf;
msgbuf << "setgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
Log(LogCritical, "cli", msgbuf.str());
return EXIT_FAILURE;
}
if (setgid(gr->gr_gid) < 0) {
std::ostringstream msgbuf;
msgbuf << "setgid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
Log(LogCritical, "cli", msgbuf.str());
return EXIT_FAILURE;
}
}
if (vm.count("user")) {
String user = vm["user"].as<std::string>();
errno = 0;
struct passwd *pw = getpwnam(user.CStr());
if (!pw) {
if (errno == 0) {
std::ostringstream msgbuf;
msgbuf << "Invalid user specified: " + user;
Log(LogCritical, "cli", msgbuf.str());
return EXIT_FAILURE;
} else {
std::ostringstream msgbuf;
msgbuf << "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
Log(LogCritical, "cli", msgbuf.str());
return EXIT_FAILURE;
}
}
// also activate the additional groups the configured user is member of
if (!vm.count("reload-internal") && initgroups(user.CStr(), pw->pw_gid) < 0) {
std::ostringstream msgbuf;
msgbuf << "initgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
Log(LogCritical, "cli", msgbuf.str());
return EXIT_FAILURE;
}
if (setuid(pw->pw_uid) < 0) {
std::ostringstream msgbuf;
msgbuf << "setuid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
Log(LogCritical, "cli", msgbuf.str());
return EXIT_FAILURE;
}
}
#endif /* _WIN32 */
ScriptVariable::Set("UseVfork", true, false, true);
Application::MakeVariablesConstant();
Log(LogInformation, "cli", "Icinga application loader (version: " + Application::GetVersion() + ")");
String appType = LoadAppType(Application::GetApplicationType());
if (!vm.count("validate") && !vm.count("reload-internal")) {
pid_t runningpid = Application::ReadPidFile(Application::GetPidPath());
if (runningpid > 0) {
Log(LogCritical, "cli", "Another instance of Icinga already running with PID " + Convert::ToString(runningpid));
return EXIT_FAILURE;
}
}
if (!LoadConfigFiles(vm, appType, Application::GetObjectsPath()))
return EXIT_FAILURE;
if (vm.count("validate")) {
Log(LogInformation, "cli", "Finished validating the configuration file(s).");
return EXIT_SUCCESS;
}
if(vm.count("reload-internal")) {
int parentpid = vm["reload-internal"].as<int>();
Log(LogInformation, "cli", "Terminating previous instance of Icinga (PID " + Convert::ToString(parentpid) + ")");
TerminateAndWaitForEnd(parentpid);
Log(LogInformation, "cli", "Previous instance has ended, taking over now.");
}
if (vm.count("daemonize")) {
if (!vm.count("reload-internal")) {
// no additional fork neccessary on reload
try {
Daemonize();
} catch (std::exception&) {
Log(LogCritical, "cli", "Daemonize failed. Exiting.");
return EXIT_FAILURE;
}
}
}
// activate config only after daemonization: it starts threads and that is not compatible with fork()
if (!ConfigItem::ActivateItems()) {
Log(LogCritical, "cli", "Error activating configuration.");
return EXIT_FAILURE;
}
if (vm.count("daemonize")) {
String errorLog;
if (vm.count("errorlog"))
errorLog = vm["errorlog"].as<std::string>();
SetDaemonIO(errorLog);
Logger::DisableConsoleLog();
}
#ifndef _WIN32
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = &SigHupHandler;
sigaction(SIGHUP, &sa, NULL);
#endif /* _WIN32 */
return Application::GetInstance()->Run();
}

48
lib/cli/daemoncommand.hpp Normal file
View File

@ -0,0 +1,48 @@
/******************************************************************************
* Icinga 2 *
* Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software Foundation *
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
#ifndef DAEMONCOMMAND_H
#define DAEMONCOMMAND_H
#include "base/clicommand.hpp"
#include "base/qstring.hpp"
#include <vector>
namespace icinga
{
/**
* The "daemon" CLI command.
*
* @ingroup cli
*/
class DaemonCommand : public CLICommand
{
public:
DECLARE_PTR_TYPEDEFS(DaemonCommand);
virtual String GetDescription(void) const;
virtual String GetShortDescription(void) const;
virtual void InitParameters(boost::program_options::options_description& desc) const;
virtual int Run(const boost::program_options::variables_map& vm) const;
};
}
#endif /* DAEMONCOMMAND_H */