chiark / gitweb /
inn: find-unhistorical
authorRichard Kettlewell <rjk@terraraq.org.uk>
Sat, 23 Feb 2013 13:58:01 +0000 (13:58 +0000)
committerRichard Kettlewell <rjk@terraraq.org.uk>
Sat, 23 Feb 2013 13:58:01 +0000 (13:58 +0000)
Tool to find articles in a news spool which aren't in history.

.gitignore
Makefile.am
README
acinclude.m4 [new file with mode: 0644]
configure.ac
inn/Makefile.am [new file with mode: 0644]
inn/find-unhistorical.1 [new file with mode: 0644]
inn/inn-includes.h [new file with mode: 0644]
inn/unhistorical.c [new file with mode: 0644]

index a154e2c..cedc3f1 100644 (file)
@@ -47,3 +47,4 @@ gateways/lj2news.1.html
 gateways/setup-lj2news.1.html
 products
 lib/seen-t
+inn/find-unhistorical
index 4e74665..b40ccbc 100644 (file)
@@ -18,7 +18,7 @@
 # USA
 #
 
-SUBDIRS=lib gateways ${SPOOLSTATS} debian
+SUBDIRS=lib gateways ${INN} ${SPOOLSTATS} debian
 
 echo-distdir:
        @echo $(distdir)
diff --git a/README b/README
index 41569d9..b87b9cc 100644 (file)
--- a/README
+++ b/README
@@ -76,6 +76,17 @@ for documentation.  You can find example output at:
     http://www.greenend.org.uk/rjk/spoolstats/
 
 
+find-unhistorical
+-----------------
+
+find-unhistorical reads an INN news spool and reports any articles
+that aren't in the history file.  Since INN doesn't know about these
+articles they are "lost" and will never be deleted in the normal
+process of expiry.
+
+This program is only built if the INN libraries can be located.
+
+
 Reporting Bugs
 --------------
 
diff --git a/acinclude.m4 b/acinclude.m4
new file mode 100644 (file)
index 0000000..a5c36bb
--- /dev/null
@@ -0,0 +1,68 @@
+dnl
+dnl This file is part of rjk-nntp-tools.
+dnl Copyright © 2013 Richard Kettlewell
+dnl
+dnl This program is free software; you can redistribute it and/or modify
+dnl it under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation; either version 2 of the License, or
+dnl (at your option) any later version.
+dnl
+dnl This program is distributed in the hope that it will be useful, but
+dnl WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+dnl General Public License for more details.
+dnl
+dnl You should have received a copy of the GNU General Public License
+dnl along with this program; if not, write to the Free Software
+dnl Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+dnl USA
+dnl
+
+dnl INN dev libs support
+dnl
+dnl Trying to use INN dev libs has the following problems:
+dnl
+dnl 1. You've no idea where they are.  There is no .pc file or anything.
+dnl
+dnl 2. You need to know whether to define HAVE_SSL to ensure that the
+dnl    installed libraries are compatible with the headers.
+dnl
+dnl 3. They are static libraries so you need to recompile after any
+dnl    updates.
+dnl
+dnl This macro addresses 1 and 2 up to a point.  You're on your own
+dnl with 3.
+
+AC_DEFUN([RJK_INN],[
+  AC_CACHE_CHECK([for INN libraries],
+                 [rjk_cv_innlib], [
+    AC_ARG_WITH([inn-libs],
+                [AS_HELP_STRING([--with-inn-libs=DIR],
+                                [location of INN libraries])],
+                [
+      rjk_cv_innlib="${withval}"
+    ],[
+      rjk_cv_innlib=no
+      for dir in /usr/lib/news /usr/local/lib/news; do
+        if test -e ${dir}/libinn.a; then
+          rjk_cv_innlib="${dir}"
+        fi
+      done
+    ])
+  ])
+  if test "${rjk_cv_innlib}" != no; then
+    INNLIB="${rjk_cv_innlib}"
+    AC_CACHE_CHECK([whether INN libraries require -DHAVE_SSL],
+                   [rjk_cv_innssl], [
+      if grep tlscafile ${INNLIB}/libinn.a >/dev/null; then
+        rjk_cv_innssl=yes
+      else
+        rjk_cv_innssl=no
+      fi
+    ])
+    if test $rjk_cv_innssl = yes; then
+      AC_DEFINE([INN_HAVE_SSL],[1],[define to 1 if INN was built with SSL support])
+    fi
+  fi
+  AC_SUBST([INNLIB])
+])
index 2ae1831..bd644eb 100644 (file)
@@ -38,6 +38,8 @@ missing_functions=""
 
 AC_DEFINE(_GNU_SOURCE, 1, [required for e.g. strsignal])
 
