source: logging.c @ 8a06468

Last change on this file since 8a06468 was 8a06468, checked in by Jason Gross <jasongross9@gmail.com>, 7 years ago
Clarify a comment about deferred logging mode As per https://github.com/barnowl/barnowl/pull/109#discussion_r131532293
  • Property mode set to 100644
File size: 19.8 KB
Line 
1#include "owl.h"
2#include <stdio.h>
3
4typedef struct _owl_log_entry { /* noproto */
5  char *filename;
6  char *message;
7} owl_log_entry;
8
9typedef struct _owl_log_options { /* noproto */
10  bool drop_failed_logs;
11  bool display_initial_log_count;
12} owl_log_options;
13
14static GMainContext *log_context;
15static GMainLoop *log_loop;
16static GThread *logging_thread;
17static bool defer_logs; /* to be accessed only on the disk-writing thread */
18static GQueue *deferred_entry_queue;
19
20/* This is now the one function that should be called to log a
21 * message.  It will do all the work necessary by calling the other
22 * functions in this file as necessary.
23 */
24void owl_log_message(const owl_message *m) {
25  owl_function_debugmsg("owl_log_message: entering");
26
27  if (m == NULL) {
28    owl_function_debugmsg("owl_log_message: passed null message");
29    return;
30  }
31
32  /* should we be logging this message? */
33  if (!owl_log_shouldlog_message(m)) {
34    owl_function_debugmsg("owl_log_message: not logging message");
35    return;
36  }
37
38  /* handle incmoing messages */
39  if (owl_message_is_direction_in(m)) {
40    owl_log_incoming(m);
41    owl_function_debugmsg("owl_log_message: leaving");
42    return;
43  }
44
45  /* handle outgoing messages */
46  owl_log_outgoing(m);
47
48  owl_function_debugmsg("owl_log_message: leaving");
49}
50
51/* Return 1 if we should log the given message, otherwise return 0 */
52int owl_log_shouldlog_message(const owl_message *m) {
53  const owl_filter *f;
54
55  /* If there's a logfilter and this message matches it, log */
56  f=owl_global_get_filter(&g, owl_global_get_logfilter(&g));
57  if (f && owl_filter_message_match(f, m)) return(1);
58
59  /* otherwise we do things based on the logging variables */
60
61  /* skip login/logout messages if appropriate */
62  if (!owl_global_is_loglogins(&g) && owl_message_is_loginout(m)) return(0);
63     
64  /* check direction */
65  if ((owl_global_get_loggingdirection(&g)==OWL_LOGGING_DIRECTION_IN) && owl_message_is_direction_out(m)) {
66    return(0);
67  }
68  if ((owl_global_get_loggingdirection(&g)==OWL_LOGGING_DIRECTION_OUT) && owl_message_is_direction_in(m)) {
69    return(0);
70  }
71
72  if (owl_message_is_type_zephyr(m)) {
73    if (owl_message_is_personal(m) && !owl_global_is_logging(&g)) return(0);
74    if (!owl_message_is_personal(m) && !owl_global_is_classlogging(&g)) return(0);
75  } else {
76    if (owl_message_is_private(m) || owl_message_is_loginout(m)) {
77      if (!owl_global_is_logging(&g)) return(0);
78    } else {
79      if (!owl_global_is_classlogging(&g)) return(0);
80    }
81  }
82  return(1);
83}
84
85CALLER_OWN char *owl_log_zephyr(const owl_message *m)
86{
87    char *tmp = NULL;
88    GString *buffer = NULL;
89    buffer = g_string_new("");
90    tmp = short_zuser(owl_message_get_sender(m));
91    g_string_append_printf(buffer, "Class: %s Instance: %s", 
92                           owl_message_get_class(m), 
93                           owl_message_get_instance(m));
94    if (strcmp(owl_message_get_opcode(m), "")) {
95      g_string_append_printf(buffer, " Opcode: %s", 
96                             owl_message_get_opcode(m));
97    }
98    g_string_append_printf(buffer, "\n");
99    g_string_append_printf(buffer, "Time: %s Host: %s\n", 
100                           owl_message_get_timestr(m), 
101                           owl_message_get_hostname(m));
102    g_string_append_printf(buffer, "From: %s <%s>\n\n", 
103                           owl_message_get_zsig(m), tmp);
104    g_string_append_printf(buffer, "%s\n\n", owl_message_get_body(m));
105    g_free(tmp);
106    return g_string_free(buffer, FALSE);
107}
108
109CALLER_OWN char *owl_log_aim(const owl_message *m)
110{
111    GString *buffer = NULL;
112    buffer = g_string_new("");
113    g_string_append_printf(buffer, "From: <%s> To: <%s>\n", 
114                           owl_message_get_sender(m), owl_message_get_recipient(m));
115    g_string_append_printf(buffer, "Time: %s\n\n", 
116                           owl_message_get_timestr(m));
117    if (owl_message_is_login(m)) {
118        g_string_append_printf(buffer, "LOGIN\n\n");
119    } else if (owl_message_is_logout(m)) {
120        g_string_append_printf(buffer, "LOGOUT\n\n");
121    } else {
122        g_string_append_printf(buffer, "%s\n\n", owl_message_get_body(m));
123    }
124    return g_string_free(buffer, FALSE);
125}
126
127CALLER_OWN char *owl_log_jabber(const owl_message *m)
128{
129    GString *buffer = NULL;
130    buffer = g_string_new("");
131    g_string_append_printf(buffer, "From: <%s> To: <%s>\n",
132                           owl_message_get_sender(m), 
133                           owl_message_get_recipient(m));
134    g_string_append_printf(buffer, "Time: %s\n\n", 
135                           owl_message_get_timestr(m));
136    g_string_append_printf(buffer, "%s\n\n", owl_message_get_body(m));
137    return g_string_free(buffer, FALSE);
138}
139
140CALLER_OWN char *owl_log_generic(const owl_message *m)
141{
142    GString *buffer;
143    buffer = g_string_new("");
144    g_string_append_printf(buffer, "From: <%s> To: <%s>\n", 
145                           owl_message_get_sender(m), 
146                           owl_message_get_recipient(m));
147    g_string_append_printf(buffer, "Time: %s\n\n", 
148                           owl_message_get_timestr(m));
149    g_string_append_printf(buffer, "%s\n\n", 
150                           owl_message_get_body(m));
151    return g_string_free(buffer, FALSE);
152}
153
154static void owl_log_error_main_thread(gpointer data)
155{
156  owl_function_error("%s", (const char*)data);
157}
158
159static void owl_log_adminmsg_main_thread(gpointer data)
160{
161  owl_function_adminmsg("Logging", (const char*)data);
162}
163
164static void owl_log_makemsg_main_thread(gpointer data)
165{
166  owl_function_makemsg((const char*)data);
167}
168
169static void G_GNUC_PRINTF(1, 2) owl_log_error(const char *fmt, ...)
170{
171  va_list ap;
172  char *data;
173
174  va_start(ap, fmt);
175  data = g_strdup_vprintf(fmt, ap);
176  va_end(ap);
177
178  owl_select_post_task(owl_log_error_main_thread,
179                       data, g_free, g_main_context_default());
180}
181
182static void G_GNUC_PRINTF(1, 2) owl_log_adminmsg(const char *fmt, ...)
183{
184  va_list ap;
185  char *data;
186
187  va_start(ap, fmt);
188  data = g_strdup_vprintf(fmt, ap);
189  va_end(ap);
190
191  owl_select_post_task(owl_log_adminmsg_main_thread,
192                       data, g_free, g_main_context_default());
193}
194
195static void G_GNUC_PRINTF(1, 2) owl_log_makemsg(const char *fmt, ...)
196{
197  va_list ap;
198  char *data;
199
200  va_start(ap, fmt);
201  data = g_strdup_vprintf(fmt, ap);
202  va_end(ap);
203
204  owl_select_post_task(owl_log_makemsg_main_thread,
205                       data, g_free, g_main_context_default());
206}
207
208static CALLER_OWN owl_log_entry *owl_log_new_entry(const char *buffer, const char *filename)
209{
210  owl_log_entry *log_msg = g_slice_new(owl_log_entry);
211  log_msg->message = g_strdup(buffer);
212  log_msg->filename = g_strdup(filename);
213  return log_msg;
214}
215
216static void owl_log_deferred_enqueue_message(const char *buffer, const char *filename)
217{
218  g_queue_push_tail(deferred_entry_queue, owl_log_new_entry(buffer, filename));
219}
220
221static void owl_log_deferred_enqueue_first_message(const char *buffer, const char *filename)
222{
223  g_queue_push_head(deferred_entry_queue, owl_log_new_entry(buffer, filename));
224}
225
226/* write out the entry if possible
227 * return 0 on success, errno on failure to open
228 */
229static int owl_log_try_write_entry(owl_log_entry *msg)
230{
231  FILE *file = NULL;
232  file = fopen(msg->filename, "a");
233  if (!file) {
234    return errno;
235  }
236  fprintf(file, "%s", msg->message);
237  fclose(file);
238  return 0;
239}
240
241static void owl_log_entry_free(void *data)
242{
243  owl_log_entry *msg = (owl_log_entry*)data;
244  if (msg) {
245    g_free(msg->message);
246    g_free(msg->filename);
247    g_slice_free(owl_log_entry, msg);
248  }
249}
250
251#if GLIB_CHECK_VERSION(2, 32, 0)
252#else
253static void owl_log_entry_free_gfunc(gpointer data, gpointer user_data)
254{
255  owl_log_entry_free(data);
256}
257#endif
258
259static void owl_log_file_error(owl_log_entry *msg, int errnum)
260{
261  owl_log_error("Unable to open file for logging: %s (file %s)",
262                g_strerror(errnum),
263                msg->filename);
264}
265
266/* If we are deferring log messages, enqueue this entry for writing.
267 * Otherwise, try to write this log message, and, if it fails with
268 * EPERM, EACCES, or ETIMEDOUT, go into deferred logging mode and
269 * queue an admin message.  If it fails with anything else, display an
270 * error message, drop the log message, and do not go into deferred
271 * logging mode.
272 *
273 * N.B. This function is called only on the disk-writing thread. */
274static void owl_log_eventually_write_entry(gpointer data)
275{
276  int ret;
277  owl_log_entry *msg = (owl_log_entry*)data;
278  if (defer_logs) {
279    owl_log_deferred_enqueue_message(msg->message, msg->filename);
280  } else {
281    ret = owl_log_try_write_entry(msg);
282    if (ret == EPERM || ret == EACCES || ret == ETIMEDOUT) {
283      defer_logs = true;
284      owl_log_error("Unable to open file for logging (%s): \n"
285                    "%s.  \n"
286                    "Consider renewing your tickets.  Logging has been \n"
287                    "suspended, and your messages will be saved.  To \n"
288                    "resume logging, use the command :flush-logs.\n\n",
289                    msg->filename,
290                    g_strerror(ret));
291      /* If we were not in deferred logging mode, either the queue should be
292       * empty, or we are attempting to log a message that we just popped off
293       * the head of the queue.  Either way, we should enqueue this message as
294       * the first message in the queue, rather than the last, so that we
295       * preserve message ordering. */
296      owl_log_deferred_enqueue_first_message(msg->message, msg->filename);
297    } else if (ret != 0) {
298      owl_log_file_error(msg, ret);
299    }
300  }
301}
302
303/* tries to write the deferred log entries
304 *
305 * N.B. This function is called only on the disk-writing thread. */
306static void owl_log_write_deferred_entries(gpointer data)
307{
308  owl_log_entry *entry;
309  owl_log_options *opts = (owl_log_options *)data;
310  int ret;
311  int logged_message_count = 0;
312  bool all_succeeded = true;
313
314  if (opts->display_initial_log_count) {
315    if (g_queue_is_empty(deferred_entry_queue)) {
316      owl_log_makemsg("There are no logs to flush.");
317    } else {
318      owl_log_makemsg("Attempting to flush %u logs...",
319                      g_queue_get_length(deferred_entry_queue));
320    }
321  }
322
323  defer_logs = false;
324  while (!g_queue_is_empty(deferred_entry_queue) && !defer_logs) {
325    logged_message_count++;
326    entry = (owl_log_entry*)g_queue_pop_head(deferred_entry_queue);
327    if (!opts->drop_failed_logs) {
328      /* Attempt to write the log entry.  If it fails, re-queue the entry at
329       * the head of the queue. */
330      owl_log_eventually_write_entry(entry);
331    } else {
332      /* Attempt to write the log entry. If it fails, print an error message,
333       * drop the log, and keep going through the queue. */
334      ret = owl_log_try_write_entry(entry);
335      if (ret != 0) {
336        all_succeeded = false;
337        owl_log_file_error(entry, ret);
338      }
339    }
340    owl_log_entry_free(entry);
341  }
342  if (logged_message_count > 0) {
343    if (opts->display_initial_log_count) {
344      /* first clear the "attempting to flush" message from the status bar */
345      owl_log_makemsg("");
346    }
347    if (!defer_logs) {
348      if (all_succeeded) {
349        owl_log_adminmsg("Flushed %d logs and resumed logging.",
350                         logged_message_count);
351      } else {
352        owl_log_adminmsg("Flushed or dropped %d logs and resumed logging.",
353                         logged_message_count);
354      }
355    } else {
356      owl_log_error("Attempted to flush %d logs; %u deferred logs remain.",
357                    logged_message_count, g_queue_get_length(deferred_entry_queue));
358    }
359  }
360}
361
362void owl_log_flush_logs(bool drop_failed_logs, bool quiet)
363{
364  owl_log_options *data = g_new(owl_log_options, 1);
365  data->drop_failed_logs = drop_failed_logs;
366  data->display_initial_log_count = !quiet;
367
368  owl_select_post_task(owl_log_write_deferred_entries,
369                       data,
370                       g_free,
371                       log_context);
372}
373
374void owl_log_enqueue_message(const char *buffer, const char *filename)
375{
376  owl_log_entry *log_msg = owl_log_new_entry(buffer, filename);
377  owl_select_post_task(owl_log_eventually_write_entry, log_msg,
378                       owl_log_entry_free, log_context);
379}
380
381void owl_log_append(const owl_message *m, const char *filename) {
382  char *buffer = NULL;
383  if (owl_message_is_type_zephyr(m)) {
384    buffer = owl_log_zephyr(m);
385  } else if (owl_message_is_type_jabber(m)) {
386    buffer = owl_log_jabber(m);
387  } else if (owl_message_is_type_aim(m)) {
388    buffer = owl_log_aim(m);
389  } else {
390    buffer = owl_log_generic(m);
391  }
392  owl_log_enqueue_message(buffer, filename);
393  g_free(buffer);
394}
395
396void owl_log_outgoing(const owl_message *m)
397{
398  char *filename, *logpath;
399  char *to, *temp;
400  GList *cc;
401
402  /* expand ~ in path names */
403  logpath = owl_util_makepath(owl_global_get_logpath(&g));
404
405  /* Figure out what path to log to */
406  if (owl_message_is_type_zephyr(m)) {
407    /* If this has CC's, do all but the "recipient" which we'll do below */
408    cc = owl_message_get_cc_without_recipient(m);
409    while (cc != NULL) {
410      temp = short_zuser(cc->data);
411      filename = g_build_filename(logpath, temp, NULL);
412      owl_log_append(m, filename);
413
414      g_free(filename);
415      g_free(temp);
416      g_free(cc->data);
417      cc = g_list_delete_link(cc, cc);
418    }
419
420    to = short_zuser(owl_message_get_recipient(m));
421  } else if (owl_message_is_type_jabber(m)) {
422    to = g_strdup_printf("jabber:%s", owl_message_get_recipient(m));
423    g_strdelimit(to, "/", '_');
424  } else if (owl_message_is_type_aim(m)) {
425    char *temp2;
426    temp = owl_aim_normalize_screenname(owl_message_get_recipient(m));
427    temp2 = g_utf8_strdown(temp,-1);
428    to = g_strdup_printf("aim:%s", temp2);
429    g_free(temp2);
430    g_free(temp);
431  } else {
432    to = g_strdup("loopback");
433  }
434
435  filename = g_build_filename(logpath, to, NULL);
436  owl_log_append(m, filename);
437  g_free(to);
438  g_free(filename);
439
440  filename = g_build_filename(logpath, "all", NULL);
441  owl_log_append(m, filename);
442  g_free(logpath);
443  g_free(filename);
444}
445
446
447void owl_log_outgoing_zephyr_error(const owl_zwrite *zw, const char *text)
448{
449  char *filename, *logpath;
450  char *tobuff, *recip;
451  owl_message *m;
452  GString *msgbuf;
453  /* create a present message so we can pass it to
454   * owl_log_shouldlog_message(void)
455   */
456  m = g_slice_new(owl_message);
457  /* recip_index = 0 because there can only be one recipient anyway */
458  owl_message_create_from_zwrite(m, zw, text, 0);
459  if (!owl_log_shouldlog_message(m)) {
460    owl_message_delete(m);
461    return;
462  }
463  owl_message_delete(m);
464
465  /* chop off a local realm */
466  recip = owl_zwrite_get_recip_n_with_realm(zw, 0);
467  tobuff = short_zuser(recip);
468  g_free(recip);
469
470  /* expand ~ in path names */
471  logpath = owl_util_makepath(owl_global_get_logpath(&g));
472  filename = g_build_filename(logpath, tobuff, NULL);
473  msgbuf = g_string_new("");
474  g_string_printf(msgbuf, "ERROR (owl): %s\n%s\n", tobuff, text);
475  if (text[strlen(text)-1] != '\n') {
476    g_string_append_printf(msgbuf, "\n");
477  }
478  owl_log_enqueue_message(msgbuf->str, filename);
479  g_string_free(msgbuf, TRUE);
480
481  filename = g_build_filename(logpath, "all", NULL);
482  g_free(logpath);
483  msgbuf = g_string_new("");
484  g_string_printf(msgbuf, "ERROR (owl): %s\n%s\n", tobuff, text);
485  if (text[strlen(text)-1] != '\n') {
486    g_string_append_printf(msgbuf, "\n");
487  }
488  owl_log_enqueue_message(msgbuf->str, filename);
489  g_string_free(msgbuf, TRUE);
490
491  g_free(tobuff);
492}
493
494void owl_log_incoming(const owl_message *m)
495{
496  char *filename, *allfilename, *logpath;
497  const char *from=NULL;
498  char *frombuff=NULL;
499  int len, ch, i, personal;
500
501  /* figure out if it's a "personal" message or not */
502  if (owl_message_is_type_zephyr(m)) {
503    if (owl_message_is_personal(m)) {
504      personal = 1;
505    } else {
506      personal = 0;
507    }
508  } else if (owl_message_is_type_jabber(m)) {
509    /* This needs to be fixed to handle groupchat */
510    const char* msgtype = owl_message_get_attribute_value(m,"jtype");
511    if (msgtype && !strcmp(msgtype,"groupchat")) {
512      personal = 0;
513    } else {
514      personal = 1;
515    }
516  } else {
517    if (owl_message_is_private(m) || owl_message_is_loginout(m)) {
518      personal = 1;
519    } else {
520      personal = 0;
521    }
522  }
523
524
525  if (owl_message_is_type_zephyr(m)) {
526    if (personal) {
527      from=frombuff=short_zuser(owl_message_get_sender(m));
528    } else {
529      from=frombuff=g_strdup(owl_message_get_class(m));
530    }
531  } else if (owl_message_is_type_aim(m)) {
532    /* we do not yet handle chat rooms */
533    char *normalto, *temp;
534    temp = owl_aim_normalize_screenname(owl_message_get_sender(m));
535    normalto = g_utf8_strdown(temp, -1);
536    from=frombuff=g_strdup_printf("aim:%s", normalto);
537    g_free(normalto);
538    g_free(temp);
539  } else if (owl_message_is_type_loopback(m)) {
540    from=frombuff=g_strdup("loopback");
541  } else if (owl_message_is_type_jabber(m)) {
542    if (personal) {
543      from=frombuff=g_strdup_printf("jabber:%s", 
544                                    owl_message_get_sender(m));
545    } else {
546      from=frombuff=g_strdup_printf("jabber:%s", 
547                                    owl_message_get_recipient(m));
548    }
549  } else {
550    from=frombuff=g_strdup("unknown");
551  }
552 
553  /* check for malicious sender formats */
554  len=strlen(frombuff);
555  if (len<1 || len>35) from="weird";
556  if (strchr(frombuff, '/')) from="weird";
557
558  ch=frombuff[0];
559  if (!g_ascii_isalnum(ch)) from="weird";
560
561  for (i=0; i<len; i++) {
562    if (frombuff[i]<'!' || frombuff[i]>='~') from="weird";
563  }
564
565  if (!strcmp(frombuff, ".") || !strcasecmp(frombuff, "..")) from="weird";
566
567  if (!personal) {
568    if (strcmp(from, "weird")) {
569      char* temp = g_utf8_strdown(frombuff, -1);
570      if (temp) {
571        g_free(frombuff);
572        from = frombuff = temp;
573      }
574    }
575  }
576
577  /* create the filename (expanding ~ in path names) */
578  if (personal) {
579    logpath = owl_util_makepath(owl_global_get_logpath(&g));
580    filename = g_build_filename(logpath, from, NULL);
581    allfilename = g_build_filename(logpath, "all", NULL);
582    owl_log_append(m, allfilename);
583    g_free(allfilename);
584  } else {
585    logpath = owl_util_makepath(owl_global_get_classlogpath(&g));
586    filename = g_build_filename(logpath, from, NULL);
587  }
588
589  owl_log_append(m, filename);
590  g_free(filename);
591
592  if (personal && owl_message_is_type_zephyr(m)) {
593    /* We want to log to all of the CC'd people who were not us, or
594     * the sender, as well.
595     */
596    char *temp;
597    GList *cc;
598    cc = owl_message_get_cc_without_recipient(m);
599    while (cc != NULL) {
600      temp = short_zuser(cc->data);
601      if (strcasecmp(temp, frombuff) != 0) {
602        filename = g_build_filename(logpath, temp, NULL);
603        owl_log_append(m, filename);
604        g_free(filename);
605      }
606
607      g_free(temp);
608      g_free(cc->data);
609      cc = g_list_delete_link(cc, cc);
610    }
611  }
612
613  g_free(frombuff);
614  g_free(logpath);
615}
616
617static gpointer owl_log_thread_func(gpointer data)
618{
619  log_context = g_main_context_new();
620  log_loop = g_main_loop_new(log_context, FALSE);
621  g_main_loop_run(log_loop);
622  return NULL;
623}
624
625void owl_log_init(void)
626{
627  log_context = g_main_context_new();
628#if GLIB_CHECK_VERSION(2, 31, 0)
629  logging_thread = g_thread_new("logging",
630                                owl_log_thread_func,
631                                NULL);
632#else
633  GError *error = NULL;
634  logging_thread = g_thread_create(owl_log_thread_func,
635                                   NULL,
636                                   TRUE,
637                                   &error);
638  if (error) {
639    endwin();
640    fprintf(stderr, "Error spawning logging thread: %s\n", error->message);
641    fflush(stderr);
642    exit(1);
643  }
644#endif
645
646  deferred_entry_queue = g_queue_new();
647}
648
649static void owl_log_quit_func(gpointer data)
650{
651  /* flush the deferred logs queue, trying to write the
652   * entries to the disk one last time.  Drop any failed
653   * entries, and be quiet about it. */
654  owl_log_options opts;
655  opts.drop_failed_logs = true;
656  opts.display_initial_log_count = false;
657  owl_log_write_deferred_entries(&opts);
658#if GLIB_CHECK_VERSION(2, 32, 0)
659  g_queue_free_full(deferred_entry_queue, owl_log_entry_free);
660#else
661  g_queue_foreach(deferred_entry_queue, owl_log_entry_free_gfunc, NULL);
662  g_queue_free(deferred_entry_queue);
663#endif
664
665  g_main_loop_quit(log_loop);
666}
667
668void owl_log_shutdown(void)
669{
670  owl_select_post_task(owl_log_quit_func, NULL,
671                       NULL, log_context);
672  g_thread_join(logging_thread);
673}
Note: See TracBrowser for help on using the repository browser.