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 () {
$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 \
|| return 1
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
# Add code here, if necessary, that waits for the process to be ready
# 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 \
|| return 1
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
}

View File

@ -1,6 +1,7 @@
debian/config/apt.conf etc/icinga2/conf.d/hosts/localhost
debian/tmp/etc/icinga2
debian/tmp/etc/logrotate.d
debian/tmp/etc/bash_completion.d
tools/syntax/* usr/share/icinga2-common/syntax
usr/bin/icinga2-build*
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/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(
FILES bash_completion.d/icinga2
DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/bash_completion.d
)
else()
install(
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: "
@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."
exit 1
else
@ -111,7 +111,7 @@ reload() {
checkconfig() {
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
cat $ICINGA2_STARTUP_LOG
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
EnvironmentFile=@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
ExecReload=/bin/kill -HUP $MAINPID

View File

@ -28,249 +28,20 @@
#include "base/convert.hpp"
#include "base/scriptvariable.hpp"
#include "base/context.hpp"
#include "base/clicommand.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;
#ifdef _WIN32
SERVICE_STATUS l_SvcStatus;
SERVICE_STATUS_HANDLE l_SvcStatusHandle;
#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 argc = Application::GetArgC();
@ -329,46 +100,39 @@ int Main(void)
Application::DeclareZonesDir(Application::GetSysconfDir() + "/icinga2/zones.d");
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()
("help", "show this help message")
("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")
("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")
("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")
# ifdef RLIMIT_STACK
("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 */
;
("no-stack-rlimit", "used internally, do not specify manually")
("autocomplete", "auto-complete arguments");
String cmdname;
CLICommand::Ptr command;
bool autocomplete;
po::variables_map vm;
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) {
std::ostringstream msgbuf;
msgbuf << "Error while parsing command-line options: " << ex.what();
Log(LogCritical, "icinga-app", msgbuf.str());
Log(LogCritical, "cli_daemon", msgbuf.str());
return EXIT_FAILURE;
}
po::notify(g_AppParams);
if (g_AppParams.count("define")) {
BOOST_FOREACH(const String& define, g_AppParams["define"].as<std::vector<std::string> >()) {
if (vm.count("define")) {
BOOST_FOREACH(const String& define, vm["define"].as<std::vector<std::string> >()) {
String key, value;
size_t pos = define.FindFirstOf('=');
if (pos != String::NPos) {
@ -386,209 +150,104 @@ int Main(void)
Application::DeclareObjectsPath(Application::GetLocalStateDir() + "/cache/icinga2/icinga2.debug");
Application::DeclarePidPath(Application::GetRunDir() + "/icinga2/icinga2.pid");
#ifndef _WIN32
if (g_AppParams.count("group")) {
String group = g_AppParams["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, "icinga-app", msgbuf.str());
return EXIT_FAILURE;
} else {
std::ostringstream msgbuf;
msgbuf << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
Log(LogCritical, "icinga-app", msgbuf.str());
return EXIT_FAILURE;
}
}
if (!g_AppParams.count("reload-internal") && setgroups(0, NULL) < 0) {
std::ostringstream msgbuf;
msgbuf << "setgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
Log(LogCritical, "icinga-app", 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, "icinga-app", msgbuf.str());
return EXIT_FAILURE;
}
}
if (g_AppParams.count("user")) {
String user = g_AppParams["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, "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;
}
}
// also activate the additional groups the configured user is member of
if (!g_AppParams.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, "icinga-app", 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, "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;
Application::DisplayInfoMessage(true);
return EXIT_SUCCESS;
}
}
if (g_AppParams.count("help")) {
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);
Application::MakeVariablesConstant();
Log(LogInformation, "icinga-app", "Icinga application loader (version: " + Application::GetVersion() + ")");
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> >()) {
if (!autocomplete && vm.count("include")) {
BOOST_FOREACH(const String& includePath, vm["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;
}
}
Logger::SetConsoleLogSeverity(logLevel);
if (!LoadConfigFiles(appType, Application::GetObjectsPath()))
return EXIT_FAILURE;
if (!autocomplete) {
if (vm.count("log-level")) {
String severity = vm["log-level"].as<std::string>();
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
LogSeverity logLevel = LogInformation;
try {
Daemonize();
logLevel = Logger::StringToSeverity(severity);
} catch (std::exception&) {
Log(LogCritical, "icinga-app", "Daemonize failed. Exiting.");
return EXIT_FAILURE;
/* use the default */
Log(LogWarning, "icinga", "Invalid log level set. Using default 'information'.");
}
Logger::SetConsoleLogSeverity(logLevel);
}
if (vm.count("library")) {
BOOST_FOREACH(const String& libraryName, vm["library"].as<std::vector<std::string> >()) {
(void)Utility::LoadExtensionLibrary(libraryName);
}
}
if (!command || vm.count("help") || vm.count("version")) {
String appName = Utility::BaseName(Application::GetArgV()[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 (!command || vm.count("help")) {
std::cout << std::endl << std::endl
<< "Usage:" << std::endl
<< " " << argv[0] << " ";
if (cmdname.IsEmpty())
std::cout << "<command>";
else
std::cout << cmdname;
std::cout << " [<arguments>]";
if (command) {
std::cout << std::endl << std::endl
<< command->GetDescription();
}
}
if (vm.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 (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;
}
}
// 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;
}
int rc = 1;
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();
if (autocomplete) {
CLICommand::ShowCommands(argc, argv, &desc, true);
rc = 0;
} else if (command)
rc = command->Run(vm);
#ifndef _DEBUG
Application::Exit(rc);

View File

@ -506,6 +506,7 @@ exit 0
%doc COPYING COPYING.Exceptions README.md NEWS AUTHORS ChangeLog tools/syntax
%attr(0750,%{icinga_user},%{icingacmd_group}) %dir %{_localstatedir}/log/%{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}/perfdata
%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.
add_subdirectory(base)
add_subdirectory(cli)
add_subdirectory(config)
add_subdirectory(icinga)
add_subdirectory(db_ido)

View File

@ -23,7 +23,7 @@ mkclass_target(streamlogger.ti streamlogger.thpp)
mkclass_target(sysloglogger.ti sysloglogger.thpp)
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
exception.cpp fifo.cpp filelogger.cpp filelogger.thpp logger.cpp logger.thpp
netstring.cpp networkstream.cpp object.cpp objectlock.cpp process.cpp

View File

@ -135,8 +135,10 @@ void Application::InitializeBase(void)
maxfds = 65536;
for (rlim_t i = 3; i < maxfds; i++) {
#ifdef _DEBUG
if (close(i) >= 0)
std::cerr << "Closed FD " << i << " which we inherited from our parent process." << std::endl;
#endif /* _DEBUG */
}
}
#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;
int rc = getaddrinfo(hostname.CStr(), NULL, &hints, &result);
if (rc < 0)
if (rc != 0)
result = NULL;
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 */