chiark / gitweb /
Cope without inotify
authorUser <richard@heceptor.anjou.terraraq.org.uk>
Sat, 5 Mar 2011 11:00:57 +0000 (11:00 +0000)
committerUser <richard@heceptor.anjou.terraraq.org.uk>
Sat, 5 Mar 2011 11:00:57 +0000 (11:00 +0000)
12 files changed:
configure.ac
src/InotifyWatcher.cc [new file with mode: 0644]
src/InotifyWatcher.h [new file with mode: 0644]
src/Makefile.am
src/PollingWatcher.cc [new file with mode: 0644]
src/PollingWatcher.h [new file with mode: 0644]
src/Watcher.cc
src/Watcher.h
src/WatcherImplementation.cc [new file with mode: 0644]
src/blockad.cc
src/timeval.h [new file with mode: 0644]
tests/watchfile.cc

index beb174b..69d860a 100644 (file)
@@ -30,8 +30,13 @@ AC_LANG([C++])
 # Checks for libraries.
 
 # Checks for header files.
+AC_CHECK_HEADERS([sys/inotify.h])
 
 # Checks for typedefs, structures, and compiler characteristics.
+AC_CHECK_SIZEOF([time_t])
+AC_CHECK_SIZEOF([int])
+AC_CHECK_SIZEOF([long])
+AC_CHECK_SIZEOF([long long])
 
 # Checks for library functions.
 AC_CHECK_FUNCS([strerror])
