source: owl.c @ cc305b5

release-1.8release-1.9
Last change on this file since cc305b5 was cc305b5, checked in by Adam Glasgall <glasgall@mit.edu>, 10 years ago
Move log-writing onto a background thread. Currently, Barnowl writes message logs on the same thread as the UI and other I/O. This means that if writing log files is very slow (e.g. if logpath or classlogpath point to somewhere in AFS and the fileserver that volume relies on is very slow to respond), barnowl freezes up until the write completes. This patch creates a background logging thread as part of initialization and runs a main loop on that thread. Logging is then accomplished by posting tasks that write to log files to that thread, a la how other threads can post tasks to the UI thread's main loop. Post a quit task to the logging thread's mainloop instead of quitting directly Calling g_main_quit() on the main thread to quit the logging thread could resultin log tasks still in its queue getting lost. Instead, after we've shut down everything that could log messages, post a quit task to the log thread so that it'll only get run after everything outstanding has been processed.
  • Property mode set to 100644
File size: 18.6 KB
Line 
1/*  Copyright (c) 2006-2011 The BarnOwl Developers. All rights reserved.
2 *  Copyright (c) 2004 James Kretchmar. All rights reserved.
3 *
4 *  This program is free software. You can redistribute it and/or
5 *  modify under the terms of the Sleepycat License. See the COPYING
6 *  file included with the distribution for more information.
7 */
8
9#include <stdio.h>
10#include <unistd.h>
11#include <getopt.h>
12#include <stdlib.h>
13#include <string.h>
14#include <signal.h>
15#include <time.h>
16#include <sys/param.h>
17#include <sys/types.h>
18#include <sys/time.h>
19#include <termios.h>
20#include <sys/stat.h>
21#include <locale.h>
22#include "owl.h"
23
24
25#if OWL_STDERR_REDIR
26#ifdef HAVE_SYS_IOCTL_H
27#include <sys/ioctl.h>
28#endif
29#ifdef HAVE_SYS_FILIO_H
30#include <sys/filio.h>
31#endif
32int stderr_replace(void);
33#endif
34
35owl_global g;
36
37typedef struct _owl_options {
38  bool load_initial_subs;
39  char *configfile;
40  char *tty;
41  char *confdir;
42  bool debug;
43} owl_options;
44
45void usage(void)
46{
47  fprintf(stderr, "Barnowl version %s\n", OWL_VERSION_STRING);
48  fprintf(stderr, "Usage: barnowl [-n] [-d] [-D] [-v] [-h] [-c <configfile>] [-s <confdir>] [-t <ttyname>]\n");
49  fprintf(stderr, "  -n,--no-subs        don't load zephyr subscriptions\n");
50  fprintf(stderr, "  -d,--debug          enable debugging\n");
51  fprintf(stderr, "  -v,--version        print the Barnowl version number and exit\n");
52  fprintf(stderr, "  -h,--help           print this help message\n");
53  fprintf(stderr, "  -c,--config-file    specify an alternate config file\n");
54  fprintf(stderr, "  -s,--config-dir     specify an alternate config dir (default ~/.owl)\n");
55  fprintf(stderr, "  -t,--tty            set the tty name\n");
56}
57
58/* TODO: free owl_options after init is done? */
59void owl_parse_options(int argc, char *argv[], owl_options *opts) {
60  static const struct option long_options[] = {
61    { "no-subs",         0, 0, 'n' },
62    { "config-file",     1, 0, 'c' },
63    { "config-dir",      1, 0, 's' },
64    { "tty",             1, 0, 't' },
65    { "debug",           0, 0, 'd' },
66    { "version",         0, 0, 'v' },
67    { "help",            0, 0, 'h' },
68    { NULL, 0, NULL, 0}
69  };
70  char c;
71
72  while((c = getopt_long(argc, argv, "nc:t:s:dDvh",
73                         long_options, NULL)) != -1) {
74    switch(c) {
75    case 'n':
76      opts->load_initial_subs = 0;
77      break;
78    case 'c':
79      opts->configfile = g_strdup(optarg);
80      break;
81    case 's':
82      opts->confdir = g_strdup(optarg);
83      break;
84    case 't':
85      opts->tty = g_strdup(optarg);
86      break;
87    case 'd':
88      opts->debug = 1;
89      break;
90    case 'v':
91      printf("This is barnowl version %s\n", OWL_VERSION_STRING);
92      exit(0);
93    case 'h':
94    default:
95      usage();
96      exit(1);
97    }
98  }
99}
100
101void owl_start_color(void) {
102  start_color();
103#ifdef HAVE_USE_DEFAULT_COLORS
104  use_default_colors();
105#endif
106
107  /* define simple color pairs */
108  if (has_colors() && COLOR_PAIRS>=8) {
109    int bg = COLOR_BLACK;
110#ifdef HAVE_USE_DEFAULT_COLORS
111    bg = -1;
112#endif
113    init_pair(OWL_COLOR_BLACK,   COLOR_BLACK,   bg);
114    init_pair(OWL_COLOR_RED,     COLOR_RED,     bg);
115    init_pair(OWL_COLOR_GREEN,   COLOR_GREEN,   bg);
116    init_pair(OWL_COLOR_YELLOW,  COLOR_YELLOW,  bg);
117    init_pair(OWL_COLOR_BLUE,    COLOR_BLUE,    bg);
118    init_pair(OWL_COLOR_MAGENTA, COLOR_MAGENTA, bg);
119    init_pair(OWL_COLOR_CYAN,    COLOR_CYAN,    bg);
120    init_pair(OWL_COLOR_WHITE,   COLOR_WHITE,   bg);
121  }
122}
123
124void owl_start_curses(void) {
125  struct termios tio;
126  /* save initial terminal settings */
127  tcgetattr(STDIN_FILENO, owl_global_get_startup_tio(&g));
128
129  tcgetattr(STDIN_FILENO, &tio);
130  tio.c_iflag &= ~(ISTRIP|IEXTEN);
131  tio.c_cc[VQUIT] = fpathconf(STDIN_FILENO, _PC_VDISABLE);
132  tio.c_cc[VSUSP] = fpathconf(STDIN_FILENO, _PC_VDISABLE);
133  tio.c_cc[VSTART] = fpathconf(STDIN_FILENO, _PC_VDISABLE);
134  tio.c_cc[VSTOP] = fpathconf(STDIN_FILENO, _PC_VDISABLE);
135  tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
136
137  /* screen init */
138  initscr();
139  cbreak();
140  noecho();
141
142  owl_start_color();
143}
144
145void owl_shutdown_curses(void) {
146  endwin();
147  /* restore terminal settings */
148  tcsetattr(STDIN_FILENO, TCSAFLUSH, owl_global_get_startup_tio(&g));
149}
150
151/*
152 * Process a new message passed to us on the message queue from some
153 * protocol. This includes adding it to the message list, updating the
154 * view and scrolling if appropriate, logging it, and so on.
155 *
156 * Either a pointer is kept to the message internally, or it is freed
157 * if unneeded. The caller no longer ``owns'' the message's memory.
158 *
159 * Returns 1 if the message was added to the message list, and 0 if it
160 * was ignored due to user settings or otherwise.
161 */
162static int owl_process_message(owl_message *m) {
163  const owl_filter *f;
164  /* if this message it on the puntlist, nuke it and continue */
165  if (owl_global_message_is_puntable(&g, m)) {
166    owl_message_delete(m);
167    return 0;
168  }
169
170  /*  login or logout that should be ignored? */
171  if (owl_global_is_ignorelogins(&g)
172      && owl_message_is_loginout(m)) {
173    owl_message_delete(m);
174    return 0;
175  }
176
177  if (!owl_global_is_displayoutgoing(&g)
178      && owl_message_is_direction_out(m)) {
179    owl_message_delete(m);
180    return 0;
181  }
182
183  /* add it to the global list */
184  owl_messagelist_append_element(owl_global_get_msglist(&g), m);
185  /* add it to any necessary views; right now there's only the current view */
186  owl_view_consider_message(owl_global_get_current_view(&g), m);
187
188  if(owl_message_is_direction_in(m)) {
189    /* let perl know about it*/
190    owl_perlconfig_getmsg(m, NULL);
191
192    /* do we need to autoreply? */
193    if (owl_global_is_zaway(&g) && !owl_message_get_attribute_value(m, "isauto")) {
194      if (owl_message_is_type_zephyr(m)) {
195        owl_zephyr_zaway(m);
196      } else if (owl_message_is_type_aim(m)) {
197        if (owl_message_is_private(m)) {
198          owl_function_send_aimawymsg(owl_message_get_sender(m), owl_global_get_zaway_msg(&g));
199        }
200      }
201    }
202
203    /* ring the bell if it's a personal */
204    if (!strcmp(owl_global_get_personalbell(&g), "on")) {
205      if (!owl_message_is_loginout(m) &&
206          !owl_message_is_mail(m) &&
207          owl_message_is_personal(m)) {
208        owl_function_beep();
209      }
210    } else if (!strcmp(owl_global_get_personalbell(&g), "off")) {
211      /* do nothing */
212    } else {
213      f=owl_global_get_filter(&g, owl_global_get_personalbell(&g));
214      if (f && owl_filter_message_match(f, m)) {
215        owl_function_beep();
216      }
217    }
218
219    /* if it matches the alert filter, do the alert action */
220    f=owl_global_get_filter(&g, owl_global_get_alert_filter(&g));
221    if (f && owl_filter_message_match(f, m)) {
222      owl_function_command_norv(owl_global_get_alert_action(&g));
223    }
224
225    /* if it's a zephyr login or logout, update the zbuddylist */
226    if (owl_message_is_type_zephyr(m) && owl_message_is_loginout(m)) {
227      if (owl_message_is_login(m)) {
228        owl_zbuddylist_adduser(owl_global_get_zephyr_buddylist(&g), owl_message_get_sender(m));
229      } else if (owl_message_is_logout(m)) {
230        owl_zbuddylist_deluser(owl_global_get_zephyr_buddylist(&g), owl_message_get_sender(m));
231      } else {
232        owl_function_error("Internal error: received login notice that is neither login nor logout");
233      }
234    }
235  }
236
237  /* let perl know about it */
238  owl_perlconfig_newmsg(m, NULL);
239  /* log the message if we need to */
240  owl_log_message(m);
241  /* redraw the sepbar; TODO: don't violate layering */
242  owl_global_sepbar_dirty(&g);
243
244  return 1;
245}
246
247static gboolean owl_process_messages_prepare(GSource *source, int *timeout) {
248  *timeout = -1;
249  return owl_global_messagequeue_pending(&g);
250}
251
252static gboolean owl_process_messages_check(GSource *source) {
253  return owl_global_messagequeue_pending(&g);
254}
255
256/*
257 * Process any new messages we have waiting in the message queue.
258 */
259static gboolean owl_process_messages_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) {
260  int newmsgs=0;
261  int followlast = owl_global_should_followlast(&g);
262  owl_message *m;
263
264  /* Grab incoming messages. */
265  while (owl_global_messagequeue_pending(&g)) {
266    m = owl_global_messagequeue_popmsg(&g);
267    if (owl_process_message(m))
268      newmsgs = 1;
269  }
270
271  if (newmsgs) {
272    /* follow the last message if we're supposed to */
273    if (followlast)
274      owl_function_lastmsg_noredisplay();
275
276    /* do the newmsgproc thing */
277    owl_function_do_newmsgproc();
278
279    /* redisplay if necessary */
280    /* this should be optimized to not run if the new messages won't be displayed */
281    owl_mainwin_redisplay(owl_global_get_mainwin(&g));
282  }
283  return TRUE;
284}
285
286static GSourceFuncs owl_process_messages_funcs = {
287  owl_process_messages_prepare,
288  owl_process_messages_check,
289  owl_process_messages_dispatch,
290  NULL
291};
292
293void owl_process_input_char(owl_input j)
294{
295  int ret;
296
297  owl_global_set_lastinputtime(&g, time(NULL));
298  ret = owl_keyhandler_process(owl_global_get_keyhandler(&g), j);
299  if (ret!=0 && ret!=1) {
300    owl_function_makemsg("Unable to handle keypress");
301  }
302}
303
304void owl_process_input(const owl_io_dispatch *d, void *data)
305{
306  owl_input j;
307
308  while (1) {
309    j.ch = wgetch(g.input_pad);
310    if (j.ch == ERR) return;
311
312    j.uch = '\0';
313    if (j.ch >= KEY_MIN && j.ch <= KEY_MAX) {
314      /* This is a curses control character. */
315    }
316    else if (j.ch > 0x7f && j.ch < 0xfe) {
317      /* Pull in a full utf-8 character. */
318      int bytes, i;
319      char utf8buf[7];
320      memset(utf8buf, '\0', 7);
321     
322      utf8buf[0] = j.ch;
323     
324      if ((j.ch & 0xc0) && (~j.ch & 0x20)) bytes = 2;
325      else if ((j.ch & 0xe0) && (~j.ch & 0x10)) bytes = 3;
326      else if ((j.ch & 0xf0) && (~j.ch & 0x08)) bytes = 4;
327      else if ((j.ch & 0xf8) && (~j.ch & 0x04)) bytes = 5;
328      else if ((j.ch & 0xfc) && (~j.ch & 0x02)) bytes = 6;
329      else bytes = 1;
330     
331      for (i = 1; i < bytes; i++) {
332        int tmp = wgetch(g.input_pad);
333        /* If what we got was not a byte, or not a continuation byte */
334        if (tmp > 0xff || !(tmp & 0x80 && ~tmp & 0x40)) {
335          /* ill-formed UTF-8 code unit subsequence, put back the
336             char we just got. */
337          ungetch(tmp);
338          j.ch = ERR;
339          break;
340        }
341        utf8buf[i] = tmp;
342      }
343     
344      if (j.ch != ERR) {
345        if (g_utf8_validate(utf8buf, -1, NULL)) {
346          j.uch = g_utf8_get_char(utf8buf);
347        }
348        else {
349          j.ch = ERR;
350        }
351      }
352    }
353    else if (j.ch <= 0x7f) {
354      j.uch = j.ch;
355    }
356
357    owl_process_input_char(j);
358  }
359}
360
361static void sig_handler_main_thread(void *data) {
362  int sig = GPOINTER_TO_INT(data);
363
364  owl_function_debugmsg("Got signal %d", sig);
365  if (sig == SIGWINCH) {
366    owl_function_resize();
367  } else if (sig == SIGTERM || sig == SIGHUP) {
368    owl_function_quit();
369  } else if (sig == SIGINT && owl_global_take_interrupt(&g)) {
370    owl_input in;
371    in.ch = in.uch = owl_global_get_startup_tio(&g)->c_cc[VINTR];
372    owl_process_input_char(in);
373  }
374}
375
376static void sig_handler(const siginfo_t *siginfo, void *data) {
377  /* If it was an interrupt, set a flag so we can handle it earlier if
378   * needbe. sig_handler_main_thread will check the flag to make sure
379   * no one else took it. */
380  if (siginfo->si_signo == SIGINT) {
381    owl_global_add_interrupt(&g);
382  }
383  /* Send a message to the main thread. */
384  owl_select_post_task(sig_handler_main_thread,
385                       GINT_TO_POINTER(siginfo->si_signo), 
386                       NULL, g_main_context_default());
387}
388
389#define CHECK_RESULT(s, syscall) \
390  G_STMT_START {                 \
391    if ((syscall) != 0) {        \
392      perror((s));               \
393      exit(1);                   \
394    }                            \
395  } G_STMT_END
396
397void owl_register_signal_handlers(void) {
398  struct sigaction sig_ignore = { .sa_handler = SIG_IGN };
399  struct sigaction sig_default = { .sa_handler = SIG_DFL };
400  sigset_t sigset;
401  int ret, i;
402  const int signals[] = { SIGABRT, SIGBUS, SIGCHLD, SIGFPE, SIGHUP, SIGILL,
403                          SIGINT, SIGQUIT, SIGSEGV, SIGTERM, SIGWINCH };
404
405  /* Sanitize our signals; the mask and dispositions from our parent
406   * aren't really useful. Signal list taken from equivalent code in
407   * Chromium. */
408  CHECK_RESULT("sigemptyset", sigemptyset(&sigset));
409  if ((ret = pthread_sigmask(SIG_SETMASK, &sigset, NULL)) != 0) {
410    errno = ret;
411    perror("pthread_sigmask");
412  }
413  for (i = 0; i < G_N_ELEMENTS(signals); i++) {
414    CHECK_RESULT("sigaction", sigaction(signals[i], &sig_default, NULL));
415  }
416
417  /* Turn off SIGPIPE; we check the return value of write. */
418  CHECK_RESULT("sigaction", sigaction(SIGPIPE, &sig_ignore, NULL));
419
420  /* Register some signals with the signal thread. */
421  CHECK_RESULT("sigaddset", sigaddset(&sigset, SIGWINCH));
422  CHECK_RESULT("sigaddset", sigaddset(&sigset, SIGTERM));
423  CHECK_RESULT("sigaddset", sigaddset(&sigset, SIGHUP));
424  CHECK_RESULT("sigaddset", sigaddset(&sigset, SIGINT));
425  owl_signal_init(&sigset, sig_handler, NULL);
426}
427
428#if OWL_STDERR_REDIR
429
430/* Replaces stderr with a pipe so that we can read from it.
431 * Returns the fd of the pipe from which stderr can be read. */
432int stderr_replace(void)
433{
434  int pipefds[2];
435  if (0 != pipe(pipefds)) {
436    perror("pipe");
437    owl_function_debugmsg("stderr_replace: pipe FAILED\n");
438    return -1;
439  }
440    owl_function_debugmsg("stderr_replace: pipe: %d,%d\n", pipefds[0], pipefds[1]);
441  if (-1 == dup2(pipefds[1], 2 /*stderr*/)) {
442    owl_function_debugmsg("stderr_replace: dup2 FAILED (%s)\n", strerror(errno));
443    perror("dup2");
444    return -1;
445  }
446  return pipefds[0];
447}
448
449/* Sends stderr (read from rfd) messages to the error console */
450void stderr_redirect_handler(const owl_io_dispatch *d, void *data)
451{
452  int navail, bread;
453  char buf[4096];
454  int rfd = d->fd;
455  char *err;
456
457  if (rfd<0) return;
458  if (-1 == ioctl(rfd, FIONREAD, &navail)) {
459    return;
460  }
461  /*owl_function_debugmsg("stderr_redirect: navail = %d\n", navail);*/
462  if (navail <= 0) return;
463  if (navail > sizeof(buf)-1) {
464    navail = sizeof(buf)-1;
465  }
466  bread = read(rfd, buf, navail);
467  if (bread == -1)
468    return;
469
470  err = g_strdup_printf("[stderr]\n%.*s", bread, buf);
471
472  owl_function_log_err(err);
473  g_free(err);
474}
475
476#endif /* OWL_STDERR_REDIR */
477
478int main(int argc, char **argv, char **env)
479{
480  int argc_copy;
481  char **argv_copy;
482  char *perlout, *perlerr;
483  const owl_style *s;
484  const char *dir;
485  owl_options opts;
486  GSource *source;
487
488  if (!GLIB_CHECK_VERSION (2, 12, 0))
489    g_error ("GLib version 2.12.0 or above is needed.");
490
491  argc_copy = argc;
492  argv_copy = g_strdupv(argv);
493
494  setlocale(LC_ALL, "");
495
496  memset(&opts, 0, sizeof opts);
497  opts.load_initial_subs = 1;
498  owl_parse_options(argc, argv, &opts);
499  g.load_initial_subs = opts.load_initial_subs;
500
501  owl_start_curses();
502
503  /* owl global init */
504  owl_global_init(&g);
505  if (opts.debug) owl_global_set_debug_on(&g);
506  if (opts.confdir) owl_global_set_confdir(&g, opts.confdir);
507  owl_function_debugmsg("startup: first available debugging message");
508  owl_global_set_startupargs(&g, argc_copy, argv_copy);
509  g_strfreev(argv_copy);
510  owl_global_set_haveaim(&g);
511
512  owl_register_signal_handlers();
513
514  /* register STDIN dispatch; throw away return, we won't need it */
515  owl_select_add_io_dispatch(STDIN_FILENO, OWL_IO_READ, &owl_process_input, NULL, NULL);
516  owl_zephyr_initialize();
517
518#if OWL_STDERR_REDIR
519  /* Do this only after we've started curses up... */
520  owl_function_debugmsg("startup: doing stderr redirection");
521  owl_select_add_io_dispatch(stderr_replace(), OWL_IO_READ, &stderr_redirect_handler, NULL, NULL);
522#endif
523
524  /* create the owl directory, in case it does not exist */
525  owl_function_debugmsg("startup: creating owl directory, if not present");
526  dir=owl_global_get_confdir(&g);
527  mkdir(dir, S_IRWXU);
528
529  /* set the tty, either from the command line, or by figuring it out */
530  owl_function_debugmsg("startup: setting tty name");
531  if (opts.tty) {
532    owl_global_set_tty(&g, opts.tty);
533  } else {
534    char *tty = owl_util_get_default_tty();
535    owl_global_set_tty(&g, tty);
536    g_free(tty);
537  }
538
539  /* Initialize perl */
540  owl_function_debugmsg("startup: processing config file");
541
542  owl_global_pop_context(&g);
543  owl_global_push_context(&g, OWL_CTX_READCONFIG, NULL, NULL, NULL);
544
545  perlerr=owl_perlconfig_initperl(opts.configfile, &argc, &argv, &env);
546  if (perlerr) {
547    endwin();
548    fprintf(stderr, "Internal perl error: %s\n", perlerr);
549    fflush(stderr);
550    printf("Internal perl error: %s\n", perlerr);
551    fflush(stdout);
552    exit(1);
553  }
554
555  owl_global_complete_setup(&g);
556
557  owl_global_setup_default_filters(&g);
558
559  /* set the current view */
560  owl_function_debugmsg("startup: setting the current view");
561  owl_view_create(owl_global_get_current_view(&g), "main",
562                  owl_global_get_filter(&g, "all"),
563                  owl_global_get_style_by_name(&g, "default"));
564
565  /* AIM init */
566  owl_function_debugmsg("startup: doing AIM initialization");
567  owl_aim_init();
568
569  /* execute the startup function in the configfile */
570  owl_function_debugmsg("startup: executing perl startup, if applicable");
571  perlout = owl_perlconfig_execute("BarnOwl::Hooks::_startup();");
572  g_free(perlout);
573
574  /* welcome message */
575  owl_function_debugmsg("startup: creating splash message");
576  owl_function_adminmsg("",
577    "-----------------------------------------------------------------------\n"
578    "Welcome to barnowl version " OWL_VERSION_STRING ".\n"
579    "To see a quick introduction, type ':show quickstart'.                  \n"
580    "Press 'h' for on-line help.                                            \n"
581    "                                                                       \n"
582    "BarnOwl is free software. Type ':show license' for more                \n"
583    "information.                                                     ^ ^   \n"
584    "                                                                 OvO   \n"
585    "Please report any bugs or suggestions to bug-barnowl@mit.edu    (   )  \n"
586    "-----------------------------------------------------------------m-m---\n"
587  );
588
589  /* process the startup file */
590  owl_function_debugmsg("startup: processing startup file");
591  owl_function_source(NULL);
592
593  owl_function_debugmsg("startup: set style for the view: %s", owl_global_get_default_style(&g));
594  s = owl_global_get_style_by_name(&g, owl_global_get_default_style(&g));
595  if(s)
596      owl_view_set_style(owl_global_get_current_view(&g), s);
597  else
598      owl_function_error("No such style: %s", owl_global_get_default_style(&g));
599
600  owl_function_debugmsg("startup: setting context interactive");
601
602  owl_global_pop_context(&g);
603  owl_global_push_context(&g, OWL_CTX_INTERACTIVE|OWL_CTX_RECV, NULL, "recv", NULL);
604
605  source = owl_window_redraw_source_new();
606  g_source_attach(source, NULL);
607  g_source_unref(source);
608
609  source = g_source_new(&owl_process_messages_funcs, sizeof(GSource));
610  g_source_attach(source, NULL);
611  g_source_unref(source);
612
613  owl_log_init();
614
615  owl_function_debugmsg("startup: entering main loop");
616  owl_select_run_loop();
617
618  /* Shut down everything. */
619  owl_zephyr_shutdown();
620  owl_signal_shutdown();
621  owl_shutdown_curses();
622  owl_log_shutdown();
623  return 0;
624}
Note: See TracBrowser for help on using the repository browser.