+/*---------- generic option parser ----------*/
+
+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,
+};
+
+typedef struct Option Option;
+typedef void OptionParser(const Option*, const char *val);
+
+struct Option {
+ int shrt;
+ const char *lng, *formarg;
+ void *store;
+ OptionParser *fn;
+ int intval;
+};
+
+static void parse_options(const Option *options, char ***argvp) {
+ /* on return *argvp is first non-option arg; argc is not updated */
+
+ for (;;) {
+ const char *arg= *++(*argvp);
+ if (!arg) break;
+ if (*arg != '-') break;
+ if (!strcmp(arg,"--")) { arg= *++(*argvp); break; }
+ int a;
+ while ((a= *++arg)) {
+ const Option *o;
+ if (a=='-') {
+ arg++;
+ char *equals= strchr(arg,'=');
+ int len= equals ? (equals - arg) : strlen(arg);
+ for (o=options; o->shrt || 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->formarg) {
+ if (equals) badusage("option --%s does not take a value",o->lng);
+ arg= 0;
+ } else if (equals) {
+ arg= equals+1;
+ } else {
+ arg= *++(*argvp);
+ if (!arg) badusage("option --%s needs a value for %s",
+ o->lng, o->formarg);
+ }
+ o->fn(o, arg);
+ break; /* eaten the whole argument now */
+ }
+ for (o=options; o->shrt || o->lng; o++)
+ if (a == o->shrt)
+ goto found_short;
+ badusage("unknown short option -%c",a);
+ found_short:
+ if (!o->formarg) {
+ o->fn(o,0);
+ } else {
+ if (!*++arg) {
+ arg= *++(*argvp);
+ if (!arg) badusage("option -%c needs a value for %s",
+ o->shrt, o->formarg);
+ }
+ o->fn(o,arg);
+ break; /* eaten the whole argument now */
+ }
+ }
+ }
+}
+
+#define DELIMPERHAPS(delim,str) (str) ? (delim) : "", (str) ? (str) : ""
+
+static void print_options(const Option *options, FILE *f) {
+ const Option *o;
+ for (o=options; o->shrt || o->lng; o++) {
+ char shrt[2] = { o->shrt, 0 };
+ char *optspec= xasprintf("%s%s%s%s%s",
+ o->shrt ? "-" : "", shrt,
+ o->shrt && o->lng ? "|" : "",
+ DELIMPERHAPS("--", o->lng));
+ fprintf(f, " %s%s%s\n", optspec, DELIMPERHAPS(" ", o->formarg));
+ free(optspec);
+ }
+}
+
+/*---------- 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->lng);
+ int *store= o->store;
+ *store= ul;
+}
+
+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->lng);
+}
+
+static void op_string(const Option *o, const char *val) {
+ const char **store= o->store;
+ *store= 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->lng);
+
+ if (!*ep || !strcmp(ep,"s") || !strcmp(ep,"sec")) unit= 1;
+ else if (!strcmp(ep,"m") || !strcmp(ep,"min")) unit= 60;
+ else if (!strcmp(ep,"h") || !strcmp(ep,"hour")) unit= 3600;
+ else if (!strcmp(ep,"d") || !strcmp(ep,"day")) unit= 86400;
+ else if (!strcmp(ep,"das")) unit= 10;
+ else if (!strcmp(ep,"hs")) unit= 100;
+ else if (!strcmp(ep,"ks")) unit= 1000;
+ else if (!strcmp(ep,"Ms")) unit= 1000000;
+ 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->lng);
+ *store= v;
+}
+
+static void op_setint(const Option *o, const char *val) {
+ int *store= o->store;
+ *store= o->intval;
+}
+
+/*---------- specific options ----------*/
+
+static void help(const Option *o, const char *val);
+
+static const Option innduct_options[]= {
+{'f',"feedfile", "F", &feedfile, op_string },
+{'q',"quiet-multiple", 0, &quiet_multiple, op_setint, 1 },
+{0,"no-daemon", 0, &become_daemon, op_setint, 0 },
+{0,"no-streaming", 0, &try_stream, op_setint, 0 },
+{0,"inndconf", "F", &inndconffile, op_string },
+{'P',"port", "PORT", &port, op_integer },
+{0,"help", 0, 0, help },
+
+{0,"max-connections", "N", &max_connections, op_integer },
+{0,"max-queue-per-conn", "N", &max_queue_per_conn, op_integer },
+{0,"feedfile-flush-size","BYTES", &target_max_feedfile_size, op_integer },
+{0,"period-interval", "TIME", &period_seconds, op_seconds },
+
+{0,"connection-timeout", "TIME", &connection_setup_timeout, op_seconds },
+{0,"stuck-flush-timeout","TIME", &inndcomm_flush_timeout, op_seconds },
+
+{0,"no-check-proportion", "PERCENT", &nocheck_thresh, op_double },
+{0,"no-check-response-time","ARTICLES", &nocheck_decay, op_double },
+
+{0,"reconnect-interval", "PERIOD", &reconnect_delay_periods, op_seconds },
+{0,"flush-retry-interval", "PERIOD", &flushfail_retry_periods, op_seconds },
+{0,"earliest-deferred-retry","PERIOD", &backlog_retry_minperiods, op_seconds },
+{0,"backlog-rescan-interval","PERIOD",&backlog_spontrescan_periods,op_seconds},
+{0,"max-flush-interval", "PERIOD", &spontaneous_flush_periods,op_seconds },
+{0,"idle-timeout", "PERIOD", &need_activity_periods, op_seconds },
+
+{0,"max-bad-input-data-ratio","PERCENT", &max_bad_data_ratio, op_double },
+{0,"max-bad-input-data-init", "PERCENT", &max_bad_data_initial, op_integer },
+
+{0,0}
+};
+
+static void printusage(FILE *f) {
+ fputs("usage: innduct [options] site [fqdn]\n"
+ "available options are:\n", f);
+ print_options(innduct_options, f);
+}
+
+static void help(const Option *o, const char *val) {
+ printusage(stdout);
+ if (ferror(stdout) || fflush(stdout)) {
+ perror("innduct: writing help");
+ exit(12);
+ }
+ exit(0);
+}
+
+static void convert_to_periods_rndup(int *store) {
+ *store += period_seconds-1;
+ *store /= period_seconds;
+}
+
+int main(int argc, char **argv) {
+ if (!argv[1]) {
+ printusage(stderr);
+ exit(8);
+ }
+
+ parse_options(innduct_options, &argv);
+
+ /* arguments */
+
+ sitename= *argv++;
+ if (!sitename) badusage("need site name argument");
+ remote_host= *argv++;
+ if (*argv) badusage("too many non-option arguments");
+
+ /* defaults */
+
+ if (!remote_host) remote_host= sitename;
+
+ if (nocheck_thresh < 0 || nocheck_thresh > 100)
+ badusage("nocheck threshold percentage must be between 0..100");
+ nocheck_thresh *= 0.01;
+
+ if (nocheck_decay < 0.1)
+ badusage("nocheck decay articles must be at least 0.1");
+ nocheck_decay= pow(0.5, 1.0/nocheck_decay);
+
+ convert_to_periods_rndup(&reconnect_delay_periods);
+ convert_to_periods_rndup(&flushfail_retry_periods);
+ convert_to_periods_rndup(&backlog_retry_minperiods);
+ convert_to_periods_rndup(&backlog_spontrescan_periods);
+ convert_to_periods_rndup(&spontaneous_flush_periods);
+ convert_to_periods_rndup(&need_activity_periods);
+
+ if (max_bad_data_ratio < 0 || max_bad_data_ratio > 100)
+ badusage("bad input data ratio must be between 0..100");
+ max_bad_data_ratio *= 0.01;
+
+ if (!feedfile) {
+ innconf_read(inndconffile);
+ feedfile= xasprintf("%s/%s",innconf->pathoutgoing,sitename);
+ } else if (!feedfile[0]) {
+ badusage("feed filename must be nonempty");
+ } else if (feedfile[strlen(feedfile)-1]=='/') {
+ feedfile= xasprintf("%s%s",feedfile,sitename);
+ }
+
+ const char *feedfile_forbidden= "?*[~#";
+ int c;
+ while ((c= *feedfile_forbidden++))
+ if (strchr(feedfile, c))
+ badusage("feed filename may not contain metacharacter %c",c);
+
+ /* set things up */
+
+ 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");
+
+ LIST_INIT(conns);
+ LIST_INIT(queue);
+
+ if (become_daemon) {
+ int i;
+ for (i=3; i<255; i++)
+ /* do this now before we open syslog, etc. */
+ close(i);
+ openlog("innduct",LOG_NDELAY|LOG_PID,LOG_NEWS);
+
+ int null= open("/dev/null",O_RDWR);
+ if (null<0) sysfatal("failed to open /dev/null");
+ dup2(null,0);
+ dup2(null,1);
+ dup2(null,2);
+ xclose(null, "/dev/null original fd",0);
+
+ pid_t child1= xfork("daemonise first fork");
+ if (child1) _exit(0);
+
+ pid_t sid= setsid();
+ if (sid != child1) sysfatal("setsid failed");
+
+ pid_t child2= xfork("daemonise second fork");
+ if (child2) _exit(0);
+ }
+
+ notice("starting");
+
+ if (!filemon_method_init()) {
+ warn("no file monitoring available, polling");