2 /**************************************************************************
3 ** (c) Copyright 2002, Andromeda Technology & Automation
4 ** This is free software; you can redistribute it and/or modify it under the
5 ** terms of the GNU General Public License, see the file COPYING.
6 ***************************************************************************
7 ** MODULE INFORMATION *
8 ***********************
9 ** FILE NAME : message.cpp
10 ** SYSTEM NAME : Gnucomo - Gnu Computer Monitoring
11 ** VERSION NUMBER : $Revision: 1.11 $
13 ** DESCRIPTION : Implementation of the message handling classes
18 ***************************************************************************
19 ** ADMINISTRATIVE INFORMATION *
20 ********************************
21 ** ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
22 ** CREATION DATE : Sep 16, 2002
23 ** LAST UPDATE : Jul 24, 2003
25 **************************************************************************/
27 /*****************************
29 Revision 1.11 2003-08-05 08:15:00 arjen
30 Debug output to the log stream instead of cerr.
31 Fixed namespace problems in XPath searches of the DOM.
32 Moved string utility functions to a separate file.
34 Revision 1.10 2003/04/29 09:16:44 arjen
36 Only cooked log entries for now.
38 Revision 1.9 2003/03/29 09:04:10 arjen
39 Extract the hostname out of the 'From:' or 'Message-Id:' line
42 Revision 1.8 2003/03/16 09:42:40 arjen
43 Read IRIX system logs.
45 Revision 1.7 2003/02/21 08:08:05 arjen
46 Gcm_input also detects packages that are removed from the system.
47 Determining the version number of a package in a RPM
48 list is improved. Only the last one or two parts of the string that
49 begin with a '-' and a number are considered the version.
51 Revision 1.6 2003/02/05 09:37:51 arjen
52 Create notifications when a new package is discovered
53 in a 'rpm -qa' list or when the version of a package is changed.
55 Revision 1.4 2002/12/06 22:26:28 arjen
56 Set the value of log.processed to FALSE when inserting a
57 new log entry into the database
58 When a syslog entry arrives from last year, gcm_input subtracts one from the
59 year of arrival to create the year of the log entry.
60 Read output from "rpm -qa" and enter packages in the parameter table.
62 Revision 1.3 2002/11/09 08:04:27 arjen
63 Added a reference to the GPL
65 Revision 1.2 2002/11/04 10:13:36 arjen
66 Use proper namespace for iostream classes
68 Revision 1.1 2002/10/05 10:25:49 arjen
69 Creation of gcm_input and a first approach to a web interface
71 *****************************/
73 static const char *RCSID = "$Id: message.cpp,v 1.11 2003-08-05 08:15:00 arjen Exp $";
76 #include <libxml/xpath.h>
77 #include <libxml/debugXML.h>
80 extern bool verbose; /* Defined in the main application */
82 extern bool incremental;
83 extern std::ostream *log;
85 /* Utility functions */
87 extern String SQL_Escape(String s);
89 /*=========================================================================
91 ** SYNOPSIS : bool operator >> (message_buffer &, String &)
93 ** RETURN VALUE : True if input was available.
95 ** DESCRIPTION : Input operator. Read the next line from the message.
101 ** LAST MODIFIED : Nov 04, 2002
102 **=========================================================================
105 bool operator >> (message_buffer &b, String &s)
107 bool input_ok = false;
109 if (b.next_line == b.buffer.end())
115 b.buffer.push_back(l);
117 // next_line keeps pointing to the end.
132 client_message::client_message(std::istream *in, gnucomo_database db)
139 gpg_encrypted = false;
140 classification = UNKNOWN;
145 static const String syslog_date_re("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
146 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}");
147 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}");
148 static const String email_address_re("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
150 static const regex re_syslog(syslog_date_re + " [[:alnum:]]+ [[:alpha:]]+.*:.+");
151 static const regex re_syslog_irix(syslog_date_re + " [0-7][A-T]:[[:alnum:]]+ [[:alpha:]]+.*:.+");
152 static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
153 static const regex re_dump("^ *DUMP: Date of this level");
154 static const regex re_accesslog("(GET|POST) .+ HTTP");
155 static const regex re_errorlog("^\\[" + unix_date_re + "\\] \\[(error|notice)\\] .+");
156 static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
158 static const regex re_syslog_date("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
159 static const regex re_uxmail_from("^From [^ \t]+[ ]+" + unix_date_re);
160 static const regex re_mail_From("^From:[[:blank:]]+");
161 static const regex re_mail_Date("^Date:[[:blank:]]+" + mail_date_re);
162 static const regex re_mail_MsId("^Message-Id:[[:blank:]]+");
163 static const regex re_email_address("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
164 static const regex re_email_user("[[:alnum:]_.-]+@");
165 static const regex re_xml_header("xml .*\?>$");
167 /*=========================================================================
168 ** NAME : readXMLinput
169 ** SYNOPSIS : int readXMLinput(String first_line)
171 ** RETURN VALUE : Parse the XML input and extract the header information
179 ** LAST MODIFIED : Jul 24, 2003
180 **=========================================================================
183 int client_message::readXMLinput(String first_line)
185 xmlParserCtxtPtr ctxt;
187 xmlNodePtr root, item;
188 xmlNsPtr namespaces[1];
190 xmlXPathObjectPtr res;
191 xmlXPathContextPtr pathcontext;
194 ctxt = xmlCreatePushParserCtxt(NULL, NULL, first_line, ~first_line, NULL);
195 while (input >> line)
197 xmlParseChunk(ctxt, line, ~line, 0);
199 xmlParseChunk(ctxt, "", 0, 1);
200 xmlDom = ctxt->myDoc;
201 xmlFreeParserCtxt(ctxt);
203 root = xmlDocGetRootElement(xmlDom);
204 namespaces[0] = root->ns;
206 //TODO Ought to check root->name and root->ns->href
208 pathcontext = xmlXPathNewContext(xmlDom);
209 pathcontext->node = xmlDocGetRootElement(xmlDom);
210 pathcontext->namespaces = namespaces;
211 pathcontext->nsNr = 1;
214 xmlDebugDumpNodeList(stdout, pathcontext->node, 0);
217 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:messagetype/text()", pathcontext);
218 if (res->nodesetval != NULL)
221 xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
223 item = *res->nodesetval->nodeTab;
227 *log << "Message type not found in XML header.\n";
230 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:hostname/text()", pathcontext);
231 if (res->nodesetval != NULL)
234 xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
236 item = *res->nodesetval->nodeTab;
237 hostname = (const char *)item->content;
241 *log << "Hostname not found in XML header.\n";
244 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:service/text()", pathcontext);
245 if (res->nodesetval != NULL)
247 item = *res->nodesetval->nodeTab;
248 service = (const char *)item->content;
250 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:time/text()", pathcontext);
251 if (res->nodesetval != NULL)
253 item = *res->nodesetval->nodeTab;
254 arrival = String((char *)item->content);
257 //xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
261 /*=========================================================================
263 ** SYNOPSIS : double classify(String host, date arriv_d, hour arriv_t, String serv)
265 ** RETURN VALUE : The certainty with which the message is classified.
273 ** LAST MODIFIED : Apr 28, 2003
274 **=========================================================================
277 double client_message::classify(String host, UTC arriv, String serv)
285 /* First, check if the message has a mail header. */
287 if (input >> line && line == re_uxmail_from)
293 /* Scan ahead for the hostname and date of arrival. */
295 while (input >> line && line != "")
297 if (line == re_mail_From)
299 from_address = line(re_email_address);
300 from_address(re_email_user) = ""; // Remove the user part;
301 if (from_address != "" && ~hostname < ~from_address)
303 *log << "Detected hostname " << from_address << "\n";
304 hostname = from_address;
307 if (line == re_mail_MsId)
309 from_address = line(re_email_address);
310 from_address(re_email_user) = ""; // Remove the user part;
311 if (from_address != "" && ~hostname < ~from_address)
313 *log << "Detected hostname " << from_address << "\n";
314 hostname = from_address;
317 if (line == re_mail_Date)
319 arrival = UTC(line(regex(mail_date_re)));
325 // Push the first line back, we need to read it again.
331 * Now that we have the mail header out of the way, try to figure
332 * out what the content of the message is.
336 while (input >> line && certainty < 0.9)
340 *log << " testing: " << line << "\n";
343 if (line == re_xml_header)
346 classification = XML;
349 *log << "XML input detected.\n";
353 else if (line == re_syslog)
356 classification = SYSLOG;
359 *log << "Syslog detected.\n";
362 else if (line == re_syslog_irix)
365 classification = SYSLOG_IRIX;
368 *log << "IRIX Syslog detected.\n";
371 else if (line == re_PGP)
374 gpg_encrypted = true;
375 *log << "The message is PGP/GnuPG encrypted.\n";
377 else if (line == re_dump)
382 *log << "DUMP output detected.\n";
385 else if (line == re_accesslog)
388 classification = ACCESSLOG;
392 *log << "HTTP access log detected.\n";
395 else if (line == re_errorlog)
398 classification = ERRORLOG;
402 *log << "HTTP error log detected.\n";
405 else if (line == re_rpm)
408 classification = RPMLIST;
412 *log << "RPM package list detected.\n";
420 *log << "Can not determine the hostname where the message came from.\n";
423 else if (!arrival.proper())
425 *log << "Arrival time is not knwon.\n";
436 /*=========================================================================
438 ** SYNOPSIS : int enterXML()
440 ** RETURN VALUE : None
442 ** DESCRIPTION : Analyze the DOM tree from the XML input.
443 ** The DOM tree was previously parsed by readXMLinput().
449 ** LAST MODIFIED : Jul 24, 2003
450 **=========================================================================
453 void client_message::enterXML()
455 xmlXPathObjectPtr res;
456 xmlXPathContextPtr pathcontext;
457 xmlNsPtr namespaces[1];
459 /* Try to find the host in the database */
463 objectid = database.find_host(hostname);
466 *log << "Please define the host " << hostname << " in the database.\n";
471 *log << "Object id for " << hostname << " is " << objectid << "\n";
474 pathcontext = xmlXPathNewContext(xmlDom);
475 pathcontext->node = xmlDocGetRootElement(xmlDom);
476 namespaces[0] = pathcontext->node->ns;
477 pathcontext->namespaces = namespaces;
478 pathcontext->nsNr = 1;
480 res = xmlXPathEval((const xmlChar *)"gcmt:data/node()", pathcontext);
482 if (res->nodesetval != NULL)
484 // Find the first child element of the <data> element.
486 xmlNodePtr node = *res->nodesetval->nodeTab;
487 while (node->type != XML_ELEMENT_NODE)
491 if (strcmp((char *)node->name, "log") == 0)
493 // Each child contains a log entry, raw or cooked.
495 node = node->children;
498 if (node->type == XML_ELEMENT_NODE)
500 if (strcmp((char *)node->name, "raw") == 0)
502 *log << "Can not cook <raw> log elements yet.\n";
504 else if (strcmp((char *)node->name, "cooked") == 0)
506 // Find the parts of the log entry
516 *log << "Analyzing cooked element.\n";
518 pathcontext->node = node;
520 res = xmlXPathEval((const xmlChar *)"hostname/text()", pathcontext);
521 if (res->nodesetval != NULL)
523 item = *res->nodesetval->nodeTab;
524 log_hostname = (const char *)item->content;
525 if (log_hostname != hostname(0, ~log_hostname))
527 *log << "Hostname " << log_hostname << " does not match.\n";
533 log_hostname = hostname;
536 res = xmlXPathEval((const xmlChar *)"service/text()", pathcontext);
537 if (res->nodesetval != NULL)
539 item = *res->nodesetval->nodeTab;
540 log_service = (const char *)item->content;
544 log_service = service;
547 res = xmlXPathEval((const xmlChar *)"timestamp/text()", pathcontext);
548 if (res->nodesetval != NULL)
550 item = *res->nodesetval->nodeTab;
551 log_date = String((const char *)item->content);
555 *log << "<timestamp> missing from cooked log element.\n";
558 res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
559 if (res->nodesetval != NULL)
561 item = *res->nodesetval->nodeTab;
562 raw = String((const char *)item->content);
566 *log << "<raw> missing from cooked log element.\n";
569 if (raw != "" && log_hostname != "" && log_date.proper())
571 String insertion("insert into log (objectid, servicecode,"
572 " object_timestamp, timestamp, rawdata, processed) values (");
574 /* Insert a new record into the log table */
576 insertion += "'" + objectid + "',";
577 insertion += "'" + log_service + "',";
578 insertion += "'" + log_date.format("%Y-%m-%d %T") + "',";
579 insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
580 insertion += "'" + SQL_Escape(raw) + "',FALSE";
585 *log << insertion << "\n";
589 database.Query(insertion);
604 *log << "Data element " << node->name << " is not supported.\n";
609 *log << "Data node not found.\n";
613 /*=========================================================================
615 ** SYNOPSIS : int enter()
617 ** RETURN VALUE : The number of lines successfully parsed from the input
625 ** LAST MODIFIED : Jul 24, 2003
626 **=========================================================================
629 int client_message::enter()
631 if (classification == XML)
641 String change_notification("");
642 String create_notification("");
643 bool initial_entry = false;
645 std::list<String> packages;
648 /* Double-check the classification of the message */
650 if (classification == UNKNOWN || certainty < 0.9 || gpg_encrypted)
657 // Skip the mail header.
659 while (input >> line && line != "");
662 /* Try to find the host in the database */
666 objectid = database.find_host(hostname);
669 *log << "Please define the host " << hostname << " in the database.\n";
674 *log << "Object id for " << hostname << " is " << objectid << "\n";
677 if (classification == RPMLIST)
682 /* Read all packages, so we will know which ones are */
683 /* missing at the end. */
685 qry = "select name from parameter where objectid='";
686 qry += objectid + "' and class='package'";
687 n_packages = database.Query(qry);
688 initial_entry = n_packages == 0;
691 *log << n_packages << " packages in database.\n";
693 for (int t = 0; t < n_packages; t++)
695 packages.push_back(database.Field(t, "name"));
698 *log << "Package list built: " << packages.size() << ".\n";
702 /* Scan the input line by line, entring records into the database */
704 String rest; // Rest of the line to be parsed
706 while (input >> line)
710 *log << line << "\n";
714 /* Check each line if it contains valid information */
718 switch (classification)
724 check = &re_syslog_irix;
727 check = &re_accesslog;
730 check = &re_errorlog;
743 String insertion("insert into log (objectid, servicecode,"
744 " object_timestamp, timestamp, rawdata, processed) values (");
747 switch (classification)
750 datestring = line(0,16);
751 log_date = datestring;
752 log_time = datestring;
753 if (log_date.Year() < 0 || log_date.Year() > 2500)
755 // The year is not in the log file. Assume the year of arrival,
756 // unless this puts the log entry at a later date than the arrival date.
757 // This happens e.g. when a log entry from December arrives in Januari.
759 log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
760 if (log_date > date(arrival))
762 log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
768 *log << " Log timestamp = " << log_date << " " << log_time << "\n";
772 if (rest(0,i) == hostname(0,i))
777 *log << " Hostname matches.\n";
778 *log << " rest = " << rest << "\n";
780 for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
783 *log << " Service name = " << rest(0,i) << "\n";
786 /* Insert a new record into the log table */
788 insertion += "'" + objectid + "',";
789 insertion += "'" + rest(0,i) + "',";
790 insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
791 insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
792 insertion += "'" + SQL_Escape(line) + "',FALSE";
797 *log << insertion << "\n";
801 database.Query(insertion);
813 *log << " Hostname " << rest(0,i) << " does not match.\n";
818 datestring = line(0,16);
819 log_date = datestring;
820 log_time = datestring;
821 if (log_date.Year() < 0 || log_date.Year() > 2500)
823 // The year is not in the log file. Assume the year of arrival,
824 // unless this puts the log entry at a later date than the arrival date.
825 // This happens e.g. when a log entry from December arrives in Januari.
827 log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
828 if (log_date > date(arrival))
830 log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
836 *log << " Log timestamp = " << log_date << " " << log_time << "\n";
840 if (rest(0,i) == hostname(0,i))
845 *log << " Hostname matches.\n";
846 *log << " rest = " << rest << "\n";
848 for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
851 *log << " Service name = " << rest(0,i) << "\n";
854 /* Insert a new record into the log table */
856 insertion += "'" + objectid + "',";
857 insertion += "'" + rest(0,i) + "',";
858 insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
859 insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
860 insertion += "'" + SQL_Escape(line) + "',FALSE";
865 *log << insertion << "\n";
869 database.Query(insertion);
881 *log << " Hostname " << rest(0,i) << " does not match.\n";
886 datestring = line(regex("\\[.+\\]"));
889 datestring[datestring.index(':')] = ' ';
890 log_date = datestring;
891 log_time = datestring;
894 *log << " Log timestamp = " << log_date << " " << log_time << "\n";
896 insertion += "'" + objectid + "',";
897 insertion += "'" + service + "',";
898 insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
899 insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
900 insertion += "'" + SQL_Escape(line) + "',FALSE";
905 *log << insertion << "\n";
909 database.Query(insertion);
921 datestring = line(regex("\\[.+\\]"));
924 log_date = datestring;
925 log_time = datestring;
928 *log << " Log timestamp = " << log_date << " " << log_time << "\n";
930 insertion += "'" + objectid + "',";
931 insertion += "'" + service + "',";
932 insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
933 insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
934 insertion += "'" + SQL_Escape(line) + "',FALSE";
939 *log << insertion << "\n";
943 database.Query(insertion);
955 // Scan a list of packages and versions from "rpm -a".
956 // A similar listing can be created on IRIX 6.5 by using the
957 // command "showprods -3 -n|awk '{printf "%s-%s\n",$2,$3}'|grep -v '^[-=]' \
958 // |grep -v Version-Description".
960 // We have to separate the package name and the version.
961 // The separation is marked by a '-', followed by a digit.
962 // However, there may be other sequences of '-'digit in the package name,
963 // do we have to scan ahead until there is at most one such sequence
964 // left in the version string. The '-'digit seqeunce inside the
965 // version usually separates the version and the release number.
967 int version_start, next_version_start;
971 next_version_start = i;
973 while (i < ~line - 1)
975 while (i < ~line - 1 && !(line[i] == '-' && isdigit(line[i + 1])))
981 version_start = next_version_start;
982 next_version_start = i;
987 if (!isdigit(line[version_start + 1]))
989 version_start = next_version_start;
991 String package(line(0,version_start));
992 String version(line(version_start + 1, ~line));
999 *log << "Package is " << package;
1000 *log << ", version is " << version << "\n";
1003 // Construct a qry to check the package's existance
1005 qry = "select paramid from parameter where objectid='";
1006 qry += objectid + "' and class='package' and name='";
1007 qry += package + "'";
1009 if (database.Query(qry) == 1)
1011 std::list<String>::iterator lp;
1013 lp = find(packages.begin(), packages.end(), package);
1014 if (lp != packages.end())
1020 *log << "Could NOT find " << package << " in list.\n";
1023 paramid = database.Field(0, "paramid");
1024 qry = "select value from property where paramid='";
1025 qry += paramid + "' and name='version'";
1026 if (database.Query(qry) == 0)
1028 *log << "Database corruption: Package " << package;
1029 *log << " does not have a 'version' property.\n";
1031 else if (database.Field(0, "value") != version)
1035 *log << " Parameter " << package << " has different version\n";
1037 insertion = "update property set value='";
1038 insertion += version + "' where paramid='";
1039 insertion += paramid + "' and name='version'";
1041 insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
1042 insert_h += " values ('";
1043 insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'MODIFIED', 'version', '";
1044 insert_h += version + "')";
1046 database.Query(insertion);
1047 database.Query(insert_h);
1049 if (change_notification == "")
1051 remark = "Gnucomo detected a different version for package parameter(s) ";
1052 change_notification = database.new_notification(objectid, "property modified", remark);
1055 if (change_notification != "")
1057 insertion = "insert into parameter_notification (notificationid, paramid) values ('";
1058 insertion += change_notification + "', '";
1059 insertion += paramid + "')";
1061 database.Query(insertion);
1065 *log << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
1072 *log << " Parameter " << package << " has not changed.\n";
1081 *log << " Parameter " << package << " does not exist.\n";
1083 // Create a new package parameter, including version property and history record
1085 insertion = "insert into parameter (objectid, name, class, description) values ('";
1086 insertion += objectid + "', '" + package + "', 'package', 'RPM package " + package + "')";
1090 *log << insertion << "\n";
1094 database.Query(insertion);
1095 qry = "select paramid from parameter where objectid='";
1096 qry += objectid + "' and class='package' and name='";
1097 qry += package + "'";
1098 database.Query(qry);
1099 paramid = database.Field(0, "paramid");
1102 insertion = "insert into property (paramid, name, value, type) values ('";
1103 insertion += paramid + "', 'version', '";
1104 insertion += version + "', 'STATIC')";
1105 insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
1106 insert_h += " values ('";
1107 insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'CREATED', 'version', '";
1108 insert_h += version + "')";
1112 *log << insertion << "\n" << insert_h << "\n";
1116 database.Query(insertion);
1117 database.Query(insert_h);
1120 if (create_notification == "")
1122 remark = "Gnucomo detected new parameter(s) of class package";
1123 create_notification = database.new_notification(objectid, "parameter created", remark);
1125 if (create_notification != "")
1127 insertion = "insert into parameter_notification (notificationid, paramid) values ('";
1128 insertion += create_notification + "', '";
1129 insertion += paramid + "')";
1131 database.Query(insertion);
1135 *log << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
1153 *log << "gcm_input WARNING: Not a valid line: " << line << "\n";
1157 if (classification == RPMLIST && !incremental)
1159 std::list<String>::iterator lp;
1160 String remove_notification("");
1163 * If there are any packages left in the list, they seem to have
1164 * disappeared from the system.
1167 for (lp = packages.begin(); lp != packages.end(); lp++)
1173 // Construct a qry to check the package's existance
1175 qry = "select paramid from parameter where objectid='";
1176 qry += objectid + "' and class='package' and name='";
1179 if (database.Query(qry) == 1)
1181 paramid = database.Field(0, "paramid");
1182 qry ="select change_nature from history where paramid='";
1183 qry += paramid + "' order by modified desc";
1184 if (database.Query(qry) <= 0)
1186 *log << "Database ERROR: no history record for parameter " << *lp << ".\n";
1188 else if (database.Field(0, "change_nature") != "REMOVED")
1192 *log << "Removing parameter " << *lp << ".\n";
1195 insert = "insert into history (paramid, modified, change_nature)";
1196 insert += " values ('";
1197 insert += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'REMOVED')";
1199 database.Query(insert);
1201 if (remove_notification == "")
1203 remark = "Gnucomo detected that package(s) have disappeared ";
1204 remove_notification = database.new_notification(objectid, "parameter removed", remark);
1207 if (remove_notification != "")
1209 insert = "insert into parameter_notification (notificationid, paramid) values ('";
1210 insert += remove_notification + "', '";
1211 insert += paramid + "')";
1213 database.Query(insert);
1217 *log << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
1226 *log << nr_lines << " lines parsed from the log file.\n";