/****************************************************************************
|
| 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 Brad Nicholes
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "omcclpc.h"
#include "omcclpprogram.h"
#include "omcclpcommon.h"
#include "omcclptargetpath.h"
#include "omcclpdconfigopts.h"
#include "omcclpdsyslogappender.h"
#include <readline/readline.h>
#include <readline/history.h>
#include <openwbem/OW_Assertion.hpp>
#include <openwbem/OW_CmdLineParser.hpp>
#include <openwbem/OW_SocketAddress.hpp>
#include <openwbem/OW_Select.hpp>
#include <openwbem/OW_AppenderLogger.hpp>
#include <openwbem/OW_Format.hpp>
using namespace OMCClpdConfigOpts;
#ifdef OMCCLPCLIENT
void init_rl ();
enum tag_OPTIONS
{
E_CMDOPT_HELP,
E_CMDOPT_URL, // specify URL
E_CMDOPT_NS, // specify namespace
E_CMDOPT_CONFIG, // specify configuration file
E_CMDOPT_CMD, // specify a single command
};
CmdLineParser::Option g_options[] =
{
{E_CMDOPT_CMD, 'c', "command", CmdLineParser::E_REQUIRED_ARG, 0, "Execute a single command from the command line."},
{E_CMDOPT_CONFIG, 'f', "config", CmdLineParser::E_REQUIRED_ARG, 0, "Alternate configuration file. Default is omcclpc.conf."},
{E_CMDOPT_HELP, 'h', "help", CmdLineParser::E_NO_ARG, 0, "Show help about options."},
{E_CMDOPT_NS, 'n', "ns", CmdLineParser::E_REQUIRED_ARG, 0, "Alternate CIM namespace to be used in place of the namespace specified in the configuration file"},
{E_CMDOPT_URL, 'u', "url", CmdLineParser::E_REQUIRED_ARG, 0, "Alternate CIM URL to be used in place of the URL specified in the configuration file."},
{0, 0, 0, CmdLineParser::E_NO_ARG, 0, 0}
};
OMCCLPProgram *gclp = NULL; // global pointer to the main program object
static void usage()
{
cerr << PROG_NAME << " " << PROG_VERSION
<< " Server Management Command Line Protocol" << endl;
cerr << "Copyright (C) 2006 by Novell Inc." << endl;
cerr << "Usage: " << PROG_NAME << " [OPTIONS]" << endl;
cout << endl << CmdLineParser::getUsage(g_options) << endl;
}
static void printCmdLineParserExceptionMessage(CmdLineParserException& e)
{
switch (e.getErrorCode())
{
case CmdLineParser::E_INVALID_OPTION:
cerr << "unknown option: " << e.getMessage() << endl;
break;
case CmdLineParser::E_MISSING_ARGUMENT:
cerr << "missing argument for option: " << e.getMessage() << endl;
break;
case CmdLineParser::E_INVALID_NON_OPTION_ARG:
cerr << "invalid non-option argument: " << e.getMessage() << endl;
break;
case CmdLineParser::E_MISSING_OPTION:
cerr << "missing required option: " << e.getMessage() << endl;
break;
default:
cerr << "failed parsing command line options: " << e << endl;
break;
}
}
void sigtermHandler(int status)
{
((OMCCLPClient*)gclp)->writePipe (status);
}
void registerSignalHandler ()
{
struct sigaction sa;
sa.sa_handler = sigtermHandler;
sa.sa_flags = 0;
sa.sa_restorer = NULL;
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
}
#endif
namespace OMCCLP
{
String OMCCLPClient::COMPONENT_NAME("omcclp.clpc");
bool OMCCLPClient::socketConnect()
{
/* If the socket is already connected,
disconnect the socket first. */
if (m_socket.isConnected())
m_socket.disconnect();
/* Get the listen port from the configuration file. */
String item = getConfigItem(OMCClpdConfigOpts::SERVER_PORT_opt,
OMCCLPD_DEFAULT_SERVER_PORT);
Int32 port = item.toInt32();
OW_LOG_ERROR(m_logger, Format("Connecting socket on port (%1) "
"in OMCCLPClient::socketConnect", port));
try {
m_socket.connect(SocketAddress::getAnyLocalHost(port));
} catch (...) {
OW_LOG_ERROR(m_logger, "OMCCLPClient::socketConnect: "
"Unable to connect to omcclpd. "
"The server may not to be running");
}
return m_socket.isConnected();
}
void OMCCLPClient::createLogger()
{
AppenderLogger* pLogger = 0;
StringArray additionalLogs = OMCClpdConfig::getMultiConfigItem(
OMCClpdConfigOpts::ADDITIONAL_CLIENT_LOGS_opt);
String logName, logClientType, logClientComponents;
String logClientCategories, logClientLevel, logClientFormat;
// this also gets set if clpd is run with -d
bool debugFlag = getConfigItem(OMCClpdConfigOpts::DEBUGFLAG_opt,
OMCCLPD_DEFAULT_DEBUGFLAG).equalsIgnoreCase("true");
if (debugFlag)
{
// stick it at the beginning as a possible slight logging performance optimization
additionalLogs.insert(additionalLogs.begin(), LOG_DEBUG_LOG_NAME);
}
for (size_t i = 0; i < additionalLogs.size(); ++i)
{
logName = additionalLogs[i];
logClientType = getConfigItem(Format(OMCClpdConfigOpts::LOG_1_TYPE_opt, logName),
OMCCLPD_DEFAULT_LOG_1_TYPE);
logClientComponents = getConfigItem(Format(OMCClpdConfigOpts::LOG_1_COMPONENTS_opt, logName),
OMCCLPD_DEFAULT_LOG_1_COMPONENTS);
logClientCategories = getConfigItem(Format(OMCClpdConfigOpts::LOG_1_CATEGORIES_opt, logName));
if (logClientCategories.empty())
{
// convert level into categories
logClientLevel = getConfigItem(Format(OMCClpdConfigOpts::LOG_1_LEVEL_opt, logName),
OMCCLPD_DEFAULT_LOG_1_LEVEL);
if (logClientLevel.equalsIgnoreCase(Logger::STR_DEBUG_CATEGORY))
{
logClientCategories = Logger::STR_DEBUG_CATEGORY + " " +
Logger::STR_INFO_CATEGORY + " " +
Logger::STR_ERROR_CATEGORY + " " +
Logger::STR_FATAL_CATEGORY;
}
else if (logClientLevel.equalsIgnoreCase(Logger::STR_INFO_CATEGORY))
{
logClientCategories = Logger::STR_INFO_CATEGORY + " " +
Logger::STR_ERROR_CATEGORY + " " +
Logger::STR_FATAL_CATEGORY;
}
else if (logClientLevel.equalsIgnoreCase(Logger::STR_ERROR_CATEGORY))
{
logClientCategories = Logger::STR_ERROR_CATEGORY + " " + Logger::STR_FATAL_CATEGORY;
}
else if (logClientLevel.equalsIgnoreCase(Logger::STR_FATAL_CATEGORY))
{
logClientCategories = Logger::STR_FATAL_CATEGORY;
}
}
logClientFormat = getConfigItem(Format(OMCClpdConfigOpts::LOG_1_FORMAT_opt, logName),
OMCCLPD_DEFAULT_LOG_1_FORMAT);
if (logClientType.equalsIgnoreCase("syslog"))
{
pLogger = new AppenderLogger(COMPONENT_NAME, E_ALL_LEVEL,
LogAppenderRef(new OMCClpdSyslogAppender(logClientComponents.tokenize(),
logClientCategories.tokenize(), logClientFormat)));
}
else
{
pLogger = new AppenderLogger(COMPONENT_NAME, E_ALL_LEVEL,
LogAppender::createLogAppender(logName, logClientComponents.tokenize(),
logClientCategories.tokenize(), logClientFormat, logClientType,
OMCClpdConfig::getLogAppenderConfig()));
}
}
// This one will eventually be handled the same as all other logs by just sticking
// "main" in the additionalLogs array but we need to handle deprecated options for
// now, so it needs special treatment.
logName = LOG_CLIENT_LOG_NAME;
logClientType = getConfigItem(Format(OMCClpdConfigOpts::LOG_1_TYPE_opt, logName));
logClientComponents = getConfigItem(Format(OMCClpdConfigOpts::LOG_1_COMPONENTS_opt, logName),
OMCCLPD_DEFAULT_LOG_1_COMPONENTS);
logClientCategories = getConfigItem(Format(OMCClpdConfigOpts::LOG_1_CATEGORIES_opt, logName));
logClientLevel = getConfigItem(Format(OMCClpdConfigOpts::LOG_1_LEVEL_opt, logName));
logClientFormat = getConfigItem(Format(OMCClpdConfigOpts::LOG_1_FORMAT_opt, logName),
OMCCLPD_DEFAULT_LOG_1_FORMAT);
// If logClientType not specified, use syslog
if (logClientType.empty())
{
logClientType = "syslog";
}
// If no logClientCategoreis and the main level not specifed
if (logClientCategories.empty() && logClientLevel.empty())
{
logClientLevel = OMCCLPD_DEFAULT_LOG_1_LEVEL;
}
// convert level into categories
if (logClientCategories.empty())
{
// convert level into categories
logClientLevel = getConfigItem(Format(OMCClpdConfigOpts::LOG_1_LEVEL_opt, logName),
OMCCLPD_DEFAULT_LOG_1_LEVEL);
if (logClientLevel.equalsIgnoreCase(Logger::STR_DEBUG_CATEGORY))
{
logClientCategories = Logger::STR_DEBUG_CATEGORY + " " +
Logger::STR_INFO_CATEGORY + " " + Logger::STR_ERROR_CATEGORY +
" " + Logger::STR_FATAL_CATEGORY;
}
else if (logClientLevel.equalsIgnoreCase(Logger::STR_INFO_CATEGORY))
{
logClientCategories = Logger::STR_INFO_CATEGORY + " " +
Logger::STR_ERROR_CATEGORY + " " + Logger::STR_FATAL_CATEGORY;
}
else if (logClientLevel.equalsIgnoreCase(Logger::STR_ERROR_CATEGORY))
{
logClientCategories = Logger::STR_ERROR_CATEGORY + " " + Logger::STR_FATAL_CATEGORY;
}
else if (logClientLevel.equalsIgnoreCase(Logger::STR_FATAL_CATEGORY))
{
logClientCategories = Logger::STR_FATAL_CATEGORY;
}
}
LogAppenderRef appender;
if (logClientType.equalsIgnoreCase("syslog"))
{
appender = LogAppenderRef(new OMCClpdSyslogAppender(logClientComponents.tokenize(),
logClientCategories.tokenize(), logClientFormat));
}
else
{
appender = LogAppender::createLogAppender(logName,
logClientComponents.tokenize(), logClientCategories.tokenize(),
logClientFormat, logClientType, OMCClpdConfig::getLogAppenderConfig());
}
if (pLogger)
{
pLogger->addLogAppender(appender);
}
else
{
pLogger = new AppenderLogger(COMPONENT_NAME, E_ALL_LEVEL, appender);
}
m_logger = LoggerRef(pLogger);
m_logger->setDefaultComponent(COMPONENT_NAME);
}
String OMCCLPClient::getServerPrompt()
{
char promptBuf[256];
String prompt;
int count = 0;
/* Wait for the prompt from the server */
while (!prompt.endsWith("-->") && (count < 10))
{
if (m_socket.waitForInput(1))
{
/* The select timed out waiting for a prompt.
Poke the server to try to get a prompt back. */
m_socket.write("\r\n", 2);;
count++;
continue;
}
memset(promptBuf, 0, sizeof(promptBuf));
m_socket.read(promptBuf, sizeof(promptBuf));
prompt = String(promptBuf);
}
return prompt;
}
bool OMCCLPClient::initializeCLPSession()
{
OMCCLPProgram::initializeCLPSession();
createLogger();
if (!socketConnect()) {
cout << "Unable to connect to the CLP service" << endl;
return false;
}
return true;
}
bool OMCCLPClient::createCLPSessionInfo(CIMClient *rch)
{
try
{
char lineBuf[1024];
bool done = false;
if (isValidPrompt(getServerPrompt()))
{
String UFiP(0);
/* We need to get the session UFiP from the CLP server.
Send the command to get the session UFiP */
String cmd = "show -o format=keyword session";
m_socket.write(cmd.c_str(), cmd.length());
m_socket.write("\r\n", 2);
/* Get the timout value from the configuration file. */
String item = getConfigItem(OMCClpdConfigOpts::SERVER_TIMEOUT_opt,
OMCCLPD_DEFAULT_SERVER_TIMEOUT);
Int32 timeout = item.toInt32();
do {
/* Wait for a response from the CLP server. If the
timeout period is exceeded then just bail out
with no connection. */
if (m_socket.waitForInput(timeout))
{
break;
}
/* Read the data from the socket and
parse it on carriage returns. */
memset(lineBuf, 0, sizeof(lineBuf));
m_socket.read(lineBuf, sizeof(lineBuf));
StringArray lines = String(lineBuf).tokenize("=\r\n");
/* Output each line to the screen while looking for
the server prompt. Once the server prompt
is found, assume that there is no more data. */
for (int i = 0; i < lines.size(); i++)
{
if (lines[i].equalsIgnoreCase("ufip"))
{
UFiP = lines[i+1];
}
/* Read the output until we hit "endoutput" */
if (lines[i].equalsIgnoreCase("endoutput"))
{
done = true;
break;
}
}
} while (!done);
if (UFiP.length())
{
setSession(getCIMObjectPathFromUFiP (this, UFiP));
return true;
}
}
}
catch (...)
{
}
return false;
}
void OMCCLPClient::doExit()
{
/* do all of the exit stuff plus
write to the sigpipe to interrupt
any pending operation. */
OMCCLPProgram::doExit();
m_sigPipe->writeInt(1);
}
void OMCCLPClient::shutdown()
{
if(!m_running)
{
return;
}
m_shuttingDown = true;
doExit();
// Wait for the client to shutdown
m_threadBarrier.wait();
}
int OMCCLPClient::run(String cmdScript)
{
char *cmd = NULL;
FILE *fscript = NULL;
bool runOnce = false;
String histFile = getenv ("HOME");
String prompt;
char lineBuf[1024];
/* Get the timout value from the configuration file. */
String item = getConfigItem(OMCClpdConfigOpts::SERVER_TIMEOUT_opt,
OMCCLPD_DEFAULT_SERVER_TIMEOUT);
Int32 timeout = item.toInt32();
/* Load the select array with the sigpipe and the socket.
The sigpipe will allow us to interupt a pending
operation. */
SelectTypeArray selArray;
selArray.push_back(m_sigPipe->getSelectObj());
selArray.push_back(m_socket.getSelectObj());
if (!cmdScript.empty())
{
fscript = fopen (cmdScript.c_str(), "r");
if (fscript)
{
rl_instream = fscript;
}
else
{
cerr << "Could not open the script file " << cmdScript << endl;
doExit();
}
}
/* Read in the command history file from the previous session.*/
if (!histFile.empty())
{
histFile += String(HIST_FILENAME);
read_history(histFile.c_str());
}
m_running = true;
/* If we got a runOnce command off of the command line, then
set the runOnce flag and allow the command to be executed. */
String execCmd = getExecCmd();
if (!execCmd.empty())
runOnce = true;
/* Continue prompting for a command until the user enters
the exit command */
while (!shouldExit())
{
/* Free any previously existing command line string */
if (cmd)
{
free (cmd);
cmd = NULL;
}
/* Get a valid prompt from the server so that
we know that the server is ready for input. */
if (!isValidPrompt(prompt))
prompt = getServerPrompt();
if (runOnce)
{
cmd = execCmd.allocateCString();
}
else
{
/* Call the readline library to prompt for the command and
specify the comand prompt */
cmd = readline (prompt.c_str());
}
/* If the command is NULL, the EOF was hit and we need to
exit. If the command is empty, go back to the readline
prompt. Otherwise add the command to the command
history and continue processing */
if (!cmd)
{
doExit();
continue;
}
if (String(cmd).startsWith("exit", String::E_CASE_INSENSITIVE))
{
doExit();
}
else if ((*cmd == '\0') || (String(cmd).trim().charAt(0) == '#'))
{
continue;
}
else
add_history(cmd);
/* Write the command to the server over the socket
interface. The erase the current prompt so that
we can detect the new server prompt after all of
the data has been recieved. */
m_socket.write(cmd, String(cmd).length());
m_socket.write("\r\n", 2);
prompt.erase();
do {
StringArray lines;
/* If the program is exiting, then see if there is
anything to display. If not then get out. */
if (shouldExit() && m_socket.waitForInput(1))
{
break;
}
else
{
int selType = Select::SELECT_INTERRUPTED;
do {
selType = Select::select(selArray, timeout * 1000); // *1000 to convert seconds to milliseconds
} while(selType == Select::SELECT_INTERRUPTED);
if (selType == Select::SELECT_ERROR)
{
/*XXX This is a rather brut force way of aborting and
probably needs to be cleaned up. */
cout << "Socket select error occured. "
"Aborting the request and attempting to reconnect." << endl;
socketConnect();
break;
}
if (selType == Select::SELECT_TIMEOUT)
{
cout << "Request timeout exceeded." << endl;
char *abortit = readline ("Would you like to abort the request? (y/n) ");
if (abortit[0] == 'y')
{
/*XXX This is a rather brut force way of aborting and
probably needs to be cleaned up. */
socketConnect();
break;
}
rl_on_new_line();
continue;
}
if (selType == 0) // Unnamed pipe/event selected
{
doExit();
continue;
}
}
/* Read the data from the socket and
parse it on carriage returns. */
memset(lineBuf, 0, sizeof(lineBuf));
int bytesRead = m_socket.read(lineBuf, sizeof(lineBuf));
/* If we got data back then process it. Otherwise consider it
to be an EOF and bail out. */
if (bytesRead) {
lines = String(lineBuf).tokenize("\r\n");
}
else {
String msg = "The socket connection to the server appears to be broken\n" \
" Exiting the omcclp application.";
cout << msg << endl;
OW_LOG_ERROR(m_logger, msg);
doExit();
continue;
}
/* Output each line to the screen while looking for
the server prompt. Once the server prompt
is found, assume that there is no more data. */
for (int i = 0; i < lines.size(); i++)
{
if (isValidPrompt(lines[i])) {
prompt = lines[i];
/* If runOnce is set then get out after the
command response has been output. */
if (runOnce)
doExit();
break;
}
cout << lines[i] << endl;
}
} while (prompt.empty() && !shouldExit());
}
if (fscript)
{
fclose (fscript);
}
/* Write out the command history file to the user's home directory */
if (!histFile.empty())
{
int err = write_history(histFile.c_str());
if (!err)
{
history_truncate_file(histFile.c_str(), 100);
}
}
/* If we are exiting due to a call to the shutdown
method, then wait on the sync thread barrier. */
if (m_shuttingDown)
m_threadBarrier.wait();
return 0;
}
}
#ifdef OMCCLPCLIENT
/**
* Main program starting point
*/
int main (int argc, char *argv[])
{
OMCCLPClient clp(cout);
int c;
// Set the global pointer to the program object so that
// it can be referenced by other objects.
gclp = &clp;
cout << "hello world" << endl;
/* Initialize the readline library */
init_rl();
registerSignalHandler ();
try
{
// parse command line, and build the urlList
CmdLineParser parser(argc, argv, g_options, CmdLineParser::E_NON_OPTION_ARGS_ALLOWED);
if (parser.isSet(E_CMDOPT_HELP))
{
usage();
return 0;
}
if (parser.isSet(E_CMDOPT_URL))
{
String val = parser.getOptionValue(E_CMDOPT_URL);
clp.setCIMURL(val);
}
if (parser.isSet(E_CMDOPT_NS))
{
String val = parser.getOptionValue(E_CMDOPT_NS);
clp.setCIMNamespace(val);
}
if (parser.isSet(E_CMDOPT_CONFIG))
{
String val = parser.getOptionValue(E_CMDOPT_CONFIG);
clp.setConfFilename(val);
}
else
clp.setConfFilename(OMCCLP_SYSCONF_DIR"/omcclpc.conf");
if (parser.isSet(E_CMDOPT_CMD))
{
String val = parser.getOptionValue(E_CMDOPT_CMD);
clp.setExecCmd(val);
}
clp.setScriptFilenames(parser.getNonOptionArgs());
}
catch (CmdLineParserException& e)
{
printCmdLineParserExceptionMessage(e);
usage();
}
catch (CIMException& e)
{
cerr << "ERROR: CIMException:" << e.getMessage() << endl;
}
catch (Exception& e)
{
cerr << "ERROR: Exception:" << e.getMessage() << endl;
}
catch (std::exception& e)
{
cerr << "ERROR: sdtException:" << e.what() << endl;
}
catch (...)
{
cerr << "ERROR: UnknownException." << endl;
}
try
{
if (clp.initializeCLPSession() &&
clp.createCLPSessionInfo(clp.getCIMClient()))
{
StringArray cmdScripts = clp.getScriptFilenames();
if (!cmdScripts.empty())
{
for (int i=0; i < cmdScripts.size(); i++)
clp.run(cmdScripts[i]);
}
else
{
clp.run();
}
return 0;
}
}
catch (AssertionException& a)
{
cerr << "Caught Assertion: " << a << endl;
}
catch (Exception& e)
{
cerr << e << endl;
}
return 1;
}
#endif