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