+AC_DEFINE([_FILE_OFFSET_BITS], [64], [use 64-bit off_t])
+
 AC_CACHE_CHECK([for fink],[rjk_cv_fink],[
   if test -d /sw; then
     rjk_cv_fink=/sw
@@ -88,6 +90,12 @@ AC_SUBST([SPOOLSTATS])
 AC_SUBST([CAIROMM_CFLAGS],[$rjk_cv_cairomm_cflags])
 AC_SUBST([CAIROMM_LIBS],[$rjk_cv_cairomm_libs])
 
+RJK_INN
+if test "$rjk_cv_innlib" != no; then
+  INN=inn
+fi
+AC_SUBST([INN])
+
 if test ! -z "$missing_libraries"; then
   AC_MSG_ERROR([missing libraries:$missing_libraries])
 fi
@@ -152,5 +160,6 @@ AC_CONFIG_FILES([Makefile
                  gateways/Makefile
                  lib/Makefile
                  graph/Makefile
+                 inn/Makefile
                  spoolstats/Makefile])
 AC_OUTPUT
diff --git a/inn/Makefile.am b/inn/Makefile.am
new file mode 100644 (file)
index 0000000..cb72701
--- /dev/null
@@ -0,0 +1,25 @@
+#
+# This file is part of rjk-nntp-tools.
+# Copyright © 2013 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 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+#
+bin_PROGRAMS=find-unhistorical
+find_unhistorical_SOURCES=unhistorical.c inn-includes.h
+find_unhistorical_LDADD=-linnhist -lstorage -linn ../lib/libmisc.a
+find_unhistorical_LDFLAGS=-L ${INNLIB}
+AM_CPPFLAGS=-I${top_srcdir}/lib
+man_MANS=find-unhistorical.1
diff --git a/inn/find-unhistorical.1 b/inn/find-unhistorical.1
new file mode 100644 (file)
index 0000000..066d679
--- /dev/null
@@ -0,0 +1,58 @@
+.\"
+.\" This file is part of rjk-nntp-tools.
+.\" Copyright © 2013 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 2 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, write to the Free Software
+.\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+.\" USA
+.\"
+.TH find-unhistorical 1
+.SH SYNOPSIS
+.B find-unhistorical
+.RI [ OPTIONS ]
+.SH DESCRIPTION
+\fBfind-unhistorical\fR writes to standard out the filenames of news
+articles that are not reflected in the history file.
+.SH OPTIONS
+.TP
+.B -h\fR, \fB--help
+Display a usage message.
+.TP
+.B -l\fR, \fB--list
+List articles.
+This is the default.
+.TP
+.B -m\fR, \fB--malformed
+Include malformed articles.
+This is the default.
+.TP
+.B -M\fR\fR, \fB--no-malformed
+Do not include malformed articles.
+.TP
+.B -r\fR, \fB--remove
+Remove articles instead of listing them.
+.TP
+.B -v\fR, \fB--verbose
+Write directory names to standard error as the search proceeds.
+.TP
+.B -V\fR, \fB--version
+Display a version string.
+.TP
+.B -0\fR, \fB--null
+With \fB--list\fR, terminate each filename with a 0 byte, instead of a
+newline character.
+.SH "SEE ALSO"
+.BR innd (8)
+.SH AUTHOR
+(c) 2013 Richard Kettlewell
diff --git a/inn/inn-includes.h b/inn/inn-includes.h
new file mode 100644 (file)
index 0000000..1e4f27d
--- /dev/null
@@ -0,0 +1,22 @@
+#ifndef INN_INCLUDES_H
+#define INN_INCLUDES_H
+
+/* INN headers produce different structure layouts depending on whether it was
+ * configured with SSL support, so we must jump through an stupid hoop to get a
+ * usable header file. */
+#if INN_HAVE_SSL
+# define HAVE_SSL 1
+#endif
+
+#include <inn/history.h>
+#include <inn/innconf.h>
+#include <inn/libinn.h>
+#include <inn/paths.h>
+#include <inn/wire.h>
+
+#if INN_HAVE_SSL
+# undef HAVE_SSL
+#endif
+
+#endif /* INN_INCLUDES_H */
+
diff --git a/inn/unhistorical.c b/inn/unhistorical.c
new file mode 100644 (file)
index 0000000..7ef0c1b
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ * This file is part of rjk-nntp-tools.
+ * Copyright © 2013 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "error.h"
+#include "inn-includes.h"
+
+static void help(void);
+static void version(void);
+static int ftw_callback(const char *fpath,
+                        const struct stat *sb,
+                        int typeflag,
+                        struct FTW *ftwbuf);
+static void check_article(const char *fpath,
+                          const struct stat *sb);
+static char *load_article(const char *fpath, const struct stat *sb, 
+                          size_t *sizep);
+static void malformed(const char *fpath);
+static void action_list(const char *fpath);
+static void action_remove(const char *fpath);
+
+static struct history *history;
+static int list_malformed = 1;
+static int eol = '\n';
+static int verbose = 0;
+static void (*action)(const char *) = action_list;
+
+static const struct option options[] = {
+  { "malformed", no_argument, 0, 'm' },
+  { "no-malformed", no_argument, 0, 'M' },
+  { "list", no_argument, 0, 'l' },
+  { "remove", no_argument, 0, 'r' },
+  { "null", no_argument, 0, '0' },
+  { "verbose", no_argument, 0, 'v' },
+  { "help", no_argument, 0, 'h' },
+  { "version", no_argument, 0, 'V' },
+  { 0, 0, 0, 0 }
+};
+
+int main(int argc, char **argv) {
+  int n;
+  const char *pathhistory;
+
+  while((n = getopt_long(argc, argv, "hVmMrl0v",
+                         options, 0)) >= 0) {
+    switch(n) {
+    case 'm':
+      list_malformed = 1;
+      break;
+    case 'M':
+      list_malformed = 0;
+      break;
+    case 'l':
+      action = action_list;
+      break;
+    case 'r':
+      action = action_remove;
+      break;
+    case '0':
+      eol = 0;
+      break;
+    case 'v':
+      ++verbose;
+      break;
+    case 'h':
+      help();
+      return 0;
+    case 'V':
+      version();
+      return 0;
+    default:
+      return 1;
+    }
+  }
+  if(!innconf_read(NULL))
+    fatal(0, "cannot open inn.conf");
+  pathhistory = concatpath(innconf->pathdb, INN_PATH_HISTORY);
+  if(!(history = HISopen(pathhistory, innconf->hismethod, HIS_RDONLY)))
+    fatal(errno, "opening %s", pathhistory);
+  if(nftw(innconf->patharticles, ftw_callback, 128, FTW_PHYS) < 0)
+    fatal(errno, "nftw %s failed", innconf->patharticles);
+  if(!HISclose(history))
+    fatal(errno, "closing %s", pathhistory);
+  if(fclose(stdout) < 0)
+    fatal(errno, "stdout");
+  return !!errors;
+}
+
+static void help(void) {
+  if(printf("Usage:\n"
+            "  find-unhistorical [OPTIONS]\n"
+            "Options:\n"
+            "  -l, --list          List lost articles (default)\n"
+            "  -r, --remove        Remove lost articles\n"
+            "  -0, --null          Terminate filenames with \\0 instead of \\n\n"
+            "  -m, --malformed     Include malformed articles (default)\n"
+            "  -M, --no-malformed  Don't include malformed articles\n"
+            "  -v, --verbose       Write directory names to stderr\n"
+            "  -h, --help          Display usage message\n"
+            "  -V, --version       Display version string\n"
+            "\n"
+            "Lists or removes files in the news spool that aren't reflected in the history\n"
+            "file.\n") < 0)
+    fatal(errno, "stdout");
+}
+
+static void version(void) {
+  if(printf("find-unhistorical from rjk-nntp-tools version " VERSION "\n") < 0)
+    fatal(errno, "stdout");
+}
+
+static int ftw_callback(const char *fpath,
+                        const struct stat *sb,
+                        int typeflag,
+                        struct FTW attribute((unused)) *ftwbuf) {
+  switch(typeflag) {
+  case FTW_F:
+    check_article(fpath, sb);
+    break;
+  case FTW_D:
+    if(verbose)
+      fprintf(stderr, "%s\n", fpath);
+    break;
+  case FTW_SL:
+  case FTW_SLN:
+    break;
+  case FTW_DNR:
+    error(errno, "cannot read %s", fpath);
+    break;
+  case FTW_NS:
+    error(errno, "cannot stat %s", fpath);
+    break;
+  default:
+    fatal(0, "unexpected typeflag %d for %s", typeflag, fpath);
+  }
+  return 0;
+}
+
+static void check_article(const char *fpath,
+                          const struct stat *sb) {
+  size_t size;
+  char *article, *mid, *end;
+
+  if(!(article = load_article(fpath, sb, &size)))
+    return;
+  /* Find the Message-ID header.  If there is no well-formed Message-ID, report
+   * the article as malformed. */
+  if(!(mid = wire_findheader(article, size, "Message-ID", 1))
+     || !(end = wire_endheader(mid, article + size)))
+    malformed(fpath);
+  else {
+    /* Isolate the ID */
+    while(end > mid && isspace((unsigned char)end[-1]))
+      --end;
+    *end = 0;
+    if(!HIScheck(history, mid))
+      action(fpath);
+  }
+  free(article);
+}
+
+static char *load_article(const char *fpath, const struct stat *sb, 
+                          size_t *sizep) {
+  int fd;
+  char *article;
+  off_t got, thisread;
+  ssize_t n;
+
+  if((fd = open(fpath, O_RDONLY)) < 0) {
+    error(errno, "opening %s", fpath);
+    return NULL;
+  }
+  article = xmalloc(sb->st_size + 1);
+  got = 0;
+  while(got < sb->st_size) {
+    thisread = sb->st_size - got;
+    if(thisread > SSIZE_MAX)
+      thisread = SSIZE_MAX;
+    n = read(fd, article + got, (size_t)thisread);
+    if(n < 0) {
+      if(errno != EINTR && errno != EAGAIN) {
+        error(errno, "reading %s", fpath);
+        close(fd);
+        return NULL;
+      }
+    } else if(n == 0) {
+      error(0, "reading %s: truncated", fpath);
+      close(fd);
+      return NULL;
+    } else
+      got += n;
+  }
+  n = read(fd, article + got, 1);
+  if(n < 0) {
+    error(errno, "reading %s", fpath);
+    close(fd);
+    return NULL;
+  }
+  if(n != 0) {
+    error(0, "reading %s: longer than expected", fpath);
+    close(fd);
+    return NULL;
+  }
+  close(fd);
+  /* Convert to wire format if necessary */
+  if(!innconf->wireformat) {
+    char *wire = wire_from_native(article, sb->st_size, sizep);
+    free(article);
+    article = wire;
+  } else
+    *sizep = sb->st_size;
+  return article;
+}
+
+static void malformed(const char *fpath) {
+  if(list_malformed)
+    action(fpath);
+}
+
+static void action_list(const char *fpath) {
+  if(printf("%s%c", fpath, eol) < 0)
+    fatal(errno, "stdout");
+}
+
+static void action_remove(const char *fpath) {
+  if(unlink(fpath) < 0)
+    error(errno, "removing %s", fpath);
+}