chiark / gitweb /
can fit 139 lines on an atp -B page
[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   assert(!persist_record_converted);
144   persist_record_converted= mstrdup(FN1(record));
145
146   if (!simulate &&
147       try(FN1(data),    FN1(conv))) return;
148   if (try(FN(data,old), FN(conv,old))) return;
149   
150   if (simulate &&
151       try(FN1(data),    FN1(conv))) return;
152   
153   if (try(FN1(data),    FN(conv,old))) return;
154
155   free(persist_record_converted);
156   persist_record_converted =0;
157 }
158
159 /*---------- stupid mmap workaround ----------*/
160
161 static Byte resaddrbuf[1024*1024];
162 static long pagesize;
163 static unsigned long resaddruse;
164
165 #define RESADDR_DIEFMT   "(persist mapping parameters:" \
166                          " %p+0x%lx%%0x%lx->%p+0x%lx)"
167 #define RESADDR_DIEARGS  resaddrbuf,(unsigned long)sizeof(resaddrbuf), \
168                          pagesize, mapbase,resaddruse
169
170 static void resaddrdiee(const char *why) {
171   diee("%s " RESADDR_DIEFMT, why, RESADDR_DIEARGS);
172 }
173
174 void persist_map_veryearly(void) {
175   int r;
176          
177   errno= 0; pagesize= sysconf(_SC_PAGE_SIZE);
178   if (pagesize<=0) diee("could not find pagesize");
179
180   if (pagesize & (pagesize-1)) return;
181   if (pagesize > sizeof(resaddrbuf)/2) return;
182
183   mapbase= (void*)(((unsigned long)resaddrbuf + pagesize - 1) &
184                    ~(pagesize - 1UL));
185   resaddruse= sizeof(resaddrbuf) - pagesize;
186
187   r= mprotect(mapbase,resaddruse,PROT_READ);
188   if (r) resaddrdiee("mprotect reserve buffer");
189 }
190
191 static void mapmem(int fd, int datalen, int prot) {
192   void *rv;
193   int r;
194
195   if (!resaddruse)
196     die("inappropriate combination of sizes " RESADDR_DIEFMT, RESADDR_DIEARGS);
197   
198   if (datalen > resaddruse)
199     die("data length %d too large " RESADDR_DIEFMT, datalen, RESADDR_DIEARGS);
200   
201   r= munmap(mapbase, resaddruse);
202   if (r) resaddrdiee("munmap reserve buffer");
203     
204   rv= mmap(mapbase, datalen, prot, MAP_SHARED|MAP_FIXED, fd, 0);
205   if (rv == MAP_FAILED)
206     resaddrdiee(prot==(PROT_READ|PROT_WRITE) ? "map data rw" :
207                 prot==PROT_READ ? "map data ro" : "map data badly");
208          
209   assert(rv == mapbase);
210 }
211
212 /*---------- installing of our data as the current one ----------*/
213
214 void persist_install(void) {
215   const char *devnull= "/dev/null";
216   FILE *src, *dst;
217   DIR *dir;
218   const char *dirname;
219   char *dirname_buf, *slash;
220   int c, dst_fd;
221
222   if (fd==-1) return;
223   
224   src= fopen("/proc/self/exe","rb");  if (!src) diee("open /proc/self/exe");
225
226   unlink_or_enoent(FN(conv,new));
227   dst_fd= open(FN(conv,new), O_WRONLY|O_CREAT|O_TRUNC, 0777);
228   if (dst_fd<0) diee("create persist new conv");
229   dst= fdopen(dst_fd,"wb");  if (!dst) diee("fdopen persist new conv");
230
231   while ((c= getc(src)) != EOF)
232     if (putc(c,dst) == EOF) diee("write persist new conv");
233
234   if (ferror(src) || fclose(src)) diee("read /proc/self/exe");
235   if (ferror(dst) || fflush(dst) || fsync(fileno(dst)) || fclose(dst))
236     diee("finish writing persist new conv");
237
238   if (fsync(fd) || msync(mapbase,datalen,MS_SYNC) || fsync(fd))
239     diee("sync persist new data");
240
241   /* Now we have the .new's, but let's just check ... */
242   persist_record_converted= (char*)devnull;
243   if (!persist_convert(FN(data,new),FN(conv,new)))
244     die("persist conversion claims .new's do not exist ?!");
245   assert(persist_record_converted == devnull);
246   persist_record_converted= 0;
247
248   dirname_buf= mstrdup(persist_fn);
249   slash= strrchr(dirname_buf, '/');
250   if (slash) do { *slash=0; } while (slash>dirname_buf && *--slash=='/');
251   dirname= slash ? dirname_buf : ".";
252   dir= opendir(dirname);
253   if (!dir) diee("opendir persist directory `%s'", dirname);
254
255   if (fe(FN1(data)) && fe(FN1(conv))) {        /* 1 ? 1 ?   A               */
256     unlink_or_enoent(FN(conv,old));            /* 1 ? 1 -   A               */
257     unlink_or_enoent(FN(data,old));            /* 1 - 1 -   A               */
258     mrename(FN1(conv),FN(conv,old));           /* 1 - 1 1   A               */
259                        /* rename completes        1 - - 1   C               */
260   }
261   /* we've converted A to C, so only B and C remain: */
262   if (fe(FN(data,old)) && fe(FN(conv,old))) {  /* ? 1 ? 1   B               */
263     unlink_or_enoent(FN1(data));               /* - 1 ? 1   B unlike C      */
264   }
265   /* B has been made not to look like C, so now only
266    * genuine C and unmistakeable B remains: */
267   if (fe(FN1(data)) && fe(FN(conv,old))) {     /* 1 ? ? 1   C               */
268     mrename(FN1(data),FN(data,old));           /* 1 1 ? 1   B               */
269                        /* rename completes        - 1 ? 1   B unlike A or C */
270   }
271   /* Just B now, ie we have */                 /* - 1 ? 1   B               */
272                                                            
273   unlink_or_enoent(FN1(conv));                 /* - 1 - 1   B               */
274                                                            
275   mrename(FN(data,new),FN1(data));             /* 2 1 - 1   B               */
276   mrename(FN(conv,new),FN1(conv));             /* 2 1 2 1   A               */
277
278   if (fsync(dirfd(dir))) diee("sync persist directory `%s'", dirname);
279
280   free(dirname_buf);
281   fd= -1; /* do not install it again */
282 }
283
284 /*---------- creation (and mmapping) of new persistent data ----------*/
285
286 void *record_allocate(int datalen_spec) {
287   /* claims lock, allocates space for new data file */
288   int lockfd, r, i;
289   FILE *data;
290   struct flock fl;
291   struct stat buf_stat, buf_fstat;
292
293   assert(fd==-1);
294   datalen= datalen_spec;
295   
296   for (;;) {
297     lockfd= open(FN1(lock), O_RDWR|O_CREAT|O_TRUNC, 0660);
298     if (lockfd<0) diee("open new persist lock file");
299
300     memset(&fl,0,sizeof(fl));
301     fl.l_type= F_WRLCK;
302     fl.l_whence= SEEK_SET;
303     r= fcntl(lockfd, F_SETLK, &fl);
304     if (r<0) diee("claim persistent lock file");
305
306     r= stat(FN1(lock), &buf_stat);  if (r) diee("stat persistent lock");
307     r= fstat(lockfd, &buf_fstat);  if (r) diee("fstat persistent lock");
308     if (!(buf_stat.st_dev != buf_fstat.st_dev ||
309           buf_stat.st_ino != buf_fstat.st_ino))
310       break;
311
312     close(lockfd);
313   }
314
315   
316   unlink_or_enoent(FN(data,new));
317
318   fd= open(FN(data,new), O_RDWR|O_CREAT|O_TRUNC, 0777);
319   if (fd<0) diee("open new persist data file");
320   data= fdopen(fd, "w+");  if (!data) diee("fdopen new persist data file");
321
322   for (i=0; i<datalen; i++) putc(0x55,data);
323   if (ferror(data) || fflush(data)) diee("clear new persist data file");
324
325   fd= fileno(data);
326
327   mapmem(fd, datalen, PROT_READ|PROT_WRITE);
328
329   return mapbase;
330 }
331
332 /*---------- reading and mapping of existing persistent data ----------*/
333
334 static void phi_load(void *object, size_t sz, int *offset) {
335   size_t r;
336
337   while (*offset % sz) { getchar(); (*offset)++; }
338
339   r= fread(object,1,sz,stdin);
340   if (feof(stdin))  die("truncated persistent data header");
341   if (ferror(stdin)) diee("read persistent data header");
342   assert (r==sz);
343
344   *offset += sz;
345 }
346
347 static void phi_check(const void *expected, size_t sz, int *offset) {
348   Byte actual[sz];
349
350   phi_load(actual, sz, offset);
351   if (memcmp(actual, expected, sz)) die("header magic check failed");
352 }
353   
354 static void persist_mapread(void) {
355   struct stat stab;
356   int offset=0, r;
357
358   r= fstat(0, &stab);  if (r) diee("could not fstat persist data file");
359   if (!(stab.st_mode & 0111)) die("persist data file is not executable");
360
361 #define PHI_CHECK(x) phi_check(&(x), sizeof(x), &offset);
362 #define PHI_LOAD(x) phi_load(&(x), sizeof(x), &offset);
363   DO_PERSIST_HEADER_ITEMS(PHI_CHECK, PHI_LOAD, PHI_LOAD)
364
365   mapmem(0, datalen, PROT_READ);
366 }
367
368 #define SANE_SEGMENT(seg)
369
370 void persist_entrails_run_converter(void) {
371   SEG_IV;
372   MovPosComb report;
373
374   persist_mapread();
375
376   FOR_SEG {
377     if (seg->i != segi || !segi->pname ||
378         !seg->owner || !seg->owner->pname)
379       continue;
380     printf("seg %s has %s%s%s\n", segi->pname,
381            (seg->tr_backwards ^ seg->owner->backwards) ? "-" : "",
382            seg->owner->pname,
383            seg->seg_inverted ? " inverted" : "");
384
385     if (segi->n_poscombs>1 &&
386         (report= movpos_poscomb_actual(seg)) >=0 &&
387         report < segi->n_poscombs)
388       printf("seg %s at %s\n", segi->pname, segi->poscombs[report].pname);
389   }
390   if (ferror(stdout) || fflush(stdout))
391     diee("entrails converter: stdout write error");
392
393   printf("end\n");
394   
395   if (ferror(stdout) || fclose(stdout))
396     diee("entrails converter: stdout write/close error");
397   exit(0);
398 }