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