/*
* TODO
* - 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 ?
*/
#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>
#include <assert.h>
#include <stdlib.h>
#include <glob.h>
+#include <time.h>
+#include <math.h>
#include <oop.h>
#include <oop-read.h>
/*----- 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;
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 nocheck_thresh_pct= 95.0;
/*========== 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 {
}
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;
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;
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))
}
}
-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];
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);
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,
/* 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:
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;
}
inputfile_tailing_start(backlog_input_file);
until_backlog_nextscan= -1;
- return 1;
+ return;
}
until_backlog_nextscan= age_deficiency / PERIOD_SECONDS;
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 ==========*/
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);
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]);
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 } \
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 ? "+" : ""
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,
/*========== 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 },
};
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:
} 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 */
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);
notice("starting");
- if (!filemon_init()) {
+ if (!filemon_method_init()) {
warn("no file monitoring available, polling");
filepoll_schedule();
}
statemc_init();
- loop->execute.
+ void *r= oop_sys_run(sysloop);
+ assert(r == OOP_ERROR);
+ sysdie("event loop failed");
}