Logo Search packages:      
Sourcecode: mswatch version File versions  Download package

mswatch.cc

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <glib.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#include <algorithm>
#include <set>
#include <sstream>
#include <string>

#include "compat.h"
#include "lib/util.h"
#include "mswatch_config.h"
#include "mailstore_watcher.h"
#include "timeout_calc.h"
#include "rcparser/rcparser.h"

using std::copy;
using std::insert_iterator;
using std::set;
using std::set_union;
using std::string;
using std::stringstream;
using std::transform;

#define DEFAULT_CONFIGFILENAME ".mswatchrc"


static mswatch_config config;

static mailstore_watcher* watcher[NWATCHERS];

static int sigchld_pipe[2];
static int sigusr_pipe[2];


mswatch_config::mswatch_config()
      : dry(false), quiet(false),
        base_delay(10), default_inter_delay(60), max_delay(600),
        sync_bin(NULL), sync_bin_len(0)
{
      mailbox_prefix[0] = mailbox_prefix[1] = NULL;
}

mswatch_config::~mswatch_config()
{
      if (sync_bin)
            for (char** eltp = sync_bin; *eltp; eltp++)
                  free(*eltp);
      // we don't bother to free inter_delay names here
      free(sync_bin);
      free(mailbox_prefix[0]);
      free(mailbox_prefix[1]);
}

static void at_exit(void)
{
      for (unsigned i = 0; i < NWATCHERS; i++)
            if (watcher[i])
                  watcher[i]->stop();
}

static void signal_exit(int)
{
      // exit() will call at_exit()
      exit(0);
}

static void signal_child(int)
{
      char c = 0;
      int r;
      r = TEMP_FAILURE_RETRY(write(sigchld_pipe[1], &c, 1));
      die_if(r <= 0, "write(sigchld_pipe)");
}

static void signal_usr(int)
{
      char c = 0;
      int r;
      r = TEMP_FAILURE_RETRY(write(sigusr_pipe[1], &c, 1));
      die_if(r <= 0, "write(sigchld_pipe)");
}


//
// mailbox sync

static string itos(int x)
{
      stringstream xs;
      xs << x;
      return xs.str();
}

static char* prefix_mailbox(const string& mailbox, bool omit_sep = false)
{
      char* name;

      if (!config.mailbox_prefix)
      {
            name = new char[mailbox.size() + 1];
            memcpy(name, mailbox.c_str(), mailbox.size());
            name[mailbox.size()] = '\0';
      }
      else
      {
            unsigned prefix_len = strlen(config.mailbox_prefix[0]);
            unsigned sep_len = omit_sep ? 0 : strlen(config.mailbox_prefix[1]);
            name = new char[prefix_len + sep_len + mailbox.size() + 1];
            memcpy(name, config.mailbox_prefix[0], prefix_len);
            memcpy(name + prefix_len, config.mailbox_prefix[1], sep_len);
            memcpy(name + prefix_len + sep_len, mailbox.c_str(), mailbox.size());
            name[prefix_len + sep_len + mailbox.size()] = 0;
      }

      return name;
}

