***********************
** FILE NAME : message.cpp
** SYSTEM NAME : Gnucomo - Gnu Computer Monitoring
-** VERSION NUMBER : $Revision: 1.8 $
+** VERSION NUMBER : $Revision: 1.19 $
**
** DESCRIPTION : Implementation of the message handling classes
**
********************************
** ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
** CREATION DATE : Sep 16, 2002
-** LAST UPDATE : Feb 28, 2003
+** LAST UPDATE : Nov 02, 2007
** MODIFICATIONS :
**************************************************************************/
/*****************************
$Log: message.cpp,v $
- Revision 1.8 2003-03-16 09:42:40 arjen
+ Revision 1.19 2011-03-24 10:20:37 arjen
+ Added some debug info
+
+ Revision 1.18 2007/11/03 10:23:53 arjen
+ Handling of parameters is greatly improved.
+ When creating a new parameter from an XML report which is fed into
+ gcm_input, the class definition is used as a template to fill in
+ the default values for the properties.
+ The class is also used as a template when a new property is added
+ to an existing parameter.
+
+ DYNAMIC properties are now handled properly. Instead of making a
+ 'changed property' notification, the value is checked against the
+ defined range for the property. An 'out of range' notification
+ is created when this condition is detected.
+
+ Revision 1.17 2005/05/31 05:51:41 arjen
+ Textual changes in parameter notifications
+
+ Revision 1.16 2003/12/04 10:38:09 arjen
+ Major redesign. All input is handled through XML. Raw input data is first
+ transformed into an XML document for further processing.
+ A collection of polymorphic classes handle the transformation of various
+ input formats into XML.
+ Classifying input data is done with a finite improbability calculation.
+
+ Revision 1.15 2003/10/27 11:28:27 arjen
+ Do not add another parameter_notification record is the notification
+ already exists for that parameter.
+
+ Revision 1.14 2003/09/01 06:57:14 arjen
+ Reject log entries that are found to be invalid.
+
+ Revision 1.13 2003/08/16 15:28:45 arjen
+ Fixed a namespace problem
+
+ Revision 1.12 2003/08/11 16:56:16 arjen
+ Different kinds of log files are parsed by a collection of objects
+ of different classes, derived from the base class line_cooker
+ Depending on the message content or the message_type element in
+ XML, one of these objects is selected.
+
+ Logrunner is integrated with gcm_input. Although its functionality
+ is still limited, a connection between logrunner and gcm_input
+ is beginning to form.
+
+ Revision 1.11 2003/08/05 08:15:00 arjen
+ Debug output to the log stream instead of cerr.
+ Fixed namespace problems in XPath searches of the DOM.
+ Moved string utility functions to a separate file.
+
+ Revision 1.10 2003/04/29 09:16:44 arjen
+ Read XML input,
+ Only cooked log entries for now.
+
+ Revision 1.9 2003/03/29 09:04:10 arjen
+ Extract the hostname out of the 'From:' or 'Message-Id:' line
+ of an email header.
+
+ Revision 1.8 2003/03/16 09:42:40 arjen
Read IRIX system logs.
Revision 1.7 2003/02/21 08:08:05 arjen
*****************************/
-static const char *RCSID = "$Id: message.cpp,v 1.8 2003-03-16 09:42:40 arjen Exp $";
+#include <unistd.h>
+#include <fstream>
#include <algorithm>
+#include <libxml/xpath.h>
+#include <libxml/debugXML.h>
+
#include "message.h"
+//#define DEBUG
+
extern bool verbose; /* Defined in the main application */
extern bool testmode;
+extern bool incremental;
+extern std::ostream *Log;
/* Utility functions */
-String SQL_Escape(String s);
+extern String SQL_Escape(String s);
/*=========================================================================
** NAME : operator >>
return input_ok;
}
+/*=========================================================================
+** NAME : client_message
+** SYNOPSIS : client_message(std::istream *in, gnucomo_database db)
+** PARAMETERS :
+** RETURN VALUE : None
+**
+** DESCRIPTION : Client message constructor.
+**
+** VARS USED :
+** VARS CHANGED :
+** FUNCTIONS USED :
+** SEE ALSO :
+** LAST MODIFIED : Nov 04, 2002
+**=========================================================================
+*/
+
client_message::client_message(std::istream *in, gnucomo_database db)
{
input.from(in);
mail_header = false;
gpg_encrypted = false;
classification = UNKNOWN;
+ xmlDom = NULL;
certainty = 0.0;
}
-static const String syslog_date_re("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
-static const String mail_date_re("[[:alpha:]]{3}, [ 123]?[0-9] [[:alpha:]]{3} [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2} [+-][0-9]{4}");
static const String unix_date_re("[[:alpha:]]{3} [[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2} [0-9]{4}");
-static const regex re_syslog(syslog_date_re + " [[:alnum:]]+ [[:alpha:]]+.*:.+");
-static const regex re_syslog_irix(syslog_date_re + " [0-7][A-T]:[[:alnum:]]+ [[:alpha:]]+.*:.+");
static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
static const regex re_dump("^ *DUMP: Date of this level");
-static const regex re_accesslog("(GET|POST) .+ HTTP");
-static const regex re_errorlog("^\\[" + unix_date_re + "\\] \\[(error|notice)\\] .+");
static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
-static const regex re_syslog_date("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
-static const regex re_uxmail_from("^From - " + unix_date_re);
-static const regex re_mail_From("^From:[[:blank:]]+");
-static const regex re_mail_Date("^Date:[[:blank:]]+" + mail_date_re);
-static const regex re_email_address("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
-static const regex re_email_user("[[:alnum:]_.-]+@");
+static const regex re_uxmail_from("^From [^ \t]+[ ]+" + unix_date_re);
+static const regex re_xml_header("xml .*\?>$");
+
+/*=========================================================================
+** NAME : extractHeader
+** SYNOPSIS : void extractHeader()
+** PARAMETERS :
+** RETURN VALUE : True if the mandatory header elements are available.
+**
+** DESCRIPTION : Extract the header information from the XML DOM tree.
+**
+** VARS USED :
+** VARS CHANGED :
+** FUNCTIONS USED :
+** SEE ALSO :
+** LAST MODIFIED : Nov 26, 2003
+**=========================================================================
+*/
+
+bool client_message::extractHeader()
+{
+ xmlNodePtr root, item;
+ xmlNsPtr namespaces[1];
+
+ xmlXPathObjectPtr res;
+ xmlXPathContextPtr pathcontext;
+
+ bool header_OK = true;
+
+ root = xmlDocGetRootElement(xmlDom);
+ namespaces[0] = root->ns;
+
+ //TODO Ought to check root->name and root->ns->href
+
+ pathcontext = xmlXPathNewContext(xmlDom);
+ pathcontext->node = xmlDocGetRootElement(xmlDom);
+ pathcontext->namespaces = namespaces;
+ pathcontext->nsNr = 1;
+
+#ifdef DEBUG
+ xmlDebugDumpNodeList(stdout, pathcontext->node, 0);
+#endif
+
+ res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:messagetype/text()", pathcontext);
+ if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+
+ // Select a line cooker based on the message type.
+
+#ifdef DEBUG
+ *Log << "Looking for a line cooker for " << item->content << "\n";
+#endif
+ pan.lc = 0;
+ std::list<xform>::iterator lci = kitchen.begin();
+ while (pan.lc == 0 && lci != kitchen.end())
+ {
+ if (lci->lc->message_type() == (const char *)(item->content))
+ {
+ pan.lc = lci->lc;
+ }
+ lci++;
+ }
+ if (pan.lc == 0)
+ {
+ *Log << "Can not find a line cooker for message type " << item->content << "\n";
+ header_OK = false;
+ }
+ }
+ else
+ {
+ *Log << "Message type not found in XML header.\n";
+ header_OK = false;
+ }
+
+ res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:hostname/text()", pathcontext);
+ if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+ hostname = (const char *)item->content;
+ }
+ else
+ {
+ *Log << "Can not determine the hostname where the message came from.\n";
+ header_OK = false;
+ }
+
+ res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:service/text()", pathcontext);
+ if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+ service = (const char *)item->content;
+ }
+ res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:time/text()", pathcontext);
+ if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+ arrival = String((char *)item->content);
+ if (!arrival.proper())
+ {
+ *Log << "Arrival time is not properly stated.\n";
+ header_OK = false;
+ }
+
+ }
+ //xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
+
+ return header_OK;
+}
+
/*=========================================================================
** NAME : classify
** VARS CHANGED :
** FUNCTIONS USED :
** SEE ALSO :
-** LAST MODIFIED : Nov 16, 2002
+** LAST MODIFIED : Nov 27, 2003
**=========================================================================
*/
arrival = arriv;
service = serv;
+ const double epsilon = 0.1; // Threshold for uncertainty
+ const double P = 0.5; // Probability of a wrong match
+
+ double uncertainty;
+
+#ifdef DEBUG
+ *Log << "Checking for a mail header.\n";
+#endif
+
/* First, check if the message has a mail header. */
if (input >> line && line == re_uxmail_from)
mail_header = true;
- /* Scan ahead for the hostname and date of arrival. */
+ /* Skip the mail header until the first empty line. */
while (input >> line && line != "")
{
- if (line == re_mail_From)
- {
- from_address = line(re_email_address);
- from_address(re_email_user) = ""; // Remove the user part;
- hostname = from_address;
- }
- if (line == re_mail_Date)
- {
- arrival = UTC(line(regex(mail_date_re)));
- }
}
}
else
{
// Push the first line back, we need to read it again.
--input;
+
}
/*
* out what the content of the message is.
*/
+#ifdef DEBUG
+ *Log << "Classifying message.\n";
+#endif
+
- while (input >> line && certainty < 0.9)
+ uncertainty = 1.0;
+
+ while (input >> line && uncertainty > epsilon)
{
- std::cout << " testing: " << line << "\n";
- if (line == re_syslog)
- {
- certainty = 1.0;
- classification = SYSLOG;
- if (verbose)
- {
- std::cout << "Syslog detected.\n";
- }
- }
- else if (line == re_syslog_irix)
+ if (verbose)
{
- certainty = 1.0;
- classification = SYSLOG_IRIX;
- if (verbose)
- {
- std::cout << "IRIX Syslog detected.\n";
- }
+ *Log << " testing: " << line << "\n";
}
- else if (line == re_PGP)
+
+ if (line == re_PGP)
{
- certainty = 1.0;
+ uncertainty = 0.0;
gpg_encrypted = true;
- std::cerr << "The message is PGP/GnuPG encrypted.\n";
+ *Log << "The message is PGP/GnuPG encrypted.\n";
}
- else if (line == re_dump)
- {
- certainty = 1.0;
- if (verbose)
- {
- std::cout << "DUMP output detected.\n";
- }
- }
- else if (line == re_accesslog)
- {
- certainty = 1.0;
- classification = ACCESSLOG;
- service = "httpd";
- if (verbose)
- {
- std::cout << "HTTP access log detected.\n";
- }
- }
- else if (line == re_errorlog)
- {
- certainty = 1.0;
- classification = ERRORLOG;
- service = "httpd";
- if (verbose)
- {
- std::cout << "HTTP error log detected.\n";
- }
- }
- else if (line == re_rpm)
+ else
{
- certainty = 1.0;
- classification = RPMLIST;
- service = "";
- if (verbose)
- {
- std::cout << "RPM package list detected.\n";
- }
+ // Scan the list of line cookers if there is anything familiar.
+
+ std::list<xform>::iterator lci = kitchen.begin();
+ uncertainty = 1.0;
+ while (lci != kitchen.end())
+ {
+ if (lci->lc->check_pattern(line))
+ {
+ // We have a match; decrease the uncertainty
+
+ lci->uncertainty *= P;
+ if (uncertainty > lci->uncertainty)
+ {
+ uncertainty = lci->uncertainty;
+ pan = *lci;
+ }
+ if (verbose)
+ {
+ *Log << lci->lc->message_type() << " detected with "
+ << lci->uncertainty << " uncertainty.\n";
+ }
+ }
+ lci++;
+ }
+ classification = COOKER_OBJECT;
}
}
+
input.rewind();
- if (hostname == "")
- {
- std::cerr << "Can not determine the hostname where the message came from.\n";
- certainty = 0.0;
- }
- else if (!arrival.proper())
- {
- std::cerr << "Arrival time is not knwon.\n";
- certainty = 0.0;
- }
- else
- {
- certainty = 1.0;
- }
+ certainty = 1.0 - uncertainty;
return certainty;
}
/*=========================================================================
-** NAME : enter
-** SYNOPSIS : int enter()
+** NAME : enterXML
+** SYNOPSIS : int enterXML()
** PARAMETERS :
-** RETURN VALUE : The number of lines successfully parsed from the input
+** RETURN VALUE : None
**
-** DESCRIPTION :
+** DESCRIPTION : Analyze the DOM tree from the XML input.
+** The DOM tree was previously parsed by readXMLinput().
**
** VARS USED :
** VARS CHANGED :
** FUNCTIONS USED :
** SEE ALSO :
-** LAST MODIFIED : Feb 19, 2003
+** LAST MODIFIED : Nov 02, 2007
**=========================================================================
*/
-int client_message::enter()
+struct param_property
{
- long nr_lines = 0;
- String line;
- String qry;
-
- String change_notification("");
- String create_notification("");
- bool initial_entry = false;
-
- std::list<String> packages;
-
-
- /* Double-check the classification of the message */
-
- if (classification == UNKNOWN || certainty < 0.9 || gpg_encrypted)
- {
- return 0;
- }
-
- if (mail_header)
- {
- // Skip the mail header.
+ String name;
+ String value;
+ String type;
+ double minimum;
+ double maximum;
+};
+
+void client_message::enterXML()
+{
+ //TODO : return the number of elements that are handled.
- while (input >> line && line != "");
- }
+ xmlXPathObjectPtr res;
+ xmlXPathContextPtr pathcontext;
+ xmlNsPtr namespaces[1];
/* Try to find the host in the database */
String objectid;
+ String remark; // For notifications
objectid = database.find_host(hostname);
if (objectid == "")
{
- std::cerr << "Please define the host " << hostname << " in the database.\n";
- return 0;
+ *Log << "Please define the host " << hostname << " in the database.\n";
+ return;
}
if (verbose)
{
- std::cout << "Object id for " << hostname << " is " << objectid << "\n";
+ *Log << "Object id for " << hostname << " is " << objectid << "\n";
}
- if (classification == RPMLIST)
- {
-
- int n_packages;
-
- /* Read all packages, so we will know which ones are */
- /* missing at the end. */
+ pathcontext = xmlXPathNewContext(xmlDom);
+ pathcontext->node = xmlDocGetRootElement(xmlDom);
+ namespaces[0] = pathcontext->node->ns;
+ pathcontext->namespaces = namespaces;
+ pathcontext->nsNr = 1;
- qry = "select name from parameter where objectid='";
- qry += objectid + "' and class='package'";
- n_packages = database.Query(qry);
- initial_entry = n_packages == 0;
+ res = xmlXPathEval((const xmlChar *)"gcmt:data/node()", pathcontext);
- std::cout << n_packages << " packages in database.\n";
- for (int t = 0; t < n_packages; t++)
- {
- packages.push_back(database.Field(t, "name"));
- }
- std::cout << "Package list built: " << packages.size() << ".\n";
- }
-
- /* Scan the input line by line, entring records into the database */
-
- String rest; // Rest of the line to be parsed
-
- while (input >> line)
+ if (res->nodesetval != NULL)
{
- if (verbose)
- {
- std::cout << line << "\n";
- }
-
+ // Find the first child element of the <data> element.
- /* Check each line if it contains valid information */
-
- const regex *check;
-
- switch (classification)
+ xmlNodePtr node = *res->nodesetval->nodeTab;
+ while (node->type != XML_ELEMENT_NODE)
{
- case SYSLOG:
- check = &re_syslog;
- break;
- case SYSLOG_IRIX:
- check = &re_syslog_irix;
- break;
- case ACCESSLOG:
- check = &re_accesslog;
- break;
- case ERRORLOG:
- check = &re_errorlog;
- break;
- case RPMLIST:
- check = &re_rpm;
- break;
+ node = node->next;
}
-
- if (line == *check)
+ if (strcmp((char *)node->name, "log") == 0)
{
- date log_date;
- hour log_time;
- int i;
+ int log_entry_counter = 0;
- String insertion("insert into log (objectid, servicecode,"
- " object_timestamp, timestamp, rawdata, processed) values (");
- String datestring;
+ // Each child contains a log entry, raw or cooked.
- switch (classification)
+ node = node->children;
+ while (node != NULL)
{
- case SYSLOG:
- log_date = line;
- log_time = line;
- if (log_date.Year() < 0 || log_date.Year() > 2500)
+ if (node->type == XML_ELEMENT_NODE)
{
- // The year is not in the log file. Assume the year of arrival,
- // unless this puts the log entry at a later date than the arrival date.
- // This happens e.g. when a log entry from December arrives in Januari.
+ log_entry_counter++;
- log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
- if (log_date > date(arrival))
- {
- log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
- }
- }
+ xmlNodePtr item;
+ String log_hostname;
+ UTC log_date;
+ String raw("");;
+ String log_service;
- if (verbose)
- {
- std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
- }
- rest = line << 16;
- i = rest.index(' ');
- if (rest(0,i) == hostname(0,i))
- {
- rest <<= i + 1;
if (verbose)
{
- std::cout << " Hostname matches.\n";
- std::cout << " rest = " << rest << "\n";
+ *Log << "Log entry " << log_entry_counter << " is " << node->name << "\n";
}
- for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
- if (verbose)
+ if (strcmp((char *)node->name, "raw") == 0 && node->children != NULL)
{
- std::cout << " Service name = " << rest(0,i) << "\n";
+ item = node->children;
+ if (pan.lc == 0)
+ {
+ *Log << "Can not cook this type of <raw> log element.\n";
+ }
+ else
+ {
+ raw = String((const char *)item->content);
+ if (pan.lc->cook_this(raw, arrival))
+ {
+ log_hostname = pan.lc->hostname();
+ if (log_hostname == "")
+ {
+ log_hostname = hostname;
+ }
+ log_service = pan.lc->service();
+ log_date = pan.lc->timestamp();
+
+ if (!log_date.proper())
+ {
+ *Log << log_date << " is not a valid timestamp.\n";
+ }
+ }
+ else
+ {
+ *Log << "gcm_input WARNING: Not a valid line: " << raw << "\n";
+ raw = "";
+ }
+ }
}
+ else if (strcmp((char *)node->name, "cooked") == 0)
+ {
+ // Find the parts of the log entry
- /* Insert a new record into the log table */
-
- insertion += "'" + objectid + "',";
- insertion += "'" + rest(0,i) + "',";
- insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
- insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
- insertion += "'" + SQL_Escape(line) + "',FALSE";
- insertion += ")";
+ if (verbose)
+ {
+ *Log << "Analyzing cooked element.\n";
+ }
+ pathcontext->node = node;
- if (testmode)
- {
- std::cout << insertion << "\n";
- }
- else
- {
- database.Query(insertion);
- }
+ res = xmlXPathEval((const xmlChar *)"hostname/text()", pathcontext);
+ if (res->nodesetval != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+ log_hostname = (const char *)item->content;
+ if (log_hostname != hostname(0, ~log_hostname))
+ {
+ *Log << "Hostname " << log_hostname << " does not match.\n";
+ log_hostname = "";
+ }
+ }
+ else
+ {
+ log_hostname = hostname;
+ }
- if (verbose)
- {
- std::cout << "\n\n";
- }
+ res = xmlXPathEval((const xmlChar *)"service/text()", pathcontext);
+ if (res->nodesetval != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+ log_service = (const char *)item->content;
+ }
+ else
+ {
+ log_service = service;
+ }
- nr_lines++;
- }
- else
- {
- std::cerr << " Hostname " << rest(0,i) << " does not match.\n";
- }
- break;
+ res = xmlXPathEval((const xmlChar *)"timestamp/text()", pathcontext);
+ if (res->nodesetval != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+ log_date = String((const char *)item->content);
+ }
+ else
+ {
+ *Log << "<timestamp> missing from cooked log element nr " << log_entry_counter << ".\n";
+ }
- case SYSLOG_IRIX:
- log_date = line;
- log_time = line;
- if (log_date.Year() < 0 || log_date.Year() > 2500)
- {
- // The year is not in the log file. Assume the year of arrival,
- // unless this puts the log entry at a later date than the arrival date.
- // This happens e.g. when a log entry from December arrives in Januari.
+ res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
+ if (res->nodesetval != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+ raw = String((const char *)item->content);
+ }
+ else
+ {
+ *Log << "<raw> missing from cooked log element nr " << log_entry_counter << ".\n";
+ }
- log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
- if (log_date > date(arrival))
- {
- log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
}
- }
- if (verbose)
- {
- std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
- }
- rest = line << 19;
- i = rest.index(' ');
- if (rest(0,i) == hostname(0,i))
- {
- rest <<= i + 1;
- if (verbose)
- {
- std::cout << " Hostname matches.\n";
- std::cout << " rest = " << rest << "\n";
- }
- for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
- if (verbose)
+ // Insert a new log record into the database.
+ if (raw != "" && log_hostname != "" && log_date.proper())
{
- std::cout << " Service name = " << rest(0,i) << "\n";
- }
+ String insertion("insert into log (objectid, servicecode,"
+ " object_timestamp, timestamp, rawdata, processed) values (");
- /* Insert a new record into the log table */
+ /* Insert a new record into the log table */
- insertion += "'" + objectid + "',";
- insertion += "'" + rest(0,i) + "',";
- insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
- insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
- insertion += "'" + SQL_Escape(line) + "',FALSE";
- insertion += ")";
+ insertion += "'" + objectid + "',";
+ insertion += "'" + log_service + "',";
+ insertion += "'" + log_date.format("%Y-%m-%d %T") + "',";
+ insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
+ insertion += "'" + SQL_Escape(raw) + "',FALSE";
+ insertion += ")";
+
+ if (testmode)
+ {
+ *Log << insertion << "\n";
+ }
+ else
+ {
+ database.Query(insertion);
+ }
- if (testmode)
- {
- std::cout << insertion << "\n";
}
else
{
- database.Query(insertion);
+ *Log << "Can not insert log element " << raw << ".\n";
}
+
+ }
+ node = node->next;
+ }
+ }
+ else if (strcmp((char *)node->name, "parameters") == 0)
+ {
+ // Each child contains a parameter entry, with at least one property
+
+ String qry;
+ String insertion;
+ String change_notification("");
+ String out_of_range_notification("");
+ String create_notification("");
+ String remove_notification("");
+ bool initial_entry = false;
+ String param_class((const char *)xmlGetProp(node, (const xmlChar *)"class"));
+
+ std::list<param_property> parameter_template;
+ int nr_properties;
+
+ // Obtain a list of properties from the parameter's class.
+ // This list is used to create new parameters.
+
+ qry = "select * from parameter_class where name='";
+ qry += param_class + "'";
+ nr_properties = database.Query(qry);
+ for (int i = 0; i < nr_properties; i++)
+ {
+ param_property pp;
- if (verbose)
- {
- std::cout << "\n\n";
- }
+ pp.name = database.Field(i, "property_name");
+ pp.type = database.Field(i, "property_type");
+ pp.minimum = database.Field(i, "min");
+ pp.maximum = database.Field(i, "max");
- nr_lines++;
- }
- else
- {
- std::cerr << " Hostname " << rest(0,i) << " does not match.\n";
- }
- break;
-
- case ACCESSLOG:
- datestring = line(regex("\\[.+\\]"));
- datestring <<= 1;
- datestring >>= 1;
- datestring[datestring.index(':')] = ' ';
- log_date = datestring;
- log_time = datestring;
- if (verbose)
- {
- std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
- }
- insertion += "'" + objectid + "',";
- insertion += "'" + service + "',";
- insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
- insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
- insertion += "'" + SQL_Escape(line) + "',FALSE";
- insertion += ")";
-
- if (testmode)
- {
- std::cout << insertion << "\n";
- }
- else
- {
- database.Query(insertion);
- }
+ parameter_template.push_back(pp);
+ }
- if (verbose)
- {
- std::cout << "\n\n";
- }
+#ifdef DEBUG
+ *Log << "Entering a list of " << param_class << " parameters.\n";
+#endif
+ pathcontext->node = node;
- nr_lines++;
- break;
+ // If we don't have any parameters of this class, this will be
+ // an initial entry.
- case ERRORLOG:
- datestring = line(regex("\\[.+\\]"));
- datestring <<= 1;
- datestring >>= 1;
- log_date = datestring;
- log_time = datestring;
- if (verbose)
- {
- std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
- }
- insertion += "'" + objectid + "',";
- insertion += "'" + service + "',";
- insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
- insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
- insertion += "'" + SQL_Escape(line) + "',FALSE";
- insertion += ")";
-
- if (testmode)
- {
- std::cout << insertion << "\n";
- }
- else
- {
- database.Query(insertion);
- }
+ qry = "select name from parameter where objectid='";
+ qry += objectid + "' and class='" + param_class + "'";
+ initial_entry = database.Query(qry) == 0;
- if (verbose)
+ node = node->children;
+ while (node != NULL)
+ {
+ if (node->type == XML_ELEMENT_NODE &&
+ strcmp((char *)node->name, "parameter") == 0)
{
- std::cout << "\n\n";
- }
+ String param_name((const char *)xmlGetProp(node, (const xmlChar *)"name"));
- nr_lines++;
- break;
-
- case RPMLIST:
- // Scan a list of packages and versions from "rpm -a".
- // A similar listing can be created on IRIX 6.5 by using the
- // command "showprods -3 -n|awk '{printf "%s-%s\n",$2,$3}'|grep -v '^[-=]' \
- // |grep -v Version-Description".
- //
- // We have to separate the package name and the version.
- // The separation is marked by a '-', followed by a digit.
- // However, there may be other sequences of '-'digit in the package name,
- // do we have to scan ahead until there is at most one such sequence
- // left in the version string. The '-'digit seqeunce inside the
- // version usually separates the version and the release number.
-
- int version_start, next_version_start;
-
- i = line.index('-');
- version_start = i;
- next_version_start = i;
-
- while (i < ~line - 1)
- {
- while (i < ~line - 1 && !(line[i] == '-' && isdigit(line[i + 1])))
- {
- i++;
- }
- if (i < ~line - 1)
- {
- version_start = next_version_start;
- next_version_start = i;
- }
- i++;
- }
-
- String package(line(0,version_start));
- String version(line(version_start + 1, ~line));
- String paramid;
- String remark;
- String insert_h;
-
- if (verbose)
- {
- std::cout << "Package is " << package;
- std::cout << ", version is " << version << "\n";
- }
+#ifdef DEBUG
+ *Log << "Parameter with name " << param_name << "\n";
+#endif
+ std::list<param_property> properties;
+ param_property prop;
+ xmlNodePtr item;
- // Construct a qry to check the package's existance
+ String paramid;
- qry = "select paramid from parameter where objectid='";
- qry += objectid + "' and class='package' and name='";
- qry += package + "'";
- if (database.Query(qry) == 1)
- {
- std::list<String>::iterator lp;
+ // Collect the parameter's properties.
- lp = find(packages.begin(), packages.end(), package);
- if (lp != packages.end())
+ item = node->children;
+ while (item != NULL)
{
- packages.erase(lp);
- }
- else
- {
- std::cerr << "Could NOT find " << package << " in list.\n";
- }
-
- paramid = database.Field(0, "paramid");
- qry = "select value from property where paramid='";
- qry += paramid + "' and name='version'";
- if (database.Query(qry) == 0)
- {
- std::cerr << "Database corruption: Package " << package;
- std::cerr << " does not have a 'version' property.\n";
- }
- else if (database.Field(0, "value") != version)
- {
- if (verbose)
+ if (item->type == XML_ELEMENT_NODE &&
+ strcmp((char *)item->name, "property") == 0)
{
- std::cout << " Parameter " << package << " has different version\n";
+ prop.name = (const char *)xmlGetProp(item, (const xmlChar *)"name");
+ if (item->children != NULL)
+ {
+ prop.value = (const char *)item->children->content;
+ properties.push_back(prop);
+ }
+ else
+ {
+ *Log << "WARNING: Property " << prop.name << " has no value.\n";
+ }
}
- insertion = "update property set value='";
- insertion += version + "' where paramid='";
- insertion += paramid + "' and name='version'";
- insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
- insert_h += " values ('";
- insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'MODIFIED', 'version', '";
- insert_h += version + "')";
+ // TODO: Hanlde description element
- database.Query(insertion);
- database.Query(insert_h);
+ item = item->next;
+ }
+
+ // Check the parameter in the database.
+
+ std::list<param_property>::iterator pi = properties.begin();
+
+ qry = "select paramid from parameter where objectid='";
+ qry += objectid + "' and class='";
+ qry += param_class + "' and name='";
+ qry += param_name + "'";
- if (change_notification == "")
+ if (database.Query(qry) == 1)
+ {
+ // The parameter exists in the database; check all properties.
+
+ bool param_changed = false;
+ bool out_of_range = false;
+
+ paramid = database.Field(0, "paramid");
+ while (pi != properties.end())
{
- remark = "Gnucomo detected a different version for package parameter(s) ";
- change_notification = database.new_notification(objectid, "property modified", remark);
+ qry = "select * from property where paramid='";
+ qry += paramid + "' and name='";
+ qry += pi->name + "'";
+ if (database.Query(qry) == 0)
+ {
+ *Log << "Property " << pi->name << " of "
+ << param_name << " does not exist.\n";
+ // Find the property in the template from the class.
+
+ String property_type("STATIC");
+ double property_minimum = 0.0;
+ double property_maximum = 0.0;
+ std::list<param_property>::iterator ti = parameter_template.begin();
+
+ while (ti != parameter_template.end() && ti->name != pi->name)
+ {
+ ti++;
+ }
+ if (ti != parameter_template.end())
+ {
+ property_type = ti->type;
+ property_minimum = ti->minimum;
+ property_maximum = ti->maximum;
+ }
+
+ insertion = "insert into property (paramid, name, value, type, min, max) values ('";
+ insertion += paramid + "', '";
+ insertion += pi->name + "', '";
+ insertion += pi->value + "', '";
+ insertion += property_type + "', '";
+ insertion += String(property_minimum) + "', '";
+ insertion += String(property_maximum) + "')";
+ database.Query(insertion);
+
+ insertion = "insert into history (paramid, modified,";
+ insertion += " change_nature, changed_property, new_value)";
+ insertion += " values ('";
+ insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
+ + "', 'CREATED', '";
+ insertion += pi->name + "', '";
+ insertion += pi->value + "')";
+ database.Query(insertion);
+ }
+ else
+ {
+ param_property stored_property;
+
+ stored_property.value = database.Field(0, "value");
+ stored_property.type = database.Field(0, "type");
+ stored_property.minimum = database.Field(0, "min");
+ stored_property.maximum = database.Field(0, "max");
+
+ if (stored_property.value != pi->value)
+ {
+ *Log << "Property " << pi->name << " of "
+ << param_name << " is different.\n";
+
+ insertion = "update property set value='";
+ insertion += pi->value + "' where paramid='";
+ insertion += paramid + "' and name='";
+ insertion += pi->name + "'";
+
+ database.Query(insertion);
+
+ insertion = "insert into history (paramid, modified,";
+ insertion += " change_nature, changed_property, new_value)";
+ insertion += " values ('";
+ insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
+ + "', 'MODIFIED', '";
+ insertion += pi->name + "', '";
+ insertion += pi->value + "')";
+
+ database.Query(insertion);
+ if (stored_property.type == "DYNAMIC")
+ {
+ // Check the value against the range of the property.
+
+ double numeric_value = pi->value;
+ if (numeric_value < stored_property.minimum || numeric_value > stored_property.maximum)
+ {
+ out_of_range = true;
+ }
+ }
+ else
+ {
+ // A STATIC property changed.
+ param_changed = true;
+ }
+
+ }
+ }
+ pi++;
}
- if (change_notification != "")
+ if (param_changed)
{
- insertion = "insert into parameter_notification (notificationid, paramid) values ('";
- insertion += change_notification + "', '";
- insertion += paramid + "')";
+ if (change_notification == "")
+ {
+ remark = "Gnucomo detected a different property for parameter(s) ";
+ change_notification = database.new_notification(objectid,
+ "property modified", remark);
+ }
- database.Query(insertion);
+ if (change_notification != "")
+ {
+ qry = "select * from parameter_notification where notificationid='";
+ qry += change_notification + "' and paramid='";
+ qry += paramid + "'";
+
+ if (database.Query(qry) == 0)
+ {
+ insertion = "insert into parameter_notification";
+ insertion += " (notificationid, paramid) values ('";
+ insertion += change_notification + "', '";
+ insertion += paramid + "')";
+
+ database.Query(insertion);
+ }
+ }
+ else
+ {
+ *Log << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
+ }
}
- else
+
+ if (out_of_range)
{
- std::cerr << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
+ if (out_of_range_notification == "")
+ {
+ remark = "Gnucomo detected that a property value is out of range.";
+ out_of_range_notification = database.new_notification(objectid,
+ "property out of range", remark);
+ }
+
+ if (out_of_range_notification != "")
+ {
+ qry = "select * from parameter_notification where notificationid='";
+ qry += out_of_range_notification + "' and paramid='";
+ qry += paramid + "'";
+
+ if (database.Query(qry) == 0)
+ {
+ insertion = "insert into parameter_notification";
+ insertion += " (notificationid, paramid) values ('";
+ insertion += out_of_range_notification + "', '";
+ insertion += paramid + "')";
+
+ database.Query(insertion);
+ }
+ }
+ else
+ {
+ *Log << "gcm_input ERROR: Cannot create 'property out of range' notification.\n";
+ }
}
}
else
{
- if (verbose)
- {
- std::cout << " Parameter " << package << " has not changed.\n";
- }
- }
- }
- else
- {
+ // The parameter does not exist; create anew.
- if (verbose)
- {
- std::cout << " Parameter " << package << " does not exist.\n";
- }
- // Create a new package parameter, including version property and history record
+ // TODO: Insert description
- insertion = "insert into parameter (objectid, name, class, description) values ('";
- insertion += objectid + "', '" + package + "', 'package', 'RPM package " + package + "')";
- if (testmode)
- {
- paramid = "0";
- std::cout << insertion << "\n";
- }
- else
- {
+ insertion = "insert into parameter (objectid, name, class, description) values ('";
+ insertion += objectid + "', '" + param_name + "', '" + param_class + "', '')";
database.Query(insertion);
+
qry = "select paramid from parameter where objectid='";
- qry += objectid + "' and class='package' and name='";
- qry += package + "'";
+ qry += objectid + "' and class='";
+ qry += param_class + "' and name='";
+ qry += param_name + "'";
database.Query(qry);
paramid = database.Field(0, "paramid");
- }
- insertion = "insert into property (paramid, name, value, type) values ('";
- insertion += paramid + "', 'version', '";
- insertion += version + "', 'STATIC')";
- insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
- insert_h += " values ('";
- insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'CREATED', 'version', '";
- insert_h += version + "')";
+ while (pi != properties.end())
+ {
+ // Find the property in the template from the class.
+
+ String property_type("STATIC");
+ double property_minimum = 0.0;
+ double property_maximum = 0.0;
+ std::list<param_property>::iterator ti = parameter_template.begin();
+
+ while (ti != parameter_template.end() && ti->name != pi->name)
+ {
+ ti++;
+ }
+ if (ti != parameter_template.end())
+ {
+ property_type = ti->type;
+ property_minimum = ti->minimum;
+ property_maximum = ti->maximum;
+ }
+
+ insertion = "insert into property (paramid, name, value, type, min, max) values ('";
+ insertion += paramid + "', '";
+ insertion += pi->name + "', '";
+ insertion += pi->value + "', '";
+ insertion += property_type + "', '";
+ insertion += String(property_minimum) + "', '";
+ insertion += String(property_maximum) + "')";
+ database.Query(insertion);
+
+ insertion = "insert into history (paramid, modified,";
+ insertion += " change_nature, changed_property, new_value)";
+ insertion += " values ('";
+ insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
+ + "', 'CREATED', '";
+ insertion += pi->name + "', '";
+ insertion += pi->value + "')";
+ database.Query(insertion);
+
+ pi++;
+ }
- if (testmode)
- {
- std::cout << insertion << "\n" << insert_h << "\n";
- }
- else
- {
- database.Query(insertion);
- database.Query(insert_h);
if (!initial_entry)
{
if (create_notification == "")
{
- remark = "Gnucomo detected new parameter(s) of class package";
- create_notification = database.new_notification(objectid, "parameter created", remark);
+ remark = "Gnucomo detected new parameter(s) of class " + param_class;
+ create_notification = database.new_notification(objectid,
+ "parameter created", remark);
}
if (create_notification != "")
{
- insertion = "insert into parameter_notification (notificationid, paramid) values ('";
+ insertion = "insert into parameter_notification";
+ insertion += " (notificationid, paramid) values ('";
insertion += create_notification + "', '";
insertion += paramid + "')";
}
else
{
- std::cerr << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
+ *Log << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
}
}
}
}
- if (verbose)
- {
- std::cout << "\n";
- }
-
- nr_lines++;
- break;
-
+ node = node->next;
}
- }
- else
- {
- std::cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
- }
- }
-
- if (classification == RPMLIST)
- {
- std::list<String>::iterator lp;
- String remove_notification("");
-
- /*
- * If there are any packages left in the list, they seem to have
- * disappeared from the system.
- */
- for (lp = packages.begin(); lp != packages.end(); lp++)
- {
- String paramid;
- String remark;
- String insert;
+ if (!incremental)
+ {
+ // Check if any parameters in this class have disappeared.
- // Construct a qry to check the package's existance
+#ifdef DEBUG
+ *Log << "Checking for disappeared parameters.\n";
+#endif
+ qry = "select name, paramid from parameter where objectid='";
+ qry += objectid + "' and class='" + param_class + "'";
- qry = "select paramid from parameter where objectid='";
- qry += objectid + "' and class='package' and name='";
- qry += *lp + "'";
+ int nr_parameters = database.Query(qry);
+ pqxx::result parameter_set = database.Result();
- if (database.Query(qry) == 1)
- {
- paramid = database.Field(0, "paramid");
- qry ="select change_nature from history where paramid='";
- qry += paramid + "' order by modified desc";
- if (database.Query(qry) <= 0)
+#ifdef DEBUG
+ *Log << nr_parameters << " parameters of class " << param_class << " found in the database.\n";
+#endif
+ for (int i = 0; i < nr_parameters; i++)
{
- std::cerr << "Database ERROR: no history record for parameter " << *lp << ".\n";
- }
- else if (database.Field(0, "change_nature") != "REMOVED")
- {
- if (verbose)
+ String XPath;
+ String param_name, paramid;
+
+ param_name = database.Field(parameter_set, i, "name");
+#ifdef DEBUG
+ *Log << "Looking for " << param_name << " in XML tree.\n";
+#endif
+ XPath = "gcmt:parameter[@name='" + param_name + "']";
+
+ res = xmlXPathEval((const xmlChar *)(const char *)XPath, pathcontext);
+#ifdef DEBUG
+ *Log << "XPATH result: " << res->type << ".\n";
+ *Log << "Nr of nodes found: " << res->nodesetval->nodeNr << ".\n";
+#endif
+ if (res->nodesetval->nodeNr == 0)
{
- std::cout << "Removing parameter " << *lp << ".\n";
- }
+ // The parameter is in the database but not in the report
+
+#ifdef DEBUG
+ *Log << "Could not find " << XPath << " in XML tree.\n";
+#endif
+ paramid = database.Field(parameter_set, i, "paramid");
+ qry ="select change_nature from history where paramid='";
+ qry += paramid + "' order by modified desc";
+ if (database.Query(qry) <= 0)
+ {
+ *Log << "Database ERROR: no history record for parameter "
+ << param_name << ".\n";
+ }
+ else if (database.Field(0, "change_nature") != "REMOVED")
+ {
+ if (verbose)
+ {
+ *Log << "Removing parameter " << param_name << ".\n";
+ }
- insert = "insert into history (paramid, modified, change_nature)";
- insert += " values ('";
- insert += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'REMOVED')";
+ insertion = "insert into history (paramid, modified, change_nature)";
+ insertion += " values ('";
+ insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
+ + "', 'REMOVED')";
- database.Query(insert);
+ database.Query(insertion);
- if (remove_notification == "")
- {
- remark = "Gnucomo detected that package(s) have disappeared ";
- remove_notification = database.new_notification(objectid, "parameter removed", remark);
- }
+ if (remove_notification == "")
+ {
+ remark = "Gnucomo detected that " + param_class
+ + " parameters(s) have disappeared ";
+ remove_notification = database.new_notification(objectid,
+ "parameter removed", remark);
+ }
- if (remove_notification != "")
- {
- insert = "insert into parameter_notification (notificationid, paramid) values ('";
- insert += remove_notification + "', '";
- insert += paramid + "')";
+ if (remove_notification != "")
+ {
+ insertion = "insert into parameter_notification";
+ insertion += " (notificationid, paramid) values ('";
+ insertion += remove_notification + "', '";
+ insertion += paramid + "')";
- database.Query(insert);
+ database.Query(insertion);
+ }
+ else
+ {
+ *Log << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
+ }
+ }
}
else
{
- std::cerr << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
+#ifdef DEBUG
+ *Log << XPath << " was found in XML tree.\n";
+#endif
}
}
}
}
+ else
+ {
+ *Log << "Data element " << node->name << " is not supported.\n";
+ }
}
-
- if (verbose)
+ else
{
- std::cout << nr_lines << " lines parsed from the log file.\n";
+ *Log << "Data node not found.\n";
}
- return nr_lines;
+}
+
+void client_message::saveXML(String filename)
+{
+ std::ofstream xmlfile;
+
+ xmlfile.open(filename);
+ xmlfile << xmlBuffer.str();
}
/*=========================================================================
-** NAME : SQL_Escape
-** SYNOPSIS : String SQL_Escape(String)
+** NAME : enter
+** SYNOPSIS : int enter()
** PARAMETERS :
-** RETURN VALUE :
+** RETURN VALUE : The number of lines successfully parsed from the input
+** or a negative number if an error is encountered.
+** The error return values are:
+** -1 No database connection.
+** -2 XML parser error.
**
-** DESCRIPTION : Insert backslashes before single quotes.
+** DESCRIPTION :
**
** VARS USED :
** VARS CHANGED :
** FUNCTIONS USED :
** SEE ALSO :
-** LAST MODIFIED :
+** LAST MODIFIED : Sep 22, 2020
**=========================================================================
*/
-String SQL_Escape(String s)
+int client_message::enter()
{
- int i;
+ int nr_lines = 0;
+
+ pan.mf->set_message_type(pan.lc->message_type());
+
+ pan.mf->construct_XML(input, xmlBuffer);
- for (i = 0; i < ~s; i++)
+#ifdef DEBUG
+ *Log << "Constructed XML document:\n\n";
+ *Log << xmlBuffer.str();
+ *Log << "\n";
+#endif
+
+ xmlDom = xmlParseMemory(xmlBuffer.str(), xmlBuffer.pcount());
+
+ if (database.is_connected())
{
- if (s[i] == '\'')
+ if (xmlDom)
+ {
+ if (extractHeader())
+ {
+ enterXML();
+ }
+ }
+ else
{
- s(i,0) = "\\";
- i++;
+ *Log << "XML parser FAILED.\n";
+ nr_lines = -2; // XML parse error
}
}
+ else
+ {
+ *Log << "Database connection FAILED.\n";
+ nr_lines = -1; // No database connection
+ }
+
+ return nr_lines;
- return s;
}