source: owl.c @ f43c777

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