// Sync the n mailboxes [begin, end). Or if n == 0, sync all mailboxes.
template <typename InputIter, class Size>
static int sync_mailboxes(InputIter begin, InputIter end, Size n)
{
      pid_t child;
      int status;
      time_t start, completed;
      string mailbox_count;
      char time_str[100];
      int r;

      if (n > 1)
      {
            mailbox_count = itos(n);
            mailbox_count += " mailboxes";
      }
      else if (n == 1)
            mailbox_count = "1 mailbox";
      else if (n == 0)
            mailbox_count = "all mailboxes";

      start = time(NULL);
      localtime_str(time_str, sizeof(time_str), &start);
      if (!config.quiet)
            printf("Sync started at %s (%s)\n", time_str, mailbox_count.c_str());

      if (!(child = fork()))
      {
            char** args;

            // TODO: either execute multiple syncs if too many args or sync all
            if (n > 0)
            {
                  args = new char*[config.sync_bin_len + n + 1];
                  for (unsigned i = 0; begin != end; ++begin, i++)
                        args[config.sync_bin_len + i] = prefix_mailbox(*begin);
                  args[config.sync_bin_len + n] = NULL;
            }
            else
            {
                  string all("");
                  args = new char*[config.sync_bin_len + 1 + 1];
                  args[config.sync_bin_len] = prefix_mailbox(all, true);
                  args[config.sync_bin_len + 1] = NULL;
            }
            copy(&config.sync_bin[0], &config.sync_bin[config.sync_bin_len], args);

            if (!config.quiet)
            {
                  printf("+ ");
                  for (unsigned i = 0; args[i]; i++)
                        printf("%s ", args[i]);
                  printf("\n");
            }

            if (config.dry)
                  _exit(0); // call _exit() to avoid at_exit()
            else
            {
                  execvp(args[0], args);
                  die_if(1, "exec(\"%s\")", args[0]);
            }
      }
      die_if(child < 0, "fork");

      // g++ 4.0.3 with -Wall reports this line as having no effect (why?):
      //r = TEMP_FAILURE_RETRY(waitpid(child, &status, 0));
      // So do it ourself:
      do
            r = waitpid(child, &status, 0);
      while (r == -1L && errno == EINTR);
      die_if(r < 0, "waitpid(%d)", child);

      r = (WIFEXITED(status) ? WEXITSTATUS(status) : -1);
      completed = time(NULL);
      if (!r)
      {
            if (!config.quiet)
            {
                  localtime_str(time_str, sizeof(time_str), &completed);
                  printf("Sync completed");
                  printf(" after %lds at %s (%s)\n", completed - start, time_str, mailbox_count.c_str());
            }

      }
      else
      {
            localtime_str(time_str, sizeof(time_str), &completed);
            fprintf(stderr, "Sync errored: %s exited (status %d)", config.sync_bin[0], r);
            fprintf(stderr, " after %lds at %s (%s)\n", completed - start, time_str, mailbox_count.c_str());

      }

      return r;
}

static int sync_changed_mailboxes(void)
{
      set<string> mailboxes;
      int r;
      set_union(watcher[0]->mailboxes.begin(), watcher[0]->mailboxes.end(),
                watcher[1]->mailboxes.begin(), watcher[1]->mailboxes.end(),
                    insert_iterator<set <string> >(mailboxes, mailboxes.begin()));
      r = sync_mailboxes(mailboxes.begin(), mailboxes.end(), mailboxes.size());
      if (!r)
      {
            watcher[0]->mailboxes.clear();
            watcher[1]->mailboxes.clear();
      }
      return r;
}


//
// mailbox watch

static void ensure_running_watchers(void)
{
      bool wrun[NWATCHERS];
      wrun[0] = watcher[0]->is_running();
      wrun[1] = watcher[1]->is_running();

      if (wrun[0] && wrun[1])
            return;

      for (unsigned i = 0; i < NWATCHERS; i++)
      {
            if (!wrun[i])
            {
                  timeout_calc start_tc(config);
                  while (1)
                  {
                        if (!config.quiet)
                              printf("Starting %s watcher\n", watcher[i]->name);
                        if (watcher[i]->start())
                              break;
                        start_tc.set_dequeue_error();
                        unsigned delay = start_tc.get_timeout();
                        while ((delay = sleep(delay)) > 0) {}
                  }
            }
      }
      if (!config.quiet)
            printf("Watching mailstores\n");

      const string inbox("INBOX");
      (void) sync_mailboxes(&inbox, (&inbox) + 1, 1);

      timeout_calc sync_tc(config);
      while (sync_mailboxes((string*) NULL, (string*) NULL, 0))
      {
            sync_tc.set_dequeue_error();
            unsigned delay = sync_tc.get_timeout();
            while ((delay = sleep(delay)) > 0) {}
      }

      // Having synced, we know all queued mailbox changes are now synced
      watcher[0]->mailboxes.clear();
      watcher[1]->mailboxes.clear();

      // Starting and syncing forked children who have exited; read in the
      // bytes the sigchild handler wrote on their behalf so that our caller
      // does not dispatch on them
      int c;
      int r;
      do
            r = TEMP_FAILURE_RETRY(read(sigchld_pipe[0], &c, 1));
      while (r > 0);
      assert(r < 0 && errno == EAGAIN);
}

