1 /**
2 * goaccess.c -- main log analyzer
3 * ______ ___
4 * / ____/___ / | _____________ __________
5 * / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
6 * / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
7 * \____/\____/_/ |_\___/\___/\___/____/____/
8 *
9 * The MIT License (MIT)
10 * Copyright (c) 2009-2020 Gerardo Orellana <hello @ goaccess.io>
11 *
12 * Permission is hereby granted, free of charge, to any person obtaining a copy
13 * of this software and associated documentation files (the "Software"), to deal
14 * in the Software without restriction, including without limitation the rights
15 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 * copies of the Software, and to permit persons to whom the Software is
17 * furnished to do so, subject to the following conditions:
18 *
19 * The above copyright notice and this permission notice shall be included in all
20 * copies or substantial portions of the Software.
21 *
22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 * SOFTWARE.
29 */
30
31 #define _LARGEFILE_SOURCE
32 #define _LARGEFILE64_SOURCE
33 #define _FILE_OFFSET_BITS 64
34
35 #include <assert.h>
36 #include <ctype.h>
37 #include <errno.h>
38
39 #include <locale.h>
40
41 #if HAVE_CONFIG_H
42 #include <config.h>
43 #endif
44
45 #include <fcntl.h>
46 #include <grp.h>
47 #include <pthread.h>
48 #include <pwd.h>
49 #include <signal.h>
50 #include <stdint.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <sys/stat.h>
55 #include <sys/types.h>
56 #include <unistd.h>
57
58 #include "gkhash.h"
59
60 #ifdef HAVE_GEOLOCATION
61 #include "geoip1.h"
62 #endif
63
64 #include "browsers.h"
65 #include "csv.h"
66 #include "error.h"
67 #include "gdashboard.h"
68 #include "gdns.h"
69 #include "gholder.h"
70 #include "goaccess.h"
71 #include "gwsocket.h"
72 #include "json.h"
73 #include "options.h"
74 #include "output.h"
75 #include "util.h"
76 #include "websocket.h"
77 #include "xmalloc.h"
78
79 GConf conf = {
80 .append_method = 1,
81 .append_protocol = 1,
82 .hl_header = 1,
83 .num_tests = 10,
84 };
85
86 /* Loading/Spinner */
87 GSpinner *parsing_spinner;
88 /* active reverse dns flag */
89 int active_gdns = 0;
90
91 /* WebSocket server - writer and reader threads */
92 static GWSWriter *gwswriter;
93 static GWSReader *gwsreader;
94 /* Dashboard data structure */
95 static GDash *dash;
96 /* Data holder structure */
97 static GHolder *holder;
98 /* Log line properties structure */
99 static Logs *logs;
100 /* Old signal mask */
101 static sigset_t oldset;
102 /* Curses windows */
103 static WINDOW *header_win, *main_win;
104
105 static int main_win_height = 0;
106
107 /* *INDENT-OFF* */
108 static GScroll gscroll = {
109 {
110 {0, 0}, /* visitors {scroll, offset} */
111 {0, 0}, /* requests {scroll, offset} */
112 {0, 0}, /* req static {scroll, offset} */
113 {0, 0}, /* not found {scroll, offset} */
114 {0, 0}, /* hosts {scroll, offset} */
115 {0, 0}, /* os {scroll, offset} */
116 {0, 0}, /* browsers {scroll, offset} */
117 {0, 0}, /* visit times {scroll, offset} */
118 {0, 0}, /* referrers {scroll, offset} */
119 {0, 0}, /* ref sites {scroll, offset} */
120 {0, 0}, /* keywords {scroll, offset} */
121 #ifdef HAVE_GEOLOCATION
122 {0, 0}, /* geolocation {scroll, offset} */
123 #endif
124 {0, 0}, /* status {scroll, offset} */
125 },
126 0, /* current module */
127 0, /* main dashboard scroll */
128 0, /* expanded flag */
129 };
130 /* *INDENT-ON* */
131
132 /* Free malloc'd holder */
133 static void
house_keeping_holder(void)134 house_keeping_holder (void) {
135 /* REVERSE DNS THREAD */
136 pthread_mutex_lock (&gdns_thread.mutex);
137
138 /* kill dns pthread */
139 active_gdns = 0;
140 /* clear holder structure */
141 free_holder (&holder);
142 /* clear reverse dns queue */
143 gdns_free_queue ();
144 /* clear the whole storage */
145 free_storage ();
146
147 pthread_mutex_unlock (&gdns_thread.mutex);
148 }
149
150 /* Free malloc'd data across the whole program */
151 static void
house_keeping(void)152 house_keeping (void) {
153 house_keeping_holder ();
154
155 /* DASHBOARD */
156 if (dash && !conf.output_stdout) {
157 free_dashboard (dash);
158 reset_find ();
159 }
160
161 /* GEOLOCATION */
162 #ifdef HAVE_GEOLOCATION
163 geoip_free ();
164 #endif
165
166 /* LOGGER */
167 free_logs (logs);
168
169 /* INVALID REQUESTS */
170 if (conf.invalid_requests_log) {
171 LOG_DEBUG (("Closing invalid requests log.\n"));
172 invalid_log_close ();
173 }
174
175 /* UNKNOWNS */
176 if (conf.unknowns_log) {
177 LOG_DEBUG (("Closing unknowns log.\n"));
178 unknowns_log_close();
179 }
180
181 /* CONFIGURATION */
182 free_formats ();
183 free_browsers_hash ();
184 if (conf.debug_log) {
185 LOG_DEBUG (("Bye.\n"));
186 dbg_log_close ();
187 }
188 if (conf.fifo_in)
189 free ((char *) conf.fifo_in);
190 if (conf.fifo_out)
191 free ((char *) conf.fifo_out);
192
193 /* clear spinner */
194 free (parsing_spinner);
195 /* free colors */
196 free_color_lists ();
197 /* free cmd arguments */
198 free_cmd_args ();
199 /* WebSocket writer */
200 free (gwswriter);
201 /* WebSocket reader */
202 free (gwsreader);
203 }
204
205 static void
cleanup(int ret)206 cleanup (int ret) {
207 /* done, restore tty modes and reset terminal into
208 * non-visual mode */
209 if (!conf.output_stdout)
210 endwin ();
211
212 /* unable to process valid data */
213 if (ret)
214 output_logerrors (logs);
215
216 house_keeping ();
217 }
218
219 /* Drop permissions to the user specified. */
220 static void
drop_permissions(void)221 drop_permissions (void) {
222 struct passwd *pw;
223
224 errno = 0;
225 if ((pw = getpwnam (conf.username)) == NULL) {
226 if (errno == 0)
227 FATAL ("No such user %s", conf.username);
228 FATAL ("Unable to retrieve user %s: %s", conf.username, strerror (errno));
229 }
230
231 if (setgroups (1, &pw->pw_gid) == -1)
232 FATAL ("setgroups: %s", strerror (errno));
233 if (setgid (pw->pw_gid) == -1)
234 FATAL ("setgid: %s", strerror (errno));
235 if (setuid (pw->pw_uid) == -1)
236 FATAL ("setuid: %s", strerror (errno));
237 }
238
239 /* Open the pidfile whose name is specified in the given path and write
240 * the daemonized given pid. */
241 static void
write_pid_file(const char * path,pid_t pid)242 write_pid_file (const char *path, pid_t pid) {
243 FILE *pidfile;
244
245 if (!path)
246 return;
247
248 if ((pidfile = fopen (path, "w"))) {
249 fprintf (pidfile, "%d", pid);
250 fclose (pidfile);
251 } else {
252 FATAL ("Unable to open the specified pid file. %s", strerror (errno));
253 }
254 }
255
256 /* Set GoAccess to run as a daemon */
257 static void
daemonize(void)258 daemonize (void) {
259 pid_t pid, sid;
260 int fd;
261
262 /* Clone ourselves to make a child */
263 pid = fork ();
264
265 if (pid < 0)
266 exit (EXIT_FAILURE);
267 if (pid > 0) {
268 write_pid_file (conf.pidfile, pid);
269 printf ("Daemonized GoAccess: %d\n", pid);
270 exit (EXIT_SUCCESS);
271 }
272
273 umask (0);
274 /* attempt to create our own process group */
275 sid = setsid ();
276 if (sid < 0) {
277 LOG_DEBUG (("Unable to setsid: %s.\n", strerror (errno)));
278 exit (EXIT_FAILURE);
279 }
280
281 /* set the working directory to the root directory.
282 * requires the user to specify absolute paths */
283 if (chdir ("/") < 0) {
284 LOG_DEBUG (("Unable to set chdir: %s.\n", strerror (errno)));
285 exit (EXIT_FAILURE);
286 }
287
288 /* redirect fd's 0,1,2 to /dev/null */
289 /* Note that the user will need to use --debug-file for log output */
290 if ((fd = open ("/dev/null", O_RDWR, 0)) == -1) {
291 LOG_DEBUG (("Unable to open /dev/null: %s.\n", strerror (errno)));
292 exit (EXIT_FAILURE);
293 }
294
295 dup2 (fd, STDIN_FILENO);
296 dup2 (fd, STDOUT_FILENO);
297 dup2 (fd, STDERR_FILENO);
298 if (fd > STDERR_FILENO) {
299 close (fd);
300 }
301 }
302
303 /* Extract data from the given module hash structure and allocate +
304 * load data from the hash table into an instance of GHolder */
305 static void
allocate_holder_by_module(GModule module)306 allocate_holder_by_module (GModule module) {
307 GRawData *raw_data;
308
309 /* extract data from the corresponding hash table */
310 raw_data = parse_raw_data (module);
311 if (!raw_data) {
312 LOG_DEBUG (("raw data is NULL for module: %d.\n", module));
313 return;
314 }
315
316 load_holder_data (raw_data, holder + module, module, module_sort[module]);
317 }
318
319 /* Iterate over all modules/panels and extract data from hash
320 * structures and load it into an instance of GHolder */
321 static void
allocate_holder(void)322 allocate_holder (void) {
323 size_t idx = 0;
324
325 holder = new_gholder (TOTAL_MODULES);
326 FOREACH_MODULE (idx, module_list) {
327 allocate_holder_by_module (module_list[idx]);
328 }
329 }
330
331 /* Extract data from the modules GHolder structure and load it into
332 * the terminal dashboard */
333 static void
allocate_data_by_module(GModule module,int col_data)334 allocate_data_by_module (GModule module, int col_data) {
335 int size = 0, max_choices = get_max_choices ();
336
337 dash->module[module].head = module_to_head (module);
338 dash->module[module].desc = module_to_desc (module);
339
340 size = holder[module].idx;
341 if (gscroll.expanded && module == gscroll.current) {
342 size = size > max_choices ? max_choices : holder[module].idx;
343 } else {
344 size = holder[module].idx > col_data ? col_data : holder[module].idx;
345 }
346
347 dash->module[module].alloc_data = size; /* data allocated */
348 dash->module[module].ht_size = holder[module].ht_size; /* hash table size */
349 dash->module[module].idx_data = 0;
350 dash->module[module].pos_y = 0;
351
352 if (gscroll.expanded && module == gscroll.current)
353 dash->module[module].dash_size = DASH_EXPANDED;
354 else
355 dash->module[module].dash_size = DASH_COLLAPSED;
356 dash->total_alloc += dash->module[module].dash_size;
357
358 pthread_mutex_lock (&gdns_thread.mutex);
359 load_data_to_dash (&holder[module], dash, module, &gscroll);
360 pthread_mutex_unlock (&gdns_thread.mutex);
361 }
362
363 /* Iterate over all modules/panels and extract data from GHolder
364 * structure and load it into the terminal dashboard */
365 static void
allocate_data(void)366 allocate_data (void) {
367 GModule module;
368 int col_data = get_num_collapsed_data_rows ();
369 size_t idx = 0;
370
371 dash = new_gdash ();
372 FOREACH_MODULE (idx, module_list) {
373 module = module_list[idx];
374 allocate_data_by_module (module, col_data);
375 }
376 }
377
378 /* A wrapper to render all windows within the dashboard. */
379 static void
render_screens(void)380 render_screens (void) {
381 GColors *color = get_color (COLOR_DEFAULT);
382 int row, col, chg = 0;
383 char time_str_buf[32];
384
385 getmaxyx (stdscr, row, col);
386 term_size (main_win, &main_win_height);
387
388 generate_time ();
389 strftime (time_str_buf, sizeof (time_str_buf), "%a %b %e %T %Y", &now_tm);
390 chg = *logs->processed - logs->offset;
391
392 draw_header (stdscr, "", "%s", row - 1, 0, col, color_default);
393
394 wattron (stdscr, color->attr | COLOR_PAIR (color->pair->idx));
395 mvaddstr (row - 1, 1, T_HELP_ENTER);
396 mvprintw (row - 1, col / 2 - 10, "%d - %s", chg, time_str_buf);
397 mvaddstr (row - 1, col - 6 - strlen (T_QUIT), T_QUIT);
398 mvprintw (row - 1, col - 5, "%s", GO_VERSION);
399 wattroff (stdscr, color->attr | COLOR_PAIR (color->pair->idx));
400
401 refresh ();
402
403 /* call general stats header */
404 display_general (header_win, holder);
405 wrefresh (header_win);
406
407 /* display active label based on current module */
408 update_active_module (header_win, gscroll.current);
409
410 display_content (main_win, dash, &gscroll);
411 }
412
413 /* Collapse the current expanded module */
414 static void
collapse_current_module(void)415 collapse_current_module (void) {
416 if (!gscroll.expanded)
417 return;
418
419 gscroll.expanded = 0;
420 reset_scroll_offsets (&gscroll);
421 free_dashboard (dash);
422 allocate_data ();
423 render_screens ();
424 }
425
426 /* Display message a the bottom of the terminal dashboard that panel
427 * is disabled */
428 static void
disabled_panel_msg(GModule module)429 disabled_panel_msg (GModule module) {
430 const char *lbl = module_to_label (module);
431 int row, col;
432
433 getmaxyx (stdscr, row, col);
434 draw_header (stdscr, lbl, ERR_PANEL_DISABLED, row - 1, 0, col, color_error);
435 }
436
437 /* Set the current module/panel */
438 static void
set_module_to(GScroll * scrll,GModule module)439 set_module_to (GScroll * scrll, GModule module) {
440 if (get_module_index (module) == -1) {
441 disabled_panel_msg (module);
442 return;
443 }
444
445 /* scroll to panel */
446 if (!conf.no_tab_scroll)
447 gscroll.dash = get_module_index (module) * DASH_COLLAPSED;
448
449 /* reset expanded module */
450 collapse_current_module ();
451 scrll->current = module;
452 render_screens ();
453 }
454
455 /* Scroll expanded module or terminal dashboard to the top */
456 static void
scroll_to_first_line(void)457 scroll_to_first_line (void) {
458 if (!gscroll.expanded)
459 gscroll.dash = 0;
460 else {
461 gscroll.module[gscroll.current].scroll = 0;
462 gscroll.module[gscroll.current].offset = 0;
463 }
464 }
465
466 /* Scroll expanded module or terminal dashboard to the last row */
467 static void
scroll_to_last_line(void)468 scroll_to_last_line (void) {
469 int exp_size = get_num_expanded_data_rows ();
470 int scrll = 0, offset = 0;
471
472 if (!gscroll.expanded)
473 gscroll.dash = dash->total_alloc - main_win_height;
474 else {
475 scrll = dash->module[gscroll.current].idx_data - 1;
476 if (scrll >= exp_size && scrll >= offset + exp_size)
477 offset = scrll < exp_size - 1 ? 0 : scrll - exp_size + 1;
478 gscroll.module[gscroll.current].scroll = scrll;
479 gscroll.module[gscroll.current].offset = offset;
480 }
481 }
482
483 /* Load the user-agent window given the selected IP */
484 static void
load_ip_agent_list(void)485 load_ip_agent_list (void) {
486 int type_ip = 0;
487 /* make sure we have a valid IP */
488 int sel = gscroll.module[gscroll.current].scroll;
489 GDashData item = dash->module[HOSTS].data[sel];
490
491 if (!invalid_ipaddr (item.metrics->data, &type_ip))
492 load_agent_list (main_win, item.metrics->data);
493 }
494
495 /* Expand the selected module */
496 static void
expand_current_module(void)497 expand_current_module (void) {
498 if (gscroll.expanded && gscroll.current == HOSTS) {
499 load_ip_agent_list ();
500 return;
501 }
502
503 /* expanded, nothing to do... */
504 if (gscroll.expanded)
505 return;
506
507 reset_scroll_offsets (&gscroll);
508 gscroll.expanded = 1;
509
510 free_holder_by_module (&holder, gscroll.current);
511 free_dashboard (dash);
512 allocate_holder_by_module (gscroll.current);
513 allocate_data ();
514 }
515
516 /* Expand the clicked module/panel given the Y event coordinate. */
517 static void
expand_module_from_ypos(int y)518 expand_module_from_ypos (int y) {
519 /* ignore header/footer clicks */
520 if (y < MAX_HEIGHT_HEADER || y == LINES - 1)
521 return;
522
523 if (set_module_from_mouse_event (&gscroll, dash, y))
524 return;
525
526 reset_scroll_offsets (&gscroll);
527 gscroll.expanded = 1;
528
529 free_holder_by_module (&holder, gscroll.current);
530 free_dashboard (dash);
531 allocate_holder_by_module (gscroll.current);
532 allocate_data ();
533
534 render_screens ();
535 }
536
537 /* Expand the clicked module/panel */
538 static void
expand_on_mouse_click(void)539 expand_on_mouse_click (void) {
540 int ok_mouse;
541 MEVENT event;
542
543 ok_mouse = getmouse (&event);
544 if (!conf.mouse_support || ok_mouse != OK)
545 return;
546
547 if (event.bstate & BUTTON1_CLICKED)
548 expand_module_from_ypos (event.y);
549 }
550
551 /* Scroll dowm expanded module to the last row */
552 static void
scroll_down_expanded_module(void)553 scroll_down_expanded_module (void) {
554 int exp_size = get_num_expanded_data_rows ();
555 int *scroll_ptr, *offset_ptr;
556
557 scroll_ptr = &gscroll.module[gscroll.current].scroll;
558 offset_ptr = &gscroll.module[gscroll.current].offset;
559
560 if (!gscroll.expanded)
561 return;
562 if (*scroll_ptr >= dash->module[gscroll.current].idx_data - 1)
563 return;
564 ++(*scroll_ptr);
565 if (*scroll_ptr >= exp_size && *scroll_ptr >= *offset_ptr + exp_size)
566 ++(*offset_ptr);
567 }
568
569 /* Scroll up expanded module */
570 static void
scroll_up_expanded_module(void)571 scroll_up_expanded_module (void) {
572 int *scroll_ptr, *offset_ptr;
573
574 scroll_ptr = &gscroll.module[gscroll.current].scroll;
575 offset_ptr = &gscroll.module[gscroll.current].offset;
576
577 if (!gscroll.expanded)
578 return;
579 if (*scroll_ptr <= 0)
580 return;
581 --(*scroll_ptr);
582 if (*scroll_ptr < *offset_ptr)
583 --(*offset_ptr);
584 }
585
586 /* Scroll up terminal dashboard */
587 static void
scroll_up_dashboard(void)588 scroll_up_dashboard (void) {
589 gscroll.dash--;
590 }
591
592 /* Page up expanded module */
593 static void
page_up_module(void)594 page_up_module (void) {
595 int exp_size = get_num_expanded_data_rows ();
596 int *scroll_ptr, *offset_ptr;
597
598 scroll_ptr = &gscroll.module[gscroll.current].scroll;
599 offset_ptr = &gscroll.module[gscroll.current].offset;
600
601 if (!gscroll.expanded)
602 return;
603 /* decrease scroll and offset by exp_size */
604 *scroll_ptr -= exp_size;
605 if (*scroll_ptr < 0)
606 *scroll_ptr = 0;
607
608 if (*scroll_ptr < *offset_ptr)
609 *offset_ptr -= exp_size;
610 if (*offset_ptr <= 0)
611 *offset_ptr = 0;
612 }
613
614 /* Page down expanded module */
615 static void
page_down_module(void)616 page_down_module (void) {
617 int exp_size = get_num_expanded_data_rows ();
618 int *scroll_ptr, *offset_ptr;
619
620 scroll_ptr = &gscroll.module[gscroll.current].scroll;
621 offset_ptr = &gscroll.module[gscroll.current].offset;
622
623 if (!gscroll.expanded)
624 return;
625
626 *scroll_ptr += exp_size;
627 if (*scroll_ptr >= dash->module[gscroll.current].idx_data - 1)
628 *scroll_ptr = dash->module[gscroll.current].idx_data - 1;
629 if (*scroll_ptr >= exp_size && *scroll_ptr >= *offset_ptr + exp_size)
630 *offset_ptr += exp_size;
631 if (*offset_ptr + exp_size >= dash->module[gscroll.current].idx_data - 1)
632 *offset_ptr = dash->module[gscroll.current].idx_data - exp_size;
633 if (*scroll_ptr < exp_size - 1)
634 *offset_ptr = 0;
635 }
636
637 /* Create a new find dialog window and render it. Upon closing the
638 * window, dashboard is refreshed. */
639 static void
render_search_dialog(int search)640 render_search_dialog (int search) {
641 if (render_find_dialog (main_win, &gscroll))
642 return;
643
644 pthread_mutex_lock (&gdns_thread.mutex);
645 search = perform_next_find (holder, &gscroll);
646 pthread_mutex_unlock (&gdns_thread.mutex);
647 if (search != 0)
648 return;
649
650 free_dashboard (dash);
651 allocate_data ();
652 render_screens ();
653 }
654
655 /* Search for the next occurrence within the dashboard structure */
656 static void
search_next_match(int search)657 search_next_match (int search) {
658 pthread_mutex_lock (&gdns_thread.mutex);
659 search = perform_next_find (holder, &gscroll);
660 pthread_mutex_unlock (&gdns_thread.mutex);
661 if (search != 0)
662 return;
663
664 free_dashboard (dash);
665 allocate_data ();
666 render_screens ();
667 }
668
669 /* Update holder structure and dashboard screen */
670 static void
tail_term(void)671 tail_term (void) {
672 pthread_mutex_lock (&gdns_thread.mutex);
673 free_holder (&holder);
674 pthread_cond_broadcast (&gdns_thread.not_empty);
675 pthread_mutex_unlock (&gdns_thread.mutex);
676
677 free_dashboard (dash);
678 allocate_holder ();
679 allocate_data ();
680
681 term_size (main_win, &main_win_height);
682 render_screens ();
683 }
684
685 static void
tail_html(void)686 tail_html (void) {
687 char *json = NULL;
688
689 pthread_mutex_lock (&gdns_thread.mutex);
690 free_holder (&holder);
691 pthread_cond_broadcast (&gdns_thread.not_empty);
692 pthread_mutex_unlock (&gdns_thread.mutex);
693
694 allocate_holder ();
695
696 pthread_mutex_lock (&gdns_thread.mutex);
697 json = get_json (holder, 0);
698 pthread_mutex_unlock (&gdns_thread.mutex);
699
700 if (json == NULL)
701 return;
702
703 broadcast_holder (gwswriter->fd, json, strlen (json));
704 free (json);
705 }
706
707 /* Fast-forward latest JSON data when client connection is opened. */
708 static void
fast_forward_client(int listener)709 fast_forward_client (int listener) {
710 char *json = NULL;
711
712 pthread_mutex_lock (&gdns_thread.mutex);
713 json = get_json (holder, 0);
714 pthread_mutex_unlock (&gdns_thread.mutex);
715
716 if (json == NULL)
717 return;
718
719 pthread_mutex_lock (&gwswriter->mutex);
720 send_holder_to_client (gwswriter->fd, listener, json, strlen (json));
721 pthread_mutex_unlock (&gwswriter->mutex);
722 free (json);
723 }
724
725 /* Start reading data coming from the client side through the
726 * WebSocket server. */
727 void
read_client(void * ptr_data)728 read_client (void *ptr_data) {
729 GWSReader *reader = (GWSReader *) ptr_data;
730
731 /* check we have a fifo for reading */
732 if (reader->fd == -1)
733 return;
734
735 pthread_mutex_lock (&reader->mutex);
736 set_self_pipe (reader->self_pipe);
737 pthread_mutex_unlock (&reader->mutex);
738
739 while (1) {
740 /* poll(2) will block */
741 if (read_fifo (reader, fast_forward_client))
742 break;
743 }
744 close (reader->fd);
745 }
746
747 /* Parse tailed lines */
748 static void
parse_tail_follow(GLog * glog,FILE * fp)749 parse_tail_follow (GLog * glog, FILE * fp) {
750 #ifdef WITH_GETLINE
751 char *buf = NULL;
752 #else
753 char buf[LINE_BUFFER] = { 0 };
754 #endif
755
756 glog->bytes = 0;
757 #ifdef WITH_GETLINE
758 while ((buf = fgetline (fp)) != NULL) {
759 #else
760 while (fgets (buf, LINE_BUFFER, fp) != NULL) {
761 #endif
762 pthread_mutex_lock (&gdns_thread.mutex);
763 pre_process_log (glog, buf, 0);
764 pthread_mutex_unlock (&gdns_thread.mutex);
765 glog->bytes += strlen (buf);
766 #ifdef WITH_GETLINE
767 free (buf);
768 #endif
769 /* If the ingress rate is greater than MAX_BATCH_LINES,
770 * then we break and allow to re-render the UI */
771 if (++glog->read % MAX_BATCH_LINES == 0)
772 break;
773 }
774 }
775
776 static void
777 verify_inode (FILE * fp, GLog * glog) {
778 struct stat fdstat;
779
780 if (stat (glog->filename, &fdstat) == -1)
781 FATAL ("Unable to stat the specified log file '%s'. %s", glog->filename, strerror (errno));
782
783 glog->size = fdstat.st_size;
784 /* Either the log got smaller, probably was truncated so start reading from 0
785 * and reset snippet.
786 * If the log changed its inode, more likely the log was rotated, so we set
787 * the initial snippet for the new log for future iterations */
788 if (fdstat.st_ino != glog->inode || glog->snippet[0] == '\0' || 0 == glog->size) {
789 glog->length = glog->bytes = 0;
790 set_initial_persisted_data (glog, fp, glog->filename);
791 }
792 glog->inode = fdstat.st_ino;
793 }
794
795 /* Process appended log data */
796 static void
797 perform_tail_follow (GLog * glog) {
798 FILE *fp = NULL;
799 char buf[READ_BYTES + 1] = { 0 };
800 uint16_t len = 0;
801 uint64_t length = 0;
802
803 if (glog->filename[0] == '-' && glog->filename[1] == '\0') {
804 parse_tail_follow (glog, glog->pipe);
805 /* did we read something from the pipe? */
806 if (0 == glog->bytes)
807 return;
808
809 glog->length += glog->bytes;
810 goto out;
811 }
812 if (logs->load_from_disk_only)
813 return;
814
815 length = file_size (glog->filename);
816
817 /* file hasn't changed */
818 /* ###NOTE: This assumes the log file being read can be of smaller size, e.g.,
819 * rotated/truncated file or larger when data is appended */
820 if (length == glog->length)
821 return;
822
823 if (!(fp = fopen (glog->filename, "r")))
824 FATAL ("Unable to read the specified log file '%s'. %s", glog->filename, strerror (errno));
825
826 verify_inode (fp, glog);
827
828 len = MIN (glog->snippetlen, length);
829 /* This is not ideal, but maybe the only reliable way to know if the
830 * current log looks different than our first read/parse */
831 if ((fread (buf, len, 1, fp)) != 1 && ferror (fp))
832 FATAL ("Unable to fread the specified log file '%s'", glog->filename);
833
834 /* For the case where the log got larger since the last iteration, we attempt
835 * to compare the first READ_BYTES against the READ_BYTES we had since the last
836 * parse. If it's different, then it means the file may got truncated but grew
837 * faster than the last iteration (odd, but possible), so we read from 0* */
838 if (glog->snippet[0] != '\0' && buf[0] != '\0' && memcmp (glog->snippet, buf, len) != 0)
839 glog->length = glog->bytes = 0;
840
841 if (!fseeko (fp, glog->length, SEEK_SET))
842 parse_tail_follow (glog, fp);
843 fclose (fp);
844
845 glog->length += glog->bytes;
846
847 /* insert the inode of the file parsed and the last line parsed */
848 if (glog->inode) {
849 glog->lp.line = glog->read;
850 glog->lp.size = glog->size;
851 ht_insert_last_parse (glog->inode, glog->lp);
852 }
853
854 out:
855
856 if (!conf.output_stdout) {
857 struct timespec ts = {.tv_sec = 0,.tv_nsec = 200000000 }; /* 0.2 seconds */
858
859 tail_term ();
860 if (nanosleep (&ts, NULL) == -1 && errno != EINTR)
861 FATAL ("nanosleep: %s", strerror (errno));
862 } else {
863 tail_html ();
864 }
865 }
866
867 /* Entry point to start processing the HTML output */
868 static void
869 process_html (const char *filename) {
870 int i = 0;
871 struct timespec refresh = {
872 .tv_sec = conf.html_refresh ? conf.html_refresh : HTML_REFRESH,
873 .tv_nsec = 0,
874 };
875
876 /* render report */
877 pthread_mutex_lock (&gdns_thread.mutex);
878 output_html (holder, filename);
879 pthread_mutex_unlock (&gdns_thread.mutex);
880 /* not real time? */
881 if (!conf.real_time_html)
882 return;
883 /* ignore loading from disk */
884 if (logs->load_from_disk_only)
885 return;
886
887 pthread_mutex_lock (&gwswriter->mutex);
888 gwswriter->fd = open_fifoin ();
889 pthread_mutex_unlock (&gwswriter->mutex);
890
891 /* open fifo for write */
892 if (gwswriter->fd == -1)
893 return;
894
895 set_ready_state ();
896 while (1) {
897 if (conf.stop_processing)
898 break;
899
900 for (i = 0; i < logs->size; ++i)
901 perform_tail_follow (&logs->glog[i]); /* 0.2 secs */
902 if (nanosleep (&refresh, NULL) == -1 && errno != EINTR)
903 FATAL ("nanosleep: %s", strerror (errno));
904 }
905 close (gwswriter->fd);
906 }
907
908 /* Iterate over available panels and advance the panel pointer. */
909 static int
910 next_module (void) {
911 int next = -1;
912
913 if ((next = get_next_module (gscroll.current)) == -1)
914 return 1;
915
916 gscroll.current = next;
917 if (!conf.no_tab_scroll)
918 gscroll.dash = get_module_index (gscroll.current) * DASH_COLLAPSED;
919
920 return 0;
921 }
922
923 /* Iterate over available panels and rewind the panel pointer. */
924 static int
925 previous_module (void) {
926 int prev = -1;
927
928 if ((prev = get_prev_module (gscroll.current)) == -1)
929 return 1;
930
931 gscroll.current = prev;
932 if (!conf.no_tab_scroll)
933 gscroll.dash = get_module_index (gscroll.current) * DASH_COLLAPSED;
934
935 return 0;
936 }
937
938 /* Perform several curses operations upon resizing the terminal. */
939 static void
940 window_resize (void) {
941 endwin ();
942 refresh ();
943 werase (header_win);
944 werase (main_win);
945 werase (stdscr);
946 term_size (main_win, &main_win_height);
947 refresh ();
948 render_screens ();
949 }
950
951 /* Create a new sort dialog window and render it. Upon closing the
952 * window, dashboard is refreshed. */
953 static void
954 render_sort_dialog (void) {
955 load_sort_win (main_win, gscroll.current, &module_sort[gscroll.current]);
956
957 pthread_mutex_lock (&gdns_thread.mutex);
958 free_holder (&holder);
959 pthread_cond_broadcast (&gdns_thread.not_empty);
960 pthread_mutex_unlock (&gdns_thread.mutex);
961
962 free_dashboard (dash);
963 allocate_holder ();
964 allocate_data ();
965 render_screens ();
966 }
967
968 /* Interfacing with the keyboard */
969 static void
970 get_keys (void) {
971 int search = 0;
972 int c, quit = 1, i;
973
974 while (quit) {
975 if (conf.stop_processing)
976 break;
977 c = wgetch (stdscr);
978 switch (c) {
979 case 'q': /* quit */
980 if (!gscroll.expanded) {
981 quit = 0;
982 break;
983 }
984 collapse_current_module ();
985 break;
986 case KEY_F (1):
987 case '?':
988 case 'h':
989 load_help_popup (main_win);
990 render_screens ();
991 break;
992 case 49: /* 1 */
993 /* reset expanded module */
994 set_module_to (&gscroll, VISITORS);
995 break;
996 case 50: /* 2 */
997 /* reset expanded module */
998 set_module_to (&gscroll, REQUESTS);
999 break;
1000 case 51: /* 3 */
1001 /* reset expanded module */
1002 set_module_to (&gscroll, REQUESTS_STATIC);
1003 break;
1004 case 52: /* 4 */
1005 /* reset expanded module */
1006 set_module_to (&gscroll, NOT_FOUND);
1007 break;
1008 case 53: /* 5 */
1009 /* reset expanded module */
1010 set_module_to (&gscroll, HOSTS);
1011 break;
1012 case 54: /* 6 */
1013 /* reset expanded module */
1014 set_module_to (&gscroll, OS);
1015 break;
1016 case 55: /* 7 */
1017 /* reset expanded module */
1018 set_module_to (&gscroll, BROWSERS);
1019 break;
1020 case 56: /* 8 */
1021 /* reset expanded module */
1022 set_module_to (&gscroll, VISIT_TIMES);
1023 break;
1024 case 57: /* 9 */
1025 /* reset expanded module */
1026 set_module_to (&gscroll, VIRTUAL_HOSTS);
1027 break;
1028 case 48: /* 0 */
1029 /* reset expanded module */
1030 set_module_to (&gscroll, REFERRERS);
1031 break;
1032 case 33: /* shift + 1 */
1033 /* reset expanded module */
1034 set_module_to (&gscroll, REFERRING_SITES);
1035 break;
1036 case 34: /* shift + 2 */
1037 /* reset expanded module */
1038 set_module_to (&gscroll, KEYPHRASES);
1039 break;
1040 case 35: /* Shift + 3 */
1041 /* reset expanded module */
1042 set_module_to (&gscroll, STATUS_CODES);
1043 break;
1044 case 36: /* Shift + 3 */
1045 /* reset expanded module */
1046 set_module_to (&gscroll, REMOTE_USER);
1047 break;
1048 #ifdef HAVE_GEOLOCATION
1049 case 37: /* Shift + 4 */
1050 /* reset expanded module */
1051 set_module_to (&gscroll, GEO_LOCATION);
1052 break;
1053 #endif
1054 case 38: /* Shift + 5 */
1055 /* reset expanded module */
1056 set_module_to (&gscroll, CACHE_STATUS);
1057 break;
1058 case 9: /* TAB */
1059 /* reset expanded module */
1060 collapse_current_module ();
1061 if (next_module () == 0)
1062 render_screens ();
1063 break;
1064 case 353: /* Shift TAB */
1065 /* reset expanded module */
1066 collapse_current_module ();
1067 if (previous_module () == 0)
1068 render_screens ();
1069 break;
1070 case 'g': /* g = top */
1071 scroll_to_first_line ();
1072 display_content (main_win, dash, &gscroll);
1073 break;
1074 case 'G': /* G = down */
1075 scroll_to_last_line ();
1076 display_content (main_win, dash, &gscroll);
1077 break;
1078 /* expand dashboard module */
1079 case KEY_RIGHT:
1080 case 0x0a:
1081 case 0x0d:
1082 case 32: /* ENTER */
1083 case 79: /* o */
1084 case 111: /* O */
1085 case KEY_ENTER:
1086 expand_current_module ();
1087 display_content (main_win, dash, &gscroll);
1088 break;
1089 case KEY_DOWN: /* scroll main dashboard */
1090 if ((gscroll.dash + main_win_height) < dash->total_alloc) {
1091 gscroll.dash++;
1092 display_content (main_win, dash, &gscroll);
1093 }
1094 break;
1095 case KEY_MOUSE: /* handles mouse events */
1096 expand_on_mouse_click ();
1097 break;
1098 case 106: /* j - DOWN expanded module */
1099 scroll_down_expanded_module ();
1100 display_content (main_win, dash, &gscroll);
1101 break;
1102 /* scroll up main_win */
1103 case KEY_UP:
1104 if (gscroll.dash > 0) {
1105 scroll_up_dashboard ();
1106 display_content (main_win, dash, &gscroll);
1107 }
1108 break;
1109 case 2: /* ^ b - page up */
1110 case 339: /* ^ PG UP */
1111 page_up_module ();
1112 display_content (main_win, dash, &gscroll);
1113 break;
1114 case 6: /* ^ f - page down */
1115 case 338: /* ^ PG DOWN */
1116 page_down_module ();
1117 display_content (main_win, dash, &gscroll);
1118 break;
1119 case 107: /* k - UP expanded module */
1120 scroll_up_expanded_module ();
1121 display_content (main_win, dash, &gscroll);
1122 break;
1123 case 'n':
1124 search_next_match (search);
1125 break;
1126 case '/':
1127 render_search_dialog (search);
1128 break;
1129 case 99: /* c */
1130 if (conf.no_color)
1131 break;
1132 load_schemes_win (main_win);
1133 free_dashboard (dash);
1134 allocate_data ();
1135 set_wbkgd (main_win, header_win);
1136 render_screens ();
1137 break;
1138 case 115: /* s */
1139 render_sort_dialog ();
1140 break;
1141 case 269:
1142 case KEY_RESIZE:
1143 window_resize ();
1144 break;
1145 default:
1146 for (i = 0; i < logs->size; ++i)
1147 perform_tail_follow (&logs->glog[i]);
1148 break;
1149 }
1150 }
1151 }
1152
1153 /* Store accumulated processing time
1154 * Note: As we store with time_t second resolution,
1155 * if elapsed time == 0, we will bump it to 1.
1156 */
1157 static void
1158 set_accumulated_time (void) {
1159 time_t elapsed = end_proc - start_proc;
1160 elapsed = (!elapsed) ? !elapsed : elapsed;
1161 ht_inc_cnt_overall ("processing_time", elapsed);
1162 }
1163
1164 /* Execute the following calls right before we start the main
1165 * processing/parsing loop */
1166 static void
1167 init_processing (void) {
1168 /* perform some additional checks before parsing panels */
1169 verify_panels ();
1170 /* initialize storage */
1171 parsing_spinner->label = "SETTING UP STORAGE";
1172 init_storage ();
1173 set_spec_date_format ();
1174 }
1175
1176 /* Determine the type of output, i.e., JSON, CSV, HTML */
1177 static void
1178 standard_output (void) {
1179 char *csv = NULL, *json = NULL, *html = NULL;
1180
1181 /* CSV */
1182 if (find_output_type (&csv, "csv", 1) == 0)
1183 output_csv (holder, csv);
1184 /* JSON */
1185 if (find_output_type (&json, "json", 1) == 0)
1186 output_json (holder, json);
1187 /* HTML */
1188 if (find_output_type (&html, "html", 1) == 0 || conf.output_format_idx == 0)
1189 process_html (html);
1190
1191 free (csv);
1192 free (html);
1193 free (json);
1194 }
1195
1196 /* Output to a terminal */
1197 static void
1198 curses_output (void) {
1199 allocate_data ();
1200 if (!conf.skip_term_resolver)
1201 gdns_thread_create ();
1202
1203 render_screens ();
1204 /* will loop in here */
1205 get_keys ();
1206 }
1207
1208 /* Set locale */
1209 static void
1210 set_locale (void) {
1211 char *loc_ctype;
1212
1213 setlocale (LC_ALL, "");
1214 #ifdef ENABLE_NLS
1215 bindtextdomain (PACKAGE, LOCALEDIR);
1216 textdomain (PACKAGE);
1217 #endif
1218
1219 loc_ctype = getenv ("LC_CTYPE");
1220 if (loc_ctype != NULL)
1221 setlocale (LC_CTYPE, loc_ctype);
1222 else if ((loc_ctype = getenv ("LC_ALL")))
1223 setlocale (LC_CTYPE, loc_ctype);
1224 else
1225 setlocale (LC_CTYPE, "");
1226 }
1227
1228 /* Attempt to get the current name of a terminal or fallback to /dev/tty
1229 *
1230 * On error, -1 is returned
1231 * On success, the new file descriptor is returned */
1232 static int
1233 open_term (char **buf) {
1234 const char *term = "/dev/tty";
1235
1236 if (!isatty (STDERR_FILENO) || (term = ttyname (STDERR_FILENO)) == 0) {
1237 if (!isatty (STDOUT_FILENO) || (term = ttyname (STDOUT_FILENO)) == 0) {
1238 if (!isatty (STDIN_FILENO) || (term = ttyname (STDIN_FILENO)) == 0) {
1239 term = "/dev/tty";
1240 }
1241 }
1242 }
1243 *buf = xstrdup (term);
1244
1245 return open (term, O_RDONLY);
1246 }
1247
1248 /* Determine if reading from a pipe, and duplicate file descriptors so
1249 * it doesn't get in the way of curses' normal reading stdin for
1250 * wgetch() */
1251 static FILE *
1252 set_pipe_stdin (void) {
1253 char *term = NULL;
1254 FILE *pipe = stdin;
1255 int term_fd = -1;
1256 int pipe_fd = -1;
1257
1258 /* If unable to open a terminal, yet data is being piped, then it's
1259 * probably from the cron, or when running as a user that can't open a
1260 * terminal. In that case it's still important to set the pipe as
1261 * non-blocking.
1262 *
1263 * Note: If used from the cron, it will require the
1264 * user to use a single dash to parse piped data such as:
1265 * cat access.log | goaccess - */
1266 if ((term_fd = open_term (&term)) == -1)
1267 goto out1;
1268
1269 if ((pipe_fd = dup (fileno (stdin))) == -1)
1270 FATAL ("Unable to dup stdin: %s", strerror (errno));
1271
1272 pipe = fdopen (pipe_fd, "r");
1273 if (freopen (term, "r", stdin) == 0)
1274 FATAL ("Unable to open input from TTY");
1275 if (fileno (stdin) != 0)
1276 (void) dup2 (fileno (stdin), 0);
1277
1278 add_dash_filename ();
1279
1280 out1:
1281
1282 /* no need to set it as non-blocking since we are simply outputting a
1283 * static report */
1284 if (conf.output_stdout && !conf.real_time_html)
1285 goto out2;
1286
1287 /* Using select(), poll(), or epoll(), etc may be a better choice... */
1288 if (pipe_fd == -1)
1289 pipe_fd = fileno (pipe);
1290 if (fcntl (pipe_fd, F_SETFL, fcntl (pipe_fd, F_GETFL, 0) | O_NONBLOCK) == -1)
1291 FATAL ("Unable to set fd as non-blocking: %s.", strerror (errno));
1292
1293 out2:
1294
1295 free (term);
1296
1297 return pipe;
1298 }
1299
1300 /* Determine if we are getting data from the stdin, and where are we
1301 * outputting to. */
1302 static void
1303 set_io (FILE ** pipe) {
1304 /* For backwards compatibility, check if we are not outputting to a
1305 * terminal or if an output format was supplied */
1306 if (!isatty (STDOUT_FILENO) || conf.output_format_idx > 0)
1307 conf.output_stdout = 1;
1308 /* dup fd if data piped */
1309 if (!isatty (STDIN_FILENO))
1310 *pipe = set_pipe_stdin ();
1311 }
1312
1313 /* Process command line options and set some default options. */
1314 static void
1315 parse_cmd_line (int argc, char **argv) {
1316 read_option_args (argc, argv);
1317 set_default_static_files ();
1318 }
1319
1320 static void
1321 handle_signal_action (GO_UNUSED int sig_number) {
1322 fprintf (stderr, "\nSIGINT caught!\n");
1323 fprintf (stderr, "Closing GoAccess...\n");
1324
1325 stop_ws_server (gwswriter, gwsreader);
1326 conf.stop_processing = 1;
1327
1328 if (!conf.output_stdout) {
1329 cleanup (EXIT_SUCCESS);
1330 exit (EXIT_SUCCESS);
1331 }
1332 }
1333
1334 static void
1335 setup_thread_signals (void) {
1336 struct sigaction act;
1337
1338 act.sa_handler = handle_signal_action;
1339 sigemptyset (&act.sa_mask);
1340 act.sa_flags = 0;
1341
1342 sigaction (SIGINT, &act, NULL);
1343 sigaction (SIGTERM, &act, NULL);
1344 signal (SIGPIPE, SIG_IGN);
1345
1346 /* Restore old signal mask for the main thread */
1347 pthread_sigmask (SIG_SETMASK, &oldset, NULL);
1348 }
1349
1350 static void
1351 block_thread_signals (void) {
1352 /* Avoid threads catching SIGINT/SIGPIPE/SIGTERM and handle them in
1353 * main thread */
1354 sigset_t sigset;
1355 sigemptyset (&sigset);
1356 sigaddset (&sigset, SIGINT);
1357 sigaddset (&sigset, SIGPIPE);
1358 sigaddset (&sigset, SIGTERM);
1359 pthread_sigmask (SIG_BLOCK, &sigset, &oldset);
1360 }
1361
1362 /* Initialize various types of data. */
1363 static void
1364 initializer (void) {
1365 int i;
1366 FILE *pipe = NULL;
1367
1368 /* drop permissions right away */
1369 if (conf.username)
1370 drop_permissions ();
1371
1372 /* then initialize modules and set */
1373 gscroll.current = init_modules ();
1374 /* setup to use the current locale */
1375 set_locale ();
1376
1377 parse_browsers_file ();
1378
1379 #ifdef HAVE_GEOLOCATION
1380 init_geoip ();
1381 #endif
1382
1383 set_io (&pipe);
1384
1385 /* init glog */
1386 if (!(logs = init_logs (conf.filenames_idx)))
1387 FATAL (ERR_NO_DATA_PASSED);
1388
1389 set_signal_data (logs);
1390
1391 for (i = 0; i < logs->size; ++i)
1392 if (logs->glog[i].filename[0] == '-' && logs->glog[i].filename[1] == '\0')
1393 logs->glog[i].pipe = pipe;
1394
1395 /* init parsing spinner */
1396 parsing_spinner = new_gspinner ();
1397 parsing_spinner->processed = &(logs->processed);
1398 parsing_spinner->filename = &(logs->filename);
1399
1400 /* init random number generator */
1401 srand (getpid ());
1402 init_pre_storage ();
1403 }
1404
1405 static char *
1406 generate_fifo_name (void) {
1407 char fname[RAND_FN];
1408 const char *tmp;
1409 char *path;
1410 size_t len;
1411
1412 if ((tmp = getenv ("TMPDIR")) == NULL)
1413 tmp = "/tmp";
1414
1415 memset (fname, 0, sizeof (fname));
1416 genstr (fname, RAND_FN - 1);
1417
1418 len = snprintf (NULL, 0, "%s/goaccess_fifo_%s", tmp, fname) + 1;
1419 path = xmalloc (len);
1420 snprintf (path, len, "%s/goaccess_fifo_%s", tmp, fname);
1421
1422 return path;
1423 }
1424
1425 static int
1426 spawn_ws (void) {
1427 gwswriter = new_gwswriter ();
1428 gwsreader = new_gwsreader ();
1429
1430 if (!conf.fifo_in)
1431 conf.fifo_in = generate_fifo_name ();
1432 if (!conf.fifo_out)
1433 conf.fifo_out = generate_fifo_name ();
1434
1435 /* open fifo for read */
1436 if ((gwsreader->fd = open_fifoout ()) == -1) {
1437 LOG (("Unable to open FIFO for read.\n"));
1438 return 1;
1439 }
1440
1441 if (conf.daemonize)
1442 daemonize ();
1443 setup_ws_server (gwswriter, gwsreader);
1444
1445 return 0;
1446 }
1447
1448 static void
1449 set_standard_output (void) {
1450 int html = 0;
1451
1452 /* HTML */
1453 if (find_output_type (NULL, "html", 0) == 0 || conf.output_format_idx == 0)
1454 html = 1;
1455
1456 /* Spawn WebSocket server threads */
1457 if (html && conf.real_time_html) {
1458 if (spawn_ws ())
1459 return;
1460 }
1461 setup_thread_signals ();
1462
1463 /* Spawn progress spinner thread */
1464 ui_spinner_create (parsing_spinner);
1465 }
1466
1467 /* Set up curses. */
1468 static void
1469 set_curses (int *quit) {
1470 const char *err_log = NULL;
1471
1472 setup_thread_signals ();
1473 set_input_opts ();
1474 if (conf.no_color || has_colors () == FALSE) {
1475 conf.color_scheme = NO_COLOR;
1476 conf.no_color = 1;
1477 } else {
1478 start_color ();
1479 }
1480 init_colors (0);
1481 init_windows (&header_win, &main_win);
1482 set_curses_spinner (parsing_spinner);
1483
1484 /* Display configuration dialog if missing formats and not piping data in */
1485 if (!conf.read_stdin && (verify_formats () || conf.load_conf_dlg)) {
1486 refresh ();
1487 *quit = render_confdlg (logs, parsing_spinner);
1488 clear ();
1489 }
1490 /* Piping data in without log/date/time format */
1491 else if (conf.read_stdin && (err_log = verify_formats ())) {
1492 FATAL ("%s", err_log);
1493 }
1494 /* straight parsing */
1495 else {
1496 ui_spinner_create (parsing_spinner);
1497 }
1498 }
1499
1500 /* Where all begins... */
1501 int
1502 main (int argc, char **argv) {
1503 int quit = 0, ret = 0;
1504
1505 block_thread_signals ();
1506 setup_sigsegv_handler ();
1507
1508 /* command line/config options */
1509 verify_global_config (argc, argv);
1510 parse_conf_file (&argc, &argv);
1511 parse_cmd_line (argc, argv);
1512
1513 initializer ();
1514
1515 /* ignore outputting, process only */
1516 if (conf.process_and_exit) {
1517 }
1518 /* set stdout */
1519 else if (conf.output_stdout) {
1520 set_standard_output ();
1521 }
1522 /* set curses */
1523 else {
1524 set_curses (&quit);
1525 }
1526
1527 /* no log/date/time format set */
1528 if (quit)
1529 goto clean;
1530
1531 init_processing ();
1532
1533 /* main processing event */
1534 time (&start_proc);
1535 parsing_spinner->label = "PARSING";
1536
1537 if ((ret = parse_log (logs, 0))) {
1538 end_spinner ();
1539 goto clean;
1540 }
1541
1542 if (conf.stop_processing)
1543 goto clean;
1544 logs->offset = *logs->processed;
1545
1546 /* init reverse lookup thread */
1547 gdns_init ();
1548 parse_initial_sort ();
1549 allocate_holder ();
1550
1551 end_spinner ();
1552 time (&end_proc);
1553
1554 set_accumulated_time ();
1555 if (conf.process_and_exit) {
1556 }
1557 /* stdout */
1558 else if (conf.output_stdout) {
1559 standard_output ();
1560 }
1561 /* curses */
1562 else {
1563 curses_output ();
1564 }
1565
1566 /* clean */
1567 clean:
1568 cleanup (ret);
1569
1570 return ret ? EXIT_FAILURE : EXIT_SUCCESS;
1571 }
1572