Configuration file in /etc, status files in /var/lib
[gnucomo.git] / src / gcm_input / logrunner.cpp
1 /*
2  * logrunner.c
3  * (c) Peter Roozemaal, feb 2003
4  *
5  * $Id: logrunner.cpp,v 1.2 2005-05-31 05:47:33 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 String confname = "/etc/logrunner.conf";
53 static const char* statusname = "/var/lib/logrunner.status";
54 static const char* newstatusname = "/var/lib/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    std::cerr << str;
147 }
148
149 void output_error(const char* str)
150 {
151    std::cerr << str << " errno=" << errno << "\n";
152 }
153
154 void write_status_file(struct fileinfo* fl)
155 {
156    FILE* dumpfile;
157    int localerror = 0;
158    dumpfile = fopen(newstatusname, "w");
159    if ( !dumpfile )
160    {
161       char error[80];
162       sprintf(error, ", errno=%d\n", errno);
163       output("!!! dumpstatus: open failed: ");
164       output(newstatusname);
165       output(error);
166       something_has_changed = 0;
167       return;
168    }
169    while ( fl )
170    {
171       if ( fprintf(dumpfile, "%s %li %lu\n",
172               fl->name, (long) fl->inode,
173               (unsigned long) fl->position) < 0 )
174       {
175          output_error("!!! dumpstatus: write failed");
176          localerror = 1;
177          break;
178       }
179       fl = fl->next;
180    }
181    if ( fclose(dumpfile) && localerror == 0 )
182    {
183       output_error("!!! dumpstatus: close failed");
184       localerror = 1;
185    }
186    if ( localerror == 0 )
187    {
188       if ( rename(newstatusname, statusname) )
189       {
190          output_error("!!! dumpstatus: rename failed");
191       }
192    }
193    something_has_changed = 0;
194 }
195
196 void read_status()
197 {
198    FILE* statusfile;
199    char buffer[4096];
200    char* xp;
201    long ino;
202    unsigned long pos;
203    struct fileinfo* fp;
204
205    statusfile = fopen(statusname, "r");
206    if ( statusfile == NULL )
207    {
208       fprintf(stderr, "logrunner: can\'t open status file \'%s\': ",
209          statusname);
210       perror("");
211       return;
212    }
213    while ( fgets(buffer, sizeof(buffer), statusfile ) )
214    {
215       xp = strchr(buffer, ' ');
216       if ( xp )
217       {
218          *xp = 0;
219          sscanf(xp+1, "%ld %lu", &ino, &pos);
220          fp = filelist;
221          while ( fp )
222          {
223             if ( strcmp(buffer, fp->name) == 0 )
224             {
225                fp->inode = ino;
226                fp->position = pos;
227                break;
228             }
229             fp = fp->next;
230          }
231       }
232    }
233    fclose(statusfile);
234 }
235
236 struct fileinfo* new_info()
237 {
238    struct fileinfo* rv;
239    rv = (struct fileinfo *)malloc(sizeof(struct fileinfo));
240    if ( rv )
241    {
242       rv->next = NULL;
243       rv->name = NULL;
244       rv->type = NULL;
245       rv->fd = -1;
246       rv->position = 0;
247       rv->inode = 0;
248    }
249    return rv;
250 }
251
252 void read_config()
253 {
254    /* The config file now is a simple list of type, filenames;
255     * it should be more user-friendly in the future!
256     */
257    FILE* conffile;
258    char buffer[4096];
259
260    conffile = fopen(confname, "r");
261    if ( !conffile )
262    {
263       std::cerr << "logrunner: can\'t open configuration file \'" << confname << "\': ";
264       perror("");
265       exit(2);
266    }
267    while ( fgets(buffer, sizeof(buffer), conffile) )
268    {
269       int len = strlen(buffer);
270       char *fname;
271       if ( buffer[len-1] == '\n' )
272       {
273          buffer[--len] = 0;
274       }
275       fname = strchr(buffer, '\t');
276       if ( fname )
277       {
278          struct fileinfo* fi;
279          fi = new_info();
280          *(fname++) = 0;
281          if ( fi )
282          {
283             fi->name = strdup(fname);
284             fi->type = strdup(buffer);
285             fi->next = filelist;
286             filelist = fi;
287          }
288          else
289          {
290             fprintf(stderr, "logrunner: out of memory\n");
291             exit(2);
292          }
293       }
294       else if ( len > 0 )
295       {
296          fprintf(stderr, "logrunner: WARNING: bad config line: %s\n", buffer);
297       }
298    }
299    fclose(conffile);
300 }
301
302 void copy_data(struct fileinfo *f)
303 {
304    char buffer[4096];
305    int ndata;
306
307    /* read data and dump to output */
308    ndata = read(f->fd, buffer, sizeof(buffer));
309    if ( ndata > 0 )
310    {
311       xml_header(f);
312       while ( ndata > 0 )
313       {
314          //  Make a separate <gcmt:raw> element from each line
315  
316          char *line, *nextline;
317
318          line = buffer;
319          nextline = buffer;
320
321          while (nextline < buffer + ndata)
322          {
323             while (*nextline != '\n' && nextline < buffer + ndata)
324             {
325                nextline++;
326             }
327             if (*nextline == '\n')
328             {
329                // Another line found - make the split.
330                *nextline++ = '\0';
331                write(out_stream, "<gcmt:raw>", 10);
332
333                String logline(line);
334                logline = XML_Entities(logline);
335                write(out_stream, logline, ~logline);
336                write(out_stream, "</gcmt:raw>\n", 12);
337                line = nextline;
338             }
339          }
340          if (line != nextline)
341          {
342             //  There is still an incomplete line in the buffer.
343             memmove(buffer, line, nextline - line);
344          }
345          f->position += ndata - (nextline - line);
346          ndata -= line - buffer;
347          ndata += read(f->fd, buffer + (nextline - line), sizeof(buffer) - (nextline - line));
348       }
349       xml_footer();
350       something_has_changed = 1;
351    }   
352    if ( ndata < 0 )
353    {
354       char error[80];
355       sprintf(error, ", errno=%d\n", errno);
356       output("!!! logfile: read failed: ");
357       output(f->name);
358       output(error);
359    }
360 }
361
362 void do_file(struct fileinfo *f)
363 {
364    struct stat statinfo;
365    char buffer[80];
366
367    /* inode check */
368    if ( stat(f->name, &statinfo) )
369    {
370       sprintf(buffer, ", errno=%d\n", errno);
371       output("!!! logfile: stat failed: ");
372       output(f->name);
373       output(buffer);
374    }
375    else
376    {
377       if ( statinfo.st_ino != f->inode )
378       {
379          if ( f->fd >= 0 )
380          {
381             copy_data(f);
382             close(f->fd);
383             f->fd = -1;
384          }
385          output("@@@ logfile: logrotate detected: ");
386          output(f->name);
387          output("\n");
388          f->inode = statinfo.st_ino;
389          f->position = 0;
390       }
391    }
392    if ( f->fd < 0 )
393    {
394       /* attempt to open the file */
395       f->fd = open(f->name, O_RDONLY);
396       if ( f->fd < 0 )
397       {
398          sprintf(buffer, ", errno=%d\n", errno);
399          output("!!! logfile: open failed: ");
400          output(f->name);
401          output(buffer);
402          return;
403       }
404       output("*** logfile: opened: ");
405       output(f->name);
406       sprintf(buffer,
407          "\n*** logfile: resumed read from position %ld\n",
408          (long) lseek(f->fd, f->position, SEEK_SET) );
409       output(buffer);
410    }
411
412    copy_data(f);
413 }
414
415 void process_options(int argc, char* argv[])
416 {
417    const char* const options = "1c:p:";
418    int opt;
419
420    opt = getopt(argc, argv, options);
421    while ( opt != -1 )
422    {
423       switch ( opt )
424       {
425       case '1':
426          oneshot = 1;
427          break;
428       case 'c':
429          confname = strdup(optarg);
430          break;
431       case 'p':
432          port = atoi(optarg);
433          break;
434       default:
435          fputs(usage, stderr);
436          exit(2);
437       }
438       opt = getopt(argc, argv, options);
439    }
440    switch ( argc-optind )
441    {
442    case 0:
443       break;
444    case 1:
445       hostname = argv[optind];
446       break;
447    default:
448       fputs(usage, stderr);
449       exit(2);
450    }
451 }
452
453 int main(int argc, char* argv[])
454 {
455    process_options(argc, argv);
456    open_output();
457    
458    read_config();
459    read_status();
460
461    set_signal_handler();
462
463    while ( sig_seen == 0 )
464    {
465       struct fileinfo* fip;
466       fip = filelist;
467       while ( fip )
468       {
469          do_file(fip);
470          if (something_has_changed && oneshot)
471          {
472             break;
473          }
474          fip = fip->next;
475       }
476       if ( something_has_changed )
477       {
478          write_status_file(filelist);
479       }
480       if ( oneshot )
481       {
482          return 0;
483       }
484       sleep(1);
485    }
486
487    fprintf(stderr, "logrunner: stopped by signal %d\n", sig_seen);
488    /* shouldn't we close files and release memory here? */
489    return 0;
490 }