static void watch(void)
{
      struct pollfd pollfds[NWATCHERS+2];
      timeout_calc tc_watchers(config);
      timeout_calc tc_sync(config);

      pollfds[0].events = POLLIN;
      pollfds[1].events = POLLIN;
      pollfds[NWATCHERS].fd = sigusr_pipe[0];
      pollfds[NWATCHERS].events = POLLIN;
      pollfds[NWATCHERS+1].fd = sigchld_pipe[0];
      pollfds[NWATCHERS+1].events = POLLIN;

      while (1)
      {
            if (watcher[0]->is_running() && watcher[1]->is_running())
            {
                  // Activity happened without watcher death, so reset watcher errors
                  tc_watchers.dequeue_events();
            }
            else
            {
                  bool already_errored = tc_watchers.get_dequeue_error();
                  tc_watchers.set_dequeue_error();
                  if (already_errored)
                  {
                        unsigned delay = tc_watchers.get_timeout();
                        while ((delay = sleep(delay)) > 0) {}
                  }

                  ensure_running_watchers();
                  for (unsigned i = 0; i < NWATCHERS; i++)
                        pollfds[i].fd = watcher[i]->from_fd;
            }

            int timeout = 1000 * tc_sync.get_timeout();
            int poll_ret = poll(pollfds, NWATCHERS+1, timeout);
            if (poll_ret < 0 && errno == EINTR)
                  continue;
            die_if(poll_ret < 0, "poll");

            if (!poll_ret)
            {
                  if (!sync_changed_mailboxes())
                        tc_sync.dequeue_events();
                  else
                        tc_sync.set_dequeue_error();
            }
            else
            {
                  for (unsigned i = 0; i < NWATCHERS; i++)
                        if (pollfds[i].revents)
                        {
                              time_t inter_delay = watcher[i]->read_changes();
                              if (inter_delay > 0)
                                    tc_sync.enqueue_event(inter_delay);
                        }
                  if (pollfds[NWATCHERS].revents)
                  {
                        char time_str[100];
                        localtime_str_now(time_str, sizeof(time_str));
                        printf("Pending mailboxes at %s:\n", time_str);
                        for (unsigned i = 0; i < NWATCHERS; i++)
                        {
                              printf("\t%s:", watcher[i]->name);
                              for (set<string>::const_iterator it = watcher[i]->mailboxes.begin(); it != watcher[i]->mailboxes.end(); ++it)
                                    printf(" %s", it->c_str());
                              printf("\n");
                        }
                        drain_fd(sigusr_pipe[0]);
                  }
                  if (pollfds[NWATCHERS+1].revents)
                  {
                        // Read a byte for each exited child.
                        // ensure_running_watchers() will restart watcher children;
                        // all others are syncers and are handled by sync_mailboxes().
                        drain_fd(sigchld_pipe[0]);
                  }
            }
      }
}


//
// main

