/****************************************************************************
|
| Copyright (c) 2006 Novell, Inc.
| All Rights Reserved.
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of version 2 of the GNU General Public License as
| published by the Free Software Foundation.
|
| 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, contact Novell, Inc.
|
| To contact Novell about this file by physical or electronic mail,
| you may find current contact information at www.novell.com
|
|***************************************************************************
/**
@author Jon Carey
*/
#include <iostream>
#include <cstring>
#include <openwbem/OW_CmdLineParser.hpp>
#include <openwbem/OW_Format.hpp>
#include <openwbem/OW_FileSystem.hpp>
#include <openwbem/OW_CerrLogger.hpp>
#include <openwbem/OW_Exception.hpp>
#include <openwbem/OW_FileSystem.hpp>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <fcntl.h>
#include <errno.h>
#include "omcclpdenv.h"
#include "omcclpdconfigopts.h"
#include "omcclpdpidfile.h"
#include "omcclpd.h"
#include "omcclpcommand.h"
#include "omcclpcmdalias.h"
using namespace OW_NAMESPACE;
OW_DECLARE_EXCEPTION(OMCClpdDaemon)
OW_DEFINE_EXCEPTION(OMCClpdDaemon)
void daemonize(OMCClpdEnvRef& env, const String& daemonName);
void initDaemonizePipe();
void sendDaemonizeStatus(int status);
namespace OMCCLP
{
/**
* This is the main command loop from the clp shell.
* rch - pointer to the CIMClient object that will be
* used from all CIM operations.
*/
int OMCCLPDaemon::runCommand(String cmd)
{
OMCCLPCommandRef rnclpCommand;
/*XXX will be enabled with job tracking exists
if (!createCLPJobInfo(m_cimClient))
{
cerr << "Could not create a CLP job instance" << CLPENDL;
cerr << "Running without CLP job data" << CLPENDL;
}
*/
/* Create a new OMCCLPCommand object that contains a pointer to the
CIMClient object, the command string and the main configuration
file name */
reparseCmd:
rnclpCommand = new OMCCLPCommand(this, cmd);
/* Parse the command and get the resulting command object. After
parsing is complete, this command object will contain all of
the information that we need to execute the command */
if (rnclpCommand->parse())
{
try
{
rnclpCommand->getCmdObjectRef()->getCmdStatusRef()->startExecutionTime();
rnclpCommand->getCmdObjectRef()->execute();
}
catch (const CIMException& ce)
{
cerr << "execute general error: " << ce << CLPENDL;
}
catch (Exception& e)
{
cerr << "execute general error: " << e << CLPENDL;
}
try
{
rnclpCommand->getCmdObjectRef()->display();
}
catch (const CIMException& ce)
{
rnclpCommand->display();
cerr << "display general error: " << ce << CLPENDL;
}
catch (Exception& e)
{
rnclpCommand->display();
cerr << "display general error: " << e << CLPENDL;
}
}
else
{
cmd = OMCCLPCmdAlias::getAlias(this, cmd.c_str());
if (cmd.empty())
rnclpCommand->display();
else
goto reparseCmd;
}
return 0;
}
}
#ifdef OMCCLPDAEMON
namespace
{
//////////////////////////////////////////////////////////////////////////////
UnnamedPipeRef sigPipe(0);
UnnamedPipeRef daemonize_upipe;
const int DAEMONIZE_PIPE_TIMEOUT = 25;
enum
{
DAEMONIZE_SUCCESS,
DAEMONIZE_FAIL
};
enum
{
SHUTDOWN,
REINIT
};
//
// Command Line Options
//
enum
{
HELP_OPT,
DEBUG_OPT,
CONFIG_FILE_OPT
};
CmdLineParser::Option g_options[] =
{
{ HELP_OPT, 'h', "help", CmdLineParser::E_NO_ARG, 0, "Show help about command line options"},
{ DEBUG_OPT, 'd', "debug", CmdLineParser::E_NO_ARG, 0, "Run in debug mode (Does not detach from the terminal" },
{ CONFIG_FILE_OPT, 'f', "config", CmdLineParser::E_REQUIRED_ARG, 0, "Alternate configuration file. Default is omcclpd.conf." },
{ 0, 0, 0, CmdLineParser::E_NO_ARG, 0, 0 }
};
//////////////////////////////////////////////////////////////////////////////
void
usage(std::ostream& ostrm)
{
ostrm << "Usage: echod [options]" << std::endl << std::endl;
ostrm << CmdLineParser::getUsage(g_options) << std::endl;
}
//////////////////////////////////////////////////////////////////////////////
void clpd_new_handler()
{
#if defined (CLP_DEBUG)
abort();
#endif
throw std::bad_alloc();
}
//////////////////////////////////////////////////////////////////////////////
void sigHandler(int sig)
{
if (sigPipe)
{
sigPipe->writeInt(sig);
}
}
//////////////////////////////////////////////////////////////////////////////
void
ignoreSignal(int sig)
{
signal(sig, SIG_IGN);
}
//////////////////////////////////////////////////////////////////////////////
void
handleSignal(int sig)
{
signal(sig, sigHandler);
}
//////////////////////////////////////////////////////////////////////////////
void
setupSigHandler(bool dbgFlg)
{
if (dbgFlg)
{
handleSignal(SIGINT);
}
else
{
ignoreSignal(SIGINT);
}
handleSignal(SIGTERM);
handleSignal(SIGHUP);
ignoreSignal(SIGTTIN);
ignoreSignal(SIGTTOU);
ignoreSignal(SIGTSTP);
ignoreSignal(SIGPOLL);
ignoreSignal(SIGIO);
ignoreSignal(SIGPIPE);
ignoreSignal(SIGIOT);
ignoreSignal(SIGCONT);
ignoreSignal(SIGURG);
ignoreSignal(SIGXCPU);
ignoreSignal(SIGXFSZ);
ignoreSignal(SIGVTALRM);
ignoreSignal(SIGPROF);
ignoreSignal(SIGPWR);
}
//////////////////////////////////////////////////////////////////////////////
void
processCommandLine(int argc, char** argv, OMCClpdEnvRef& env)
{
try
{
// Parser command line options
CmdLineParser parser(argc, argv, g_options,
CmdLineParser::E_NON_OPTION_ARGS_INVALID);
if (parser.isSet(HELP_OPT))
{
usage(std::cerr);
exit(0);
}
if (parser.isSet(DEBUG_OPT))
{
env->setDebug(true);
}
if (parser.isSet(CONFIG_FILE_OPT))
{
String confFile = parser.getOptionValue(CONFIG_FILE_OPT);
try
{
confFile = FileSystem::Path::realPath(confFile);
}
catch(...)
{
std::cerr << confFile << " is not a valid configuration file"
<< std::endl;
exit(1);
}
if (!FileSystem::canRead(confFile))
{
std::cerr << "Unable to access configuration file: " <<
confFile << std::endl;
exit(1);
}
env->setConfigItem(OMCClpdConfigOpts::CONFIG_FILE_opt,
parser.getOptionValue(CONFIG_FILE_OPT));
}
}
catch (const CmdLineParserException& e)
{
switch (e.getErrorCode())
{
case CmdLineParser::E_INVALID_OPTION:
std::cerr << "Invalid option: " << e.getMessage() << std::endl;
break;
case CmdLineParser::E_MISSING_ARGUMENT:
std::cerr << "Argument not specified for option: "
<< e.getMessage() << std::endl;
break;
}
usage(std::cerr);
exit(1);
}
}
} // End of unnamed namespace
//////////////////////////////////////////////////////////////////////////////
int
main(int argc, char** argv)
{
int cc;
OMCClpdEnvRef env = OMCClpdEnv::instance();
LoggerRef logger(new CerrLogger);
sigPipe = env->getSigPipe();
try
{
processCommandLine(argc, argv, env);
env->init();
logger = env->getLogger(OMCClpdEnv::COMPONENT_NAME);
OW_LOG_DEBUG(logger, "clpd Environment initialized");
bool debugMode = env->getConfigItem(OMCClpdConfigOpts::DEBUGFLAG_opt,
OMCCLPD_DEFAULT_DEBUGFLAG).equalsIgnoreCase("true");
try
{
daemonize(env, "clpd");
}
catch (OMCClpdDaemonException& e)
{
OW_LOG_FATAL_ERROR(logger, e.getMessage());
OW_LOG_FATAL_ERROR(logger, "clpd failed to initialize. Aborting...");
return 1;
}
env->startServices();
OW_LOG_INFO(logger, "clpd is now running");
// Do this after initialization to prevent an infinite loop.
std::unexpected_handler oldUnexpectedHandler = 0;
std::terminate_handler oldTerminateHandler = 0;
std::new_handler oldNewHandler = std::set_new_handler(clpd_new_handler);
int sig;
bool shuttingDown(false);
sendDaemonizeStatus(DAEMONIZE_SUCCESS);
while (!shuttingDown)
{
// runSelectEngine will only return once something has been put into
// the signal pipe or an error has happened
env->runSelectEngine();
if ((cc = sigPipe->readInt(&sig)) == -1)
{
continue;
}
switch (sig)
{
case SIGTERM:
case SIGINT:
OW_LOG_INFO(logger,
Format("clpd received shutdown notification."
" Initiating shutdown. signal: %1", sig));
shuttingDown = true;
#if !defined(CLP_DEBUG)
if (oldUnexpectedHandler)
{
std::set_unexpected(oldUnexpectedHandler);
}
if (oldTerminateHandler)
{
std::set_terminate(oldTerminateHandler);
}
#endif
env->shutdown();
break;
case SIGHUP:
// Re-init code goes here
default:
OW_LOG_INFO(logger, Format("Ignoring signal %1", sig));
break;
}
}
}
catch (Exception& e)
{
OW_LOG_FATAL_ERROR(logger, "* EXCEPTION CAUGHT IN clpd MAIN!");
OW_LOG_FATAL_ERROR(logger, Format("* %1", e));
sendDaemonizeStatus(DAEMONIZE_FAIL);
cc = 1;
}
catch (std::exception& e)
{
OW_LOG_FATAL_ERROR(logger, "* std::exception CAUGHT IN clpd MAIN!");
OW_LOG_FATAL_ERROR(logger, Format("* Message: %1", e.what()));
sendDaemonizeStatus(DAEMONIZE_FAIL);
cc = 1;
}
catch (...)
{
OW_LOG_FATAL_ERROR(logger, "* UNKNOWN EXCEPTION CAUGHT IN clpd MAIN!");
sendDaemonizeStatus(DAEMONIZE_FAIL);
cc = 1;
}
OW_LOG_DEBUG(logger, "Exiting main");
return 0;
}
//////////////////////////////////////////////////////////////////////////////
void initDaemonizePipe()
{
daemonize_upipe = UnnamedPipe::createUnnamedPipe();
daemonize_upipe->setTimeouts(DAEMONIZE_PIPE_TIMEOUT);
}
//////////////////////////////////////////////////////////////////////////////
void sendDaemonizeStatus(int status)
{
if (daemonize_upipe)
{
daemonize_upipe->writeInt(status);
}
}
//////////////////////////////////////////////////////////////////////////////
void
daemonize(OMCClpdEnvRef& env, const String& daemonName)
{
bool dbgFlg = env->getConfigItem(
OMCClpdConfigOpts::DEBUGFLAG_opt).equalsIgnoreCase("true");
LoggerRef logger = env->getLogger(OMCClpdEnv::COMPONENT_NAME);
if (geteuid() == 0 &&
!env->getConfigItem(OMCClpdConfigOpts::DROP_ROOT_PRIVILEGES_opt,
OMCCLPD_DEFAULT_DROP_ROOT_PRIVILEGES).equalsIgnoreCase("false"))
{
const char OMCCLPD_USER[] = "omcclpd";
struct passwd* clpdInfo = ::getpwnam(OMCCLPD_USER);
if (!clpdInfo)
{
OW_THROW_ERRNO_MSG(OMCClpdDaemonException, "daemonize(): getpwnam(\"owcimomd\")");
}
if (::setgid(clpdInfo->pw_gid) != 0)
{
OW_THROW_ERRNO_MSG(OMCClpdDaemonException, "daemonize(): setgid");
}
if (::initgroups(clpdInfo->pw_name, clpdInfo->pw_gid) != 0)
{
OW_THROW_ERRNO_MSG(OMCClpdDaemonException, "daemonize(): initgroups");
}
if (::setuid(clpdInfo->pw_uid) != 0)
{
OW_THROW_ERRNO_MSG(OMCClpdDaemonException, "daemonize(): setuid");
}
}
int pid = -1;
String pidFile(env->getConfigItem(OMCClpdConfigOpts::PIDFILE_opt, OMCCLPD_DEFAULT_PIDFILE));
pid = OMCCLPDPidFile::checkPid(pidFile.c_str());
// Is there already another instance of the clpd running?
if (pid != -1)
{
OW_LOG_INFO(logger,
Format("Another instance of %1 is already running [%2]",
daemonName, pid));
OW_THROW(OMCClpdDaemonException,
Format("Another instance of %1 is already running [%2]",
daemonName, pid).c_str());
}
if (!dbgFlg)
{
OW_LOG_DEBUG(logger, "clp will daemonize now");
initDaemonizePipe();
pid = ::fork();
switch (pid)
{
case 0: // We're in the child
break;
case -1: // Fork failed
OW_LOG_INFO(logger,
"FAILED TO DETACH FROM THE TERMINAL - First fork");
OW_THROW_ERRNO_MSG(OMCClpdDaemonException,
"FAILED TO DETACH FROM THE TERMINAL - First fork");
default: // Parent process
int status = DAEMONIZE_FAIL;
// The child process should write the DAEMONIZE_SUCCESS value
// to the pipe once it is up and running. We will assume that
// the daemon has failed to come up of we time out on the
// following read or we don't read DAEMONIZE_SUCCESS.
if (daemonize_upipe->readInt(&status) < 1
|| status != DAEMONIZE_SUCCESS)
{
std::cerr << "Error starting clpd. Check the log files."
<< std::endl;
_exit(1); // Failure exit
}
_exit(0); // Success exit. Daemon is up!
}
// setsid() to become a process group and session group leader. Since
// a controlling terminal is associated with a session, and this new
// session has not yet acquired a controlling terminal our process
// now has no controlling terminal, which is a Good Thing for daemons.
if (::setsid() < 0) // shoudn't fail on linux
{
OW_LOG_INFO(logger,
"FAILED TO DETACH FROM THE TERMINAL - setsid failed");
OW_THROW(OMCClpdDaemonException,
"FAILED TO DETACH FROM THE TERMINAL - setsid failed");
}
// fork again so the parent, (the session group leader), can exit.
// This means that we, as a non-session group leader, can never
// regain a controlling terminal.
pid = ::fork();
switch (pid)
{
case 0: // We're in the child
break;
case -1: // Fork failed
{
// Save the error number, since the sendDaemonizeStatus function can cause it to change.
int saved_errno = errno;
sendDaemonizeStatus(DAEMONIZE_FAIL);
// Restore the real error number.
errno = saved_errno;
OW_LOG_INFO(logger,
"FAILED TO DETACH FROM THE TERMINAL - Second fork");
OW_THROW_ERRNO_MSG(OMCClpdDaemonException,
"FAILED TO DETACH FROM THE TERMINAL - Second fork");
::exit(1);
} // We're in the parent process
default:
::_exit(0);
}
chdir("/"); // Change to the root directory
close(0); // Close stdin
close(1); // Close stdout
close(2); // Close stderr
open("/dev/null", O_RDONLY); // stdin = /dev/null
open("/dev/null", O_WRONLY); // stdout = /dev/null
dup(1); // stderr = /dev/null
}
else
{
pid = ::getpid();
}
::umask(0077); // ensure all files we create are only accessible by us.
if (OMCCLPDPidFile::writePid(pidFile.c_str()) == -1)
{
OW_LOG_INFO(logger, Format("CWD == %1", FileSystem::Path::getCurrentWorkingDirectory()));
// Save the error number, since the sendDaemonizeStatus function can cause it to change.
int saved_errno = errno;
sendDaemonizeStatus(DAEMONIZE_FAIL);
// Restore the real error number.
errno = saved_errno;
OW_LOG_INFO(logger, Format("Failed to write the pid file (%1)", pidFile));
OW_THROW_ERRNO_MSG(OMCClpdDaemonException,
Format("Failed to write the pid file (%1)", pidFile).c_str());
}
OW_LOG_INFO(logger, Format("clpd running. pid: %1", ::getpid()));
setupSigHandler(dbgFlg);
}
#endif