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

mailstore_watcher.cc

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

#include "compat.h"
#include "lib/util.h"
#include "mailstore_watcher.h"

using std::map;
using std::set;
using std::string;


mailstore_config::mailstore_config()
      : name(NULL), watch_bin(NULL), watch_bin_len(0)
{
}

mailstore_config::~mailstore_config()
{
      free(name);
      if (watch_bin)
            for (char** eltp = watch_bin; *eltp; eltp++)
                  free(*eltp);
      free(watch_bin);
}


mailstore_watcher::mailstore_watcher(bool quiet, const mailstore_config* config, time_t id, const map<string, time_t>* mds, const set<string>* igs)
      : name(config->name), from_fd(-1),
        quiet(quiet), config(config), default_inter_delay(id), inter_delays(mds),
        ignores(igs), watcher_pid(-1), to_fd(-1), mailboxes_buf_len(0)
{
}

mailstore_watcher::~mailstore_watcher()
{
      stop();
}

bool mailstore_watcher::start()
{
      int fdto[2];
      int fdfrom[2];
      pid_t child;
      int r;

      if (is_running())
            return true;

      r = pipe(fdto);
      die_if(r < 0, "pipe");
      r = pipe(fdfrom);
      die_if(r < 0, "pipe");

      child = fork();
      if (!child)
      {
            if (!quiet)
            {
                  printf("+ ");
                  for (unsigned i = 0; config->watch_bin[i]; i++)
                        printf("%s ", config->watch_bin[i]);
                  printf("\n");
            }

            r = close(fdfrom[0]);
            die_if(r < 0, "close");
            if (fdto[0] != STDIN_FILENO)
            {
                  r = dup2(fdto[0], STDIN_FILENO);
                  die_if(r < 0, "dup2");
                  r = close(fdto[0]);
                  die_if(r < 0, "close");
            }

            r = close(fdto[1]);
            die_if(r < 0, "close");
            if (fdfrom[1] != STDOUT_FILENO)
            {
                  r = dup2(fdfrom[1], STDOUT_FILENO);
                  die_if(r < 0, "dup2");
                  r = close(fdfrom[1]);
                  die_if(r < 0, "close");
            }

            execvp(config->watch_bin[0], config->watch_bin);
            die_if(1, "execvp(%s)", config->watch_bin[0]);
      }
      die_if(child < 0, "fork");

      this->watcher_pid = child;
      this->from_fd = fdfrom[0];
      this->to_fd = fdto[1];

      r = TEMP_FAILURE_RETRY(close(fdfrom[1]));
      die_if(r < 0, "close");
      r = TEMP_FAILURE_RETRY(close(fdto[0]));
      die_if(r < 0, "close");

      // wait for watcher startup
      char c;
      r = TEMP_FAILURE_RETRY(read(from_fd, &c, 1));
      die_if(r == -1, "%s watcher start failed; read()", name);
      if (!r)
      {
            char time_str[100];
            localtime_str_now(time_str, sizeof(time_str));
            fprintf(stderr, "Error: %s watcher connection closed at %s\n", name, time_str);
            stop();
            return false;
      }

      if (c != '\n')
      {
            // give the watcher a chance to exit in case it will do so
            sleep(1);

            if (!is_running())
            {
                  // start errored and quit
                  // this may be transient so return false to allow another try
                  return false;
            }

            // watcher seems to still be running, but sent an invalid first char
            // this is probably a misconfiguration so exit
            die_if(1, "Expected start message '\n' from %s watcher, received '%c'\n", name, c);
      }

      r = fcntl(from_fd, F_SETFL, O_NONBLOCK | FD_CLOEXEC);
      die_if(r < 0, "fcntl(NONBLOCK | FD_CLOEXEC)");
      r = fcntl(to_fd, F_SETFL, FD_CLOEXEC);
      die_if(r < 0, "fcntl(FD_CLOEXEC)");

      return true;
}