static void configure_mswatch(int argc, char** argv, char** configfilename)
{
      gboolean dry = config.dry;
      gboolean quiet = config.quiet;
      gboolean version = 0;
      char** argv_remains = NULL;
      GOptionContext* ctx;
      GError* gerror = NULL;
      gboolean r;

      GOptionEntry options[] =
      {
            { "config", 'c', 0, G_OPTION_ARG_FILENAME, configfilename,
              "Read an alternate config file (default: ~/" DEFAULT_CONFIGFILENAME ")", "FILE" },
            { "dry", 'd', 0, G_OPTION_ARG_NONE, &dry,
              "Only watch, do not sync, mailstores", NULL },
            { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet,
              "Do not print success status messages", NULL },
            { "version", 'V', 0, G_OPTION_ARG_NONE, &version,
              "Display version", NULL },
            { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &argv_remains,
              NULL, NULL },
            { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
      };

      ctx = g_option_context_new("- watch mailstores for changes and initiate mailbox syncs");
      g_option_context_add_main_entries(ctx, options, NULL);
      r = g_option_context_parse(ctx, &argc, &argv, &gerror);
      if (r == FALSE)
      {
            GQuark error = G_OPTION_ERROR;
            assert(error == G_OPTION_ERROR_BAD_VALUE);
            fprintf(stderr, "%s\n", gerror->message);
            g_error_free(gerror);
            exit(1);
      }
      assert(!gerror);
      g_option_context_free(ctx);
      ctx = NULL;

      if (argv_remains && *argv_remains)
      {
            fprintf(stderr, "Unknown arguments starting with \"%s\"\n", *argv_remains);
            exit(1);
      }

      if (version)
      {
            printf(PACKAGE " " VERSION "\n");
            exit(1);
      }

      if (dry == TRUE)
            config.dry = true;

      if (quiet == TRUE)
            config.quiet = true;

      if (!*configfilename)
      {
            char* dirname = getenv("HOME");
            unsigned dirname_len = strlen(dirname);
            *configfilename = (char*) malloc(dirname_len + 1 + strlen(DEFAULT_CONFIGFILENAME) + 1);
            die_if(!*configfilename, "malloc");
            strcpy(*configfilename, dirname);
            (*configfilename)[dirname_len] = '/';
            strcpy(*configfilename + dirname_len + 1, DEFAULT_CONFIGFILENAME);
      }
}

int main(int argc, char* argv[])
{
      int r;

      (void) argc;
      (void) argv;

      r = pipe(sigusr_pipe);
      die_if(r < 0, "pipe");
      r = fcntl(sigusr_pipe[0], F_SETFL, O_NONBLOCK);
      die_if(r < 0, "fcntl(NONBLOCK)");

      r = pipe(sigchld_pipe);
      die_if(r < 0, "pipe");
      r = fcntl(sigchld_pipe[0], F_SETFL, O_NONBLOCK);
      die_if(r < 0, "fcntl(NONBLOCK)");

      // SIGPIPE ignored in case STDOUT_FILENO is a pipe without a reader
      if ((set_signal_handler(SIGHUP, signal_exit) == -1)
           || (set_signal_handler(SIGINT, signal_exit) == -1)
           || (set_signal_handler(SIGTERM, signal_exit) == -1)
             || (set_signal_handler(SIGCHLD, signal_child) == -1)
             || (set_signal_handler(SIGUSR1, signal_usr) == -1)
             || (set_signal_handler(SIGPIPE, SIG_IGN) == -1))
            die_if(1, "set_signal_handlers");

      r = atexit(at_exit);
      die_if(r != 0, "atexit");

      setlinebuf(stdout);

      char* configfilename = NULL;
      configure_mswatch(argc, argv, &configfilename);

      FILE* configfile = fopen(configfilename, "r");
      die_if(!configfile, "Unable to open configuration file \"%s\"", configfilename);
      r = parse_config(configfile, &config);
      if (r < 0)
      {
            fprintf(stderr, "Unable to load configuration file\n");
            exit(1);
      }
      r = fclose(configfile);
      die_if(r != 0, "fclose(%s)", configfilename);
      free(configfilename);
      configfilename = NULL;

      mailstore_watcher watcher0(config.quiet, &config.store[0], config.default_inter_delay, &config.inter_delays);
      mailstore_watcher watcher1(config.quiet, &config.store[1], config.default_inter_delay, &config.inter_delays);
      watcher[0] = &watcher0;
      watcher[1] = &watcher1;

      watch();

      // can't reach here
      assert(0);
      return 1;
}

Generated by  Doxygen 1.6.0   Back to index