Different kinds of log files are parsed by a collection of objects
[gnucomo.git] / src / gcm_input / logrunner.cpp
1 /*
2  * logrunner.c
3  * (c) Peter Roozemaal, feb 2003
4  *
5  * $Id: logrunner.cpp,v 1.1 2003-08-11 16:56:16 arjen Exp $
6  *
7  * 1) compile,
8  * 2) create 'logrunner.conf' containing a list of filenames and types of the
9  *    logfiles to scan; one file per line:
10  *       <gnucomo_messagetype> TAB <logfilename>
11  *    example:
12  *      syslog   /var/log/messages
13  *      syslog   /var/log/secure
14  * 3) run executable
15  */
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <sys/socket.h>
19 #include <netinet/in.h>
20 #include <arpa/inet.h>
21 #include <unistd.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <netdb.h>
25 #include <stdio.h>
26 #include <signal.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include <AXE/String.h>
31
32 extern String XML_Entities(String s);
33
34 static const char* const usage =
35    "Usage: logrunner [<options>] [<hostname>]\n\n"
36    "<hostname> is the gnucomo data collection server\n"
37    "Options are:\n"
38    " -1: one-shot; stop after a single pass over one of the configured logfiles\n"
39    " -p <number>: set IP port number to connect to\n"
40    " -c <file>: specify alternative configuration file, default is \'logrunner.conf\'\n"
41    "If no hostname is specified, logrunner sends output to stdout\n";
42
43 struct fileinfo {
44    struct fileinfo* next;
45    char*   name;      /* strdupped name */
46    char*   type;      /* strdupped logtype */
47    int     fd;
48    ino_t   inode;
49    off_t   position;
50 };
51
52 static const char* confname = "logrunner.conf";
53 static const char* statusname = "logrunner.status";
54 static const char* newstatusname = "logrunner.status.new";
55
56 static const char *hostname = NULL;
57 static int port = 2996;         /* random magic number */
58 static int out_stream = 1;
59
60 static int oneshot = 0;
61
62 static struct fileinfo* filelist = NULL;
63
64 static int something_has_changed = 0;
65    
66 static volatile sig_atomic_t sig_seen = 0;
67
68 static void sighandler(int sig)
69 {
70    sig_seen = sig;
71 }
72
73 static void set_signal_handler(void)
74 {
75    /* These aren't all; but it's a nice set to start with */
76    signal(SIGHUP, sighandler);
77    signal(SIGINT, sighandler);
78    signal(SIGQUIT, sighandler);
79    signal(SIGABRT, sighandler);
80    signal(SIGPIPE, sighandler);
81    signal(SIGTERM, sighandler);
82 }
83
84 void open_output()
85 {
86    struct hostent* hostptr;
87    struct sockaddr_in addr;
88    unsigned int namelen = sizeof(addr);
89
90    if ( hostname )
91    {
92       hostptr = gethostbyname(hostname);
93       if ( !hostptr )
94       {
95          fprintf(stderr, "logrunner: FATAL: cannot resolve %s\n", hostname);
96          exit(2);
97       }
98       out_stream = socket(PF_INET, SOCK_STREAM, 0);
99       if ( out_stream < 0 )
100       {
101          fprintf(stderr, "logrunner: FATAL: Socket creation failed\n");
102          exit(2);
103       }
104       addr.sin_family = AF_INET;
105       addr.sin_addr.s_addr = *((long*)(hostptr->h_addr));
106       addr.sin_port = htons(port);
107       if ( connect(out_stream, (struct sockaddr*) &addr, namelen) < 0 )
108       {
109          fprintf(stderr, "logrunner: FATAL: connect to %s failed\n", hostname);
110          exit(2);
111       }
112    }
113    else
114    {
115       out_stream = 1;
116    }
117 }
118
119 void xsend(const char* str)
120 {
121    write(out_stream, str, strlen(str));
122 }
123
124 void xml_header(struct fileinfo *f)
125 {
126    char buffer[256];
127    *buffer = 0;
128    gethostname(buffer, sizeof(buffer));
129         xsend("<?xml version='1.0'?>\n");
130    xsend("<gcmt:message xmlns:gcmt=\"http://gnucomo.org/transport/\">\n");
131    xsend("<gcmt:header>\n<gcmt:hostname>");
132    xsend(buffer);
133    xsend("</gcmt:hostname>\n<gcmt:messagetype>");
134    xsend(f->type);
135    xsend("</gcmt:messagetype>\n</gcmt:header>\n");
136    xsend("<gcmt:data><gcmt:log>");
137 }
138
139 void xml_footer(void)
140 {
141    xsend("</gcmt:log></gcmt:data>\n</gcmt:message>\n");
142 }
143
144 void output(const char* str)
145 {
146    write(2, str, strlen(str));
147 }
148
149 void output_error(const char* str)
150 {
151    char error[80];
152    sprintf(error, " errno=%d\n", errno);
153    output(str);
154    output(error);
155 }
156
157 void write_status_file(struct fileinfo* fl)
158 {
159    FILE* dumpfile;
160    int localerror = 0;
161    dumpfile = fopen(newstatusname, "w");
162    if ( !dumpfile )
163    {
164       char error[80];
165       sprintf(error, ", errno=%d\n", errno);
166       output("!!! dumpstatus: open failed: ");
167       output(newstatusname);
168       output(error);
169       something_has_changed = 0;
170       return;
171    }
172    while ( fl )
173    {
174       if ( fprintf(dumpfile, "%s %li %lu\n",
175               fl->name, (long) fl->inode,
176               (unsigned long) fl->position) < 0 )
177       {
178          output_error("!!! dumpstatus: write failed");
179          localerror = 1;
180          break;
181       }
182       fl = fl->next;
183    }
184    if ( fclose(dumpfile) && localerror == 0 )
185    {
186       output_error("!!! dumpstatus: close failed");
187       localerror = 1;
188    }
189    if ( localerror == 0 )
190    {
191       if ( rename(newstatusname, statusname) )
192       {
193          output_error("!!! dumpstatus: rename failed");
194       }
195    }
196    something_has_changed = 0;
197 }
198
199 void read_status()
200 {
201    FILE* statusfile;
202    char buffer[4096];
203    char* xp;
204    long ino;
205    unsigned long pos;
206    struct fileinfo* fp;
207
208    statusfile = fopen(statusname, "r");
209    if ( statusfile == NULL )
210    {
211       fprintf(stderr, "logrunner: can\'t open status file \'%s\': ",
212          statusname);
213       perror("");
214       return;
215    }
216    while ( fgets(buffer, sizeof(buffer), statusfile ) )
217    {
218       xp = strchr(buffer, ' ');
219       if ( xp )
220       {
221          *xp = 0;
222          sscanf(xp+1, "%ld %lu", &ino, &pos);
223          fp = filelist;
224          while ( fp )
225          {
226             if ( strcmp(buffer, fp->name) == 0 )
227             {
228                fp->inode = ino;
229                fp->position = pos;
230                break;
231             }
232             fp = fp->next;
233          }
234       }
235    }
236    fclose(statusfile);
237 }
238
239 struct fileinfo* new_info()
240 {
241    struct fileinfo* rv;
242    rv = (struct fileinfo *)malloc(sizeof(struct fileinfo));
243    if ( rv )
244    {
245       rv->next = NULL;
246       rv->name = NULL;
247       rv->type = NULL;
248       rv->fd = -1;
249       rv->position = 0;
250       rv->inode = 0;
251    }
252    return rv;
253 }
254
255 void read_config()
256 {
257    /* The config file now is a simple list of type, filenames;
258     * it should be more user-friendly in the future!
259     */
260    FILE* conffile;
261    char buffer[4096];
262
263    conffile = fopen(confname, "r");
264    if ( !conffile )
265    {
266       fprintf(stderr,
267          "logrunner: can\'t open configuration file \'%s\': ",
268          confname);
269       perror("");
270       exit(2);
271    }
272    while ( fgets(buffer, sizeof(buffer), conffile) )
273    {
274       int len = strlen(buffer);
275       char *fname;
276       if ( buffer[len-1] == '\n' )
277       {
278          buffer[--len] = 0;
279       }
280       fname = strchr(buffer, '\t');
281       if ( fname )
282       {
283          struct fileinfo* fi;
284          fi = new_info();
285          *(fname++) = 0;
286          if ( fi )
287          {
288             fi->name = strdup(fname);
289             fi->type = strdup(buffer);
290             fi->next = filelist;
291             filelist = fi;
292          }
293          else
294          {
295             fprintf(stderr, "logrunner: out of memory\n");
296             exit(2);
297          }
298       }
299       else if ( len > 0 )
300       {
301          fprintf(stderr, "logrunner: WARNING: bad config line: %s\n", buffer);
302       }
303    }
304    fclose(conffile);
305 }
306
307 void copy_data(struct fileinfo *f)
308 {
309    char buffer[4096];
310    int ndata;
311
312    /* read data and dump to output */
313    ndata = read(f->fd, buffer, sizeof(buffer));
314    if ( ndata > 0 )
315    {
316       xml_header(f);
317       while ( ndata > 0 )
318       {
319          //  Make a separate <gcmt:raw> element from each line
320  
321          char *line, *nextline;
322
323          line = buffer;
324          nextline = buffer;
325
326          while (nextline < buffer + ndata)
327          {
328             while (*nextline != '\n' && nextline < buffer + ndata)
329             {
330                nextline++;
331             }
332             if (*nextline == '\n')
333             {
334                // Another line found - make the split.
335                *nextline++ = '\0';
336                write(out_stream, "<gcmt:raw>", 10);
337
338                String logline(line);
339                logline = XML_Entities(logline);
340                write(out_stream, logline, ~logline);
341                write(out_stream, "</gcmt:raw>\n", 12);
342                line = nextline;
343             }
344          }
345          if (line != nextline)
346          {
347             //  There is still an incomplete line in the buffer.
348             memmove(buffer, line, nextline - line);
349          }
350          f->position += ndata - (nextline - line);
351          ndata -= line - buffer;
352          ndata += read(f->fd, buffer + (nextline - line), sizeof(buffer) - (nextline - line));
353       }
354       xml_footer();
355       something_has_changed = 1;
356    }   
357    if ( ndata < 0 )
358    {
359       char error[80];
360       sprintf(error, ", errno=%d\n", errno);
361       output("!!! logfile: read failed: ");
362       output(f->name);
363       output(error);
364    }
365 }
366
367 void do_file(struct fileinfo *f)
368 {
369    struct stat statinfo;
370    char buffer[80];
371
372    /* inode check */
373    if ( stat(f->name, &statinfo) )
374    {
375       sprintf(buffer, ", errno=%d\n", errno);
376       output("!!! logfile: stat failed: ");
377       output(f->name);
378       output(buffer);
379    }
380    else
381    {
382       if ( statinfo.st_ino != f->inode )
383       {
384          if ( f->fd >= 0 )
385          {
386             copy_data(f);
387             close(f->fd);
388             f->fd = -1;
389          }
390          output("@@@ logfile: logrotate detected: ");
391          output(f->name);
392          output("\n");
393          f->inode = statinfo.st_ino;
394          f->position = 0;
395       }
396    }
397    if ( f->fd < 0 )
398    {
399       /* attempt to open the file */
400       f->fd = open(f->name, O_RDONLY);
401       if ( f->fd < 0 )
402       {
403          sprintf(buffer, ", errno=%d\n", errno);
404          output("!!! logfile: open failed: ");
405          output(f->name);
406          output(buffer);
407          return;
408       }
409       output("*** logfile: opened: ");
410       output(f->name);
411       sprintf(buffer,
412          "\n*** logfile: resumed read from position %ld\n",
413          (long) lseek(f->fd, f->position, SEEK_SET) );
414       output(buffer);
415    }
416
417    copy_data(f);
418 }
419
420 void process_options(int argc, char* argv[])
421 {
422    const char* const options = "1c:p:";
423    int opt;
424
425    opt = getopt(argc, argv, options);
426    while ( opt != -1 )
427    {
428       switch ( opt )
429       {
430       case '1':
431          oneshot = 1;
432          break;
433       case 'c':
434          confname = strdup(optarg);
435          break;
436       case 'p':
437          port = atoi(optarg);
438          break;
439       default:
440          fputs(usage, stderr);
441          exit(2);
442       }
443       opt = getopt(argc, argv, options);
444    }
445    switch ( argc-optind )
446    {
447    case 0:
448       break;
449    case 1:
450       hostname = argv[optind];
451       break;
452    default:
453       fputs(usage, stderr);
454       exit(2);
455    }
456 }
457
458 int main(int argc, char* argv[])
459 {
460    process_options(argc, argv);
461    open_output();
462    
463    read_config();
464    read_status();
465
466    set_signal_handler();
467
468    while ( sig_seen == 0 )
469    {
470       struct fileinfo* fip;
471       fip = filelist;
472       while ( fip )
473       {
474          do_file(fip);
475          if (something_has_changed && oneshot)
476          {
477             break;
478          }
479          fip = fip->next;
480       }
481       if ( something_has_changed )
482       {
483          write_status_file(filelist);
484       }
485       if ( oneshot )
486       {
487          return 0;
488       }
489       sleep(1);
490    }
491
492    fprintf(stderr, "logrunner: stopped by signal %d\n", sig_seen);
493    /* shouldn't we close files and release memory here? */
494    return 0;
495 }