chiark / gitweb /
some todos
[inn-innduct.git] / backends / innduct.c
index 0258ad022de0ce6b17341679f0449c7fb43745be..34f7e3768582b29f7e85db08ae09ee71878b8aab 100644 (file)
@@ -3,6 +3,13 @@
  *  - close idle connections
  *  - cope better with garbage in feed file
  *  - cope better with NULs in feed file
+ *  - check all structs initialised
+ *  - check all fd watches properly undone
+ *  - check all init functions called
+ *  - actually implement badusage
+ *  - options for all options
+ *  - manpage
+ *  - pid, sitename, hostname in lockfile
  *  - -k kill mode ?
  */
 
@@ -157,11 +164,14 @@ perl -ne 'print if m/-8\<-/..m/-\>8-/; print "\f" if m/-\^L-/' backends/innduct.
 
 #define _GNU_SOURCE
 
-#include "inn/list.h"
 #include "config.h"
 #include "storage.h"
 #include "nntp.h"
 #include "libinn.h"
+#include "inndcomm.h"
+
+#include "inn/list.h"
+#include "inn/innconf.h"
 
 #include <sys/uio.h>
 #include <sys/types.h>
@@ -178,6 +188,9 @@ perl -ne 'print if m/-8\<-/..m/-\>8-/; print "\f" if m/-\^L-/' backends/innduct.
 #include <stdarg.h>
 #include <assert.h>
 #include <stdlib.h>
+#include <glob.h>
+#include <time.h>
+#include <math.h>
 
 #include <oop.h>
 #include <oop-read.h>
@@ -291,7 +304,7 @@ static void filemon_callback(InputFile *ipf);
 
 /*----- configuration options -----*/
 
-static char *sitename, *feedfile;
+static const char *sitename, *feedfile, *pathoutgoing;
 static const char *remote_host;
 static int quiet_multiple=0, become_daemon=1;
 
@@ -307,14 +320,13 @@ static int connection_setup_timeout=200, port=119, try_stream=1;
 static int inndcomm_flush_timeout=100;
 static int reconnect_delay_periods, flushfail_retry_periods, open_wait_periods;
 static int backlog_retry_minperiods, backlog_spontaneous_rescan_periods;
+static int spontaneous_flush_periods;
 static const char *inndconffile;
 
-static double accept_proportion;
 static double nocheck_thresh_pct= 95.0;
 static double nocheck_thresh; /* computed in main from _pct */
 static double nocheck_decay_articles= 100; /* converted to _decay */
 static double nocheck_decay; /* computed in main from _articles */
-static int nocheck, nocheck_reported;
 
 
 /*----- statistics -----*/
@@ -446,16 +458,20 @@ static char *path_lock, *path_flushing, *path_defer, *globpat_backlog;
 static StateMachineState sms;
 static FILE *defer;
 static InputFile *main_input_file, *flushing_input_file, *backlog_input_file;
-static int sm_period_counter;
+static int sm_period_counter, until_backlog_nextscan;
 
+static double accept_proportion;
+static int nocheck, nocheck_reported;
 
 /*========== logging ==========*/
 
