source: owl.c @ a7fac14

release-1.10release-1.9
Last change on this file since a7fac14 was a7fac14, checked in by David Benjamin <davidben@mit.edu>, 13 years ago
Assign all watched signals to a dummy no-op disposition Otherwise some (namely SIGWINCH) may have SIG_DFL = SIG_IGN. This means that the signal is ignored if the signal thread is in the middle of processing another signal (a race condition), and on Solaris it's not delivered at all. Also more consistently consider errors in setup here fatal. And rename CHECK_RESULT to OR_DIE. Reported-by: Benjamin Kaduk <kaduk@mit.edu> Reviewed-by: Adam Glasgall <adam@crossproduct.net>
  • Property mode set to 100644
File size: 18.9 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
304gboolean owl_process_input(GIOChannel *source, GIOCondition condition, void *data)
305{
306  owl_global *g = data;
307  owl_input j;
308
309  while (1) {
310    j.ch = wgetch(g->input_pad);
311    if (j.ch == ERR) return TRUE;
312
313    j.uch = '\0';
314    if (j.ch >= KEY_MIN && j.ch <= KEY_MAX) {
315      /* This is a curses control character. */
316    }
317    else if (j.ch > 0x7f && j.ch < 0xfe) {
318      /* Pull in a full utf-8 character. */
319      int bytes, i;
320      char utf8buf[7];
321      memset(utf8buf, '\0', 7);
322     
323      utf8buf[0] = j.ch;
324     
325      if ((j.ch & 0xc0) && (~j.ch & 0x20)) bytes = 2;
326      else if ((j.ch & 0xe0) && (~j.ch & 0x10)) bytes = 3;
327      else if ((j.ch & 0xf0) && (~j.ch & 0x08)) bytes = 4;
328      else if ((j.ch & 0xf8) && (~j.ch & 0x04)) bytes = 5;
329      else if ((j.ch & 0xfc) && (~j.ch & 0x02)) bytes = 6;
330      else bytes = 1;
331     
332      for (i = 1; i < bytes; i++) {
333        int tmp = wgetch(g->input_pad);
334        /* If what we got was not a byte, or not a continuation byte */
335        if (tmp > 0xff || !(tmp & 0x80 && ~tmp & 0x40)) {
336          /* ill-formed UTF-8 code unit subsequence, put back the
337             char we just got. */
338          ungetch(tmp);
339          j.ch = ERR;
340          break;
341        }
342        utf8buf[i] = tmp;
343      }
344     
345      if (j.ch != ERR) {
346        if (g_utf8_validate(utf8buf, -1, NULL)) {
347          j.uch = g_utf8_get_char(utf8buf);
348        }
349        else {
350          j.ch = ERR;
351        }
352      }
353    }
354    else if (j.ch <= 0x7f) {
355      j.uch = j.ch;
356    }
357
358    owl_process_input_char(j);
359  }
360  return TRUE;
361}
362
363static void sig_handler_main_thread(void *data) {
364  int sig = GPOINTER_TO_INT(data);
365
366  owl_function_debugmsg("Got signal %d", sig);
367  if (sig == SIGWINCH) {
368    owl_function_resize();
369  } else if (sig == SIGTERM || sig == SIGHUP) {
370    owl_function_quit();
371  } else if (sig == SIGINT && owl_global_take_interrupt(&g)) {
372    owl_input in;
373    in.ch = in.uch = owl_global_get_startup_tio(&g)->c_cc[VINTR];
374    owl_process_input_char(in);
375  }
376}
377
378static void sig_handler(const siginfo_t *siginfo, void *data) {
379  /* If it was an interrupt, set a flag so we can handle it earlier if
380   * needbe. sig_handler_main_thread will check the flag to make sure
381   * no one else took it. */
382  if (siginfo->si_signo == SIGINT) {
383    owl_global_add_interrupt(&g);
384  }
385  /* Send a message to the main thread. */
386  owl_select_post_task(sig_handler_main_thread,
387                       GINT_TO_POINTER(siginfo->si_signo), 
388                       NULL, g_main_context_default());
389}
390
391#define OR_DIE(s, syscall)       \
392  G_STMT_START {                 \
393    if ((syscall) == -1) {       \
394      perror((s));               \
395      exit(1);                   \
396    }                            \
397  } G_STMT_END
398
399void owl_register_signal_handlers(void) {
400  struct sigaction sig_ignore = { .sa_handler = SIG_IGN };
401  struct sigaction sig_default = { .sa_handler = SIG_DFL };
402  sigset_t sigset;
403  int ret, i;
404  const int reset_signals[] = { SIGABRT, SIGBUS, SIGCHLD, SIGFPE, SIGILL,
405                                SIGQUIT, SIGSEGV, };
406  /* Don't bother resetting watched ones because owl_signal_init will. */
407  const int watch_signals[] = { SIGWINCH, SIGTERM, SIGHUP, SIGINT, };
408
409  /* Sanitize our signals; the mask and dispositions from our parent
410   * aren't really useful. Signal list taken from equivalent code in
411   * Chromium. */
412  OR_DIE("sigemptyset", sigemptyset(&sigset));
413  if ((ret = pthread_sigmask(SIG_SETMASK, &sigset, NULL)) != 0) {
414    errno = ret;
415    perror("pthread_sigmask");
416    exit(1);
417  }
418  for (i = 0; i < G_N_ELEMENTS(reset_signals); i++) {
419    OR_DIE("sigaction", sigaction(reset_signals[i], &sig_default, NULL));
420  }
421
422  /* Turn off SIGPIPE; we check the return value of write. */
423  OR_DIE("sigaction", sigaction(SIGPIPE, &sig_ignore, NULL));
424
425  /* Register some signals with the signal thread. */
426  owl_signal_init(watch_signals, G_N_ELEMENTS(watch_signals),
427                  sig_handler, NULL);
428}
429
430#if OWL_STDERR_REDIR
431
432/* Replaces stderr with a pipe so that we can read from it.
433 * Returns the fd of the pipe from which stderr can be read. */
434int stderr_replace(void)
435{
436  int pipefds[2];
437  if (0 != pipe(pipefds)) {
438    perror("pipe");
439    owl_function_debugmsg("stderr_replace: pipe FAILED\n");
440    return -1;
441  }
442    owl_function_debugmsg("stderr_replace: pipe: %d,%d\n", pipefds[0], pipefds[1]);
443  if (-1 == dup2(pipefds[1], 2 /*stderr*/)) {
444    owl_function_debugmsg("stderr_replace: dup2 FAILED (%s)\n", strerror(errno));
445    perror("dup2");
446    return -1;
447  }
448  return pipefds[0];
449}
450
451/* Sends stderr (read from rfd) messages to the error console */
452gboolean stderr_redirect_handler(GIOChannel *source, GIOCondition condition, void *data)
453{
454  int navail, bread;
455  char buf[4096];
456  int rfd = g_io_channel_unix_get_fd(source);
457  char *err;
458
459  /* TODO: Use g_io_channel_read_line? We'd have to be careful about
460   * blocking on the read. */
461
462  if (rfd<0) return TRUE;
463  if (-1 == ioctl(rfd, FIONREAD, &navail)) {
464    return TRUE;
465  }
466  /*owl_function_debugmsg("stderr_redirect: navail = %d\n", navail);*/
467  if (navail <= 0) return TRUE;
468  if (navail > sizeof(buf)-1) {
469    navail = sizeof(buf)-1;
470  }
471  bread = read(rfd, buf, navail);
472  if (bread == -1)
473    return TRUE;
474
475  err = g_strdup_printf("[stderr]\n%.*s", bread, buf);
476
477  owl_function_log_err(err);
478  g_free(err);
479  return TRUE;
480}
481
482#endif /* OWL_STDERR_REDIR */
483
484int main(int argc, char **argv, char **env)
485{
486  int argc_copy;
487  char **argv_copy;
488  char *perlout, *perlerr;
489  const owl_style *s;
490  const char *dir;
491  owl_options opts;
492  GSource *source;
493  GIOChannel *channel;
494
495  argc_copy = argc;
496  argv_copy = g_strdupv(argv);
497
498  setlocale(LC_ALL, "");
499
500  memset(&opts, 0, sizeof opts);
501  opts.load_initial_subs = 1;
502  owl_parse_options(argc, argv, &opts);
503  g.load_initial_subs = opts.load_initial_subs;
504
505  owl_start_curses();
506
507  /* owl global init */
508  owl_global_init(&g);
509  if (opts.debug) owl_global_set_debug_on(&g);
510  if (opts.confdir) owl_global_set_confdir(&g, opts.confdir);
511  owl_function_debugmsg("startup: first available debugging message");
512  owl_global_set_startupargs(&g, argc_copy, argv_copy);
513  g_strfreev(argv_copy);
514  owl_global_set_haveaim(&g);
515
516  owl_register_signal_handlers();
517
518  /* register STDIN dispatch; throw away return, we won't need it */
519  channel = g_io_channel_unix_new(STDIN_FILENO);
520  g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_ERR, &owl_process_input, &g);
521  g_io_channel_unref(channel);
522  owl_zephyr_initialize();
523
524#if OWL_STDERR_REDIR
525  /* Do this only after we've started curses up... */
526  owl_function_debugmsg("startup: doing stderr redirection");
527  channel = g_io_channel_unix_new(stderr_replace());
528  g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_ERR, &stderr_redirect_handler, NULL);
529  g_io_channel_unref(channel);
530#endif
531
532  /* create the owl directory, in case it does not exist */
533  owl_function_debugmsg("startup: creating owl directory, if not present");
534  dir=owl_global_get_confdir(&g);
535  mkdir(dir, S_IRWXU);
536
537  /* set the tty, either from the command line, or by figuring it out */
538  owl_function_debugmsg("startup: setting tty name");
539  if (opts.tty) {
540    owl_global_set_tty(&g, opts.tty);
541  } else {
542    char *tty = owl_util_get_default_tty();
543    owl_global_set_tty(&g, tty);
544    g_free(tty);
545  }
546
547  /* Initialize perl */
548  owl_function_debugmsg("startup: processing config file");
549
550  owl_global_pop_context(&g);
551  owl_global_push_context(&g, OWL_CTX_READCONFIG, NULL, NULL, NULL);
552
553  perlerr=owl_perlconfig_initperl(opts.configfile, &argc, &argv, &env);
554  if (perlerr) {
555    endwin();
556    fprintf(stderr, "Internal perl error: %s\n", perlerr);
557    fflush(stderr);
558    printf("Internal perl error: %s\n", perlerr);
559    fflush(stdout);
560    exit(1);
561  }
562
563  owl_global_complete_setup(&g);
564
565  owl_global_setup_default_filters(&g);
566
567  /* set the current view */
568  owl_function_debugmsg("startup: setting the current view");
569  owl_view_create(owl_global_get_current_view(&g), "main",
570                  owl_global_get_filter(&g, "all"),
571                  owl_global_get_style_by_name(&g, "default"));
572
573  /* AIM init */
574  owl_function_debugmsg("startup: doing AIM initialization");
575  owl_aim_init();
576
577  /* execute the startup function in the configfile */
578  owl_function_debugmsg("startup: executing perl startup, if applicable");
579  perlout = owl_perlconfig_execute("BarnOwl::Hooks::_startup();");
580  g_free(perlout);
581
582  /* welcome message */
583  owl_function_debugmsg("startup: creating splash message");
584  owl_function_adminmsg("",
585    "-----------------------------------------------------------------------\n"
586    "Welcome to BarnOwl version " OWL_VERSION_STRING ".\n"
587    "To see a quick introduction, type ':show quickstart'.                  \n"
588    "Press 'h' for on-line help.                                            \n"
589    "                                                                       \n"
590    "BarnOwl is free software. Type ':show license' for more                \n"
591    "information.                                                     ^ ^   \n"
592    "                                                                 OvO   \n"
593    "Please report any bugs or suggestions to bug-barnowl@mit.edu    (   )  \n"
594    "-----------------------------------------------------------------m-m---\n"
595  );
596
597  /* process the startup file */
598  owl_function_debugmsg("startup: processing startup file");
599  owl_function_source(NULL);
600
601  owl_function_debugmsg("startup: set style for the view: %s", owl_global_get_default_style(&g));
602  s = owl_global_get_style_by_name(&g, owl_global_get_default_style(&g));
603  if(s)
604      owl_view_set_style(owl_global_get_current_view(&g), s);
605  else
606      owl_function_error("No such style: %s", owl_global_get_default_style(&g));
607
608  owl_function_debugmsg("startup: setting context interactive");
609
610  owl_global_pop_context(&g);
611  owl_global_push_context(&g, OWL_CTX_INTERACTIVE|OWL_CTX_RECV, NULL, "recv", NULL);
612
613  source = owl_window_redraw_source_new();
614  g_source_attach(source, NULL);
615  g_source_unref(source);
616
617  source = g_source_new(&owl_process_messages_funcs, sizeof(GSource));
618  g_source_attach(source, NULL);
619  g_source_unref(source);
620
621  owl_log_init();
622
623  owl_function_debugmsg("startup: entering main loop");
624  owl_select_run_loop();
625
626  /* Shut down everything. */
627  owl_zephyr_shutdown();
628  owl_signal_shutdown();
629  owl_shutdown_curses();
630  owl_log_shutdown();
631  return 0;
632}
Note: See TracBrowser for help on using the repository browser.