0e9dca91414d01743b4aaaa8ebbbbb2ef1efe803
[gnucomo.git] / src / gcm_input / message.cpp
1
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.8 $
12 **
13 **  DESCRIPTION      :  Implementation of the message handling classes
14 **
15 **  EXPORTED OBJECTS : 
16 **  LOCAL    OBJECTS : 
17 **  MODULES  USED    :
18 ***************************************************************************
19 **  ADMINISTRATIVE INFORMATION *
20 ********************************
21 **      ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
22 **      CREATION DATE   : Sep 16, 2002
23 **      LAST UPDATE     : Feb 28, 2003
24 **      MODIFICATIONS   : 
25 **************************************************************************/
26
27 /*****************************
28    $Log: message.cpp,v $
29    Revision 1.8  2003-03-16 09:42:40  arjen
30    Read IRIX system logs.
31
32    Revision 1.7  2003/02/21 08:08:05  arjen
33    Gcm_input also detects packages that are removed from the system.
34    Determining the version number of a package in a RPM
35    list is improved. Only the last one or two parts of the string that
36    begin with a '-' and a number are considered the version.
37
38    Revision 1.6  2003/02/05 09:37:51  arjen
39    Create notifications when a new package is discovered
40    in a 'rpm -qa' list or when the version of a package is changed.
41
42    Revision 1.4  2002/12/06 22:26:28  arjen
43    Set the value of log.processed to FALSE when inserting a
44    new log entry into the database
45    When a syslog entry arrives from last year, gcm_input subtracts one from the
46    year of arrival to create the year of the log entry.
47    Read output from "rpm -qa" and enter packages in the parameter table.
48
49    Revision 1.3  2002/11/09 08:04:27  arjen
50    Added a reference to the GPL
51
52    Revision 1.2  2002/11/04 10:13:36  arjen
53    Use proper namespace for iostream classes
54
55    Revision 1.1  2002/10/05 10:25:49  arjen
56    Creation of gcm_input and a first approach to a web interface
57
58 *****************************/
59
60 static const char *RCSID = "$Id: message.cpp,v 1.8 2003-03-16 09:42:40 arjen Exp $";
61
62 #include <algorithm>
63 #include "message.h"
64
65 extern bool verbose;   /*  Defined in the main application */
66 extern bool testmode;
67
68 /*   Utility functions   */
69
70 String SQL_Escape(String s);
71
72 /*=========================================================================
73 **  NAME           : operator >>
74 **  SYNOPSIS       : bool operator >> (message_buffer &, String &)
75 **  PARAMETERS     : 
76 **  RETURN VALUE   : True if input was available.
77 **
78 **  DESCRIPTION    : Input operator. Read the next line from the message.
79 **
80 **  VARS USED      :
81 **  VARS CHANGED   :
82 **  FUNCTIONS USED :
83 **  SEE ALSO       :
84 **  LAST MODIFIED  : Nov 04, 2002
85 **=========================================================================
86 */
87
88 bool operator >> (message_buffer &b, String &s)
89 {
90    bool   input_ok = false;
91
92    if (b.next_line == b.buffer.end())
93    {
94       String   l;
95
96       if (*(b.input) >> l)
97       {
98          b.buffer.push_back(l);
99
100          //   next_line keeps pointing to the end.
101
102          s = l;
103          input_ok = true;
104       }
105    }
106    else
107    {
108       s = *(b.next_line);
109       b.next_line++;
110       input_ok = true;
111    }
112    return input_ok;
113 }
114
115 client_message::client_message(std::istream *in, gnucomo_database db)
116 {
117    input.from(in);
118    database = db;
119
120    hostname = "";
121    mail_header   = false;
122    gpg_encrypted = false;
123    classification = UNKNOWN;
124    certainty      = 0.0;
125 }
126
127 static const String syslog_date_re("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
128 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}");
129 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}");
130
131 static const regex re_syslog(syslog_date_re + " [[:alnum:]]+ [[:alpha:]]+.*:.+");
132 static const regex re_syslog_irix(syslog_date_re + " [0-7][A-T]:[[:alnum:]]+ [[:alpha:]]+.*:.+");
133 static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
134 static const regex re_dump("^ *DUMP: Date of this level");
135 static const regex re_accesslog("(GET|POST) .+ HTTP");
136 static const regex re_errorlog("^\\[" + unix_date_re + "\\] \\[(error|notice)\\] .+");
137 static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
138
139 static const regex re_syslog_date("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
140 static const regex re_uxmail_from("^From - " + unix_date_re);
141 static const regex re_mail_From("^From:[[:blank:]]+");
142 static const regex re_mail_Date("^Date:[[:blank:]]+" + mail_date_re);
143 static const regex re_email_address("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
144 static const regex re_email_user("[[:alnum:]_.-]+@");
145
146 /*=========================================================================
147 **  NAME           : classify
148 **  SYNOPSIS       : double classify(String host, date arriv_d, hour arriv_t, String serv)
149 **  PARAMETERS     : 
150 **  RETURN VALUE   : The certainty with which the message is classified.
151 **
152 **  DESCRIPTION    : 
153 **
154 **  VARS USED      :
155 **  VARS CHANGED   :
156 **  FUNCTIONS USED :
157 **  SEE ALSO       :
158 **  LAST MODIFIED  : Nov 16, 2002
159 **=========================================================================
160 */
161
162 double client_message::classify(String host, UTC arriv, String serv)
163 {
164    String    line;
165
166    hostname    = host;
167    arrival     = arriv;
168    service     = serv;
169
170    /*  First, check if the message has a mail header. */
171
172    if (input >> line && line == re_uxmail_from)
173    {
174       String    from_address;
175
176       mail_header = true;
177
178       /*  Scan ahead for the hostname and date of arrival.  */
179
180       while (input >> line && line != "")
181       {
182          if (line == re_mail_From)
183          {
184             from_address = line(re_email_address);
185             from_address(re_email_user) = "";            //  Remove the user part;
186             hostname = from_address;
187          }
188          if (line == re_mail_Date)
189          {
190             arrival = UTC(line(regex(mail_date_re)));
191          }
192       }
193    }
194    else
195    {
196       //  Push the first line back, we need to read it again.
197       --input;
198    }
199
200    /*
201     *  Now that we have the mail header out of the way, try to figure
202     *  out what the content of the message is.
203     */
204
205
206    while (input >> line && certainty < 0.9)
207    {
208       std::cout << "  testing: " << line << "\n";
209       if (line == re_syslog)
210       {
211          certainty = 1.0;
212          classification = SYSLOG;
213          if (verbose)
214          {
215             std::cout << "Syslog detected.\n";
216          }
217       }
218       else if (line == re_syslog_irix)
219       {
220          certainty = 1.0;
221          classification = SYSLOG_IRIX;
222          if (verbose)
223          {
224             std::cout << "IRIX Syslog detected.\n";
225          }
226       }
227       else if (line == re_PGP)
228       {
229          certainty = 1.0;
230          gpg_encrypted = true;
231          std::cerr << "The message is PGP/GnuPG encrypted.\n";
232       }
233       else if (line == re_dump)
234       {
235           certainty = 1.0;
236           if (verbose)
237           {
238              std::cout << "DUMP output detected.\n";
239           }
240       }
241       else if (line == re_accesslog)
242       {
243           certainty = 1.0;
244           classification = ACCESSLOG;
245           service = "httpd";
246           if (verbose)
247           {
248              std::cout << "HTTP access log detected.\n";
249           }
250       }
251       else if (line == re_errorlog)
252       {
253           certainty = 1.0;
254           classification = ERRORLOG;
255           service = "httpd";
256           if (verbose)
257           {
258              std::cout << "HTTP error log detected.\n";
259           }
260       }
261       else if (line == re_rpm)
262       {
263           certainty = 1.0;
264           classification = RPMLIST;
265           service = "";
266           if (verbose)
267           {
268              std::cout << "RPM package list detected.\n";
269           }
270       }
271    }
272    input.rewind();
273
274    if (hostname == "")
275    {
276       std::cerr <<  "Can not determine the hostname where the message came from.\n";
277       certainty = 0.0;
278    }
279    else if (!arrival.proper())
280    {
281       std::cerr << "Arrival time is not knwon.\n";
282       certainty = 0.0;
283    }
284    else
285    {
286       certainty = 1.0;
287    }
288
289    return certainty;
290 }
291
292 /*=========================================================================
293 **  NAME           : enter
294 **  SYNOPSIS       : int enter()
295 **  PARAMETERS     : 
296 **  RETURN VALUE   : The number of lines successfully parsed from the input
297 **
298 **  DESCRIPTION    : 
299 **
300 **  VARS USED      :
301 **  VARS CHANGED   :
302 **  FUNCTIONS USED :
303 **  SEE ALSO       :
304 **  LAST MODIFIED  : Feb 19, 2003
305 **=========================================================================
306 */
307
308 int client_message::enter()
309 {
310    long   nr_lines = 0;
311    String line;
312    String qry;
313
314    String change_notification("");
315    String create_notification("");
316    bool initial_entry = false;
317
318    std::list<String>  packages;
319
320
321    /*  Double-check the classification of the message */
322
323    if (classification == UNKNOWN || certainty < 0.9 || gpg_encrypted)
324    {
325       return 0;
326    }
327
328    if (mail_header)
329    {
330       //  Skip the mail header.
331
332       while (input >> line && line != "");
333    }
334
335    /*  Try to find the host in the database */
336
337    String objectid;
338
339    objectid = database.find_host(hostname);
340    if (objectid == "")
341    {
342       std::cerr << "Please define the host " << hostname << " in the database.\n";
343       return 0;
344    }
345    if (verbose)
346    {
347       std::cout << "Object id for " << hostname << " is " << objectid << "\n";
348    }
349
350    if (classification == RPMLIST)
351    {
352
353       int n_packages;
354
355       /*   Read all packages, so we will know which ones are  */
356       /*   missing at the end.                                */
357
358       qry = "select name from parameter where objectid='";
359       qry += objectid + "' and class='package'";
360       n_packages = database.Query(qry);
361       initial_entry = n_packages == 0;
362
363       std::cout << n_packages << " packages in database.\n";
364       for (int t = 0; t < n_packages; t++)
365       {
366          packages.push_back(database.Field(t, "name"));
367       }
368       std::cout << "Package list built: " << packages.size() << ".\n";
369    }
370
371    /*  Scan the input line by line, entring records into the database */
372
373    String rest;   //  Rest of the line to be parsed
374
375    while (input >> line)
376    {
377       if (verbose)
378       {
379          std::cout << line << "\n";
380       }
381
382
383       /*  Check each line if it contains valid information */
384
385       const regex *check;
386
387       switch (classification)
388       {
389       case SYSLOG:
390             check = &re_syslog;
391             break;
392       case SYSLOG_IRIX:
393             check = &re_syslog_irix;
394             break;
395       case ACCESSLOG:
396             check = &re_accesslog;
397             break;
398       case ERRORLOG:
399             check = &re_errorlog;
400             break;
401       case RPMLIST:
402             check = &re_rpm;
403             break;
404       }
405
406       if (line == *check)
407       {
408          date   log_date;
409          hour   log_time;
410          int    i;
411
412          String insertion("insert into log (objectid, servicecode,"
413                            " object_timestamp, timestamp, rawdata, processed) values (");
414          String datestring;
415
416          switch (classification)
417          {
418          case SYSLOG:
419             log_date = line;
420             log_time = line;
421             if (log_date.Year() < 0 || log_date.Year() > 2500)
422             {
423                //  The year is not in the log file. Assume the year of arrival,
424                //  unless this puts the log entry at a later date than the arrival date.
425                //  This happens e.g. when a log entry from December arrives in Januari.
426
427                log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
428                if (log_date > date(arrival))
429                {
430                   log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
431                }
432             }
433
434             if (verbose)
435             {
436                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
437             }
438             rest = line << 16;
439             i = rest.index(' ');
440             if (rest(0,i) == hostname(0,i))
441             {
442                rest <<= i + 1;
443                if (verbose)
444                {
445                   std::cout << "   Hostname matches.\n";
446                   std::cout << "   rest = " << rest << "\n";
447                }
448                for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
449                if (verbose)
450                {
451                   std::cout << "   Service name = " << rest(0,i) << "\n";
452                }
453
454                /*   Insert a new record into the log table   */
455
456                insertion += "'" + objectid + "',";
457                insertion += "'" + rest(0,i) + "',";
458                insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
459                insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
460                insertion += "'" + SQL_Escape(line) + "',FALSE";
461                insertion += ")";
462
463                if (testmode)
464                {
465                   std::cout << insertion << "\n";
466                }
467                else
468                {
469                   database.Query(insertion);
470                }
471
472                if (verbose)
473                {
474                   std::cout << "\n\n";
475                }
476
477                nr_lines++;
478             }
479             else
480             {
481                std::cerr << "   Hostname " << rest(0,i) << " does not match.\n";
482             }
483             break;
484
485          case SYSLOG_IRIX:
486             log_date = line;
487             log_time = line;
488             if (log_date.Year() < 0 || log_date.Year() > 2500)
489             {
490                //  The year is not in the log file. Assume the year of arrival,
491                //  unless this puts the log entry at a later date than the arrival date.
492                //  This happens e.g. when a log entry from December arrives in Januari.
493
494                log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
495                if (log_date > date(arrival))
496                {
497                   log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year() - 1);
498                }
499             }
500
501             if (verbose)
502             {
503                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
504             }
505             rest = line << 19;
506             i = rest.index(' ');
507             if (rest(0,i) == hostname(0,i))
508             {
509                rest <<= i + 1;
510                if (verbose)
511                {
512                   std::cout << "   Hostname matches.\n";
513                   std::cout << "   rest = " << rest << "\n";
514                }
515                for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
516                if (verbose)
517                {
518                   std::cout << "   Service name = " << rest(0,i) << "\n";
519                }
520
521                /*   Insert a new record into the log table   */
522
523                insertion += "'" + objectid + "',";
524                insertion += "'" + rest(0,i) + "',";
525                insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
526                insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
527                insertion += "'" + SQL_Escape(line) + "',FALSE";
528                insertion += ")";
529
530                if (testmode)
531                {
532                   std::cout << insertion << "\n";
533                }
534                else
535                {
536                   database.Query(insertion);
537                }
538
539                if (verbose)
540                {
541                   std::cout << "\n\n";
542                }
543
544                nr_lines++;
545             }
546             else
547             {
548                std::cerr << "   Hostname " << rest(0,i) << " does not match.\n";
549             }
550             break;
551
552          case ACCESSLOG:
553             datestring = line(regex("\\[.+\\]"));
554             datestring <<= 1;
555             datestring >>= 1;
556             datestring[datestring.index(':')] = ' ';
557             log_date = datestring;
558             log_time = datestring;
559             if (verbose)
560             {
561                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
562             }
563             insertion += "'" + objectid + "',";
564             insertion += "'" + service + "',";
565             insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
566             insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
567             insertion += "'" + SQL_Escape(line) + "',FALSE";
568             insertion += ")";
569
570             if (testmode)
571             {
572                std::cout << insertion << "\n";
573             }
574             else
575             {
576                database.Query(insertion);
577             }
578
579             if (verbose)
580             {
581                std::cout << "\n\n";
582             }
583
584             nr_lines++;
585             break;
586
587          case ERRORLOG:
588             datestring = line(regex("\\[.+\\]"));
589             datestring <<= 1;
590             datestring >>= 1;
591             log_date = datestring;
592             log_time = datestring;
593             if (verbose)
594             {
595                std::cout << "   Log timestamp  = " << log_date << " " << log_time << "\n";
596             }
597             insertion += "'" + objectid + "',";
598             insertion += "'" + service + "',";
599             insertion += "'" + log_date.format("%Y-%m-%d") + " " + log_time.format() + "',";
600             insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
601             insertion += "'" + SQL_Escape(line) + "',FALSE";
602             insertion += ")";
603
604             if (testmode)
605             {
606                std::cout << insertion << "\n";
607             }
608             else
609             {
610                database.Query(insertion);
611             }
612
613             if (verbose)
614             {
615                std::cout << "\n\n";
616             }
617
618             nr_lines++;
619             break;
620
621          case RPMLIST:
622             //  Scan a list of packages and versions from "rpm -a".
623             //  A similar listing can be created on IRIX 6.5 by using the
624             //  command "showprods -3 -n|awk '{printf "%s-%s\n",$2,$3}'|grep -v '^[-=]' \
625             //            |grep -v Version-Description".
626             //
627             //  We have to separate the package name and the version.
628             //  The separation is marked by a '-', followed by a digit.
629             //  However, there may be other sequences of '-'digit in the package name,
630             //  do we have to scan ahead until there is at most one such sequence
631             //  left in the version string. The '-'digit seqeunce inside the
632             //  version usually separates the version and the release number.
633
634             int  version_start, next_version_start;
635
636             i = line.index('-');
637             version_start = i;
638             next_version_start = i;
639
640             while (i < ~line - 1)
641             {
642                while (i < ~line - 1 && !(line[i] == '-' && isdigit(line[i + 1])))
643                {
644                   i++;
645                }
646                if (i < ~line - 1)
647                {
648                   version_start = next_version_start;
649                   next_version_start = i;
650                }
651                i++;
652             }
653             
654             String package(line(0,version_start));
655             String version(line(version_start + 1, ~line));
656             String paramid;
657             String remark;
658             String insert_h;
659
660             if (verbose)
661             {
662                std::cout << "Package is " << package;
663                std::cout << ", version is " << version << "\n";
664             }
665
666             //  Construct a qry to check the package's existance
667
668             qry = "select paramid from parameter where objectid='";
669             qry += objectid + "' and class='package' and name='";
670             qry += package + "'";
671
672             if (database.Query(qry) == 1)
673             {
674                std::list<String>::iterator  lp;
675
676                lp = find(packages.begin(), packages.end(), package);
677                if (lp != packages.end())
678                {
679                   packages.erase(lp);
680                }
681                else
682                {
683                   std::cerr <<  "Could NOT find " << package << " in list.\n";
684                }
685
686                paramid = database.Field(0, "paramid");
687                qry = "select value from property where paramid='";
688                qry += paramid + "' and name='version'";
689                if (database.Query(qry) == 0)
690                {
691                   std::cerr << "Database corruption: Package " << package;
692                   std::cerr << " does not have a 'version' property.\n";
693                }
694                else if (database.Field(0, "value") != version)
695                {
696                   if (verbose)
697                   {
698                      std::cout << "  Parameter " << package << " has different version\n";
699                   }
700                   insertion = "update property set value='";
701                   insertion += version + "' where paramid='";
702                   insertion += paramid + "' and name='version'";
703
704                   insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
705                   insert_h += " values ('";
706                   insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'MODIFIED', 'version', '";
707                   insert_h += version + "')";
708
709                   database.Query(insertion);
710                   database.Query(insert_h);
711
712                   if (change_notification == "")
713                   {
714                      remark = "Gnucomo detected a different version for package parameter(s) ";
715                      change_notification = database.new_notification(objectid, "property modified", remark);
716                   }
717
718                   if (change_notification != "")
719                   {
720                      insertion = "insert into parameter_notification (notificationid, paramid) values ('";
721                      insertion += change_notification + "', '";
722                      insertion += paramid + "')";
723
724                      database.Query(insertion);
725                   }
726                   else
727                   {
728                      std::cerr << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
729                   }
730                }
731                else
732                {
733                   if (verbose)
734                   {
735                      std::cout << "   Parameter " << package << " has not changed.\n";
736                   }
737                }
738             }
739             else
740             {
741
742                if (verbose)
743                {
744                   std::cout << "  Parameter " << package << " does not exist.\n";
745                }
746                //  Create a new package parameter, including version property and history record
747
748                insertion = "insert into parameter (objectid, name, class, description) values ('";
749                insertion += objectid + "', '" + package + "', 'package', 'RPM package " + package + "')";
750                if (testmode)
751                {
752                   paramid = "0";
753                   std::cout << insertion << "\n";
754                }
755                else
756                {
757                   database.Query(insertion);
758                   qry = "select paramid from parameter where objectid='";
759                   qry += objectid + "' and class='package' and name='";
760                   qry += package + "'";
761                   database.Query(qry);
762                   paramid = database.Field(0, "paramid");
763                }
764
765                insertion = "insert into property (paramid, name, value, type) values ('";
766                insertion += paramid + "', 'version', '";
767                insertion += version + "', 'STATIC')";
768                insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
769                insert_h += " values ('";
770                insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'CREATED', 'version', '";
771                insert_h += version + "')";
772
773                if (testmode)
774                {
775                   std::cout << insertion << "\n" << insert_h << "\n";
776                }
777                else
778                {
779                   database.Query(insertion);
780                   database.Query(insert_h);
781                   if (!initial_entry)
782                   {
783                      if (create_notification == "")
784                      {
785                         remark = "Gnucomo detected new parameter(s) of class package";
786                         create_notification = database.new_notification(objectid, "parameter created", remark);
787                      }
788                      if (create_notification != "")
789                      {
790                         insertion = "insert into parameter_notification (notificationid, paramid) values ('";
791                         insertion += create_notification + "', '";
792                         insertion += paramid + "')";
793
794                         database.Query(insertion);
795                      }
796                      else
797                      {
798                         std::cerr << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
799                      }
800                   }
801                }
802             }
803
804             if (verbose)
805             {
806                std::cout << "\n";
807             }
808
809             nr_lines++;
810             break;
811
812          }
813       }
814       else
815       {
816          std::cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
817       }
818    }
819
820    if (classification == RPMLIST)
821    {
822       std::list<String>::iterator  lp;
823       String     remove_notification("");
824
825       /*
826        *     If there are any packages left in the list, they seem to have
827        *     disappeared from the system.
828        */
829
830       for (lp = packages.begin(); lp != packages.end(); lp++)
831       {
832          String paramid;
833          String remark;
834          String insert;
835
836          //  Construct a qry to check the package's existance
837
838          qry = "select paramid from parameter where objectid='";
839          qry += objectid + "' and class='package' and name='";
840          qry += *lp + "'";
841
842          if (database.Query(qry) == 1)
843          {
844             paramid = database.Field(0, "paramid");
845             qry ="select change_nature from history where paramid='";
846             qry += paramid + "' order by modified desc";
847             if (database.Query(qry) <= 0)
848             {
849                std::cerr << "Database ERROR: no history record for parameter " << *lp << ".\n";
850             }
851             else if (database.Field(0, "change_nature") != "REMOVED")
852             {
853                if (verbose)
854                {
855                  std::cout << "Removing parameter " << *lp << ".\n";
856                }
857
858                insert = "insert into history (paramid, modified, change_nature)";
859                insert += " values ('";
860                insert += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'REMOVED')";
861
862                database.Query(insert);
863
864                if (remove_notification == "")
865                {
866                   remark = "Gnucomo detected that package(s) have disappeared ";
867                   remove_notification = database.new_notification(objectid, "parameter removed", remark);
868                }
869
870                if (remove_notification != "")
871                {
872                   insert = "insert into parameter_notification (notificationid, paramid) values ('";
873                   insert += remove_notification + "', '";
874                   insert += paramid + "')";
875
876                   database.Query(insert);
877                }
878                else
879                {
880                   std::cerr << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
881                }
882             }
883          }
884       }
885    }
886
887    if (verbose)
888    {
889       std::cout << nr_lines << " lines parsed from the log file.\n";
890    }
891    return nr_lines;
892 }
893
894 /*=========================================================================
895 **  NAME           : SQL_Escape
896 **  SYNOPSIS       : String SQL_Escape(String)
897 **  PARAMETERS     : 
898 **  RETURN VALUE   : 
899 **
900 **  DESCRIPTION    : Insert backslashes before single quotes.
901 **
902 **  VARS USED      :
903 **  VARS CHANGED   :
904 **  FUNCTIONS USED :
905 **  SEE ALSO       :
906 **  LAST MODIFIED  : 
907 **=========================================================================
908 */
909
910 String SQL_Escape(String s)
911 {
912    int i;
913
914    for (i = 0; i < ~s; i++)
915    {
916       if (s[i] == '\'')
917       {
918          s(i,0) = "\\";
919          i++;
920       }
921    }
922
923    return s;
924 }