chiark / gitweb /
83aa147eb25df16f5c2d70ccb282e8551dd34808
[trains.git] / hostside / persist.c
1 /*
2  * persist
3  * persistent state management.
4  */
5
6 /*
7  * We use these files:
8  *      persist.lock              - protocol is as for with-lock-ex
9  *      persist.data[.new,.old]   - mmap, see record.c alloc
10  *      persist.conv[.new,.old]   - copy of our own convutable
11  *
12  *      persist.record     generated and updated automatically
13  *
14  * we mark the data files as executable, and then later insist on that,
15  * because we map them into our own address space and trust them completely
16  *
17  * Update protocol:
18  *
19  *   interpretation of the states      data conv
20  *                                       .o  .o
21  *
22  *      case A                          y ? y ?
23  *      case B                          ? y ? y         but not A
24  *      case C                          y ? ? y         but not A or B
25  *
26  *      (y means file exists, use this version; ? existence irrelevant)
27  *      (update protocol ignores .new files, which are used only for
28  *       atomic creation of actual files)
29  *
30  *                                     data conv
31  *   procedure for updating              .o  .o
32  *
33  *      normal state                    1 0 1 0         case A, 1
34  *      delete old converter            1 0 1 -         case A, 1
35  *      delete data.old                 1 - 1 -         case A, 1
36  *      rename converter -> .old        1 - 1 1         case A, 1
37  *       rename completes               1 - - 1         case C, 1
38  *      rename data -> .old             1 1 - 1         case B, 1
39  *       rename completes               - 1 - 1         case B, 1
40  *      create new data                 2 1 - 1         case B, 1
41  *      create new converter            2 1 2 1         case A, 2
42  *
43  *     (0, 1, 2 are successive versions; - is ENOENT)
44  */
45
46 #include "realtime.h"
47
48 const char *persist_fn= "+persist";
49 char *persist_record_converted;
50
51 static int fd= -1;
52 static void *mapbase;
53 static int datalen;
54
55 /*---------- filename handling ----------*/
56
57 #define FN(dcl,suffix) persist_fn_ephemeral("." #dcl "." #suffix)
58 #define FN1(dcl)       persist_fn_ephemeral("." #dcl)
59
60 #define PFES 50
61 static const char *persist_fn_ephemeral(const char *suffix) {
62   static char *rr[PFES];
63   static int i;
64
65   i++;
66   i %= PFES;
67
68   free(rr[i]);
69   if (asprintf(&rr[i], "%s%s", persist_fn, suffix) <= 0)
70     diee("vasprintf failed for persist_fn");
71   return rr[i];
72 }
73
74 /*---------- utilities ----------*/
75
76 static void unlink_or_enoent(const char *filename) {
77   int r;
78
79   r= unlink(filename);
80   if (r && errno != ENOENT) diee("unlink `%s'", filename);
81 }
82
83 static int fe(const char *fn) {
84   struct stat stab;
85   int r;
86
87   r= stat(fn,&stab);
88   if (r) {
89     if (errno==ENOENT) return 0;
90     else diee("failed stat to check for existence of `%s'", fn);
91   }
92
93   if (!S_ISREG(stab.st_mode))
94     die("checking for existence of `%s' but it is not a plain file", fn);
95
96   return 1;
97 }
98
99 /*---------- finding and interpreting of old persistent data ----------*/
100
101 static int persist_convert(const char *data, const char *conv) {
102   int data_fd, newrecord_fd;
103   pid_t child;
104
105   if (!fe(conv)) return 0;
106
107   data_fd= open(data, O_RDONLY);
108   if (data_fd<0) {
109     if (errno==ENOENT) return 0;
110     else diee("persist data failed to check/open `%s'",data);
111   }
112
113   newrecord_fd= open(persist_record_converted, O_WRONLY|O_CREAT|O_TRUNC, 0666);
114   if (newrecord_fd<0)
115     diee("persist data failed to create new record");
116
117   child= fork();
118   if (child<0) diee("persist conversion: failed to fork");
119
120   if (!child) {
121     if (dup2(data_fd,0)) diee("persist child: failed to dup2 0");
122     if (dup2(newrecord_fd,1)!=1) diee("persist child: failed to dup2 1");
123     execl(conv, conv, PERSIST_CONVERT_OPTION, (char*)0);
124     diee("persist child: failed to exec `%s'", conv);
125   }
126
127   mwaitpid(child, "persist conversion");
128
129   if (close(newrecord_fd)) diee("persist data close new record");
130   close(data_fd);
131
132   return 1;
133 }
134
135 static int try(const char *data, const char *conv) {
136   if (!persist_convert(data,conv)) return 0;
137   ouprintf("info : converted %s using %s\n",data,conv);
138   return 1;
139 }
140
141 void persist_entrails_interpret(void) {
142   /* creates persist_record_converted */
143   if (!persist_fn[0]) return;
144   assert(!persist_record_converted);
145   persist_record_converted= mstrdup(FN1(record));
146
147   if (!simulate &&
148       try(FN1(data),    FN1(conv))) return;
149   if (try(FN(data,old), FN(conv,old))) return;
150   
151   if (simulate &&
152       try(FN1(data),    FN1(conv))) return;
153   
154   if (try(FN1(data),    FN(conv,old))) return;
155
156   free(persist_record_converted);
157   persist_record_converted= 0;
158 }
159
160 /*---------- stupid mmap workaround ----------*/
161
162 static Byte resaddrbuf[1024*1024];
163 static long pagesize;
164 static unsigned long resaddruse;
165
166 #define RESADDR_DIEFMT   "(persist mapping parameters:" \
167                          " %p+0x%lx%%0x%lx->%p+0x%lx)"
168 #define RESADDR_DIEARGS  resaddrbuf,(unsigned long)sizeof(resaddrbuf), \
169                          pagesize, mapbase,resaddruse
170
171 static void resaddrdiee(const char *why) {
172   diee("%s " RESADDR_DIEFMT, why, RESADDR_DIEARGS);
173 }
174
175 void persist_map_veryearly(void) {
176   int r;
177          
178   errno= 0; pagesize= sysconf(_SC_PAGE_SIZE);
179   if (pagesize<=0) diee("could not find pagesize");
180
181   if (pagesize & (pagesize-1)) return;
182   if (pagesize > sizeof(resaddrbuf)/2) return;
183
184   mapbase= (void*)(((unsigned long)resaddrbuf + pagesize - 1) &
185                    ~(pagesize - 1UL));
186   resaddruse= sizeof(resaddrbuf) - pagesize;
187
188   r= mprotect(mapbase,resaddruse,PROT_READ);
189   if (r) resaddrdiee("mprotect reserve buffer");
190 }
191
192 static void mapmem(int fd, int datalen, int prot) {
193   void *rv;
194   int r;
195
196   if (!resaddruse)
197     die("inappropriate combination of sizes " RESADDR_DIEFMT, RESADDR_DIEARGS);
198   
199   if (datalen > resaddruse)
200     die("data length %d too large " RESADDR_DIEFMT, datalen, RESADDR_DIEARGS);
201   
202   r= munmap(mapbase, resaddruse);
203   if (r) resaddrdiee("munmap reserve buffer");
204     
205   rv= mmap(mapbase, datalen, prot, MAP_SHARED|MAP_FIXED, fd, 0);
206   if (rv == MAP_FAILED)
207     resaddrdiee(prot==(PROT_READ|PROT_WRITE) ? "map data rw" :
208                 prot==PROT_READ ? "map data ro" : "map data badly");
209          
210   assert(rv == mapbase);
211 }
212
213 /*---------- installing of our data as the current one ----------*/
214
215 void persist_install(void) {
216   const char *devnull= "/dev/null";
217   FILE *src, *dst;
218   DIR *dir;
219   const char *dirname;
220   char *dirname_buf, *slash;
221   int c, dst_fd;
222
223   if (fd==-1) return;
224   if (!persist_fn[0]) return;
225   
226   src= fopen("/proc/self/exe","rb");  if (!src) diee("open /proc/self/exe");
227
228   unlink_or_enoent(FN(conv,new));
229   dst_fd= open(FN(conv,new), O_WRONLY|O_CREAT|O_TRUNC, 0777);
230   if (dst_fd<0) diee("create persist new conv");
231   dst= fdopen(dst_fd,"wb");  if (!dst) diee("fdopen persist new conv");
232
233   while ((c= getc(src)) != EOF)
234     if (putc(c,dst) == EOF) diee("write persist new conv");
235
236   if (ferror(src) || fclose(src)) diee("read /proc/self/exe");
237   if (ferror(dst) || fflush(dst) || fsync(fileno(dst)) || fclose(dst))
238     diee("finish writing persist new conv");
239
240   if (fsync(fd) || msync(mapbase,datalen,MS_SYNC) || fsync(fd))
241     diee("sync persist new data");
242
243   /* Now we have the .new's, but let's just check ... */
244   persist_record_converted= (char*)devnull;
245   if (!persist_convert(FN(data,new),FN(conv,new)))
246     die("persist conversion claims .new's do not exist ?!");
247   assert(persist_record_converted == devnull);
248   persist_record_converted= 0;
249
250   dirname_buf= mstrdup(persist_fn);
251   slash= strrchr(dirname_buf, '/');
252   if (slash) do { *slash=0; } while (slash>dirname_buf && *--slash=='/');
253   dirname= slash ? dirname_buf : ".";
254   dir= opendir(dirname);
255   if (!dir) diee("opendir persist directory `%s'", dirname);
256
257   if (fe(FN1(data)) && fe(FN1(conv))) {        /* 1 ? 1 ?   A               */
258     unlink_or_enoent(FN(conv,old));            /* 1 ? 1 -   A               */
259     unlink_or_enoent(FN(data,old));            /* 1 - 1 -   A               */
260     mrename(FN1(conv),FN(conv,old));           /* 1 - 1 1   A               */
261                        /* rename completes        1 - - 1   C               */
262   }
263   /* we've converted A to C, so only B and C remain: */
264   if (fe(FN(data,old)) && fe(FN(conv,old))) {  /* ? 1 ? 1   B               */
265     unlink_or_enoent(FN1(data));               /* - 1 ? 1   B unlike C      */
266   }
267   /* B has been made not to look like C, so now only
268    * genuine C and unmistakeable B remains: */
269   if (fe(FN1(data)) && fe(FN(conv,old))) {     /* 1 ? ? 1   C               */
270     mrename(FN1(data),FN(data,old));           /* 1 1 ? 1   B               */
271                        /* rename completes        - 1 ? 1   B unlike A or C */
272   }
273   /* Just B now, ie we have */                 /* - 1 ? 1   B               */
274                                                            
275   unlink_or_enoent(FN1(conv));                 /* - 1 - 1   B               */
276                                                            
277   mrename(FN(data,new),FN1(data));             /* 2 1 - 1   B               */
278   mrename(FN(conv,new),FN1(conv));             /* 2 1 2 1   A               */
279
280   if (fsync(dirfd(dir))) diee("sync persist directory `%s'", dirname);
281
282   free(dirname_buf);
283   fd= -1; /* do not install it again */
284 }
285
286 /*---------- creation (and mmapping) of new persistent data ----------*/
287
288 void *record_allocate(int datalen_spec) {
289   /* claims lock, allocates space for new data file */
290   int lockfd, r, i;
291   FILE *data;
292   struct flock fl;
293   struct stat buf_stat, buf_fstat;
294
295   assert(fd==-1);
296   datalen= datalen_spec;
297   
298   for (;;) {
299     lockfd= open(FN1(lock), O_RDWR|O_CREAT|O_TRUNC, 0660);
300     if (lockfd<0) diee("open new persist lock file");
301
302     memset(&fl,0,sizeof(fl));
303     fl.l_type= F_WRLCK;
304     fl.l_whence= SEEK_SET;
305     r= fcntl(lockfd, F_SETLK, &fl);
306     if (r<0) diee("claim persistent lock file");
307
308     r= stat(FN1(lock), &buf_stat);  if (r) diee("stat persistent lock");
309     r= fstat(lockfd, &buf_fstat);  if (r) diee("fstat persistent lock");
310     if (!(buf_stat.st_dev != buf_fstat.st_dev ||
311           buf_stat.st_ino != buf_fstat.st_ino))
312       break;
313
314     close(lockfd);
315   }
316
317   
318   unlink_or_enoent(FN(data,new));
319
320   fd= open(FN(data,new), O_RDWR|O_CREAT|O_TRUNC, 0777);
321   if (fd<0) diee("open new persist data file");
322   data= fdopen(fd, "w+");  if (!data) diee("fdopen new persist data file");
323
324   for (i=0; i<datalen; i++) putc(0x55,data);
325   if (ferror(data) || fflush(data)) diee("clear new persist data file");
326
327   fd= fileno(data);
328
329   mapmem(fd, datalen, PROT_READ|PROT_WRITE);
330
331   return mapbase;
332 }
333
334 /*---------- reading and mapping of existing persistent data ----------*/
335
336 static void phi_load(void *object, size_t sz, int *offset) {
337   size_t r;
338
339   while (*offset % sz) { getchar(); (*offset)++; }
340
341   r= fread(object,1,sz,stdin);
342   if (feof(stdin))  die("truncated persistent data header");
343   if (ferror(stdin)) diee("read persistent data header");
344   assert (r==sz);
345
346   *offset += sz;
347 }
348
349 static void phi_check(const void *expected, size_t sz,
350                       int *offset, const char *what) {
351   Byte actual[sz];
352
353   phi_load(actual, sz, offset);
354   if (memcmp(actual, expected, sz))
355     die("header magic check failed, in `%s'", what);
356 }
357   
358 static void persist_mapread(void) {
359   struct stat stab;
360   int offset=0, r;
361
362   r= fstat(0, &stab);  if (r) diee("could not fstat persist data file");
363   if (!(stab.st_mode & 0111)) die("persist data file is not executable");
364
365 #define PHI_CHECK(x) phi_check(&(x), sizeof(x), &offset, STR(x));
366 #define PHI_LOAD(x) phi_load(&(x), sizeof(x), &offset);
367   DO_PERSIST_HEADER_ITEMS(PHI_CHECK, PHI_LOAD, PHI_LOAD)
368
369   mapmem(0, datalen, PROT_READ);
370 }
371
372 #define SANE_SEGMENT(seg)
373
374 void persist_entrails_run_converter(void) {
375   SEG_IV;
376   MovPosComb report;
377
378   persist_mapread();
379
380   FOR_SEG {
381     if (seg->i != segi || !segi->pname ||
382         !seg->owner || !seg->owner->pname)
383       continue;
384     printf("seg %s has %s%s%s\n", segi->pname,
385            (seg->tr_backwards ^ seg->owner->backwards) ? "-" : "",
386            seg->owner->pname,
387            seg->seg_inverted ? " inverted" : "");
388
389     if (segi->n_poscombs>1 &&
390         !seg->moving &&
391         (report= seg->movposcomb) >=0 &&
392         report < segi->n_poscombs)
393       printf("seg %s at %s\n", segi->pname, segi->poscombs[report].pname);
394   }
395   if (ferror(stdout) || fflush(stdout))
396     diee("entrails converter: stdout write error");
397
398   printf("end\n");
399   
400   if (ferror(stdout) || fclose(stdout))
401     diee("entrails converter: stdout write/close error");
402   exit(0);
403 }