gcm_daemon: Use lock file to prevent parallel execution
[gnucomo.git] / src / gcm_input / gcm_input.cpp
1 /**************************************************************************
2 **  (c) Copyright 2002, Andromeda Technology & Automation
3 ** This is free software; you can redistribute it and/or modify it under the
4 ** terms of the GNU General Public License, see the file COPYING.
5 ***************************************************************************
6 ** MODULE INFORMATION *
7 ***********************
8 **      FILE NAME      : gcm_input.cpp
9 **      SYSTEM NAME    : Gnucomo - Gnu Computer Monitoring
10 **      VERSION NUMBER : $Revision: 1.16 $
11 **
12 **  DESCRIPTION      :  Application to store client messages into the database
13 **                      The client message contains a log file from one of the
14 **                      system logs. Gcm_input parses the log file and enters
15 **                      the raw data into the 'log' table of the gnucomo database.
16 **
17 **                      The system log may arrive in one of several forms:
18 **                      1. Log file (archived or not) from a client machine.
19 **                      2. Log file from a client system, arriving in a clear Email.
20 **                      3. Log file from a client machine, arriving in an
21 **                         encrypted Email.
22 **
23 **                      additional information we need that may not be availble in
24 **                      the content of the log is:
25 **                      - FQDN of the client.
26 **                      - Time of arrival of the log
27 **                      - Service that created the log.
28 **
29 **                      If the log arrives in an Email, these items probably are
30 **                      available in the mail header. Otherwise, they have to be
31 **                      provided as command line arguments.
32 **
33 **                      Command line arguments:
34 **                      -c <name>        Configuration name (default = gnucomo).
35 **                      -d <date>        Date and time of log arrival.
36 **                      -h <hostname>    FQDN of the client.
37 **                      -i               Incremental - partial list of parameters.
38 **                      -s <service>     Service that created the log.
39 **                      -T               Test mode. Do not alter the database.
40 **                      -v               Verbose (debug) output.
41 **                      -V               Print version and exit.
42 **
43 **  EXPORTED OBJECTS : 
44 **  LOCAL    OBJECTS : 
45 **  MODULES  USED    :
46 ***************************************************************************
47 **  ADMINISTRATIVE INFORMATION *
48 ********************************
49 **      ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
50 **      CREATION DATE   : Aug 29, 2002
51 **      LAST UPDATE     : Nov 26, 2003
52 **      MODIFICATIONS   : 
53 **************************************************************************/
54
55 /*****************************
56    $Log: gcm_input.cpp,v $
57    Revision 1.16  2011-03-24 10:20:37  arjen
58    Added some debug info
59
60    Revision 1.15  2007/11/14 16:19:25  arjen
61    Bugfix: Segmentation fault when reading an rpm package list
62    with empty lines.
63
64    Revision 1.14  2007/11/03 10:26:13  arjen
65    Added a new filter which can directly read the output
66    of the UNIX df command. A brief description is added in
67    the user manual.
68
69    Revision 1.13  2003/12/22 10:20:21  arjen
70    Report if the message type can not be detected.
71
72    Revision 1.12  2003/12/04 10:38:09  arjen
73    Major redesign. All input is handled through XML. Raw input data is first
74    transformed into an XML document for further processing.
75    A collection of polymorphic classes handle the transformation of various
76    input formats into XML.
77    Classifying input data is done with a finite improbability calculation.
78
79    Revision 1.11  2003/10/27 13:00:15  arjen
80    Catch exceptions from the database library
81
82    Revision 1.10  2003/09/03 06:58:31  arjen
83    Changed version string to 0.0.8
84
85    Revision 1.9  2003/09/01 06:59:26  arjen
86    A date without the time for the '-d <date> option will
87    assume midnight on that date.
88
89    Revision 1.8  2003/08/16 15:30:19  arjen
90    Fixed a gcc 2 vs. gcc 3 problem
91
92    Revision 1.7  2003/08/14 10:28:37  arjen
93    Use parameters from a new section 'logging' with three configuration parameters:
94       method       - Output method to use for logging.
95       destination  - Name of the log output destination.
96       level        - Log level: Verbose output if greater than 0.
97
98    Revision 1.6  2003/08/11 16:56:16  arjen
99    Different kinds of log files are parsed by a collection of objects
100    of different classes, derived from the base class line_cooker
101    Depending on the message content or the message_type element in
102    XML, one of these objects is selected.
103
104    Logrunner is integrated with gcm_input. Although its functionality
105    is still limited, a connection between logrunner and gcm_input
106    is beginning to form.
107
108    Revision 1.5  2003/08/05 08:11:06  arjen
109    Added two configuration parameters:
110       logfile   - Log to this file instead of stderr.
111       verbosity - Verbose output if greater than 0.
112    Added '-i' option for incremental parameter updates
113
114    Revision 1.4  2003/03/29 08:42:00  arjen
115    Exit without reading any input if the database connection fails.
116
117    Revision 1.3  2002/11/09 08:04:27  arjen
118    Added a reference to the GPL
119
120    Revision 1.2  2002/11/04 10:13:36  arjen
121    Use proper namespace for iostream classes
122
123    Revision 1.1  2002/10/05 10:25:49  arjen
124    Creation of gcm_input and a first approach to a web interface
125
126 *****************************/
127
128 #include <fstream>
129
130 #include <getopt.h>
131 #include <unistd.h>
132
133 #include "message.h"
134 #include "log_filter.h"
135 #include "rpm_filter.h"
136 #include "df_filter.h"
137
138 #include "xml_cooker.h"
139 #include "syslog_cooker.h"
140 #include "irix_syslog_cooker.h"
141 #include "access_cooker.h"
142 #include "error_cooker.h"
143
144 bool verbose = false;
145 bool testmode = false;
146 bool incremental = false;
147 std::ostream *Log = &std::cerr;
148
149 static char Version[] = "gcm_input version 0.0.13 - Sep 22, 2020";
150
151
152 /*=========================================================================
153 **  NAME           : main
154 **  SYNOPSIS       : int main(int argc, char *argv[])
155 **  PARAMETERS     : 
156 **  RETURN VALUE   : 
157 **
158 **  DESCRIPTION    : Parse command line arguments and establish a connection
159 **                   to the database.
160 **                   When we have a database connection, parse the log file
161 **                   from stdin.
162 **
163 **  VARS USED      :
164 **  VARS CHANGED   :
165 **  FUNCTIONS USED :
166 **  SEE ALSO       :
167 **  LAST MODIFIED  : Aug 11, 2003
168 **=========================================================================
169 */
170
171 int main(int argc, char *argv[])
172 {
173    const char *usage = "Usage: gcm_input [-c configname] [-h hostname] [-i] [-d date]"
174                        " [-s service] [-T] [-v] [-V]\n";
175
176    gnucomo_config    cfg;
177    String           config_name("gnucomo");
178    std::ofstream    logfile;
179
180
181    /*   Parse command line arguments */
182
183    UTC    arrival = Now();
184    String  hostname(""), service("");
185    int     option;
186
187
188    while ((option = getopt(argc, argv, "c:h:d:s:iTvV")) != -1)
189    {
190       switch (option)
191       {
192       case 'c':
193          config_name = optarg;
194          break;
195
196       case 'h':
197          hostname = optarg;
198          break;
199
200       case 'd':
201          arrival = String(optarg);
202          if (!date(arrival).proper())
203          {
204             std::cerr << "gcm_input: Invalid date string: " << optarg
205                       << "(" << arrival << ").\n";
206             exit(1);
207          }
208          else if (!hour(arrival).proper())
209          {
210             arrival = UTC(date(arrival), hour(0,0,0));
211          }
212          break;
213
214       case 's':
215          service = optarg;
216          break;
217
218       case 'i':
219          incremental = true;
220          break;
221
222       case 'T':
223          testmode = true;
224          break;
225
226       case 'v':
227          verbose = true;
228          break;
229
230       case 'V':
231          std::cout << Version << "\n";
232          exit(0);
233
234       case '?':
235       case ':':
236          std::cerr << usage;
237          exit(1);
238       }
239    }
240    /*  Get the configuration file */
241
242    if (!cfg.read(config_name))
243    {
244       std::cerr << "Can not read Gnucomo configuration file for " << config_name << ".\n";
245       exit(1);
246    }
247
248    String log_method      = cfg.find_parameter("logging", "method");
249    String log_destination = cfg.find_parameter("logging", "destination");
250    int    level           = cfg.find_parameter("logging", "level");
251
252    if (log_method == "file" && log_destination != "")
253    {
254       logfile.open(log_destination, std::ios_base::app);
255       if (!logfile)
256       {
257          std::cerr << "Can't open logfile " << log_destination << " for writing.\n";
258       }
259       else
260       {
261          Log = &logfile;
262       }
263    }
264    verbose = verbose || level > 0;
265
266    *Log << Now() << " gcm_input starting.\n";
267
268    if (verbose)
269    {
270       *Log << "Hostname = " << hostname;
271       *Log << " Arrival = " << arrival;
272       *Log << " Service = " << service << "\n";
273       *Log << "Config OK.\n";
274    }
275
276    /*  Try to connect to the database */
277
278    gnucomo_database db(&cfg);
279
280    int gcm_input_result = 0;
281
282
283    client_message      msg(&std::cin, db);
284
285    double              message_probability;
286
287    message_filter      shortcircuit(hostname, arrival, service);
288    log_filter          lf(hostname, arrival, service);
289    rpm_filter          rf(hostname, arrival, service);
290    df_filter           df(hostname, arrival, service);
291
292    syslog_cooker       slc;
293    irix_syslog_cooker  islc;
294    access_cooker       alc;
295    error_cooker        elc;
296    xml_cooker          xlc;
297    rpm_cooker          rlc;
298    df_cooker           dlc;
299
300    msg.add_cooker(&xlc,  &shortcircuit);
301    msg.add_cooker(&slc,  &lf);
302    msg.add_cooker(&islc, &lf);
303    msg.add_cooker(&alc,  &lf);
304    msg.add_cooker(&elc,  &lf);
305    msg.add_cooker(&rlc,  &rf);
306    msg.add_cooker(&dlc,  &df);
307
308    message_probability = msg.classify(hostname, arrival, service);
309    if (message_probability > 0.75)
310    {
311       try
312       {
313          if (msg.enter() < 0)
314          {
315             // Can not store the input in the database. Dump the message in a file.
316
317             String spool_dir      = cfg.find_parameter("spool", "directory");
318             String pid(getpid());
319             String xmlfilename;
320
321             if (spool_dir)
322             {
323                xmlfilename = spool_dir + "/";
324             }
325             xmlfilename += "gnucomo" + pid + ".xml";
326
327             msg.saveXML(xmlfilename);
328
329             // Report the error to the log and stderr.
330             *Log << "Entering the content into the database failed. XML content stored in " + xmlfilename + "\n";
331             std::cerr << "Entering the content into the database failed. XML content stored in " + xmlfilename + "\n";
332
333             gcm_input_result = 1;
334          }
335          *Log << Now() << " gcm_input finished successfully.\n";
336       }
337       catch (std::exception &e)
338       {
339          *Log << "Caught an exception: " << e.what() << "\n";
340       }
341    }
342    else
343    {
344       *Log << "Cannot determine message type with sufficient certainty.\n";
345    }
346
347    return gcm_input_result;
348 }
349