source: logging.c @ 979dc4f

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