void mailstore_watcher::stop()
{
      if (from_fd >= 0)
      {
            if (TEMP_FAILURE_RETRY(close(from_fd)) < 0)
                  perror("close");
            from_fd = -1;
      }
      if (to_fd >= 0)
      {
            if (TEMP_FAILURE_RETRY(close(to_fd)) < 0)
                  perror("close");
            to_fd = -1;
      }
      if (watcher_pid >= 0)
      {
            kill(watcher_pid, SIGTERM);

            // Try to reap the zombie watcher, but don't get hung up on it
            // TODO: it would be nice not leave zombies
            int max_reap_time = 10;
            for (int i = 0; i < max_reap_time; i++)
            {
                  int status;
                  if (waitpid(watcher_pid, &status, WNOHANG))
                        break;

                  unsigned delay = 1;
                  while ((delay = sleep(delay))) {}

                  if (i >= max_reap_time / 2)
                        kill(watcher_pid, SIGKILL);
            }

            watcher_pid = -1;
      }
}

bool mailstore_watcher::is_running()
{
      int status;
      pid_t pid;
      char time_str[100];

      if (watcher_pid == -1)
            return false;

      pid = waitpid(watcher_pid, &status, WNOHANG);
      assert(pid >= 0);
      if (!pid)
            return true;

      fprintf(stderr, "Error: %s watcher", name);
      if (WIFEXITED(status))
      {
            fprintf(stderr, " exited");
            if (WEXITSTATUS(status))
                  fprintf(stderr, " with error %d", WEXITSTATUS(status));
      }
      else if (WIFSIGNALED(status))
            fprintf(stderr, " killed (by signal %d)", WTERMSIG(status));
      else if (WIFSTOPPED(status))
            fprintf(stderr, " stopped (by signal %d)", WSTOPSIG(status));
      else
            fprintf(stderr, " exited for unknown reason");
      localtime_str_now(time_str, sizeof(time_str));
      fprintf(stderr, " by %s\n", time_str);

      watcher_pid = -1;
      stop();
      return false;
}

time_t mailstore_watcher::read_changes()
{
      char buf[MAILBOXNAME_MAX];
      int buf_index = 0;
      int read_len;
      int buf_len;
      time_t min_inter_delay = -1;

      if (mailboxes_buf_len > 0)
      {
            memcpy(buf, mailboxes_buf, mailboxes_buf_len);
            buf_index = mailboxes_buf_len;
            mailboxes_buf_len = 0;
      }

      read_len = TEMP_FAILURE_RETRY(read(from_fd, buf + buf_index, MAILBOXNAME_MAX - buf_index - 1));
      if (read_len < 0)
      {
            if (errno == EAGAIN)
                  return -1;
            die_if(1, "read");
      }
      else if (!read_len)
      {
            char time_str[100];
            localtime_str_now(time_str, sizeof(time_str));
            fprintf(stderr, "Error: %s watcher connection closed by %s\n", config->name, time_str);
            stop();
            return -1;
      }
      buf_len = buf_index + read_len;
      buf[buf_len] = 0;

      for (int i = 0; i < buf_len; )
      {
            char* eol = strchr(buf + i, '\n');
            if (!eol)
            {
                  if (!i && buf_len == MAILBOXNAME_MAX - 1)
                  {
                        fprintf(stderr, "WARNING: mailbox name too large; syncing all mailboxes to get change");
                        mailboxes.insert("");
                        mailboxes_buf_len = 0;
                        return default_inter_delay;
                  }
                  mailboxes_buf_len = buf_len - i;
                  memcpy(mailboxes_buf, buf + i, mailboxes_buf_len);
                  return (i != 0 ? min_inter_delay : -1);
            }

            int len = eol - (buf + i);
            string mailbox_name(buf + i, len);
            if (ignores->find(mailbox_name) == ignores->end())
            {
                  mailboxes.insert(mailbox_name);

                  map<string, time_t>::const_iterator delay_it = inter_delays->find(mailbox_name);
                  time_t delay = default_inter_delay;
                  if (delay_it != inter_delays->end())
                        delay = delay_it->second;
                  if (delay < min_inter_delay || min_inter_delay < 0)
                        min_inter_delay = delay;
            }

            i += len + 1;
      }
      return min_inter_delay;
}

Generated by  Doxygen 1.6.0   Back to index