--- /dev/null
+/*
+ * persist
+ * persistent state management.
+ */
+
+/*
+ * We use these files:
+ * persist.lock - protocol is as for with-lock-ex
+ * persist.data[.new,.old] - mmap, see record.c alloc
+ * persist.conv[.new,.old] - copy of our own convutable
+ *
+ * persist.record generated and updated automatically
+ *
+ * we mark the data files as executable, and then later insist on that,
+ * because we map them into our own address space and trust them completely
+ *
+ * Update protocol:
+ *
+ * interpretation of the states data conv
+ * .o .o
+ *
+ * case A y ? y ?
+ * case B ? y ? y but not A
+ * case C y ? ? y but not A or B
+ *
+ * (y means file exists, use this version; ? existence irrelevant)
+ * (update protocol ignores .new files, which are used only for
+ * atomic creation of actual files)
+ *
+ * data conv
+ * procedure for updating .o .o
+ *
+ * normal state 1 0 1 0 case A, 1
+ * delete old converter 1 0 1 - case A, 1
+ * delete data.old 1 - 1 - case A, 1
+ * rename converter -> .old 1 - 1 1 case A, 1
+ * rename completes 1 - - 1 case C, 1
+ * rename data -> .old 1 1 - 1 case B, 1
+ * rename completes - 1 - 1 case B, 1
+ * create new data 2 1 - 1 case B, 1
+ * create new converter 2 1 2 1 case A, 2
+ *
+ * (0, 1, 2 are successive versions; - is ENOENT)
+ */
+
+#include "realtime.h"
+
+const char *persist_fn= "persist";
+const char *persist_record_converted;
+
+static int fd= -1;
+static void *mapbase;
+static int datalen;
+
+/*---------- filename handling ----------*/
+
+#define FN(dcl,suffix) persist_fn_ephemeral("." #dcl "." #suffix)
+#define FN1(dcl) persist_fn_ephemeral("." #dcl)
+
+#define PFES 20
+static const char *persist_fn_ephemeral(const char *suffix) {
+ static char *rr[PFES];
+ static int i;
+
+ i++;
+ i %= PFES;
+
+ free(rr[i]);
+ if (asprintf(&rr[i], "%s%s", persist_fn, suffix) <= 0)
+ diee("vasprintf failed for persist_fn");
+ return rr[i];
+}
+
+/*---------- utilities ----------*/
+
+static void unlink_or_enoent(const char *filename) {
+ int r;
+
+ r= unlink(filename);
+ if (r && errno != ENOENT) diee("unlink `%s'", filename);
+}
+
+static int fe(const char *fn) {
+ struct stat stab;
+ int r;
+
+ r= stat(fn,&stab);
+ if (r) {
+ if (errno==ENOENT) return 0;
+ else diee("failed stat to check for existence of `%s'", fn);
+ }
+
+ if (!S_ISREG(stab.st_mode))
+ die("checking for existence of `%s' but it is not a plain file", fn);
+
+ return 1;
+}
+
+/*---------- finding and interpreting of old persistent data ----------*/
+
+static int persist_convert(const char *data, const char *conv) {
+ int data_fd, newrecord_fd, status;
+ pid_t child, rpid;
+
+ if (!fe(conv)) return 0;
+
+ data_fd= open(data, O_RDONLY);
+ if (data_fd<0) {
+ if (errno==ENOENT) return 0;
+ else diee("persist data failed to check/open `%s'",data);
+ }
+
+ newrecord_fd= open(persist_record_converted, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+ if (newrecord_fd<0)
+ diee("persist data failed to create new record");
+
+ child= fork();
+ if (child<0) diee("persist conversion: failed to fork");
+
+ if (!child) {
+ if (dup2(data_fd,0)) diee("persist child: failed to dup2 0");
+ if (dup2(newrecord_fd,1)) diee("persist child: failed to dup2 1");
+ execl(conv, conv, PERSIST_CONVERT_OPTION, (char*)0);
+ diee("persist child: failed to exec `%s'", conv);
+ }
+
+ rpid= waitpid(child,&status,0); if (rpid!=child) diee("waitpid");
+ if (WIFEXITED(status)) {
+ int st= WEXITSTATUS(status);
+ if (st) die("persist conversion exited with nonzero status %d",st);
+ } else if (WIFSIGNALED(status)) {
+ die("persist conversion died due to %s%s",
+ strsignal(WTERMSIG(status)),
+ WCOREDUMP(status) ? " (core dumped)" : "");
+ } else {
+ die("persist conversion failed with unexpected wait status 0x%x",status);
+ }
+
+ if (close(newrecord_fd)) diee("persist data close new record");
+ close(data_fd);
+
+ return 1;
+}
+
+static int try(const char *data, const char *conv) {
+ if (!persist_convert(data,conv)) return 0;
+ logmsg(0,0,0, "converted %s using %s",data,conv);
+ return 1;
+}
+
+void persist_entrails_interpret(void) {
+ /* creates persist_record_converted */
+ persist_record_converted= mstrdup(FN1(record));
+
+ try(FN1(data), FN1(conv)) ||
+ try(FN(data,old), FN(conv,old)) ||
+ try(FN1(data), FN(conv,old)) ||
+ (persist_record_converted= 0);
+}
+
+/*---------- installing of our data as the current one ----------*/
+
+void persist_install(void) {
+ FILE *src, *dst;
+ DIR *dir;
+ const char *dirname;
+ char *dirname_buf, *slash;
+ int c;
+
+ if (fd==-1) return;
+
+ src= fopen("/proc/self/exe","rb"); if (!src) diee("open /proc/self/exe");
+
+ unlink_or_enoent(FN(conv,new));
+ dst= fopen(FN(conv,new),"wb"); if (!dst) diee("create persist new conv");
+
+ while ((c= getc(src)) != EOF)
+ if (putc(c,dst) == EOF) diee("write persist new conv");
+
+ if (ferror(src) || fclose(src)) diee("read /proc/self/exe");
+ if (ferror(dst) || fflush(dst) || fsync(fileno(dst)) || fclose(dst))
+ diee("finish writing persist new conv");
+
+ if (fsync(fd) || msync(mapbase,datalen,MS_SYNC) || fsync(fd))
+ diee("sync persist new data");
+
+ /* Now we have the .new's, but let's just check ... */
+ if (!persist_convert(FN(data,new),FN(conv,new)))
+ die("persist conversion claims .new's do not exist ?!");
+
+ dirname_buf= mstrdup(persist_fn);
+ slash= strrchr(dirname_buf, '/');
+ if (slash) do { *slash=0; } while (slash>dirname_buf && *--slash=='/');
+ dirname= slash ? dirname_buf : ".";
+ dir= opendir(dirname);
+ if (!dir) diee("opendir persist directory `%s'", dirname);
+
+ if (fe(FN1(data)) && fe(FN1(conv))) { /* 1 ? 1 ? A */
+ unlink_or_enoent(FN(conv,old)); /* 1 ? 1 - A */
+ unlink_or_enoent(FN(data,old)); /* 1 - 1 - A */
+ mrename(FN1(conv),FN(conv,old)); /* 1 - 1 1 A */
+ /* rename completes 1 - - 1 C */
+ }
+ /* we've converted A to C, so only B and C remain: */
+ if (fe(FN(data,old)) && fe(FN(conv,old))) { /* ? 1 ? 1 B */
+ unlink_or_enoent(FN1(data)); /* - 1 ? 1 B unlike C */
+ }
+ /* B has been made not to look like C, so now only
+ * genuine C and unmistakeable B remains: */
+ if (fe(FN1(data)) && fe(FN(conv,old))) { /* 1 ? ? 1 C */
+ mrename(FN1(data),FN(data,old)); /* 1 1 ? 1 B */
+ /* rename completes - 1 ? 1 B unlike A or C */
+ }
+ /* Just B now, ie we have */ /* - 1 ? 1 B */
+
+ unlink_or_enoent(FN1(conv)); /* - 1 - 1 B */
+
+ mrename(FN(data,new),FN1(data)); /* 2 1 - 1 B */
+ mrename(FN(conv,new),FN1(conv)); /* 2 1 2 1 A */
+
+ if (fsync(dirfd(dir))) diee("sync persist directory `%s'", dirname);
+
+ free(dirname_buf);
+ fd= -1; /* do not install it again */
+}
+
+/*---------- creation (and mmapping) of new persistent data ----------*/
+
+void *record_allocate(int datalen_spec) {
+ /* claims lock, allocates space for new data file */
+ int lockfd, r, i;
+ FILE *data;
+ struct flock fl;
+ struct stat buf_stat, buf_fstat;
+
+ assert(fd==-1);
+ datalen= datalen_spec;
+
+ for (;;) {
+ lockfd= open(FN1(lock), O_RDWR|O_CREAT|O_TRUNC, 0660);
+ if (lockfd<0) diee("open new persist lock file");
+
+ memset(&fl,0,sizeof(fl));
+ fl.l_type= F_WRLCK;
+ fl.l_whence= SEEK_SET;
+ r= fcntl(lockfd, F_SETLK, &fl);
+ if (r<0) diee("claim persistent lock file");
+
+ r= stat(FN1(lock), &buf_stat); if (r) diee("stat persistent lock");
+ r= fstat(lockfd, &buf_fstat); if (r) diee("fstat persistent lock");
+ if (!(buf_stat.st_dev != buf_fstat.st_dev ||
+ buf_stat.st_ino != buf_fstat.st_ino))
+ break;
+
+ close(lockfd);
+ }
+
+
+ unlink_or_enoent(FN(data,new));
+
+ fd= open(FN(data,new), O_RDWR|O_CREAT|O_TRUNC, 0777);
+ if (fd<0) diee("open new persist data file");
+ data= fdopen(fd, "w+"); if (!data) diee("fdopen new persist data file");
+
+ for (i=0; i<datalen; i++) putc(0x55,data);
+ if (ferror(data) || fflush(data)) diee("clear new persist data file");
+
+ fd= fileno(data);
+
+ mapbase= mmap(0, datalen, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+ if (mapbase == MAP_FAILED) diee("could not write mmap persist data file");
+
+ return mapbase;
+}
+
+/*---------- reading and mapping of existing persistent data ----------*/
+
+static void phi_load(void *object, size_t sz, int *offset) {
+ size_t r;
+
+ while (*offset % sz) { getchar(); (*offset)++; }
+
+ r= fread(object,1,sz,stdin);
+ if (feof(stdin)) die("truncated persistent data header");
+ if (ferror(stdin)) diee("read persistent data header");
+ assert (r==sz);
+
+ *offset += sz;
+}
+
+static void phi_check(const void *expected, size_t sz, int *offset) {
+ Byte actual[sz];
+
+ phi_load(actual, sz, offset);
+ if (memcmp(actual, expected, sz)) die("header magic check failed");
+}
+
+static void *persist_mapread(void) {
+ struct stat stab;
+ int offset=0, r;
+ void *rv;
+
+ r= fstat(0, &stab); if (r) diee("could not fstat persist data file");
+ if (!(stab.st_mode & 0111)) die("persist data file is not executable");
+
+#define PHI_CHECK(x) phi_check(&(x), sizeof(x), &offset);
+#define PHI_LOAD(x) phi_load(&(x), sizeof(x), &offset);
+ DO_PERSIST_HEADER_ITEMS(PHI_CHECK, PHI_LOAD, PHI_LOAD)
+
+ rv= mmap(0, datalen, PROT_READ|PROT_WRITE, MAP_PRIVATE, 0,0);
+ if (rv == MAP_FAILED) diee("could not read mmap persist data file");
+ return rv;
+}
+
+void persist_entrails_run_converter(void) {
+ Train *tra; int tran;
+ Segment *seg; const SegmentInfo *segi; int segn;
+ void *realbase;
+ ptrdiff_t adjust;
+
+ realbase= persist_mapread();
+ adjust= (Byte*)realbase - (Byte*)mapbase;
+
+#define CP(lvalue) \
+ ((lvalue)= (lvalue) ? (void*)((Byte*)(lvalue) + adjust) : 0);
+
+#define PHI_IGNORE(x) /*nothing*/
+ DO_PERSIST_HEADER_ITEMS(PHI_IGNORE, PHI_IGNORE, CP)
+
+ for (tran=0, tra=trains; tran<n_trains; tran++, tra++) {
+ CP(tra->pname);
+ CP(tra->foredetect);
+
+ if (!tra->pname || !tra->foredetect ||
+ !tra->foredetect->i || !tra->foredetect->i->pname)
+ continue;
+ printf("train %s at %s%s:%d+-%d\n",
+ tra->pname, tra->backwards ? "-" : "",
+ tra->foredetect->i->pname, tra->maxinto, tra->uncertainty);
+ }
+ for (segn=0, seg=segments, segi=info_segments; segn<info_nsegments;
+ segn++, seg++, segi++) {
+ CP(seg->owner);
+
+ if (seg->i != segi || !segi->pname ||
+ !seg->owner || !seg->owner->pname)
+ continue;
+ printf("seg %s has %s%s\n",
+ segi->pname, seg->tr_backwards ? "-" : "", seg->owner->pname);
+ }
+ if (ferror(stdout) || fflush(stdout))
+ diee("entrails converter: stdout write error");
+
+ printf("end\n");
+
+ if (ferror(stdout) || fclose(stdout))
+ diee("entrails converter: stdout write/close error");
+ exit(0);
+}
* train <trainpn> at [-]<foredetectpn>:<maxinto>+-<uncertainty>
* train <trainpn> is <addr> <head>+<detectable>+<tail>
* train <trainpn> step <step>=<speed> <upwait>/<downwait>
- * seg <segpn> has [-]<ownerpn>|$
+ * seg <segpn> has [-]<ownerpn>
* seg <segpn> at <movposcomb>
*
* speed is in um/s, upwait and downwait are in us
#include <sys/mman.h>
-#include "record.h"
+#include "record-i.h"
#include "record-l.h"
/*---------- input and error handling ----------*/
}
}
-/*---------- entrypoint from main, and its subroutines ----------*/
+/*---------- persistent data file layout ----------*/
+
+static void *alloc_some(void *mapbase, int *offset, size_t sz, int count) {
+ void *r;
+
+ while (*offset % sz) {
+ if (mapbase) ((Byte*)mapbase)[*offset]= 0xaa;
+ (*offset)++;
+ }
+ r= mapbase ? (Byte*)mapbase + *offset : 0;
+ *offset += sz*count;
+ return r;
+}
static void alloc(void) {
Train *tra; Segment *seg; const SegmentInfo *segi;
+ void *mapbase=0;
char **trap;
- int i;
-
- segments= mmap(0, sizeof(*segments)*NUM_SEGMENTS + sizeof(*trains)*n_trains,
- PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1,0);
- if (!segments) diee("mmap for shared region");
+ int i, phase, offset, datalen=0;
+
+#define ALLOC(array,count) \
+ ((array)= alloc_some(mapbase,&offset,sizeof(*(array)),(count)))
+
+ for (phase=0; ; phase++) {
+ /* phase 0: count up how much space will be needed
+ * phase 1: fill in most of the details, leaving header blank
+ * phase 2: fill in the header, and then exit before doing rest
+ */
+ offset= 0;
+
+ if (phase==1)
+ mapbase= record_allocate(datalen);
+
+#define PHI_SAVE(x) { \
+ typeof(x) *p; \
+ ALLOC(p,1); \
+ if (phase==2) \
+ *p= (x); \
+ }
+#define PHI_DUMP(x) { \
+ Byte *p; \
+ ALLOC(p,sizeof(x)); \
+ if (phase==2) \
+ memcpy(p, &(x), sizeof(x)); \
+ }
- for (i=0, seg=segments, segi=info_segments;
- i<NUM_SEGMENTS;
- i++, seg++, segi++) {
- seg->movposcomb= -1;
- seg->i= segi;
- }
+ DO_PERSIST_HEADER_ITEMS(PHI_DUMP, PHI_SAVE, PHI_SAVE)
+
+ if (phase==2)
+ break;
+
+ ALLOC(trains, n_trains);
+ for (i=0, tra=trains, trap=train_pnames;
+ i<n_trains;
+ i++, tra++, trap++) {
+ char *pname;
+ ALLOC(pname, strlen(*trap)+1);
+ if (phase) {
+ strcpy(pname, *trap);
+ free(*trap);
+ tra->pname= *trap= pname;
+ tra->addr= -1;
+ tra->head= -1;
+ }
+ }
- trains= (void*)(segments + NUM_SEGMENTS);
- for (i=0, tra=trains, trap=train_pnames;
- i<n_trains;
- i++, tra++, trap++) {
- tra->pname= *trap;
- tra->addr= -1;
- tra->head= -1;
+ ALLOC(segments, info_nsegments);
+ if (phase)
+ for (i=0, seg=segments, segi=info_segments;
+ i<NUM_SEGMENTS;
+ i++, seg++, segi++) {
+ seg->movposcomb= -1;
+ seg->i= segi;
+ }
+
+ if (phase==0)
+ datalen= offset;
}
curvebuf= mmalloc(sizeof(*curvebuf) * curvebufsz);
}
+
+/*---------- entrypoint from main, and its subroutines ----------*/
-static void parse_pass(const char **argv) {
+static void parse_file(const char *why) {
FILE *f;
-
- while ((filename= *argv++)) {
- f= fopen(filename,"r");
- if (!f) diee("config: cannot open input file: %s", filename);
- record_yyrestart(f);
- record_yyparse();
- }
+
+ f= fopen(filename,"r");
+ if (!f) diee("config: cannot open %s: %s", why, filename);
+ record_yyrestart(f);
+ record_yyparse();
+}
+
+static void parse_pass(const char **argv) {
+ while ((filename= *argv++))
+ parse_file("commandline-specified record file");
+
+ filename= persist_record_converted;
+ if (filename)
+ parse_file("converted persistent data file");
}
void records_parse(const char **argv) {