diff --git a/src/InotifyWatcher.cc b/src/InotifyWatcher.cc
new file mode 100644 (file)
index 0000000..cda5d80
--- /dev/null
@@ -0,0 +1,166 @@
+//
+// Copyright © 2011 Richard Kettlewell
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+#include <config.h>
+
+#if HAVE_SYS_INOTIFY_H
+
+#include "Watcher.h"
+#include "InotifyWatcher.h"
+#include <sys/inotify.h>
+#include <cerrno>
+#include <cstdio>
+#include "log.h"
+
+InotifyWatcher::InotifyWatcher(const std::string &path_arg,
+                               Watcher *watcher): 
+  WatcherImplementation(path_arg, watcher),
+  base(getBaseName(path)),
+  dir(getDirName(path)),
+  ifd(-1),
+  file_wd(-1),
+  dir_wd(-1) {
+  // Get an inotify FD
+  if((ifd = inotify_init()) < 0)
+    throw SystemError("inotify_init", errno);
+  // Watch the containing directory
+  if((dir_wd = inotify_add_watch(ifd, dir.c_str(),
+                                 IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO)) < 0)
+    throw SystemError("inotify_add_watch for " + dir, errno);
+  // Try to open the file.  This will add file_wd if it exists.
+  openFile();
+  // Discard initial content
+  if(fp) {
+    if(fseek(fp, 0, SEEK_END) < 0)
+      throw IOError("seeking " + path, errno);
+  }
+}
+
+InotifyWatcher::~InotifyWatcher() {
+  if(ifd >= 0)
+    close(ifd);
+}
+
+int InotifyWatcher::pollfd(time_t &) const {
+  return ifd;
+}
+
+void InotifyWatcher::openFile() {
+  if(!fp) {
+    // Try to open the file.  If it doesn't exist, that's OK, we'll continue to
+    // wait for it to come into existence.
+    if(!(fp = fopen(path.c_str(), "r"))) {
+      if(errno != ENOENT)
+        throw IOError("opening " + path, errno);
+    } else {
+      // If we did open it then detect changes to it.
+      // (But see below concerning IN_*_SELF.)
+      if((file_wd = inotify_add_watch(ifd, path.c_str(),
+                                      IN_MODIFY
+                                      |IN_MOVE_SELF
+                                      |IN_DELETE_SELF)) < 0)
+        throw SystemError("inotify_add_watch for " + path, errno);
+    }
+  }
+}
+
+void InotifyWatcher::closeFile() {
+  if(fp) {
+    if(line.size()) {
+      processLine(line);
+      line.clear();
+    }
+    if(file_wd >= 0) {
+      if(inotify_rm_watch(ifd, file_wd) < 0) {
+        file_wd = -1;
+        // On 2.6.26 we get a spurious EINVAL removing a wd sometimes.  It
+        // doesn't happen on 2.6.32.  We ignore it and issue a warning instead.
+        // There's not much else that can be done.
+        if(errno == EINVAL)
+          warn("inotify_rm_watch: %s", strerror(errno));
+        else
+          throw SystemError("inotify_rm_watch for " + path, errno);
+      }
+      file_wd = -1;
+    }
+    fclose(fp);
+    fp = NULL;
+  }
+}
+
+void InotifyWatcher::work() {
+  char buffer[4096];
+  int n = read(ifd, buffer, sizeof buffer);
+  if(n < 0) {
+    if(errno != EINTR && errno != EAGAIN)
+      throw SystemError("read from inotify fd", errno);
+    return;
+  }
+  // We might have more than one event.
+  char *ptr = buffer;
+  while(n > 0) {
+    // Make sure we have a whole event
+    if((size_t)n < sizeof (struct inotify_event))
+      throw SystemError("short read from inotify fd");
+    const struct inotify_event &event = *(const struct inotify_event *)ptr;
+    const size_t total = sizeof event + event.len;
+    if((size_t)n < total)
+      throw SystemError("short read from inotify fd");
+    
+    // See if the file contents changed.
+    if(event.wd == file_wd) {
+      // Read as much as we can from the file, whatever happened
+      readLines();
+      // If the file was deleted or renamed away, we're done with this version
+      // of it.  Note that we don't seem to get (at least) IN_DELETE_SELF
+      // reliably!  However we handle the cases that ought to produce it below
+      // in the dir_wd checking too.
+      if(event.mask & (IN_MOVE_SELF|IN_DELETE_SELF)) {
+        // Close the file.
+        closeFile();
+      }
+    }
+
+    if(event.wd == dir_wd
+       && event.len
+       && !strcmp(event.name, base.c_str())) {
+      // The file has been newly created or replaced by another file.  Close
+      // the old version (if open).
+      closeFile();
+      // Open the new version (if it exists).
+      openFile();
+      if(fp) {
+        // Something might have been written to it since it was created but
+        // before we started watching it, so we check straight away.
+        readLines();
+      }
+    }
+    ptr += total;
+    n -= total;
+  }
+}
+
+#endif
+
+/*
+Local Variables:
+mode:c++
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
diff --git a/src/InotifyWatcher.h b/src/InotifyWatcher.h
new file mode 100644 (file)
index 0000000..20204cb
--- /dev/null
@@ -0,0 +1,53 @@
+//
+// Copyright © 2011 Richard Kettlewell
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+#ifndef INOTIFYWATCHER_H
+#define INOTIFYWATCHER_H
+
+#if HAVE_SYS_INOTIFY_H
+#include "Watcher.h"
+
+class InotifyWatcher: public WatcherImplementation {
+public:
+  InotifyWatcher(const std::string &path,
+                 Watcher *watcher);
+  ~InotifyWatcher();
+  int pollfd(time_t &limit) const;
+  void work();
+private:
+  const std::string base;               // base filename
+  const std::string dir;                // containing directory
+  int ifd;                              // inotify descriptor
+  int file_wd;                          // file watch descriptor or -1
+  int dir_wd;                           // directory watch descriptor
+
+  void openFile();                      // try to ensure the watched file open
+  void closeFile();                     // close the watched file if open
+
+};
+#endif
+
+#endif /* INOTIFYWATCHER_H */
+
+/*
+Local Variables:
+mode:c++
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
index 326c88b..e44190d 100644 (file)
@@ -20,7 +20,9 @@ man_MANS=blockad.8
 libutils_a_SOURCES=Address.cc ConfFile.cc Regex.cc StdioFile.cc                \
 Watcher.cc Address.h ConfFile.h Regex.h StdioFile.h Watcher.h log.h    \
 log.cc IOError.cc IOError.h nonblock.cc utils.h execute.cc             \
-BlockMethod.h BlockMethod.cc
+BlockMethod.h BlockMethod.cc WatcherImplementation.h                   \
+WatcherImplementation.cc InotifyWatcher.cc InotifyWatcher.h            \
+PollingWatcher.cc PollingWatcher.h timeval.h
 blockad_SOURCES=blockad.cc iptables.cc hosts.deny.cc
 LDADD=libutils.a
 EXTRA_DIST=${man_MANS}
diff --git a/src/PollingWatcher.cc b/src/PollingWatcher.cc
new file mode 100644 (file)
index 0000000..d71d150
--- /dev/null
@@ -0,0 +1,105 @@
+//
+// Copyright © 2011 Richard Kettlewell
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+#include <config.h>
+#include "Watcher.h"
+#include "PollingWatcher.h"
+#include "IOError.h"
+#include <cerrno>
+#include <sys/stat.h>
+
+PollingWatcher::PollingWatcher(const std::string &path,
+                               Watcher *watcher):
+  WatcherImplementation(path, watcher),
+  next(time(NULL)),
+  dev(-1),
+  ino(-1) {
+  // Try opening the file
+  if((fp = fopen(path.c_str(), "r"))) {
+    if(fseek(fp, 0, SEEK_END) < 0)
+      throw IOError("seeking " + path, errno);
+  }
+}
+
+PollingWatcher::~PollingWatcher() {
+  if(fp)
+    fclose(fp);
+}
+
+int PollingWatcher::pollfd(time_t &limit) const {
+  if(limit > next)
+    limit = next;
+  return -1;
+}
+
+void PollingWatcher::openFile() {
+  struct stat sb;
+  if(!fp) {
+    if(!(fp = fopen(path.c_str(), "r"))) {
+      if(errno != ENOENT)
+        throw IOError("opening " + path, errno);
+      return;
+    }
+    if(fstat(fileno(fp), &sb) < 0)
+      throw IOError("fstat " + path, errno);
+    dev = sb.st_dev;
+    ino = sb.st_ino;
+  }
+}
+
+void PollingWatcher::closeFile() {
+  if(fp) {
+    if(line.size()) {
+      processLine(line);
+      line.clear();
+    }
+    fclose(fp);
+    fp = NULL;
+    dev = -1;
+    ino = -1;
+  }
+}
+
+void PollingWatcher::work() {
+  // Check again in a second
+  next = time(NULL) + 1;
+  // If the file is open, verify that we're still reading the right one
+  if(fp) {
+    struct stat sb;
+    if(stat(path.c_str(), &sb) < 0
+       || sb.st_ino != ino
+       || sb.st_dev != dev)
+      closeFile();
+  }
+  // If the file wasn't open, see if it is now
+  if(!fp) {
+    openFile();
+    if(!fp)
+      return;
+  }
+  // The file must now be open
+  readLines();
+}
+
+/*
+Local Variables:
+mode:c++
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
diff --git a/src/PollingWatcher.h b/src/PollingWatcher.h
new file mode 100644 (file)
index 0000000..7b94433
--- /dev/null
@@ -0,0 +1,47 @@
+//
+// Copyright © 2011 Richard Kettlewell
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+#ifndef POLLINGWATCHER_H
+#define POLLINGWATCHER_H
+
+#include "Watcher.h"
+
+class PollingWatcher: public WatcherImplementation {
+public:
+  PollingWatcher(const std::string &path,
+                 Watcher *watcher);
+  ~PollingWatcher();
+  int pollfd(time_t &limit) const;
+  void work();
+private:
+  time_t next;
+  dev_t dev;
+  ino_t ino;
+  void openFile();
+  void closeFile();
+};
+
+#endif /* POLLINGWATCHER_H */
+
+/*
+Local Variables:
+mode:c++
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
index 330ab35..6d69b11 100644 (file)
 //
 #include <config.h>
 #include "Watcher.h"
-#include <sys/inotify.h>
-#include <cerrno>
-#include <cstdio>
-#include "log.h"
+#include "InotifyWatcher.h"
+#include "PollingWatcher.h"
 
-Watcher::Watcher(const std::string &path_arg): path(path_arg),
-                                               base(getBaseName(path)),
-                                               dir(getDirName(path)),
-                                               fp(NULL),
-                                               ifd(-1),
-                                               file_wd(-1),
-                                               dir_wd(-1) {
-  // Get an inotify FD
-  if((ifd = inotify_init()) < 0)
-    throw SystemError("inotify_init", errno);
-  // Watch the containing directory
-  if((dir_wd = inotify_add_watch(ifd, dir.c_str(),
-                                 IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO)) < 0)
-    throw SystemError("inotify_add_watch for " + dir, errno);
-  // Try to open the file.  This will add file_wd if it exists.
-  openFile();
-  // Discard initial content
-  if(fp) {
-    if(fseek(fp, 0, SEEK_END) < 0)
-      throw IOError("seeking " + path, errno);
-  }
+Watcher::Watcher(const std::string &path): impl(new WATCHER(path, this)) {
 }
 
 Watcher::~Watcher() {
-  if(ifd >= 0)
-    close(ifd);
-  if(fp)
-    fclose(fp);
-}
-
-void Watcher::openFile() {
-  if(!fp) {
-    // Try to open the file.  If it doesn't exist, that's OK, we'll continue to
-    // wait for it to come into existence.
-    if(!(fp = fopen(path.c_str(), "r"))) {
-      if(errno != ENOENT)
-        throw IOError("opening " + path, errno);
-    } else {
-      // If we did open it then detect changes to it.
-      // (But see below concerning IN_*_SELF.)
-      if((file_wd = inotify_add_watch(ifd, path.c_str(),
-                                      IN_MODIFY
-                                      |IN_MOVE_SELF
-                                      |IN_DELETE_SELF)) < 0)
-        throw SystemError("inotify_add_watch for " + path, errno);
-    }
-  }
-}
-
-void Watcher::closeFile() {
-  if(fp) {
-    if(line.size()) {
-      processLine(line);
-      line.clear();
-    }
-    if(file_wd >= 0) {
-      if(inotify_rm_watch(ifd, file_wd) < 0) {
-        file_wd = -1;
-        // On 2.6.26 we get a spurious EINVAL removing a wd sometimes.  It
-        // doesn't happen on 2.6.32.  We ignore it and issue a warning instead.
-        // There's not much else that can be done.
-        if(errno == EINVAL)
-          warn("inotify_rm_watch: %s", strerror(errno));
-        else
-          throw SystemError("inotify_rm_watch for " + path, errno);
-      }
-      file_wd = -1;
-    }
-    fclose(fp);
-    fp = NULL;
-  }
-}
-
-std::string Watcher::getDirName(const std::string &p) {
-  std::string::size_type s = p.rfind('/');
-  if(s == std::string::npos)
-    return ".";
-  else
-    return std::string(p, 0, s + 1);
-}
-
-std::string Watcher::getBaseName(const std::string &p) {
-  std::string::size_type s = p.rfind('/');
-  if(s == std::string::npos)
-    return p;
-  else
-    return std::string(p, s + 1);
-}
-
-void Watcher::work() {
-  char buffer[4096];
-  int n = read(ifd, buffer, sizeof buffer);
-  if(n < 0) {
-    if(errno != EINTR && errno != EAGAIN)
-      throw SystemError("read from inotify fd", errno);
-    return;
-  }
-  // We might have more than one event.
-  char *ptr = buffer;
-  while(n > 0) {
-    // Make sure we have a whole event
-    if((size_t)n < sizeof (struct inotify_event))
-      throw SystemError("short read from inotify fd");
-    const struct inotify_event &event = *(const struct inotify_event *)ptr;
-    const size_t total = sizeof event + event.len;
-    if((size_t)n < total)
-      throw SystemError("short read from inotify fd");
-    
-    // See if the file contents changed.
-    if(event.wd == file_wd) {
-      // Read as much as we can from the file, whatever happened
-      readLines();
-      // If the file was deleted or renamed away, we're done with this version
-      // of it.  Note that we don't seem to get (at least) IN_DELETE_SELF
-      // reliably!  However we handle the cases that ought to produce it below
-      // in the dir_wd checking too.
-      if(event.mask & (IN_MOVE_SELF|IN_DELETE_SELF)) {
-        // Close the file.
-        closeFile();
-      }
-    }
-
-    if(event.wd == dir_wd
-       && event.len
-       && !strcmp(event.name, base.c_str())) {
-      // The file has been newly created or replaced by another file.  Close
-      // the old version (if open).
-      closeFile();
-      // Open the new version (if it exists).
-      openFile();
-      if(fp) {
-        // Something might have been written to it since it was created but
-        // before we started watching it, so we check straight away.
-        readLines();
-      }
-    }
-    ptr += total;
-    n -= total;
-  }
-}
-
-void Watcher::readLines() {
-  int c;
-  for(;;) {
-    // Read the line up to the next newline or EOF
-    while((c = getc(fp)) != EOF) {
-      line += c;
-      if(c == '\n')
-        break;
-    }
-    if(c == '\n') {
-      // We got a complete line
-      processLine(line);
-      line.clear();
-    } else {
-      // We did not get a complete line.  If it was an error, close it
-      // and throw.
-      if(ferror(fp)) {
-        closeFile();
-        throw IOError("reading " + path, errno);
-      }
-      // Otherwise it must be EOF.
-      // Make sure future reads, after the file is extended, will succeed.
-      clearerr(fp);
-      // Done for now.  If the file is deleted before a newline is read,
-      // closeFile() will handle the partial line.
-      return;
-    }
-  }
+  delete(impl);
 }
 
 /*
index 339430e..0d82fa3 100644 (file)
 #include <stdexcept>
 #include <cstring>
 
+#if HAVE_SYS_INOTIFY_H
+# define WATCHER InotifyWatcher
+#else
+# define WATCHER PollingWatcher
+#endif
+
+class Watcher;
+
+// Base class for watcher implementations
+class WatcherImplementation {
+public:
+  WatcherImplementation(const std::string &path_,
+                        Watcher *watcher_): path(path_),
+                                            fp(NULL),
+                                            watcher(watcher_) {
+  }
+  virtual ~WatcherImplementation();
+  virtual int pollfd(time_t &limit) const = 0;
+  virtual void work() = 0;
+  inline void processLine(const std::string &line);
+protected:
+  const std::string path;
+  FILE *fp;
+  std::string line;                     // line read so far
+
+  virtual void closeFile() = 0;
+
+  static std::string getBaseName(const std::string &); // get base name
+  static std::string getDirName(const std::string &); // get directory name
+
+  void readLines();                     // read lines from the watch file
+private:
+  Watcher *watcher;
+};
+
 // Watch a filename and supply lines written to it to a callback.
 class Watcher {
 public:
@@ -29,11 +64,11 @@ public:
   virtual ~Watcher();
 
   // Return the file descriptor to poll
-  inline int pollfd() const { return ifd; }
+  int pollfd(time_t &limit) const { return impl->pollfd(limit); }
 
   // Do some work.  Call this when pollfd() polls readable, or just
   // repeatedly if you didn't make it nonblocking.
-  void work();
+  void work() { impl->work(); }
 
   // Passed a new line from the watched file.  line includes the trailing '\n',
   // if there was one; there may not be if a file is finished off without a
@@ -70,22 +105,13 @@ public:
   };
 
 private:
-  const std::string path;               // filename to watch
-  const std::string base;               // base filename
-  const std::string dir;                // containing directory
-  std::string line;                     // line read so far
-  FILE *fp;                             // (may be NULL) current file
-  int ifd;                              // inotify descriptor
-  int file_wd;                          // file watch descriptor or -1
-  int dir_wd;                           // directory watch descriptor
-
-  void openFile();                      // try to ensure the watched file open
-  void closeFile();                     // close the watched file if open
-  void readLines();                     // read lines from the watch file
-  static std::string getBaseName(const std::string &); // get base name
-  static std::string getDirName(const std::string &); // get directory name
+  WatcherImplementation *impl;
 };
 
+inline void WatcherImplementation::processLine(const std::string &line) {
+  watcher->processLine(line);
+}
+
 #endif /* WATCHER_H */
 
 /*
diff --git a/src/WatcherImplementation.cc b/src/WatcherImplementation.cc
new file mode 100644 (file)
index 0000000..c96f58c
--- /dev/null
@@ -0,0 +1,81 @@
+//
+// Copyright © 2011 Richard Kettlewell
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+#include <config.h>
+#include "Watcher.h"
+#include "IOError.h"
+#include <cerrno>
+
+WatcherImplementation::~WatcherImplementation() {
+  if(fp)
+    fclose(fp);
+}
+
+std::string WatcherImplementation::getDirName(const std::string &p) {
+  std::string::size_type s = p.rfind('/');
+  if(s == std::string::npos)
+    return ".";
+  else
+    return std::string(p, 0, s + 1);
+}
+
+std::string WatcherImplementation::getBaseName(const std::string &p) {
+  std::string::size_type s = p.rfind('/');
+  if(s == std::string::npos)
+    return p;
+  else
+    return std::string(p, s + 1);
+}
+
+void WatcherImplementation::readLines() {
+  int c;
+  for(;;) {
+    // Read the line up to the next newline or EOF
+    while((c = getc(fp)) != EOF) {
+      line += c;
+      if(c == '\n')
+        break;
+    }
+    if(c == '\n') {
+      // We got a complete line
+      processLine(line);
+      line.clear();
+    } else {
+      // We did not get a complete line.  If it was an error, close it
+      // and throw.
+      if(ferror(fp)) {
+        closeFile();
+        throw IOError("reading " + path, errno);
+      }
+      // Otherwise it must be EOF.
+      // Make sure future reads, after the file is extended, will succeed.
+      clearerr(fp);
+      // Done for now.  If the file is deleted before a newline is read,
+      // closeFile() will handle the partial line.
+      return;
+    }
+  }
+}
+
+/*
+Local Variables:
+mode:c++
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
index fbe5886..88f3aaf 100644 (file)
@@ -20,6 +20,7 @@
 #include "Address.h"
 #include "Regex.h"
 #include "BlockMethod.h"
+#include "timeval.h"
 #include "log.h"
 #include "utils.h"
 #include <sys/select.h>
@@ -130,6 +131,8 @@ static void updateWatchers(const ConfFile *oldConfig,
                            std::vector<Watcher *> &oldWatchers) {
   size_t i, j;
   std::vector<Watcher *> newWatchers;
+  time_t limit = 0;
+  int fd;
   for(i = 0; i < newConfig->files.size(); ++i) {
     // Try and re-use the existing watcher
     if(oldConfig) {
@@ -145,7 +148,10 @@ static void updateWatchers(const ConfFile *oldConfig,
     }
     // We didn't manage to re-use an existing watcher
     newWatchers.push_back(new BanWatcher(newConfig->files[i]));
-    nonblock(newWatchers[i]->pollfd());
+    // Make the new watcher's FD nonblocking (if it has one)
+    fd = newWatchers[i]->pollfd(limit);
+    if(fd >= 0)
+      nonblock(fd);
   }
   // Delete redundant watchers
   for(j = 0; j < oldWatchers.size(); ++j)
@@ -242,21 +248,34 @@ int main(int argc, char **argv) {
         // Construct FD set
         int maxfd;
         fd_set fds;
+        struct timeval now, delta;
+        time_t limit = TIME_MAX;
         FD_ZERO(&fds);
         FD_SET(signal_pipe[0], &fds);
         maxfd = signal_pipe[0];
         for(size_t i = 0; i < watchers.size(); ++i) {
-          const int fd = watchers[i]->pollfd();
-          FD_SET(fd, &fds);
-          if(fd > maxfd)
-            maxfd = fd;
+          const int fd = watchers[i]->pollfd(limit);
+          if(fd >= 0) {
+            FD_SET(fd, &fds);
+            if(fd > maxfd)
+              maxfd = fd;
+          }
         }
         // Unblock signal while waiting
         if(sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL) < 0) {
           error("sigprocmask: %s", strerror(errno));
           exit(-1);
         }
-        n = select(maxfd + 1, &fds, NULL, NULL, NULL);
+        if(limit != TIME_MAX) {
+          gettimeofday(&now, NULL);
+          delta = limit - now;
+          if(delta < 0) {
+            delta.tv_sec = 0;
+            delta.tv_usec = 0;
+          }
+          n = select(maxfd + 1, &fds, NULL, NULL, &delta);
+        } else
+          n = select(maxfd + 1, &fds, NULL, NULL, NULL);
         if(sigprocmask(SIG_BLOCK, &sighup_mask, NULL) < 0) {
           error("sigprocmask: %s", strerror(errno));
           exit(-1);
@@ -268,9 +287,14 @@ int main(int argc, char **argv) {
           exit(-1);
         }
         // Check logfiles for updates
+        gettimeofday(&now, NULL);
         for(size_t i = 0; i < watchers.size(); ++i) {
-          const int fd = watchers[i]->pollfd();
-          if(FD_ISSET(fd, &fds))
+          time_t limit = TIME_MAX;
+          const int fd = watchers[i]->pollfd(limit);
+          if(fd >= 0) {
+            if(FD_ISSET(fd, &fds))
+              watchers[i]->work();
+          } else if(now >= limit)
             watchers[i]->work();
         }
         // Check for signals
diff --git a/src/timeval.h b/src/timeval.h
new file mode 100644 (file)
index 0000000..32157c1
--- /dev/null
@@ -0,0 +1,85 @@
+//
+// Copyright © 2011 Richard Kettlewell
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+#ifndef TIMEVAL_H
+#define TIMEVAL_H
+
+#include <sys/time.h>
+
+#if SIZEOF_TIME_T == SIZEOF_INT
+# define TIME_MAX INT_MAX
+#elif SIZEOF_TIME_T == SIZEOF_LONG
+# define TIME_MAX LONG_MAX
+#elif SIZEOF_TIME_T == SIZEOF_LONG_LONG
+# define TIME_MAX LLONG_MAX
+#else
+# error cannot figure out TIME_MAX
+#endif
+
+inline struct timeval operator-(const struct timeval &a,
+                               const struct timeval &b) {
+  struct timeval r;
+  r.tv_sec = a.tv_sec - b.tv_sec;
+  r.tv_usec = a.tv_usec - b.tv_usec;
+  if(r.tv_usec < 0) {
+    r.tv_usec += 1000000;
+    r.tv_sec -= 1;
+  }
+  return r;
+}
+
+inline struct timeval operator-(time_t a,
+                               const struct timeval &b) {
+  struct timeval aa;
+  aa.tv_sec = a;
+  aa.tv_usec = 0;
+  return aa - b;
+}
+
+inline bool operator<(const struct timeval &a,
+                     const struct timeval &b) {
+  if(a.tv_sec < b.tv_sec)
+    return true;
+  if(a.tv_sec == b.tv_sec && a.tv_usec < b.tv_usec)
+    return true;
+  return false;
+}
+
+inline bool operator<(const struct timeval &a,
+                     time_t b) {
+  if(a.tv_sec < b)
+    return true;
+  return false;
+}
+
+inline bool operator>=(const struct timeval &a,
+                     time_t b) {
+  if(a.tv_sec >= b)
+    return true;
+  return false;
+}
+
+#endif /* TIMEVAL_H */
+
+/*
+Local Variables:
+mode:c++
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
index 1090884..2416003 100644 (file)
@@ -37,6 +37,10 @@ int main(int argc, char **argv) {
   if(argc != 2)
     fprintf(stderr, "Usage: watchfile PATH\n");
   MyWatcher w(argv[1]);
-  for(;;)
+  for(;;) {
+    time_t limit;
+    if(w.pollfd(limit) < 0)
+      sleep(1);
     w.work();
+  }
 }