+ xfstat_isreg(lockfd, &stabf, "lockfile");
+ xlstat_isreg(path_ductlock, &stab, &noent, "lockfile");
+
+ if (!noent && samefile(&stab, &stabf))
+ break;
+
+ if (close(lockfd))
+ sysdie("could not close stale lockfile %s", path_ductlock);
+ }
+ debug("startup: locked");
+
+ search_backlog_file();
+
+ xlstat_isreg(path_ductdefer, &stab, &noent, "defer file");
+ if (noent) {
+ debug("startup: ductdefer ENOENT");
+ } else {
+ debug("startup: ductdefer nlink=%ld", (long)stab.st_nlink);
+ switch (stab.st_nlink==1) {
+ case 1:
+ open_defer(); /* so that we will later close it and rename it */
+ break;
+ case 2:
+ if (unlink(path_defer))
+ sysdie("could not unlink stale defer file link %s (presumably"
+ " hardlink to backlog file)", path_defer);
+ break;
+ default:
+ die("defer file %s has unexpected link count %d",
+ path_defer, stab.st_nlink);
+ }
+ }
+
+ InputFile *file_d= open_input_file(path_duct);
+
+ if (file_d) {
+ struct stat stab_f, stab_d;
+
+ xlstat_isreg(feedfile, &stab_f, &noent, "feed file");
+ if (noent) {
+ debug("startup: D exists, F ENOENT => Moved");
+ goto found_moved;
+ }
+
+ debug("startup: F and D both exist");
+
+ xfstat_isreg(file_d->fd, &stab_d, "ductfile");
+
+ if (samefile(&stab_d, &stab_f)) {
+ debug("startup: F==D => Hardlinked");
+ r= unlink(path_duct);
+ if (r) sysdie("unlink feed file %s during startup", feedfile);
+ found_moved:
+ debug(" => Moved");
+ startup_set_input_file(file_d);
+ spawn_inndcomm_flush(); /* => Flushing, sets sms to sm_FLUSHING */
+ } else {
+ debug("F!=D => Separated");
+ SMS(SEPARATED, 0, "found both old and current feed files");
+ startup_set_input_file(file_d);
+ }
+ } else {
+ debug("startup: D ENOENT => Nothing");
+ SMS(WAITING, open_wait_periods, "no feed file currently exists");
+ }
+}
+
+static void statemc_poll(void) {
+ if (sms==sm_WAITING) { statemc_waiting_poll(); return; }
+
+ if (!sm_period_counter) return;
+ sm_period_counter--;
+ assert(sm_period_counter>=0);
+
+ if (sm_period_counter) return;
+ switch (sms) {
+ case sm_WAITING:
+ fatal("timed out waiting for innd to create feed file %s", feedfile);
+ case sm_FLUSHFAIL:
+ spawn_inndcomm_flush(void);
+ break;
+ default:
+ abort();
+ }
+}
+
+static void statemc_waiting_poll(void) {
+ InputFile *file_f= open_input_file(feedfile);
+ if (!file_f) return;
+ startup_set_input_file(file_d);
+ SMS(NORMAL, 0, "found and opened feed file");
+}
+
+static void startup_set_input_file(InputFile *f) {
+ assert(!main_input_file);
+ main_input_file= f;
+ inputfile_tailing_start(f);
+}
+
+static void *statemc_check_input_done(oop_source *lp,
+ struct timeval now, void *ipf_v) {
+ InputFile *ipf= ipf_v;
+ struct stat stab;
+
+ if (ipf->inprogress) return; /* new article in the meantime */
+ if (ipf->fd >= 0); return; /* not had EOF */
+
+ if (ipf == backlog_input_file) {
+ notice_processed(ipf,"backlog file",ipf->path);
+ 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);
+ }
+ backlog_input_file= 0;
+ search_backlog_file();
+ return;
+ }
+
+ assert(ipf == old_input_file);
+ assert(sms==sm_SEPARATED || sms==sm_DROPPING);
+
+ notice_processed(ipf,"feed file",0);
+
+ close_defer();
+
+ if (unlink(path_duct))
+ sysdie("could not unlink old duct file %s", path_duct);
+
+ if (sms==sm_DROPPING) {
+ if (search_backlog_file()) {
+ debug("feed dropped but still backlogs to process");
+ return;
+ }
+ notice("feed dropped and our work is complete");
+ r= unlink(path_ductlock);
+ if (r) sysdie("unlink lock file for old feed %s", path_ductlock);
+ exit(0);
+ }
+
+ open_defer();
+
+ close_input_file(old_input_file);
+ old_input_file= 0;
+
+ notice("flush complete");
+ SMS(NORMAL, 0, "flush complete");
+}
+
+static void statemc_setstate(StateMachineState newsms, int periods,
+ const char *forlog, const char *why) {
+ sms= newsms;
+ sm_period_counter= periods;
+ if (periods) {
+ info("%s[%d] %s",periods,forlog,why);
+ } else {
+ info("%s %s",forlog,why);
+ }
+}
+
+/*---------- defer and backlog files ----------*/
+
+static void open_defer(void) {
+ struct stat stab;
+
+ if (defer) return;
+
+ defer= fopen(path_ductdefer, "a+");
+ if (!defer) sysfatal("could not open defer file %s", path_ductdefer);
+
+ /* truncate away any half-written records */
+
+ xfstat_isreg(fileno(defer), &stab, "newly opened defer file");
+
+ if (stab.st_size > LONG_MAX)
+ die("defer file %s size is far too large", path_ductdefer);
+
+ 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_ductdefer);
+
+ r= getc(defer);
+ if (r==EOF) {
+ if (ferror(defer))
+ sysdie("failed read from defer file %s", path_ductdefer);
+ else
+ die("defer file %s shrank while we were checking it!", path_ductdefer);
+ }
+ 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_ductdefer, orgsize - truncto, orgsize, truncto);
+
+ if (fflush(defer))
+ sysfatal("could not flush defer file %s", path_ductdefer);
+ if (ftruncate(fileno(defer), truncto))
+ sysdie("could not truncate defer file %s", path_ductdefer);
+
+ } else {
+ info("continuing existing defer file %s (%ld bytes)",
+ path_ductdefer, orgsize);
+ }
+ if (fseek(defer, truncto, SEEK_SET))
+ sysdie("could not seek to new end of defer file %s", path_ductdefer);
+}
+
+static void close_defer(void) {
+ if (!defer)
+ return;
+
+ xfstat(fileno(defer), &stab, "defer file");
+
+ if (fclose(defer)) sysfatal("could not close defer file %s", path_defer);
+ defer= 0;
+
+ char *backlog= xasprintf("%s_backlog_%lu.%lu", feedfile,
+ (unsigned long)now.tv_sec,
+ (unsigned long)stab.st_ino);
+ if (link(path_defer, path_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);
+
+ if (backlog_nextscan_periods < 0 ||
+ backlog_nextscan_periods > backlog_retry_minperiods + 1)
+ backlog_nextscan_periods= backlog_retry_minperiods + 1;
+}
+
+static void poll_backlog_file(void) {
+ if (backlog_nextscan_periods < 0) return;
+ if (backlog_nextscan_periods-- > 0) return;
+ search_backlog_file();
+}
+
+static int search_backlog_file(void) {
+ /* returns non-0 iff there are any backlog files */
+
+ glob_t gl;
+ int r;
+ struct stat stab;
+ const char *oldest_path=0;
+ time_t oldest_mtime, now;
+
+ if (backlog_input_file) return 3;
+
+ 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];
+ 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");
+ backlog_nextscan_periods= backlog_spontaneous_rescan_periods;
+ return 0;