+ close_input_file(ipf);
+ if (unlink(ipf->path)) {
+ if (errno != ENOENT)
+ sysdie("could not unlink processed backlog file %s", ipf->path);
+ warn("backlog file %s vanished while we were reading it"
+ " so we couldn't remove it (but it's done now, anyway)",
+ ipf->path);
+ }
+ free(ipf);
+ backlog_input_file= 0;
+ search_backlog_file();
+ return;
+}
+
+static void statemc_check_flushing_done(void) {
+ InputFile *ipf= flushing_input_file;
+ if (!inputfile_is_done(ipf)) return;
+
+ assert(sms==sm_SEPARATED || sms==sm_DROPPING);
+
+ notice_processed(ipf,"feedfile",0);
+
+ close_defer();
+
+ xunlink(path_flushing, "old flushing file");
+
+ close_input_file(flushing_input_file);
+ free(flushing_input_file);
+ flushing_input_file= 0;
+
+ if (sms==sm_SEPARATED) {
+ notice("flush complete");
+ SMS(NORMAL, 0, "flush complete");
+ } else if (sms==sm_DROPPING) {
+ SMS(DROPPED, 0, "old flush complete");
+ search_backlog_file();
+ notice("feed dropped, but will continue until backlog is finished");
+ }
+}
+
+static void *statemc_check_input_done(oop_source *lp, struct timeval now,
+ void *u) {
+ assert(!inputfile_is_done(main_input_file));
+ statemc_check_flushing_done();
+ statemc_check_backlog_done();
+ return OOP_CONTINUE;
+}
+
+static void queue_check_input_done(void) {
+ loop->on_time(loop, OOP_TIME_NOW, statemc_check_input_done, 0);
+}
+
+static void statemc_setstate(StateMachineState newsms, int periods,
+ const char *forlog, const char *why) {
+ sms= newsms;
+ sm_period_counter= periods;
+
+ const char *xtra= "";
+ switch (sms) {
+ case sm_FLUSHING: sm_FLUSHFAILED:
+ if (!main_input_file) xtra= "-ABSENT";
+ break;
+ case sm_SEPARATED: case sm_DROPPING:
+ xtra= flushing_input_file->fd ? "-1" : "-2";
+ break;
+ default:;
+ }
+
+ if (periods) {
+ info("%s%s[%d] %s",forlog,xtra,periods,why);
+ } else {
+ info("%s%s %s",forlog,xtra,why);
+ }
+}
+
+/*---------- defer and backlog files ----------*/
+
+static void open_defer(void) {
+ struct stat stab;
+
+ if (defer) return;
+
+ defer= fopen(path_defer, "a+");
+ if (!defer) sysfatal("could not open defer file %s", path_defer);
+
+ /* truncate away any half-written records */
+
+ xfstat_isreg(fileno(defer), &stab, path_defer, "newly opened defer file");
+
+ if (stab.st_size > LONG_MAX)
+ die("defer file %s size is far too large", path_defer);
+
+ if (!stab.st_size)
+ return;
+
+ long orgsize= stab.st_size;
+ long truncto= stab.st_size;
+ for (;;) {
+ if (!truncto) break; /* was only (if anything) one half-truncated record */
+ if (fseek(defer, truncto-1, SEEK_SET) < 0)
+ sysdie("seek in defer file %s while truncating partial", path_defer);
+
+ int r= getc(defer);
+ if (r==EOF) {
+ if (ferror(defer))
+ sysdie("failed read from defer file %s", path_defer);
+ else
+ die("defer file %s shrank while we were checking it!", path_defer);
+ }
+ if (r=='\n') break;
+ truncto--;
+ }
+
+ if (stab.st_size != truncto) {
+ warn("truncating half-record at end of defer file %s -"
+ " shrinking by %ld bytes from %ld to %ld",
+ path_defer, orgsize - truncto, orgsize, truncto);
+
+ if (fflush(defer))
+ sysfatal("could not flush defer file %s", path_defer);
+ if (ftruncate(fileno(defer), truncto))
+ sysdie("could not truncate defer file %s", path_defer);
+
+ } else {
+ info("continuing existing defer file %s (%ld bytes)",
+ path_defer, orgsize);
+ }
+ if (fseek(defer, truncto, SEEK_SET))
+ sysdie("could not seek to new end of defer file %s", path_defer);
+}
+
+static void close_defer(void) {
+ if (!defer)
+ return;
+
+ 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= xtime();
+
+ char *backlog= xasprintf("%s_backlog_%lu.%lu", feedfile,
+ (unsigned long)now,
+ (unsigned long)stab.st_ino);
+ if (link(path_defer, backlog))
+ sysfatal("could not install defer file %s as backlog file %s",
+ path_defer, backlog);
+ if (unlink(path_defer))
+ sysdie("could not unlink old defer link %s to backlog file %s",
+ path_defer, backlog);
+
+ free(backlog);
+
+ if (until_backlog_nextscan < 0 ||
+ until_backlog_nextscan > backlog_retry_minperiods + 1)
+ until_backlog_nextscan= backlog_retry_minperiods + 1;
+}
+
+static void poll_backlog_file(void) {
+ if (until_backlog_nextscan < 0) return;
+ if (until_backlog_nextscan-- > 0) return;
+ search_backlog_file();
+}
+
+static void search_backlog_file(void) {
+ /* returns non-0 iff there are any backlog files */
+
+ glob_t gl;
+ int r, i;
+ struct stat stab;
+ const char *oldest_path=0;
+ time_t oldest_mtime, now;
+
+ if (backlog_input_file) return;
+
+ try_again:
+
+ r= glob(globpat_backlog, GLOB_ERR|GLOB_MARK|GLOB_NOSORT, 0, &gl);
+
+ switch (r) {
+ case GLOB_ABORTED:
+ sysdie("failed to expand backlog pattern %s", globpat_backlog);
+ case GLOB_NOSPACE:
+ die("out of memory expanding backlog pattern %s", globpat_backlog);
+ case 0:
+ for (i=0; i<gl.gl_pathc; i++) {
+ const char *path= gl.gl_pathv[i];
+
+ if (strchr(path,'#') || strchr(path,'~')) {
+ debug("backlog file search skipping %s", path);
+ continue;
+ }
+ r= stat(path, &stab);
+ if (r) {
+ syswarn("failed to stat backlog file %s", path);
+ continue;
+ }
+ if (!S_ISREG(stab.st_mode)) {
+ warn("backlog file %s is not a plain file (or link to one)", path);
+ continue;
+ }
+ if (!oldest_path || stab.st_mtime < oldest_mtime) {
+ oldest_path= path;
+ oldest_mtime= stab.st_mtime;
+ }
+ }
+ case GLOB_NOMATCH: /* fall through */
+ break;
+ default:
+ sysdie("glob expansion of backlog pattern %s gave unexpected"
+ " nonzero (error?) return value %d", globpat_backlog, r);
+ }
+
+ globfree(&gl);
+
+ if (!oldest_path) {
+ debug("backlog scan: none");
+
+ if (sms==sm_DROPPED) {
+ notice("feed dropped and our work is complete");
+ xunlink(path_lock, "lockfile for old feed");
+ exit(0);
+ }
+ until_backlog_nextscan= backlog_spontaneous_rescan_periods;
+ return;
+ }
+
+ now= xtime();
+ double age= difftime(now, oldest_mtime);
+ long age_deficiency= (backlog_retry_minperiods * period_seconds) - age;
+
+ if (age_deficiency <= 0) {
+ debug("backlog scan: found age=%f deficiency=%ld oldest=%s",
+ age, age_deficiency, oldest_path);
+
+ backlog_input_file= open_input_file(oldest_path);
+ if (!backlog_input_file) {
+ warn("backlog file %s vanished as we opened it", backlog_input_file);
+ goto try_again;
+ }
+ inputfile_tailing_start(backlog_input_file);
+ until_backlog_nextscan= -1;
+ return;
+ }
+
+ until_backlog_nextscan= age_deficiency / period_seconds;
+
+ if (backlog_spontaneous_rescan_periods >= 0 &&
+ until_backlog_nextscan > backlog_spontaneous_rescan_periods)
+ until_backlog_nextscan= backlog_spontaneous_rescan_periods;
+
+ debug("backlog scan: young age=%f deficiency=%ld nextscan=%d oldest=%s",
+ age, age_deficiency, until_backlog_nextscan, oldest_path);
+ return;
+}
+
+/*========== flushing the feed ==========*/
+
+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");
+ cancel_fd_read_except(fd);
+ close(fd);
+
+ assert(!flushing_input_file);
+
+ if (WIFEXITED(status)) {
+ switch (WEXITSTATUS(status)) {
+
+ case INNDCOMMCHILD_ESTATUS_FAIL:
+ goto failed;
+
+ case INNDCOMMCHILD_ESTATUS_NONESUCH:
+ warn("feed has been dropped by innd, finishing up");
+ flushing_input_file= main_input_file;
+ tailing_queue_readable(flushing_input_file);
+ /* we probably previously returned EAGAIN from our fake read method
+ * when in fact we were at EOF, so signal another readable event
+ * so we actually see the EOF */
+
+ main_input_file= 0;
+
+ if (flushing_input_file) {
+ SMS(DROPPING, 0, "feed dropped by innd, but must finish last flush");
+ } else {
+ close_defer();
+ SMS(DROPPED, 0, "feed dropped by innd");
+ search_backlog_file();
+ }
+ return OOP_CONTINUE;
+
+ case 0:
+ /* as above */
+ flushing_input_file= main_input_file;
+ tailing_queue_readable(flushing_input_file);
+
+ main_input_file= open_input_file(feedfile);
+ if (!main_input_file)
+ die("flush succeeded but feedfile %s does not exist!", feedfile);
+
+ if (flushing_input_file) {
+ SMS(SEPARATED, spontaneous_flush_periods, "recovery flush complete");
+ } else {
+ close_defer();
+ SMS(NORMAL, spontaneous_flush_periods, "flush complete");
+ }
+ return OOP_CONTINUE;
+
+ default:
+ goto unexpected_exitstatus;
+
+ }
+ } else if (WIFSIGNALED(status) && WTERMSIG(status) == SIGALRM) {
+ warn("flush timed out trying to talk to innd");
+ goto failed;
+ } else {
+ unexpected_exitstatus:
+ report_child_status("inndcomm child", status);
+ }
+
+ failed:
+ SMS(FLUSHFAILED, flushfail_retry_periods, "flush failed, will retry");
+}
+
+static void inndcommfail(const char *what) {
+ syswarn("error communicating with innd: %s failed: %s", what, ICCfailure);
+ exit(INNDCOMMCHILD_ESTATUS_FAIL);
+}
+
+void spawn_inndcomm_flush(const char *why) { /* Moved => Flushing */
+ int pipefds[2];
+
+ notice("flushing %s",why);
+
+ assert(sms==sm_NORMAL || sms==sm_FLUSHFAILED);
+ assert(!inndcomm_child);
+
+ if (pipe(pipefds)) sysdie("create pipe for inndcomm child sentinel");
+
+ inndcomm_child= xfork("inndcomm child");
+
+ if (!inndcomm_child) {
+ const char *flushargv[2]= { sitename, 0 };
+ char *reply;
+ int r;
+
+ close(pipefds[0]);
+
+ alarm(inndcomm_flush_timeout);
+ r= ICCopen(); if (r) inndcommfail("connect");
+ r= ICCcommand('f',flushargv,&reply); if (r<0) inndcommfail("transmit");
+ if (!r) exit(0); /* yay! */
+
+ if (!strcmp(reply, "1 No such site")) exit(INNDCOMMCHILD_ESTATUS_NONESUCH);
+ syswarn("innd ctlinnd flush failed: innd said %s", reply);
+ exit(INNDCOMMCHILD_ESTATUS_FAIL);
+ }
+
+ close(pipefds[1]);
+ int sentinel_fd= pipefds[0];
+ on_fd_read_except(sentinel_fd, inndcomm_event);
+
+ SMS(FLUSHING, 0, why);
+}