source: logging.c @ 253f37f

Last change on this file since 253f37f was 253f37f, checked in by Jason Gross <jasongross9@gmail.com>, 7 years ago
Change int ret to int errnum as per PR review As per https://github.com/barnowl/barnowl/pull/109#discussion_r131532257
  • 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, but do not go into deferred logging mode.
271 *
272 * N.B. This function is called only on the disk-writing thread. */
273static void owl_log_eventually_write_entry(gpointer data)
274{
275  int ret;
276  owl_log_entry *msg = (owl_log_entry*)data;
277  if (defer_logs) {
278    owl_log_deferred_enqueue_message(msg->message, msg->filename);
279  } else {
280    ret = owl_log_try_write_entry(msg);
281    if (ret == EPERM || ret == EACCES || ret == ETIMEDOUT) {
282      defer_logs = true;
283      owl_log_error("Unable to open file for logging (%s): \n"
284                    "%s.  \n"
285                    "Consider renewing your tickets.  Logging has been \n"
286                    "suspended, and your messages will be saved.  To \n"
287                    "resume logging, use the command :flush-logs.\n\n",
288                    msg->filename,
289                    g_strerror(ret));
290      /* If we were not in deferred logging mode, either the queue should be
291       * empty, or we are attempting to log a message that we just popped off
292       * the head of the queue.  Either way, we should enqueue this message as
293       * the first message in the queue, rather than the last, so that we
294       * preserve message ordering. */
295      owl_log_deferred_enqueue_first_message(msg->message, msg->filename);
296    } else if (ret != 0) {
297      owl_log_file_error(msg, ret);
298    }
299  }
300}
301
302/* tries to write the deferred log entries
303 *
304 * N.B. This function is called only on the disk-writing thread. */
305static void owl_log_write_deferred_entries(gpointer data)
306{
307  owl_log_entry *entry;
308  owl_log_options *opts = (owl_log_options *)data;
309  int ret;
310  int logged_message_count = 0;
311  bool all_succeeded = true;
312
313  if (opts->display_initial_log_count) {
314    if (g_queue_is_empty(deferred_entry_queue)) {
315      owl_log_makemsg("There are no logs to flush.");
316    } else {
317      owl_log_makemsg("Attempting to flush %u logs...",
318                      g_queue_get_length(deferred_entry_queue));
319    }
320  }
321
322  defer_logs = false;
323  while (!g_queue_is_empty(deferred_entry_queue) && !defer_logs) {
324    logged_message_count++;
325    entry = (owl_log_entry*)g_queue_pop_head(deferred_entry_queue);
326    if (!opts->drop_failed_logs) {
327      /* Attempt to write the log entry.  If it fails, re-queue the entry at
328       * the head of the queue. */
329      owl_log_eventually_write_entry(entry);
330    } else {
331      /* Attempt to write the log entry. If it fails, print an error message,
332       * drop the log, and keep going through the queue. */
333      ret = owl_log_try_write_entry(entry);
334      if (ret != 0) {
335        all_succeeded = false;
336        owl_log_file_error(entry, ret);
337      }
338    }
339    owl_log_entry_free(entry);
340  }
341  if (logged_message_count > 0) {
342    if (opts->display_initial_log_count) {
343      /* first clear the "attempting to flush" message from the status bar */
344      owl_log_makemsg("");
345    }
346    if (!defer_logs) {
347      if (all_succeeded) {
348        owl_log_adminmsg("Flushed %d logs and resumed logging.",
349                         logged_message_count);
350      } else {
351        owl_log_adminmsg("Flushed or dropped %d logs and resumed logging.",
352                         logged_message_count);
353      }
354    } else {
355      owl_log_error("Attempted to flush %d logs; %u deferred logs remain.",
356                    logged_message_count, g_queue_get_length(deferred_entry_queue));
357    }
358  }
359}
360
361void owl_log_flush_logs(bool drop_failed_logs, bool quiet)
362{
363  owl_log_options *data = g_new(owl_log_options, 1);
364  data->drop_failed_logs = drop_failed_logs;
365  data->display_initial_log_count = !quiet;
366
367  owl_select_post_task(owl_log_write_deferred_entries,
368                       data,
369                       g_free,
370                       log_context);
371}
372
373void owl_log_enqueue_message(const char *buffer, const char *filename)
374{
375  owl_log_entry *log_msg = owl_log_new_entry(buffer, filename);
376  owl_select_post_task(owl_log_eventually_write_entry, log_msg,
377                       owl_log_entry_free, log_context);
378}
379
380void owl_log_append(const owl_message *m, const char *filename) {
381  char *buffer = NULL;
382  if (owl_message_is_type_zephyr(m)) {
383    buffer = owl_log_zephyr(m);
384  } else if (owl_message_is_type_jabber(m)) {
385    buffer = owl_log_jabber(m);
386  } else if (owl_message_is_type_aim(m)) {
387    buffer = owl_log_aim(m);
388  } else {
389    buffer = owl_log_generic(m);
390  }
391  owl_log_enqueue_message(buffer, filename);
392  g_free(buffer);
393}
394
395void owl_log_outgoing(const owl_message *m)
396{
397  char *filename, *logpath;
398  char *to, *temp;
399  GList *cc;
400
401  /* expand ~ in path names */
402  logpath = owl_util_makepath(owl_global_get_logpath(&g));
403
404  /* Figure out what path to log to */
405  if (owl_message_is_type_zephyr(m)) {
406    /* If this has CC's, do all but the "recipient" which we'll do below */
407    cc = owl_message_get_cc_without_recipient(m);
408    while (cc != NULL) {
409      temp = short_zuser(cc->data);
410      filename = g_build_filename(logpath, temp, NULL);
411      owl_log_append(m, filename);
412
413      g_free(filename);
414      g_free(temp);
415      g_free(cc->data);
416      cc = g_list_delete_link(cc, cc);
417    }
418
419    to = short_zuser(owl_message_get_recipient(m));
420  } else if (owl_message_is_type_jabber(m)) {
421    to = g_strdup_printf("jabber:%s", owl_message_get_recipient(m));
422    g_strdelimit(to, "/", '_');
423  } else if (owl_message_is_type_aim(m)) {
424    char *temp2;
425    temp = owl_aim_normalize_screenname(owl_message_get_recipient(m));
426    temp2 = g_utf8_strdown(temp,-1);
427    to = g_strdup_printf("aim:%s", temp2);
428    g_free(temp2);
429    g_free(temp);
430  } else {
431    to = g_strdup("loopback");
432  }
433
434  filename = g_build_filename(logpath, to, NULL);
435  owl_log_append(m, filename);
436  g_free(to);
437  g_free(filename);
438
439  filename = g_build_filename(logpath, "all", NULL);
440  owl_log_append(m, filename);
441  g_free(logpath);
442  g_free(filename);
443}
444
445
446void owl_log_outgoing_zephyr_error(const owl_zwrite *zw, const char *text)
447{
448  char *filename, *logpath;
449  char *tobuff, *recip;
450  owl_message *m;
451  GString *msgbuf;
452  /* create a present message so we can pass it to
453   * owl_log_shouldlog_message(void)
454   */
455  m = g_slice_new(owl_message);
456  /* recip_index = 0 because there can only be one recipient anyway */
457  owl_message_create_from_zwrite(m, zw, text, 0);
458  if (!owl_log_shouldlog_message(m)) {
459    owl_message_delete(m);
460    return;
461  }
462  owl_message_delete(m);
463
464  /* chop off a local realm */
465  recip = owl_zwrite_get_recip_n_with_realm(zw, 0);
466  tobuff = short_zuser(recip);
467  g_free(recip);
468
469  /* expand ~ in path names */
470  logpath = owl_util_makepath(owl_global_get_logpath(&g));
471  filename = g_build_filename(logpath, tobuff, NULL);
472  msgbuf = g_string_new("");
473  g_string_printf(msgbuf, "ERROR (owl): %s\n%s\n", tobuff, text);
474  if (text[strlen(text)-1] != '\n') {
475    g_string_append_printf(msgbuf, "\n");
476  }
477  owl_log_enqueue_message(msgbuf->str, filename);
478  g_string_free(msgbuf, TRUE);
479
480  filename = g_build_filename(logpath, "all", NULL);
481  g_free(logpath);
482  msgbuf = g_string_new("");
483  g_string_printf(msgbuf, "ERROR (owl): %s\n%s\n", tobuff, text);
484  if (text[strlen(text)-1] != '\n') {
485    g_string_append_printf(msgbuf, "\n");
486  }
487  owl_log_enqueue_message(msgbuf->str, filename);
488  g_string_free(msgbuf, TRUE);
489
490  g_free(tobuff);
491}
492
493void owl_log_incoming(const owl_message *m)
494{
495  char *filename, *allfilename, *logpath;
496  const char *from=NULL;
497  char *frombuff=NULL;
498  int len, ch, i, personal;
499
500  /* figure out if it's a "personal" message or not */
501  if (owl_message_is_type_zephyr(m)) {
502    if (owl_message_is_personal(m)) {
503      personal = 1;
504    } else {
505      personal = 0;
506    }
507  } else if (owl_message_is_type_jabber(m)) {
508    /* This needs to be fixed to handle groupchat */
509    const char* msgtype = owl_message_get_attribute_value(m,"jtype");
510    if (msgtype && !strcmp(msgtype,"groupchat")) {
511      personal = 0;
512    } else {
513      personal = 1;
514    }
515  } else {
516    if (owl_message_is_private(m) || owl_message_is_loginout(m)) {
517      personal = 1;
518    } else {
519      personal = 0;
520    }
521  }
522
523
524  if (owl_message_is_type_zephyr(m)) {
525    if (personal) {
526      from=frombuff=short_zuser(owl_message_get_sender(m));
527    } else {
528      from=frombuff=g_strdup(owl_message_get_class(m));
529    }
530  } else if (owl_message_is_type_aim(m)) {
531    /* we do not yet handle chat rooms */
532    char *normalto, *temp;
533    temp = owl_aim_normalize_screenname(owl_message_get_sender(m));
534    normalto = g_utf8_strdown(temp, -1);
535    from=frombuff=g_strdup_printf("aim:%s", normalto);
536    g_free(normalto);
537    g_free(temp);
538  } else if (owl_message_is_type_loopback(m)) {
539    from=frombuff=g_strdup("loopback");
540  } else if (owl_message_is_type_jabber(m)) {
541    if (personal) {
542      from=frombuff=g_strdup_printf("jabber:%s", 
543                                    owl_message_get_sender(m));
544    } else {
545      from=frombuff=g_strdup_printf("jabber:%s", 
546                                    owl_message_get_recipient(m));
547    }
548  } else {
549    from=frombuff=g_strdup("unknown");
550  }
551 
552  /* check for malicious sender formats */
553  len=strlen(frombuff);
554  if (len<1 || len>35) from="weird";
555  if (strchr(frombuff, '/')) from="weird";
556
557  ch=frombuff[0];
558  if (!g_ascii_isalnum(ch)) from="weird";
559
560  for (i=0; i<len; i++) {
561    if (frombuff[i]<'!' || frombuff[i]>='~') from="weird";
562  }
563
564  if (!strcmp(frombuff, ".") || !strcasecmp(frombuff, "..")) from="weird";
565
566  if (!personal) {
567    if (strcmp(from, "weird")) {
568      char* temp = g_utf8_strdown(frombuff, -1);
569      if (temp) {
570        g_free(frombuff);
571        from = frombuff = temp;
572      }
573    }
574  }
575
576  /* create the filename (expanding ~ in path names) */
577  if (personal) {
578    logpath = owl_util_makepath(owl_global_get_logpath(&g));
579    filename = g_build_filename(logpath, from, NULL);
580    allfilename = g_build_filename(logpath, "all", NULL);
581    owl_log_append(m, allfilename);
582    g_free(allfilename);
583  } else {
584    logpath = owl_util_makepath(owl_global_get_classlogpath(&g));
585    filename = g_build_filename(logpath, from, NULL);
586  }
587
588  owl_log_append(m, filename);
589  g_free(filename);
590
591  if (personal && owl_message_is_type_zephyr(m)) {
592    /* We want to log to all of the CC'd people who were not us, or
593     * the sender, as well.
594     */
595    char *temp;
596    GList *cc;
597    cc = owl_message_get_cc_without_recipient(m);
598    while (cc != NULL) {
599      temp = short_zuser(cc->data);
600      if (strcasecmp(temp, frombuff) != 0) {
601        filename = g_build_filename(logpath, temp, NULL);
602        owl_log_append(m, filename);
603        g_free(filename);
604      }
605
606      g_free(temp);
607      g_free(cc->data);
608      cc = g_list_delete_link(cc, cc);
609    }
610  }
611
612  g_free(frombuff);
613  g_free(logpath);
614}
615
616static gpointer owl_log_thread_func(gpointer data)
617{
618  log_context = g_main_context_new();
619  log_loop = g_main_loop_new(log_context, FALSE);
620  g_main_loop_run(log_loop);
621  return NULL;
622}
623
624void owl_log_init(void)
625{
626  log_context = g_main_context_new();
627#if GLIB_CHECK_VERSION(2, 31, 0)
628  logging_thread = g_thread_new("logging",
629                                owl_log_thread_func,
630                                NULL);
631#else
632  GError *error = NULL;
633  logging_thread = g_thread_create(owl_log_thread_func,
634                                   NULL,
635                                   TRUE,
636                                   &error);
637  if (error) {
638    endwin();
639    fprintf(stderr, "Error spawning logging thread: %s\n", error->message);
640    fflush(stderr);
641    exit(1);
642  }
643#endif
644
645  deferred_entry_queue = g_queue_new();
646}
647
648static void owl_log_quit_func(gpointer data)
649{
650  /* flush the deferred logs queue, trying to write the
651   * entries to the disk one last time.  Drop any failed
652   * entries, and be quiet about it. */
653  owl_log_options opts;
654  opts.drop_failed_logs = true;
655  opts.display_initial_log_count = false;
656  owl_log_write_deferred_entries(&opts);
657#if GLIB_CHECK_VERSION(2, 32, 0)
658  g_queue_free_full(deferred_entry_queue, owl_log_entry_free);
659#else
660  g_queue_foreach(deferred_entry_queue, owl_log_entry_free_gfunc, NULL);
661  g_queue_free(deferred_entry_queue);
662#endif
663
664  g_main_loop_quit(log_loop);
665}
666
667void owl_log_shutdown(void)
668{
669  owl_select_post_task(owl_log_quit_func, NULL,
670                       NULL, log_context);
671  g_thread_join(logging_thread);
672}
Note: See TracBrowser for help on using the repository browser.