-static void logcore(int sysloglevel, const char *fmt, ...)
-     __attribute__((__format__(printf,2,3)));
+#define VA                va_list al;  va_start(al,fmt)
+#define PRINTF(f,a)       __attribute__((__format__(printf,f,a)))
+#define NORET_PRINTF(f,a) __attribute__((__noreturn__,__format__(printf,f,a)))
+
+static void logcore(int sysloglevel, const char *fmt, ...) PRINTF(2,3);
 static void logcore(int sysloglevel, const char *fmt, ...) {
-  va_list al;
-  va_start(al,fmt);
+  VA;
   if (become_daemon) {
     vsyslog(sysloglevel,fmt,al);
   } else {
@@ -466,10 +482,9 @@ static void logcore(int sysloglevel, const char *fmt, ...) {
 }
 
 static void logv(int sysloglevel, const char *pfx, int errnoval,
-                int exitstatus, const char *fmt, va_list al)
-     __attribute__((__format__(printf,5,0)));
+                const char *fmt, va_list al) PRINTF(5,0);
 static void logv(int sysloglevel, const char *pfx, int errnoval,
-                int exitstatus, const char *fmt, va_list al) {
+                const char *fmt, va_list al) {
   char msgbuf[256]; /* NB do not call xvasprintf here or you'll recurse */
   vsnprintf(msgbuf,sizeof(msgbuf), fmt,al);
   msgbuf[sizeof(msgbuf)-1]= 0;
@@ -483,44 +498,48 @@ static void logv(int sysloglevel, const char *pfx, int errnoval,
         errnoval>=0 ? strerror(errnoval) : "");
 }
 
-#define logwrap(fn, pfx, sysloglevel, err, estatus)    \
-  static void fn(const char *fmt, ...)                 \
-      __attribute__((__format__(printf,1,2)));         \
+#define diewrap(fn, pfx, sysloglevel, err, estatus)            \
+  static void fn(const char *fmt, ...) NORET_PRINTF(1,2);      \
+  static void fn(const char *fmt, ...) {                       \
+    VA;                                                                \
+    logv(sysloglevel, pfx, err, fmt, al);                      \
+    exit(estatus);                                             \
+  }
+
+#define logwrap(fn, pfx, sysloglevel, err)             \
+  static void fn(const char *fmt, ...) PRINTF(1,2);    \
   static void fn(const char *fmt, ...) {               \
-    va_list al;                                                \
-    va_start(al,fmt);                                  \
-    logv(sysloglevel, pfx, err, estatus, fmt, al);     \
+    VA;                                                        \
+    logv(sysloglevel, pfx, err, fmt, al);              \
+    va_end(al);                                                \
   }
 
-logwrap(sysdie,   " critical", LOG_CRIT,    errno, 16);
-logwrap(die,      " critical", LOG_CRIT,    -1,    16);
+diewrap(sysdie,   " critical", LOG_CRIT,    errno, 16);
+diewrap(die,      " critical", LOG_CRIT,    -1,    16);
 
-logwrap(sysfatal, " fatal",    LOG_ERR,     errno, 12);
-logwrap(fatal,    " fatal",    LOG_ERR,     -1,    12);
+diewrap(sysfatal, " fatal",    LOG_ERR,     errno, 12);
+diewrap(fatal,    " fatal",    LOG_ERR,     -1,    12);
 
-logwrap(syswarn,  " warning",  LOG_WARNING, errno, 0);
-logwrap(warn,     " warning",  LOG_WARNING, -1,    0);
+logwrap(syswarn,  " warning",  LOG_WARNING, errno);
+logwrap(warn,     " warning",  LOG_WARNING, -1);
 
-logwrap(notice,   "",          LOG_NOTICE,  -1,    0);
-logwrap(info,     " info",     LOG_INFO,    -1,    0);
-logwrap(debug,    " debug",    LOG_DEBUG,   -1,    0);
+logwrap(notice,   "",          LOG_NOTICE,  -1);
+logwrap(info,     " info",     LOG_INFO,    -1);
+logwrap(debug,    " debug",    LOG_DEBUG,   -1);
 
 
 /*========== utility functions etc. ==========*/
 
-static char *xvasprintf(const char *fmt, va_list al)
-     __attribute__((__format__(printf,1,0)));
+static char *xvasprintf(const char *fmt, va_list al) PRINTF(1,0);
 static char *xvasprintf(const char *fmt, va_list al) {
   char *str;
   int rc= vasprintf(&str,fmt,al);
   if (rc<0) sysdie("vasprintf(\"%s\",...) failed", fmt);
   return str;
 }
-static char *xasprintf(const char *fmt, ...)
-     __attribute__((__format__(printf,1,2)));
+static char *xasprintf(const char *fmt, ...) PRINTF(1,2);
 static char *xasprintf(const char *fmt, ...) {
-  va_list al;
-  va_start(al,fmt);
+  VA;
   char *str= xvasprintf(fmt,al);
   va_end(al);
   return str;
@@ -585,6 +604,11 @@ static void xunlink(const char *path, const char *what) {
   if (r) sysdie("can't unlink %s %s", path, what);
 }
 
+static time_t xtime(void) {
+  time_t now= time(0);
+  if (now==-1) sysdie("time(2) failed");
+}
+
 static void check_isreg(const struct stat *stab, const char *path,
                        const char *what) {
   if (!S_ISREG(stab->st_mode))
@@ -932,9 +956,7 @@ static void conn_maybe_write(Conn *conn)  {
   }
 }
 
-static void vconnfail(Conn *conn, const char *fmt, va_list al)
-     __attribute__((__format__(printf,2,0)));
-
+static void vconnfail(Conn *conn, const char *fmt, va_list al) PRINTF(2,0);
 static void vconnfail(Conn *conn, const char *fmt, va_list al) {
   int requeue[art_MaxState];
 
@@ -962,8 +984,7 @@ static void vconnfail(Conn *conn, const char *fmt, va_list al) {
   check_master_queue();
 }
 
-static void connfail(Conn *conn, const char *fmt, ...)
-     __attribute__((__format__(printf,2,3)));
+static void connfail(Conn *conn, const char *fmt, ...) PRINTF(2,3);
 static void connfail(Conn *conn, const char *fmt, ...) {
   va_list al;
   va_start(al,fmt);
@@ -2078,13 +2099,13 @@ static void close_defer(void) {
   if (!defer)
     return;
 
-  xfstat(fileno(defer), &stab, path_defer, "defer file");
+  struct stat stab;
+  xfstat_isreg(fileno(defer), &stab, path_defer, "defer file");
 
   if (fclose(defer)) sysfatal("could not close defer file %s", path_defer);
   defer= 0;
 
-  time_t now= time(0);
-  if (now==-1) sysdie("time(2) failed");
+  time_t now= xtime();
 
   char *backlog= xasprintf("%s_backlog_%lu.%lu", feedfile,
                           (unsigned long)now,
@@ -2113,12 +2134,12 @@ static void search_backlog_file(void) {
   /* returns non-0 iff there are any backlog files */
 
   glob_t gl;
-  int r;
+  int r, i;
   struct stat stab;
   const char *oldest_path=0;
   time_t oldest_mtime, now;
 
-  if (backlog_input_file) return 3;
+  if (backlog_input_file) return;
 
  try_again:
 
@@ -2169,10 +2190,10 @@ static void search_backlog_file(void) {
       exit(0);
     }
     until_backlog_nextscan= backlog_spontaneous_rescan_periods;
-    return 0;
+    return;
   }
 
-  now= time();  if (now==-1) sysdie("time(2) failed");
+  now= xtime();
   double age= difftime(now, oldest_mtime);
   long age_deficiency= (backlog_retry_minperiods * PERIOD_SECONDS) - age;
 
@@ -2187,7 +2208,7 @@ static void search_backlog_file(void) {
     }
     inputfile_tailing_start(backlog_input_file);
     until_backlog_nextscan= -1;
-    return 1;
+    return;
   }
 
   until_backlog_nextscan= age_deficiency / PERIOD_SECONDS;
@@ -2198,7 +2219,7 @@ static void search_backlog_file(void) {
 
   debug("backlog scan: young age=%f deficiency=%ld nextscan=%d oldest=%s",
        age, age_deficiency, until_backlog_nextscan, oldest_path);
-  return 2;
+  return;
 }
 
 /*========== flushing the feed ==========*/
@@ -2208,7 +2229,7 @@ static pid_t inndcomm_child;
 static void *inndcomm_event(oop_source *lp, int fd, oop_event e, void *u) {
   assert(inndcomm_child);
   int status= xwaitpid(&inndcomm_child, "inndcomm");
-  loop->cancel_fd(fd);
+  cancel_fd_read_except(fd);
   close(fd);
 
   assert(!flushing_input_file);
@@ -2286,11 +2307,12 @@ void spawn_inndcomm_flush(const char *why) { /* Moved => Flushing */
 
   if (pipe(pipefds)) sysdie("create pipe for inndcomm child sentinel");
 
-  inndcomm_child= xfork();
+  inndcomm_child= xfork("inndcomm child");
 
   if (!inndcomm_child) {
-    static char flushargv[2]= { sitename, 0 };
+    const char *flushargv[2]= { sitename, 0 };
     char *reply;
+    int r;
 
     close(pipefds[0]);
 
@@ -2339,8 +2361,9 @@ static void postfork(const char *what) {
   postfork_stdio(defer);
 }
 
-#define EVERY(what, interval, body)                                         \
-  static const struct timeval what##_timeout = { 5, 0 };                    \
+#define EVERY(what, interval_sec, interval_usec, body)                      \
+  static const struct timeval what##_timeout =                              \
+            { interval_sec, interval_usec };                                \
   static void what##_schedule(void);                                        \
   static void *what##_timedout(oop_source *lp, struct timeval tv, void *u) { \
     { body }                                                                \
@@ -2350,13 +2373,13 @@ static void postfork(const char *what) {
     loop->on_time(loop, what##_timeout, what##_timedout, 0);                \
   }
 
-EVERY(filepoll, {5,0}, {
+EVERY(filepoll, 5,0, {
   if (main_input_file && main_input_file->readable_callback)
     filemon_callback(main_input_file);
 });
 
-#define DEBUGF_IPF(wh) " " #wh "=%p/%s:ip=%ld,off=%ld,fd=%d%s" \
-#define DEBUG_IPF(sh)                                          \
+#define DEBUGF_IPF(wh) " " #wh "=%p/%s:ip=%ld,off=%ld,fd=%d%s"
+#define DEBUG_IPF(wh)                                          \
   wh##_input_file, debug_ipf_path(wh##_input_file),            \
   wh##_input_file->inprogress, (long)wh##_input_file->offset,  \
   wh##_input_file->fd, wh##_input_file->rd ? "+" : ""
@@ -2365,10 +2388,10 @@ static const char *debug_ipf_path(InputFile *ipf) {
   return slash ? slash+1 : ipf->path;
 }
 
-EVERY(period, {PERIOD_SECONDS,0}, {
+EVERY(period, PERIOD_SECONDS,0, {
   debug("PERIOD"
        " sms=%s[%d] conns=%d queue=%d until_connect=%d"
-       " input_files" DEBUGF_IPF(main) DEBUGF_IPF(old) DEBUGF_FMT(flushing)
+       " input_files" DEBUGF_IPF(main) DEBUGF_IPF(old) DEBUGF_IPF(flushing)
        " children connecting=%ld inndcomm_child"
        ,
        sms_names[sms], sm_period_counter,
@@ -2388,103 +2411,115 @@ EVERY(period, {PERIOD_SECONDS,0}, {
 
 /*========== option parsing ==========*/
 
+/*---------- generic option parser and logging ----------*/
+
+static void vbadusage(const char *fmt, va_list al) NORET_PRINTF(1,0);
+static void vbadusage(const char *fmt, va_list al) {
+  abort();
+}
+static void badusage(const char *fmt, ...) NORET_PRINTF(1,2);
+static void badusage(const char *fmt, ...) {
+  va_list al;
+  va_start(al,fmt);
+  vbadusage(fmt,al);
+}
+
 enum OptFlags {
-  of_seconds= 001000u;
-  of_boolean= 002000u;
+  of_seconds= 001000u,
+  of_boolean= 002000u,
 };
 
 typedef struct Option Option;
 typedef void OptionParser(const Option*, const char *val);
 
 struct Option {
-  int short;
-  const char *long;
+  int shrt;
+  const char *lng;
   void *store;
   OptionParser *fn;
-  int noarg;
+  int noarg, intval;
 };
 
-void op_integer(const Option *o, const char *val) {
+/*---------- specific option types ----------*/
+
+static void op_integer(const Option *o, const char *val) {
   char *ep;
   errno= 0;
   unsigned long ul= strtoul(val,&ep,10);
   if (*ep || ep==val || errno || ul>INT_MAX)
-    badusage("bad integer value for %s",o->long);
+    badusage("bad integer value for %s",o->lng);
   int *store= o->store;
   *store= ul;
 }
 
-void op_double(const Option *o, const char *val) {
+static void op_double(const Option *o, const char *val) {
   int *store= o->store;
   char *ep;
   errno= 0;
   *store= strtod(val, &ep);
   if (*ep || ep==val || errno)
-    badusage("bad floating point value for %s",o->long);
+    badusage("bad floating point value for %s",o->lng);
 }
 
-void op_string(const Option *o, const char *val) {
-  char **store= o->store;
-  free(*store);
+static void op_string(const Option *o, const char *val) {
+  const char **store= o->store;
   *store= val;
 }
 
-void op_seconds(const Option *o, const char *val) {
+static void op_seconds(const Option *o, const char *val) {
   int *store= o->store;
   char *ep;
+  int unit;
 
   double v= strtod(val,&ep);
-  if (ep==val) badusage("bad time/duration value for %s",o->long);
+  if (ep==val) badusage("bad time/duration value for %s",o->lng);
 
   if (!*ep || !strcmp(ep,"s")) unit= 1;
   else if (!strcmp(ep,"m")) unit= 60;
   else if (!strcmp(ep,"h")) unit= 3600;
   else if (!strcmp(ep,"d")) unit= 86400;
-  else badusage("bad units %s for time/duration value for %s",ep,o->long);
+  else badusage("bad units %s for time/duration value for %s",ep,o->lng);
 
   v *= unit;
   v= ceil(v);
-  if (v > INT_MAX) badusage("time/duration value for %s out of range",o->long);
+  if (v > INT_MAX) badusage("time/duration value for %s out of range",o->lng);
   *store= v;
 }
 
-void op_periods_rndup(const Option *o, const char *val) {
+static void op_periods_rndup(const Option *o, const char *val) {
   int *store= o->store;
   op_seconds(o,val);
   *store += PERIOD_SECONDS-1;
   *store /= PERIOD_SECONDS;
 }
 
-void op_periods_booltrue(const Option *o, const char *val) {
-  int *store= o->store;
-  *store= 1;
-}
-void op_periods_boolfalse(const Option *o, const char *val) {
+static void op_setint(const Option *o, const char *val) {
   int *store= o->store;
-  *store= 0;
+  *store= o->intval;
 }
 
+/*---------- specific options ----------*/
+
 static const Option options[]= {
 {'f',"feedfile",              &feedfile,                 op_string           },
-{'q',"quiet-multiple",        &quiet_multiple,           op_booltrue,  1     },
+{'q',"quiet-multiple",        &quiet_multiple,           op_setint,   1,1    },
 
-{ 0, "max-connections",       &max_connections           op_integer          },
-{ 0, "max-queue-per-conn",    &max_queue_per_conn        op_integer          },
+{ 0, "max-connections",       &max_connections,          op_integer          },
+{ 0, "max-queue-per-conn",    &max_queue_per_conn,       op_integer          },
 
 
-{ 0, "streaming",             &try_stream,               op_booltrue,  1     },
-{ 0, "no-streaming",          &try_stream,               op_boolfalse, 1     },
-{'P',"port",                  &port                      op_integer          },
+{ 0, "streaming",             &try_stream,               op_setint,  1,1     },
+{ 0, "no-streaming",          &try_stream,               op_setint,  1,0     },
+{'P',"port",                  &port,                     op_integer          },
 { 0, "inndconf",              &inndconffile,             op_string           },
-{'d',"daemon",                &become_daemon,            op_booltrue,  1     },
-{ 0, "no-daemon",             &become_daemon,            op_boolfalse, 1     },
+{'d',"daemon",                &become_daemon,            op_setint,  1,1     },
+{ 0, "no-daemon",             &become_daemon,            op_setint,  1,0     },
 
 { 0, "no-check-proportion",   &nocheck_thresh_pct,       op_double           },
 { 0, "no-check-filter",       &nocheck_decay_articles,   op_double           },
 
 { 0, "reconnect-interval",    &reconnect_delay_periods,  op_periods_rndup    },
 { 0, "flush-retry-interval",  &flushfail_retry_periods,  op_periods_rndup    },
-{ 0, "connection-timeout",    &connection_timeout,       op_seconds          },
 { 0, "inndcomm-timeout",      &inndcomm_flush_timeout,   op_seconds          },
 };
 
@@ -2503,25 +2538,25 @@ int main(int argc, char **argv) {
        arg++;
        char *equals= strchr(arg,'=');
        int len= equals ? (equals - arg) : strlen(arg);
-       for (o=options; o->long; o++)
-         if (strlen(o->long) == len && !memcmp(o->long,arg,len))
+       for (o=options; o->lng; o++)
+         if (strlen(o->lng) == len && !memcmp(o->lng,arg,len))
            goto found_long;
        badusage("unknown long option --%s",arg);
       found_long:
        if (o->noarg) {
-         if (equals) badusage("option --%s does not take a value",o->long);
+         if (equals) badusage("option --%s does not take a value",o->lng);
          arg= 0;
        } else if (equals) {
          arg= equals+1;
        } else {
          arg= *++argv;
-         if (!arg) badusage("option --%s needs a value",o->long);
+         if (!arg) badusage("option --%s needs a value",o->lng);
        }
        o->fn(o, arg);
        break; /* eaten the whole argument now */
       }
-      for (o=options; o->long; o++)
-       if (a == o->short)
+      for (o=options; o->lng; o++)
+       if (a == o->shrt)
          goto found_short;
       badusage("unknown short option -%c",a);
     found_short:
@@ -2530,7 +2565,7 @@ int main(int argc, char **argv) {
       } else {
        if (!*++arg) {
          arg= *++argv;
-         if (!arg) badusage("option -%c needs a value",o->short);
+         if (!arg) badusage("option -%c needs a value",o->shrt);
        }
        o->fn(o,arg);
        break; /* eaten the whole argument now */
@@ -2571,13 +2606,15 @@ int main(int argc, char **argv) {
     if (strchr(feedfile, c))
       badusage("feed filename may not contain metacharacter %c",c);
 
-  loop= oop_sys_new();
-  if (!loop) sysdie("could not create liboop event loop");
+  oop_source_sys *sysloop= oop_sys_new();
+  if (!sysloop) sysdie("could not create liboop event loop");
+  loop= (oop_source*)sysloop;
 
   if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
     sysdie("could not ignore SIGPIPE");
 
   if (become_daemon) {
+    int i;
     for (i=3; i<255; i++)
       /* do this now before we open syslog, etc. */
       close(i);
@@ -2602,7 +2639,7 @@ int main(int argc, char **argv) {
 
   notice("starting");
 
-  if (!filemon_init()) {
+  if (!filemon_method_init()) {
     warn("no file monitoring available, polling");
     filepoll_schedule();
   }
@@ -2611,5 +2648,7 @@ int main(int argc, char **argv) {
 
   statemc_init();
 
-  loop->execute.
+  void *r= oop_sys_run(sysloop);
+  assert(r == OOP_ERROR);
+  sysdie("event loop failed");
 }