Coverage Report

Created: 2026-10-28 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hobbes/include/hobbes/fregion.H
Line
Count
Source
1
/*
2
 * fregion : structured data file I/O, interoperable with hobbes structured file programs
3
 *
4
 *    to write a file:
5
 *      writer f("/path/to/file.ext");
6
 *      auto& s = f.series<T>("yourTableName");
7
 *      s(T(...)); // write a value of type T
8
 *
9
 *    to read a file (receiving values in the order they were written):
10
 *      reader f("/path/to/file.ext");
11
 *      auto& s = f.series<T>("yourTableName");
12
 *      T t;
13
 *      while (s.next(&t)) {
14
 *        // do something with t
15
 *      }
16
 *
17
 *    to write a file series recording sequencing of other series (creating the "log" or "transactions" variant of hog):
18
 *      writer f("/path/to/file.ext");
19
 *      auto& s = f.series<T>("yourTableName");
20
 *      auto& r = f.series<U>("yourOtherTableName");
21
 *      f.recordOrdering("log", s, r); // creates a series named "log" as a variant over references to s or r
22
 *      // write into s and r as needed
23
 *
24
 *    to read a file series sequencing other series (matching just the series of interest, not necessary to match all series nor to match in order):
25
 *      reader f("/path/to/file.ext");
26
 *      auto& log = f.ordering("log");
27
 *      log.match<T>("yourTableName",      [](const T& t) { ... }); // process T values in write order
28
 *      log.match<U>("yourOtherTableName", [](const U& u) { ... }); // process U values in write order
29
 *      while (log.next());                                         // read all of them across the file
30
 *
31
 *    to signal data availability to all out of process readers waiting on data:
32
 *      writer f("/path/to/file.ext");
33
 *      // write to f as needed
34
 *      f.signal();
35
 */
36
37
#ifndef HOBBES_HFREGION_H_INCLUDED
38
#define HOBBES_HFREGION_H_INCLUDED
39
40
#include <string>
41
#include <vector>
42
#include <map>
43
#include <set>
44
#include <unordered_map>
45
#include <functional>
46
#include <stack>
47
#include <limits>
48
#include <stdexcept>
49
#include <sstream>
50
#include <array>
51
#include <type_traits>
52
53
#include <cassert>
54
#include <cstring>
55
#include <fcntl.h>
56
#include <sys/mman.h>
57
#include <sys/stat.h>
58
#include <sys/types.h>
59
#include <unistd.h>
60
61
// filesystem waits depend on platform details
62
#include <sys/time.h>
63
64
#if defined(__APPLE__) && defined(__MACH__)
65
#include <sys/event.h>
66
#else
67
#include <sys/inotify.h>
68
#include <sys/epoll.h>
69
#endif
70
71
// types with static reflection info (for reflective structs, variants, etc)
72
#include "reflect.H"
73
74
namespace hobbes { namespace fregion {
75
76
// the file header
77
struct filehead {
78
  uint32_t magic;    // trivial sanity check for file-type
79
  uint16_t pagesize; // how large does this file assume that one memory page is?
80
  uint16_t version;  // a file format version number (incremented when incompatibilities are introduced)
81
};
82
0
#define HFREGION_CURRENT_FILE_FORMAT_VERSION static_cast<uint16_t>(2)
83
1.43k
#define HFREGION_FILE_PREFIX_BYTES           static_cast<uint32_t>(0x10a1db0d)
84
1.32k
#define HFREGION_MAX_PAGE_SIZE               static_cast<size_t>(1<<14)
85
1.33k
#define HFREGION_MIN_PAGE_SIZE               static_cast<size_t>(256)
86
87
// a single system page record
88
//   this will be stored as two bytes, the upper 2 bits used for storing a page type and the lower 14 used for free size in that page
89
struct pagetype {
90
  enum code {
91
    null        = 0, // a "null" page (used to mark the end of system page data)
92
    toc         = 1, // a "toc" page (a page of data about other pages)
93
    environment = 2, // an "environment" page (describing variable bindings)
94
    data        = 3  // a "data" page (raw data for bound variables)
95
  };
96
97
98.0k
  static uint8_t encode(code    x) { return static_cast<uint8_t>(x); }
98
109k
  static code    decode(uint8_t x) { return static_cast<code>   (x); }
99
};
100
101
struct pagedata {
102
  uint16_t ptfd;
103
104
98.0k
  pagedata(pagetype::code ty = pagetype::toc, uint16_t availSz = 0) : ptfd(encode(ty, availSz)) { }
105
98.0k
  static uint16_t encode(pagetype::code ty, uint16_t availSz) { return ((pagetype::encode(ty) & 3) << 14) | (availSz & 0x3FFF); }
106
109k
  pagetype::code type() const { return pagetype::decode(this->ptfd >> 14); }
107
87.2k
  uint16_t availBytes() const { return this->ptfd & 0x3FFF; }
108
0
  void type(pagetype::code x) { this->ptfd = encode(x, availBytes()); }
109
0
  void availBytes(uint16_t x) { this->ptfd = encode(type(), x); }
110
};
111
using pagetable = std::vector<pagedata>;
112
113
// the index of a page of data in the underlying file
114
using file_pageindex_t = uint64_t;
115
116
// convenience for raising errors out of errno
117
807
inline void raiseSysError [[noreturn]] (const std::string& msg, const std::string& fname) {
118
807
  std::ostringstream ss;
119
807
  ss << fname << ": " << msg << " (" << strerror(errno) << ")" << std::flush;
120
807
  throw std::runtime_error(ss.str());
121
807
}
122
123
using bytes = std::vector<uint8_t>;
124
125
// a stored name/value binding
126
struct binding {
127
90.7k
  binding(const bytes& type = bytes(), size_t offset = 0, size_t boffset = 0) : type(type), offset(offset), boffset(boffset) { }
128
129
  bytes  type;    // what type of data is stored here?
130
  size_t offset;  // where is it stored?
131
  size_t boffset; // where is this binding data stored in the file?
132
};
133
134
using bindingset = std::map<std::string, binding>;
135
136
// a mem-mapped file region
137
struct fregion {
138
  file_pageindex_t base_page; // the base file page for this mapping
139
  size_t           pages;     // the number of mapped pages
140
  char*            base;      // the base address of this mapped region
141
  size_t           used;      // the number of bytes actually used out of this mapped region (when decremented to 0 we can safely unmap)
142
};
143
144
// account for mappings by absolute page
145
using fmappings = std::map<file_pageindex_t, fregion>;
146
147
// remember which mapped sections correspond to which pages
148
struct falloc {
149
  file_pageindex_t page; // the page that this was allocated out of
150
  size_t           size; // the number of bytes allocated to this thing
151
};
152
153
using fallocs = std::map<char *, falloc>;
154
155
// fast page searches (to avoid linear searches of the entire page table)
156
using pageseq = std::vector<file_pageindex_t>;
157
using ptyorder = std::map<pagetype::code, pageseq>;
158
159
// an image file, opened either for reading or writing
160
struct imagefile {
161
1.43k
  imagefile() : fd(-1) { }
162
163
  // stable open file properties
164
  std::string path;
165
  bool        readonly;
166
  int         fd;
167
  uint16_t    page_size;
168
  uint16_t    version;
169
170
  // mutable incremental read/write state
171
  size_t file_size;
172
  size_t head_toc_pos; // the head position for writing TOC entries
173
  size_t empty_array;  // empty arrays of all types can be aliased
174
175
  // the fewest number of pages to mmap at one time
176
  // (this prevents us from making too many tiny mmap regions and hitting the OS map limit)
177
  size_t mmapPageMultiple;
178
179
  // toc page -> absolute page
180
  pageseq tocpages;
181
182
  // system, environment, data
183
  pagetable  pages;
184
  ptyorder   freespace;
185
  bindingset bindings;
186
  fmappings  mappings;
187
  fallocs    allocs;
188
};
189
190
// how many bytes are remaining in the page for a given index?
191
95.5k
inline uint16_t restInPage(const imagefile* f, size_t idx) { return f->page_size - (idx % f->page_size); }
192
193
// how many pages are covered by a sequence of bytes?
194
269
inline size_t pageCount(const imagefile* f, size_t sz) { return (sz / f->page_size) + ((sz % f->page_size) > 0 ? 1 : 0); }
195
196
// basic file I/O primitives
197
1.43k
inline void closeFile(imagefile* f) {
198
1.43k
  if (f->fd > -1) {
199
1.43k
    close(f->fd);
200
1.43k
  }
201
1.43k
  delete f;
202
1.43k
}
203
204
1.43k
inline bool loadFileSize(imagefile* f) {
205
1.43k
  struct stat sb;
206
1.43k
  if (::fstat(f->fd, &sb) < 0) {
207
0
    return false;
208
0
  }
209
1.43k
  f->file_size = sb.st_size;
210
1.43k
  return true;
211
1.43k
}
212
213
2.95k
inline void seekAbs(const imagefile* f, size_t pos) {
214
2.95k
  if (::lseek(f->fd, pos, SEEK_SET) == -1) {
215
0
    raiseSysError("Can't seek to offset=" + hobbes::string::from(pos) + " in file with size=" + hobbes::string::from(f->file_size), f->path);
216
0
  }
217
2.95k
}
218
219
175k
inline off_t filePosition(const imagefile* f) {
220
175k
  off_t r = ::lseek(f->fd, 0, SEEK_CUR);
221
175k
  if (r == static_cast<off_t>(-1)) {
222
0
    raiseSysError("Can't query position in file", f->path);
223
0
  }
224
175k
  return r;
225
175k
}
226
227
0
inline void allocPages(imagefile* f, size_t pages) {
228
0
  off_t dsz = pages * f->page_size;
229
0
  int r = ::posix_fallocate(f->fd, f->file_size, dsz);
230
0
  if (r != 0) {
231
0
    if (r == ENOSPC) {
232
0
      raiseSysError("Can't resize file, no space available", f->path);
233
0
    } else {
234
0
      raiseSysError("Can't resize file", f->path);
235
0
    }
236
0
  }
237
0
  f->file_size += dsz;
238
0
}
239
240
0
inline void allocPage(imagefile* f) {
241
0
  allocPages(f, 1);
242
0
}
243
244
// trivial read and write to files, assuming type T is POD
245
0
inline void fdwrite(imagefile* f, const char* x, size_t len) {
246
0
  size_t i = 0;
247
0
  while (i < len) {
248
0
    ssize_t di = write(f->fd, x + i, len - i);
249
250
0
    if (di < 0) {
251
0
      if (errno != EINTR) {
252
0
        raiseSysError("Failed to write " + hobbes::string::from(len) + " bytes to file", f->path);
253
0
      }
254
0
    } else if (di == 0) {
255
0
      raiseSysError("Empty write error", f->path);
256
0
    } else {
257
0
      i += di;
258
0
    }
259
0
  }
260
0
}
261
template <typename T>
262
0
  inline void write(imagefile* f, const T& x) {
263
0
    fdwrite(f, reinterpret_cast<const char*>(&x), sizeof(T));
264
0
  }
Unexecuted instantiation: void hobbes::fregion::write<hobbes::fregion::filehead>(hobbes::fregion::imagefile*, hobbes::fregion::filehead const&)
Unexecuted instantiation: void hobbes::fregion::write<hobbes::fregion::pagedata>(hobbes::fregion::imagefile*, hobbes::fregion::pagedata const&)
Unexecuted instantiation: void hobbes::fregion::write<unsigned long>(hobbes::fregion::imagefile*, unsigned long const&)
Unexecuted instantiation: void hobbes::fregion::write<unsigned char>(hobbes::fregion::imagefile*, unsigned char const&)
265
template <typename TIter>
266
0
  inline void writes(imagefile* f, TIter begin, TIter end) {
267
0
    if (begin == end) {
268
0
      return;
269
0
    }
270
0
    fdwrite(f, reinterpret_cast<const char*>(&(*begin)), (end - begin) * sizeof(*begin));
271
0
  }
Unexecuted instantiation: void hobbes::fregion::writes<__gnu_cxx::__normal_iterator<hobbes::fregion::pagedata const*, std::vector<hobbes::fregion::pagedata, std::allocator<hobbes::fregion::pagedata> > > >(hobbes::fregion::imagefile*, __gnu_cxx::__normal_iterator<hobbes::fregion::pagedata const*, std::vector<hobbes::fregion::pagedata, std::allocator<hobbes::fregion::pagedata> > >, __gnu_cxx::__normal_iterator<hobbes::fregion::pagedata const*, std::vector<hobbes::fregion::pagedata, std::allocator<hobbes::fregion::pagedata> > >)
Unexecuted instantiation: void hobbes::fregion::writes<char const*>(hobbes::fregion::imagefile*, char const*, char const*)
Unexecuted instantiation: void hobbes::fregion::writes<__gnu_cxx::__normal_iterator<unsigned char const*, std::vector<unsigned char, std::allocator<unsigned char> > > >(hobbes::fregion::imagefile*, __gnu_cxx::__normal_iterator<unsigned char const*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char const*, std::vector<unsigned char, std::allocator<unsigned char> > >)
272
0
inline void write(imagefile* f, const std::string& x) {
273
0
  write(f, x.size());
274
0
  writes(f, x.data(), x.data() + x.size());
275
0
}
276
0
inline void write(imagefile* f, const std::vector<unsigned char>& xs) {
277
0
  write(f, xs.size());
278
0
  writes(f, xs.begin(), xs.end());
279
0
}
280
281
367k
inline void fdread(const imagefile* f, char* x, size_t len) {
282
367k
  size_t i = 0;
283
735k
  while (i < len) {
284
367k
    ssize_t di = read(f->fd, x + i, len - i);
285
286
367k
    if (di < 0) {
287
0
      if (errno != EINTR) {
288
0
        raiseSysError("Failed to read " + hobbes::string::from(len) + " bytes from file", f->path);
289
0
      }
290
367k
    } else if (di == 0) {
291
807
      raiseSysError("Empty read error", f->path);
292
367k
    } else {
293
367k
      i += di;
294
367k
    }
295
367k
  }
296
367k
}
297
template <typename T>
298
363k
  inline void read(const imagefile* f, T* x) {
299
363k
    fdread(f, reinterpret_cast<char*>(x), sizeof(T));
300
363k
  }
void hobbes::fregion::read<hobbes::fregion::filehead>(hobbes::fregion::imagefile const*, hobbes::fregion::filehead*)
Line
Count
Source
298
1.43k
  inline void read(const imagefile* f, T* x) {
299
1.43k
    fdread(f, reinterpret_cast<char*>(x), sizeof(T));
300
1.43k
  }
void hobbes::fregion::read<hobbes::fregion::pagedata>(hobbes::fregion::imagefile const*, hobbes::fregion::pagedata*)
Line
Count
Source
298
98.0k
  inline void read(const imagefile* f, T* x) {
299
98.0k
    fdread(f, reinterpret_cast<char*>(x), sizeof(T));
300
98.0k
  }
void hobbes::fregion::read<unsigned long>(hobbes::fregion::imagefile const*, unsigned long*)
Line
Count
Source
298
263k
  inline void read(const imagefile* f, T* x) {
299
263k
    fdread(f, reinterpret_cast<char*>(x), sizeof(T));
300
263k
  }
301
template <typename T>
302
4.78k
  inline void reads(const imagefile* f, size_t sz, T* x) {
303
4.78k
    fdread(f, reinterpret_cast<char*>(x), sz * sizeof(T));
304
4.78k
  }
void hobbes::fregion::reads<char>(hobbes::fregion::imagefile const*, unsigned long, char*)
Line
Count
Source
302
3.75k
  inline void reads(const imagefile* f, size_t sz, T* x) {
303
3.75k
    fdread(f, reinterpret_cast<char*>(x), sz * sizeof(T));
304
3.75k
  }
void hobbes::fregion::reads<unsigned char>(hobbes::fregion::imagefile const*, unsigned long, unsigned char*)
Line
Count
Source
302
1.02k
  inline void reads(const imagefile* f, size_t sz, T* x) {
303
1.02k
    fdread(f, reinterpret_cast<char*>(x), sz * sizeof(T));
304
1.02k
  }
305
// A length read out of the file image cannot describe more data than the file
306
// itself holds. Without this check a corrupt or crafted image drives an
307
// enormous allocation before the read that would fail, so validate the count
308
// against the file size the same way other malformed-image cases are handled.
309
174k
inline void ensureLengthInFile(const imagefile* f, size_t n, size_t esz) {
310
174k
  if (n == 0) {
311
169k
    return;
312
169k
  }
313
5.03k
  if (esz != 0 && n > (std::numeric_limits<size_t>::max() / esz)) {
314
0
    throw std::runtime_error(
315
0
      "Not a valid structured data file: length (" + hobbes::string::from(n) + ") overflows: " + f->path);
316
0
  }
317
5.03k
  const size_t need = n * esz;
318
319
  // Bound by the bytes actually left from the current offset rather than the
320
  // whole file: a length near the end can be smaller than the file and still
321
  // describe far more data than remains.
322
5.03k
  size_t avail = f->file_size;
323
5.03k
  if (f->fd >= 0) {
324
5.03k
    const off_t cur = lseek(f->fd, 0, SEEK_CUR);
325
5.03k
    if (cur >= 0 && static_cast<size_t>(cur) <= f->file_size) {
326
5.03k
      avail = f->file_size - static_cast<size_t>(cur);
327
5.03k
    }
328
5.03k
  }
329
330
5.03k
  if (need > avail) {
331
255
    throw std::runtime_error(
332
255
      "Not a valid structured data file: length (" + hobbes::string::from(need) +
333
255
      " bytes) exceeds the " + hobbes::string::from(avail) + " bytes remaining: " + f->path);
334
255
  }
335
5.03k
}
336
337
87.6k
inline void read(imagefile* f, std::string* x) {
338
87.6k
  size_t sz = 0;
339
87.6k
  read(f, &sz);
340
341
87.6k
  ensureLengthInFile(f, sz, sizeof(char));
342
87.6k
  x->resize(sz);
343
87.6k
  if (sz > 0) {
344
3.75k
    reads(f, sz, &((*x)[0]));
345
3.75k
  }
346
87.6k
}
347
87.3k
inline void read(imagefile* f, std::vector<unsigned char>* xs) {
348
87.3k
  size_t sz = 0;
349
87.3k
  read(f, &sz);
350
351
87.3k
  ensureLengthInFile(f, sz, sizeof(unsigned char));
352
87.3k
  xs->resize(sz);
353
87.3k
  if (sz > 0) {
354
1.02k
    reads(f, sz, &((*xs)[0]));
355
1.02k
  }
356
87.3k
}
357
358
// the absolute position of an indexed page
359
1.51k
inline size_t pageOffset(const imagefile* f, file_pageindex_t page) { return f->page_size * page; }
360
361
// the page index implied by an absolute position
362
0
inline file_pageindex_t pageIndex(const imagefile* f, uint64_t fpos) { return fpos / f->page_size; }
363
364
// A page index derived from a file position is only meaningful if the page
365
// table actually describes that page. Reading a record can leave the cursor
366
// anywhere when the image is malformed, so the index has to be checked before
367
// it is used to look up page metadata.
368
87.3k
inline void ensurePageInTable(const imagefile* f, file_pageindex_t page) {
369
  // widen the table size to the index type rather than narrowing the index:
370
  // file_pageindex_t is 64 bits, so where size_t is narrower the cast would
371
  // truncate and a large index could wrap into range and pass this check
372
87.3k
  if (page >= static_cast<file_pageindex_t>(f->pages.size())) {
373
86
    throw std::runtime_error(
374
86
      "Not a valid structured data file: page index (" + hobbes::string::from(page) +
375
86
      ") is outside the page table of " + hobbes::string::from(f->pages.size()) +
376
86
      " page(s): " + f->path);
377
86
  }
378
87.3k
}
379
380
// an absolute file position from a page and relative position
381
0
inline size_t position(const imagefile* f, file_pageindex_t page, uint16_t offset) { return pageOffset(f, page) + static_cast<uint64_t>(offset); }
382
383
// the file page for a given TOC page
384
0
inline file_pageindex_t tocPageToFilePage(const imagefile* f, uint64_t tpage) {
385
0
  assert(tpage < f->tocpages.size());
386
0
  return f->tocpages[tpage];
387
0
}
388
389
// the absolute position for some TOC data
390
0
inline uint64_t tocPosToFilePos(const imagefile* f, uint64_t spos) {
391
0
  uint64_t page   = spos / f->page_size;
392
0
  uint64_t offset = spos % f->page_size;
393
0
394
0
  return (tocPageToFilePage(f, page) * f->page_size) + offset;
395
0
}
396
397
// the absolute file position of a page's entry in the page table
398
0
inline size_t pageTOCPosition(const imagefile* f, file_pageindex_t page) {
399
  // this is how many page entries fit in the first TOC page (which includes the file header)
400
0
  const uint64_t firstTPCount = (f->page_size - sizeof(filehead) - sizeof(file_pageindex_t)) / sizeof(pagedata);
401
402
  // is the given page in the first TOC page?
403
0
  if (page < firstTPCount) {
404
    // if so, its TOC entry offset is just straight past the header
405
0
    return sizeof(filehead) + (page * sizeof(pagedata));
406
0
  } else {
407
    // ok, let's forget about the TOC entries from the first page
408
0
    page -= firstTPCount;
409
410
    // this is how many page entries fit in every subsequent TOC page
411
0
    const uint64_t restTPCount = (f->page_size - sizeof(file_pageindex_t)) / sizeof(pagedata);
412
413
    // from this we can find the TOC page that we belong on and the offset within that page
414
0
    const uint64_t tpage  = (page / restTPCount) + 1; // +1 because we've covered the first page already
415
0
    const uint64_t offset = (page % restTPCount) * sizeof(pagedata);
416
417
    // and that's all we need for the file position of this TOC entry
418
0
    return (tocPageToFilePage(f, tpage) * f->page_size) + offset;
419
0
  }
420
0
}
421
422
// rearrange our free list to accomodate this page resize
423
0
inline void updatePageSizeIndex(imagefile* f, file_pageindex_t page) {
424
0
  const pagedata& pd = f->pages[page];
425
0
  pageseq& pord = f->freespace[pd.type()];
426
427
0
  for (size_t k = 0; k < pord.size(); ++k) {
428
0
    if (pord[k] == page) {
429
0
      for (size_t i = k; i < pord.size() - 1; ++i) {
430
0
        if (f->pages[pord[i]].availBytes() < f->pages[pord[i+1]].availBytes()) {
431
0
          std::swap(pord[i], pord[i+1]);
432
0
        } else {
433
0
          break;
434
0
        }
435
0
      }
436
0
      break;
437
0
    }
438
0
  }
439
0
}
440
441
// insert this page (assumed new) into our size-index
442
//  (if it's too small to bother with, don't bother remembering it)
443
0
inline void insertPageSizeIndex(imagefile* f, file_pageindex_t page) {
444
0
  static const size_t minPageSize   = 30;
445
0
  static const size_t maxPageMemory = 200;
446
447
0
  const pagedata& pd = f->pages[page];
448
0
  if (pd.availBytes() < minPageSize) return;
449
450
  // figure out where to put this page
451
0
  pageseq& pord = f->freespace[pd.type()];
452
0
  size_t k = pord.size();
453
454
0
  for (size_t i = 0; i < pord.size(); ++i) {
455
0
    if (pd.availBytes() > f->pages[i].availBytes()) {
456
0
      k = i;
457
0
      break;
458
0
    }
459
0
  }
460
461
0
  pord.insert(pord.begin() + k, page);
462
0
  if (pord.size() > maxPageMemory) { pord.resize(maxPageMemory); }
463
0
}
464
465
// align a value to a boundary
466
template <typename T>
467
0
  inline T align(T x, T m) {
468
0
    if (m == 0 || (x % m) == 0) {
469
0
      return x;
470
0
    } else {
471
0
      return (1 + (x / m)) * m;
472
0
    }
473
0
  }
474
475
// try to find a page with as much free space as requested
476
0
inline bool findPageWithSpace(imagefile* f, pagetype::code pt, size_t datalen, size_t alignment, file_pageindex_t* idx) {
477
0
  const pageseq& pord = f->freespace[pt];
478
0
  if (pord.empty()) {
479
0
    return false;
480
0
  }
481
482
0
  const auto& pd  = f->pages[pord[0]];
483
484
0
  if (align<size_t>(f->page_size - pd.availBytes(), alignment) + datalen > f->page_size) {
485
0
    return false;
486
0
  } else {
487
0
    *idx = pord[0];
488
0
    return true;
489
0
  }
490
0
}
491
492
// update the TOC entry for a given page
493
0
inline void updateTOCData(imagefile* f, file_pageindex_t page, const pagedata& pd) {
494
  // pages never suddenly get free space
495
0
  pagedata& opd = f->pages[page];
496
0
  assert(pd.availBytes() <= opd.availBytes());
497
498
  // update the page table data in memory and on disk
499
0
  opd = pd;
500
0
  seekAbs(f, pageTOCPosition(f, page));
501
0
  write(f, pd);
502
503
  // re-evaluate where this page belongs in the ordering of pages with free space
504
0
  updatePageSizeIndex(f, page);
505
0
}
506
507
// append a sequence of TOC entries (representing allocated pages)
508
0
inline void appendTOCData(imagefile* f, const pagetable& newpages) {
509
  // add these new pages to the TOC
510
0
  for (const auto& newpage : newpages) {
511
0
    f->pages.push_back(newpage);
512
513
    // maintain an index of pages by size
514
0
    insertPageSizeIndex(f, f->pages.size() - 1);
515
516
    // keep track of where system pages are
517
0
    if (newpage.type() == pagetype::toc) {
518
0
      f->tocpages.push_back(f->pages.size() - 1);
519
0
    }
520
0
  }
521
522
  // we may allocate new TOC entry pages as we go about writing these entries
523
0
  pagetable newtocpages;
524
525
  // now let's write the new pages to the TOC, allocating new pages for the TOC as necessary
526
0
  size_t idx = 0;
527
0
  while (idx < newpages.size()) {
528
    // the write position within this page
529
0
    uint16_t relpos = f->head_toc_pos % f->page_size;
530
531
    // the number of bytes available to write in this page
532
    //  (account for the last bit of the page used to link to the next system page)
533
0
    size_t bytes_avail = (f->page_size - relpos) - sizeof(file_pageindex_t);
534
535
    // the number of pagedata slots available to write in this page
536
0
    size_t slots_avail = bytes_avail / sizeof(pagedata);
537
538
    // the number of pagedata slots that we have left to write
539
0
    size_t slots_left = newpages.size() - idx;
540
541
    // the number of pagedata slots that we will fill on this cycle
542
0
    size_t slots_write = std::min(slots_avail, slots_left);
543
544
    // move to this page offset, and write all of the pagedata entries that we can
545
0
    seekAbs(f, f->head_toc_pos);
546
0
    writes(f, newpages.begin() + idx, newpages.begin() + idx + slots_write);
547
548
    // advance our read heads as far as we've just written
549
0
    idx += slots_write;
550
0
    f->head_toc_pos += slots_write * sizeof(pagedata);
551
552
    // have we written up to the link section of the page?
553
    // if so, we need to make a new system page and link to it
554
0
    file_pageindex_t nextpage = -1;
555
556
0
    if (restInPage(f, f->head_toc_pos) == sizeof(file_pageindex_t)) {
557
      // make a new TOC page and move the TOC write head to it
558
0
      f->head_toc_pos = f->file_size;
559
0
      newtocpages.push_back(pagedata(pagetype::toc, 0));
560
0
      allocPage(f);
561
562
      // link this finished TOC page to the new TOC page
563
0
      nextpage = f->pages.size() + newtocpages.size() - 1;
564
0
      write(f, nextpage);
565
0
    }
566
0
  }
567
568
  // if we added TOC pages while writing the input TOC entries, then TOC entries for those TOC pages need to be added too
569
0
  if (!newtocpages.empty()) {
570
0
    appendTOCData(f, newtocpages);
571
0
  }
572
0
}
573
574
// find a location within pages of a given type where we can put a value with this length/alignment
575
// (if no such space can be found, allocate new page(s) as necessary)
576
0
inline size_t findSpace(imagefile* f, pagetype::code pt, size_t datalen, size_t alignment) {
577
0
  assert(datalen > 0 && alignment > 0);
578
579
  // can we find an existing page with the space that we need?
580
0
  file_pageindex_t fpage = -1;
581
0
  if (findPageWithSpace(f, pt, datalen, alignment, &fpage)) {
582
0
    pagedata& pd = f->pages[fpage];
583
584
0
    auto offset = align<size_t>(f->page_size - pd.availBytes(), alignment);
585
586
0
    updateTOCData(f, fpage, pagedata(pt, f->page_size - (offset+datalen)));
587
0
    return position(f, fpage, offset);
588
0
  }
589
590
  // if we got here, then we need to allocate one or more contiguous pages for this data
591
  // all allocated pages will have 0 free size, except the last one which will leave the remainder
592
0
  size_t    pages = pageCount(f, datalen);
593
0
  size_t    result = f->file_size;
594
0
  pagetable tocdata;
595
596
  // every intermediate page in this allocation will be fully exhausted
597
0
  for (size_t p = 1; p < pages; ++p) {
598
0
    tocdata.push_back(pagedata(pt, 0));
599
0
  }
600
601
  // the last page of the allocation is only fully exhausted if an exact multiple of the page size has been allocated
602
  // otherwise claim only the remainder as allocated
603
0
  uint64_t lpallocd = datalen % f->page_size;
604
605
0
  if (lpallocd == 0) {
606
0
    tocdata.push_back(pagedata(pt, 0));
607
0
  } else {
608
0
    tocdata.push_back(pagedata(pt, f->page_size - lpallocd));
609
0
  }
610
0
  allocPages(f, pages);
611
612
  // keep track of these pages that we've added
613
0
  appendTOCData(f, tocdata);
614
615
0
  return result;
616
0
}
617
618
// add a variable binding (name, type, size, and file location)
619
0
inline void addBinding(imagefile* f, const std::string& vname, const bytes& type, size_t offset) {
620
  // determine how much space we'll need to store it
621
0
  size_t bsz = sizeof(size_t)                // the stored data offset
622
0
             + sizeof(size_t) + vname.size() // the variable name
623
0
             + sizeof(size_t) + type.size()  // the variable's type
624
0
             ;
625
626
  // find the best place to put this data
627
0
  size_t boffset = findSpace(f, pagetype::environment, bsz, 1);
628
629
  // now just write the data there
630
0
  seekAbs(f, boffset);
631
0
  write(f, offset);
632
0
  write(f, vname);
633
0
  write(f, type);
634
635
  // oh and we'll want to keep track of it too
636
0
  f->bindings[vname] = binding(type, offset, boffset);
637
0
}
638
639
// mmap a region out of this file
640
0
inline fregion& createFileRegionMap(imagefile* f, file_pageindex_t page, size_t pages) {
641
  // leave no gaps in page mappings
642
0
  if (!f->mappings.empty()) {
643
0
    fregion& mr = f->mappings.rbegin()->second;
644
645
0
    size_t pend = mr.base_page + mr.pages;
646
0
    if (pend < page) {
647
0
      pages += (page - pend);
648
0
      page   = pend;
649
0
    }
650
0
  }
651
652
  // adjust our map page count to match the set increment
653
0
  pages = align<size_t>(pages, f->mmapPageMultiple);
654
655
  // map the specified file region into memory
656
0
  char* d = reinterpret_cast<char*>(mmap(nullptr, pages * f->page_size, PROT_READ | (f->readonly ? 0 : PROT_WRITE), MAP_SHARED, f->fd, page * f->page_size));
657
0
  if (d == MAP_FAILED) {
658
0
    raiseSysError
659
0
    (
660
0
      "Failed to map " + hobbes::string::from(pages) +
661
0
      " pages from page " + hobbes::string::from(page) +
662
0
      " out of " + hobbes::string::from(f->file_size) +
663
0
      " bytes with a page size of " + hobbes::string::from(f->page_size) +
664
0
      " bytes",
665
0
      f->path
666
0
    );
667
0
  }
668
669
0
  fregion& r = f->mappings[page];
670
0
  r.base_page = page;
671
0
  r.pages     = pages;
672
0
  r.base      = d;
673
0
  r.used      = 0;
674
0
  return r;
675
0
}
676
677
// munmap a region out of this file
678
0
inline void releaseFileRegionMap(imagefile* f, const fregion& fr) {
679
0
  if (munmap(fr.base, fr.pages * f->page_size) != 0) {
680
0
    raiseSysError("Failed to unmap page " + hobbes::string::from(fr.base_page) + " from file", f->path);
681
0
  }
682
0
}
683
684
// the greatest map position <= a point
685
template <typename K, typename V>
686
0
  inline typename std::map<K, V>::iterator gleb(std::map<K, V>& m, const K& x) {
687
0
    auto r = m.lower_bound(x);
688
689
0
    if (r == m.end()) {
690
0
      if (m.size() > 0) {
691
0
        --r;
692
0
      }
693
0
    } else if (r->first != x) {
694
0
      --r;
695
0
    }
696
0
    return r;
697
0
  }
Unexecuted instantiation: std::map<unsigned long, hobbes::fregion::fregion, std::less<unsigned long>, std::allocator<std::pair<unsigned long const, hobbes::fregion::fregion> > >::iterator hobbes::fregion::gleb<unsigned long, hobbes::fregion::fregion>(std::map<unsigned long, hobbes::fregion::fregion, std::less<unsigned long>, std::allocator<std::pair<unsigned long const, hobbes::fregion::fregion> > >&, unsigned long const&)
Unexecuted instantiation: std::map<char*, hobbes::fregion::falloc, std::less<char*>, std::allocator<std::pair<char* const, hobbes::fregion::falloc> > >::iterator hobbes::fregion::gleb<char*, hobbes::fregion::falloc>(std::map<char*, hobbes::fregion::falloc, std::less<char*>, std::allocator<std::pair<char* const, hobbes::fregion::falloc> > >&, char* const&)
698
699
// find the mapping data for a region, or create it if necessary
700
0
inline fregion& mappedFileRegion(imagefile* f, file_pageindex_t page, size_t pages) {
701
  // try to find the nearest possible mapping for this page
702
0
  auto fm = gleb(f->mappings, page);
703
704
  // if we couldn't find a mapping, then we have to make one
705
0
  if (fm == f->mappings.end()) {
706
0
    return createFileRegionMap(f, page, pages);
707
0
  }
708
0
  fregion& r = fm->second;
709
710
  // if we found a mapping, and the requested region is in it, ship it!
711
0
  if (page >= r.base_page && (page + pages) <= (r.base_page + r.pages)) {
712
0
    return r;
713
0
  }
714
715
  // otherwise, we just need to make a new one
716
0
  return createFileRegionMap(f, page, pages);
717
0
}
718
719
// allocate a region of this file as mapped memory
720
// map data that the file is supposed to contain
721
//
722
// mapFileData maps whatever it is asked for, in multiples of mmapPageMultiple
723
// pages, so an offset past the end of the file usually lands inside a mapping
724
// anyway and faults only when it is touched. Every offset and length a reader
725
// takes *from the file* has to be checked against the file's size before it
726
// is used, or a corrupt or crafted image decides what the reader reads: the
727
// arithmetic here is written so that neither the sum nor a product computed
728
// by a caller can wrap past the check.
729
0
inline void ensureDataInFile(imagefile* f, size_t fpos, size_t sz, const char* what) {
730
0
  if (fpos <= f->file_size && sz <= f->file_size - fpos) {
731
0
    return;
732
0
  }
733
734
  // a reader's idea of the file's size goes stale as soon as a writer extends
735
  // the file, and a reader is expected to follow a writer that is still
736
  // appending, so what is out of range here may simply be new. Ask the
737
  // filesystem before refusing: only data that is outside the file *now* is
738
  // data the file does not have.
739
0
  if (loadFileSize(f) && fpos <= f->file_size && sz <= f->file_size - fpos) {
740
0
    return;
741
0
  }
742
743
0
  throw std::runtime_error(
744
0
    std::string(what) + " at offset " + hobbes::string::from(fpos) +
745
0
    " runs past the end of a " + hobbes::string::from(f->file_size) +
746
0
    " byte file (" + hobbes::string::from(sz) + " bytes)"
747
0
  );
748
0
}
749
750
0
inline char* mapFileData(imagefile* f, size_t fpos, size_t sz) {
751
0
  file_pageindex_t pagei  = fpos        / f->page_size;
752
0
  file_pageindex_t pagef  = (fpos + sz) / f->page_size;
753
  
754
0
  assert(pagef >= pagei);
755
756
  // get the mapped region where this data lives
757
  // and increment its use count
758
0
  fregion& r = mappedFileRegion(f, pagei, 1 + pagef - pagei);
759
0
  r.used += sz;
760
761
  // the result will be offset from the base of the mapped page
762
  // (plus any intervening pages from the base of the mapping to the page for this data)
763
0
  char* result = r.base + (f->page_size * (pagei - r.base_page)) + (fpos % f->page_size);
764
765
  // remember where this allocated data came from (in case we want to release it later)
766
0
  falloc& fa = f->allocs[result];
767
0
  fa.page = r.base_page;
768
0
  fa.size = sz;
769
770
0
  return result;
771
0
}
772
773
0
inline char* mapFileDataInFile(imagefile* f, size_t fpos, size_t sz, const char* what) {
774
0
  ensureDataInFile(f, fpos, sz, what);
775
0
  return mapFileData(f, fpos, sz);
776
0
}
777
778
// bound an element count read out of the file before it is multiplied by an
779
// element size: 'n * esz' is what decides how much is mapped and copied, and
780
// a count near 2^64 wraps that product to something small that passes any
781
// check made after the multiplication
782
0
inline void ensureCountInFile(imagefile* f, size_t dataPos, size_t esz, size_t n, const char* what) {
783
0
  if (esz == 0) {
784
0
    return; // no data behind the count, so no count is too large
785
0
  }
786
0
  if (dataPos <= f->file_size && n <= (f->file_size - dataPos) / esz) {
787
0
    return;
788
0
  }
789
  // as in ensureDataInFile: the cached size goes stale under a writer that is
790
  // still appending, so confirm against the file before refusing
791
0
  if (loadFileSize(f) && dataPos <= f->file_size && n <= (f->file_size - dataPos) / esz) {
792
0
    return;
793
0
  }
794
795
0
  throw std::runtime_error(
796
0
    std::string(what) + " at offset " + hobbes::string::from(dataPos) +
797
0
    " claims " + hobbes::string::from(n) +
798
0
    " elements of " + hobbes::string::from(esz) +
799
0
    " bytes, past the end of a " + hobbes::string::from(f->file_size) + " byte file"
800
0
  );
801
0
}
802
803
// deallocate memory mapped out of this file
804
// (if this means that there are no outstanding references to the mapping, then the mapping itself is released)
805
0
inline void unmapFileData(imagefile* f, const void* p, size_t sz) {
806
0
  auto fa = f->allocs.find(const_cast<char*>(reinterpret_cast<const char*>(p)));
807
0
  if (fa == f->allocs.end()) {
808
0
    return;
809
0
  }
810
811
  // remember what page mapping this allocation was out of, but forget the mapping
812
0
  file_pageindex_t dpage = fa->second.page;
813
0
  f->allocs.erase(fa);
814
815
  // dereference these bytes from the page mapping
816
  // if the page mapping has no references, we can remove the page mapping too
817
0
  auto fm = f->mappings.find(dpage);
818
0
  if (fm == f->mappings.end()) {
819
0
    throw std::runtime_error("Internal error, inconsistent file mapping state");
820
0
  }
821
822
0
  if (fm->second.used > sz) {
823
0
    fm->second.used -= sz;
824
0
  } else {
825
0
    releaseFileRegionMap(f, fm->second);
826
0
    f->mappings.erase(fm);
827
0
  }
828
0
}
829
830
// map a stored sequence: a count, and 'esz' bytes of element data behind it
831
//
832
// the count comes out of the file, and 'count * esz' decides how much is
833
// mapped and then copied out, so a count near 2^64 wraps that product to
834
// something small while the count itself stays huge -- the copy then runs off
835
// the end of the mapping -- and 'dloc + sizeof(size_t) + count' can wrap past
836
// the file as well. Bound the count by what the file could hold before it is
837
// multiplied by anything.
838
0
inline uint8_t* mapStoredSeq(imagefile* f, size_t dloc, size_t esz, size_t* count, const char* what) {
839
0
  auto* c = reinterpret_cast<size_t*>(mapFileDataInFile(f, dloc, sizeof(size_t), what));
840
0
  size_t n = *c;
841
0
  unmapFileData(f, c, sizeof(size_t));
842
0
843
0
  ensureCountInFile(f, dloc + sizeof(size_t), esz, n, what);
844
0
845
0
  *count = n;
846
0
  return reinterpret_cast<uint8_t*>(mapFileDataInFile(f, dloc + sizeof(size_t), n * esz, what));
847
0
}
848
849
// we shouldn't ever work with files that have invalid page sizes
850
1.33k
inline uint16_t assertValidPageSize(const imagefile* f, size_t psize) {
851
1.33k
  if (psize < HFREGION_MIN_PAGE_SIZE) {
852
9
    throw std::runtime_error(f->path + ": System page size too small for db support (" + hobbes::string::from(psize) + ")");
853
1.32k
  } else if (psize > HFREGION_MAX_PAGE_SIZE) {
854
8
    throw std::runtime_error(f->path + ": System page size too large for db support (" + hobbes::string::from(psize) + ")");
855
1.32k
  } else if ((psize % sizeof(pagedata)) != 0) {
856
12
    throw std::runtime_error(f->path + ": System page size must be a multiple of " + hobbes::string::from(sizeof(pagedata)) + " (" + hobbes::string::from(psize) + ")");
857
1.30k
  } else if ((sizeof(filehead) % sizeof(pagedata)) != 0) {
858
    // should be a static assert :T
859
0
    throw std::runtime_error("No page size is valid, file format internally inconsistent");
860
0
  }
861
1.30k
  return static_cast<uint16_t>(psize);
862
1.33k
}
863
864
// put a new file into a valid empty state
865
0
inline void createFile(imagefile* f) {
866
0
  if (f->readonly) {
867
0
    throw std::runtime_error("Can't initialize empty file for read: " + f->path);
868
0
  }
869
0
  f->page_size = assertValidPageSize(f, sysconf(_SC_PAGESIZE));
870
0
  f->version   = HFREGION_CURRENT_FILE_FORMAT_VERSION;
871
872
  // start the first page!
873
0
  allocPage(f);
874
0
  seekAbs(f, 0);
875
876
  // the first page of the file begins with a standard header
877
0
  filehead fh;
878
0
  memset(&fh, 0, sizeof(fh));
879
0
  fh.magic    = HFREGION_FILE_PREFIX_BYTES;
880
0
  fh.pagesize = f->page_size;
881
0
  fh.version  = f->version;
882
0
  write(f, fh);
883
884
  // now begin the page table, starting with a description of this page
885
0
  f->pages.push_back(pagedata(pagetype::toc, 0));
886
0
  write(f, f->pages.back());
887
0
  f->tocpages.push_back(0);
888
889
  // and point the TOC head here
890
0
  f->head_toc_pos = sizeof(filehead) + sizeof(pagedata);
891
0
}
892
893
// The page table is a chain of TOC pages, each ending with the index of the
894
// next one. That link is read out of the image, so a corrupt or crafted file
895
// can point it at a page the file does not have, or back at a page the chain
896
// has already read. The read loop below only ends when it finds a null page
897
// entry, so a link that closes a cycle never ends it: the same pages are read
898
// forever while the page table grows without bound, which surfaces as a hang
899
// rather than as the malformed image it is. Requiring each link to name a page
900
// that lies wholly within the file and that this chain has not read yet bounds
901
// the chain by the number of pages in the file.
902
306
inline void ensureTOCPageLink(const imagefile* f, file_pageindex_t page, const std::set<file_pageindex_t>& readPages) {
903
306
  const uint64_t pagesInFile = f->file_size / f->page_size;
904
905
306
  if (page >= pagesInFile) {
906
110
    throw std::runtime_error(
907
110
      "Not a valid structured data file: page table continues on page (" + hobbes::string::from(page) +
908
110
      ") beyond the " + hobbes::string::from(pagesInFile) + " page(s) in the file: " + f->path);
909
196
  } else if (readPages.find(page) != readPages.end()) {
910
19
    throw std::runtime_error(
911
19
      "Not a valid structured data file: page table returns to page (" + hobbes::string::from(page) +
912
19
      "), which it has already read: " + f->path);
913
19
  }
914
306
}
915
916
// read all of the page data entries possible from the current file position
917
1.29k
inline void readPageData(imagefile* f) {
918
  // the TOC pages read so far, seeded with the page the caller started on
919
1.29k
  std::set<file_pageindex_t> readpages(f->tocpages.begin(), f->tocpages.end());
920
921
  // we stop reading page data when we hit the 0 page
922
  // and if we get to the end of a page, there's a link to the next page
923
96.8k
  while (true) {
924
96.7k
    pagedata pd;
925
96.7k
    read(f, &pd);
926
96.7k
    if (pd.type() == pagetype::null) {
927
1.11k
      break;
928
1.11k
    }
929
95.6k
    f->pages.push_back(pd);
930
95.6k
    f->head_toc_pos += sizeof(pagedata);
931
932
95.6k
    if (restInPage(f, f->head_toc_pos) == sizeof(file_pageindex_t)) {
933
308
      file_pageindex_t nextpage = -1;
934
308
      read(f, &nextpage);
935
308
      ensureTOCPageLink(f, nextpage, readpages);
936
308
      readpages.insert(nextpage);
937
308
      f->head_toc_pos = pageOffset(f, nextpage);
938
308
      f->tocpages.push_back(nextpage);
939
308
      seekAbs(f, f->head_toc_pos);
940
308
    }
941
95.6k
  }
942
1.29k
}
943
944
// read a single environment variable definition
945
88.3k
inline void readEnvironmentRecord(imagefile* f) {
946
88.3k
  size_t      offset;
947
88.3k
  std::string vname;
948
88.3k
  bytes       type;
949
88.3k
  size_t      boffset = filePosition(f);
950
951
88.3k
  read(f, &offset);
952
88.3k
  read(f, &vname);
953
88.3k
  read(f, &type);
954
955
88.3k
  f->bindings[vname] = binding(type, offset, boffset);
956
88.3k
}
957
958
// read environment data starting at some page
959
// (return the number of pages read to avoid erroneously double-reading environment data that might span multiple pages)
960
1.34k
inline size_t readEnvironmentPage(imagefile* f, file_pageindex_t p) {
961
  // remember the offset where we began reading environment data
962
1.34k
  size_t initOffset = pageOffset(f, p);
963
964
  // go there
965
1.34k
  seekAbs(f, initOffset);
966
967
  // we're done when we've read as much data as is reported for whatever page we're in
968
89.3k
  while (true) {
969
    // we expect to read at least one environment binding
970
88.3k
    readEnvironmentRecord(f);
971
 
972
    // exit when we've read to the end of whatever page we're in
973
    //  NOTE: we have to adjust -/+ 1 byte to account for the case when an environment
974
    //        page is filled up to and including the last byte (otherwise we'd mistakenly
975
    //        assume that we have to continue reading from the next page)
976
88.3k
    size_t           pos   = filePosition(f) - 1;
977
88.3k
    auto tpage = file_pageindex_t(pos / f->page_size);
978
88.3k
    uint16_t         rpos  = (pos % f->page_size) + 1;
979
980
    // a crafted image can make the record above run past the end of the page
981
    // table, and this loop only terminates by consulting that page's metadata
982
88.3k
    ensurePageInTable(f, tpage);
983
984
88.3k
    if (rpos == (f->page_size - f->pages[tpage].availBytes())) {
985
269
      break;
986
269
    }
987
88.3k
  }
988
989
  // now just report on the number of pages we've read
990
1.34k
  return pageCount(f, filePosition(f) - initOffset);
991
1.34k
}
992
993
// read the file
994
1.43k
inline void readFile(imagefile* f, uint16_t minVersion, uint16_t maxVersion) {
995
  // start reading the first page
996
1.43k
  seekAbs(f, 0);
997
998
  // get the header, make sure it's sound
999
1.43k
  filehead fh;
1000
1.43k
  read(f, &fh);
1001
1002
1.43k
  if (fh.magic != HFREGION_FILE_PREFIX_BYTES) {
1003
55
    throw std::runtime_error("Not a valid structured data file: " + f->path);
1004
1.38k
  } else if (fh.version < minVersion || fh.version > maxVersion) {
1005
28
    if (minVersion == maxVersion) {
1006
28
      throw std::runtime_error("Cannot read file format version=" + hobbes::string::from(fh.version) + " (expected version=" + hobbes::string::from(maxVersion) + ")");
1007
28
    } else {
1008
0
      throw std::runtime_error("Cannot read file format version=" + hobbes::string::from(fh.version) + " (expected version in [" + hobbes::string::from(minVersion) + "," + hobbes::string::from(maxVersion) + "])");
1009
0
    }
1010
28
  }
1011
1.35k
  f->page_size = assertValidPageSize(f, fh.pagesize);
1012
1.35k
  f->version   = fh.version;
1013
1014
  // load the first page descriptor
1015
1.35k
  pagedata pd;
1016
1.35k
  read(f, &pd);
1017
1.35k
  f->pages.push_back(pd);
1018
1019
1.35k
  f->tocpages.push_back(0);
1020
1.35k
  f->head_toc_pos = sizeof(filehead) + sizeof(pagedata);
1021
1022
  // now read all page descriptors
1023
1.35k
  readPageData(f);
1024
1025
  // and then read in the environment
1026
13.7k
  for (file_pageindex_t p = 0; p < f->pages.size(); ++p) {
1027
12.3k
    if (f->pages[p].type() == pagetype::environment) {
1028
      // read environment data starting from this page
1029
      // (skip any contiguous pages we might have read as part of this)
1030
1.34k
      p += readEnvironmentPage(f, p) - 1;
1031
1.34k
    }
1032
12.3k
  }
1033
1.35k
}
1034
1035
// open a file, or create it if necessary
1036
1.43k
inline imagefile* openFile(const std::string& fname, bool readonly, uint16_t minVersion = HFREGION_CURRENT_FILE_FORMAT_VERSION, uint16_t maxVersion = HFREGION_CURRENT_FILE_FORMAT_VERSION, size_t mmapPageMultiple = 262144 /* 1GB */) {
1037
1.43k
  auto* f        = new imagefile();
1038
1.43k
  f->path             = fname;
1039
1.43k
  f->readonly         = readonly;
1040
1.43k
  f->mmapPageMultiple = mmapPageMultiple;
1041
1042
1.43k
  try {
1043
    // open the file
1044
1.43k
    f->fd = open(fname.c_str(), readonly ? O_RDONLY : (O_RDWR | O_CREAT), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1045
1046
1.43k
    if (f->fd < 0) {
1047
0
      raiseSysError("Unable to open for " + std::string(readonly ? "read" : "write"), fname);
1048
0
    }
1049
1050
    // load the initial file size
1051
1.43k
    if (!loadFileSize(f)) {
1052
0
      raiseSysError("Can't stat file", f->path);
1053
0
    }
1054
1055
    // if we've just created this file, initialize it, else read it
1056
1.43k
    if (f->file_size == 0) {
1057
0
      createFile(f);
1058
1.43k
    } else {
1059
1.43k
      readFile(f, minVersion, maxVersion);
1060
1.43k
    }
1061
1062
    // keep a dummy value for writing all 0-length arrays
1063
1.43k
    if (!readonly) {
1064
0
      auto za = f->bindings.find(".za");
1065
0
      if (za != f->bindings.end()) {
1066
0
        f->empty_array = za->second.offset;
1067
0
      } else {
1068
0
        f->empty_array = findSpace(f, pagetype::data, sizeof(size_t), sizeof(size_t));
1069
0
        addBinding(f, ".za", bytes(), f->empty_array);
1070
0
      }
1071
0
    }
1072
1073
    // there, we've loaded this file
1074
1.43k
    return f;
1075
1.43k
  } catch (...) {
1076
1.38k
    closeFile(f);
1077
1.38k
    throw;
1078
1.38k
  }
1079
1.43k
}
1080
1081
// does this file even represent a structured data file?
1082
// we'll say yes if it can be opened, it has the magic number, and its size is a multiple of its page size
1083
0
inline bool isFRegion(const std::string& fname) {
1084
0
  auto* f = new imagefile();
1085
0
  f->path      = fname;
1086
0
  f->readonly  = true;
1087
1088
0
  try {
1089
    // open it
1090
0
    f->fd = ::open(fname.c_str(), O_RDONLY, 0);
1091
0
    if (f->fd < 0) { closeFile(f); return false; }
1092
1093
    // get its size
1094
0
    if (!loadFileSize(f)) { closeFile(f); return false; }
1095
1096
    // get the header
1097
0
    seekAbs(f, 0);
1098
0
    filehead fh;
1099
0
    read(f, &fh);
1100
1101
0
    if (fh.magic != HFREGION_FILE_PREFIX_BYTES) { closeFile(f); return false; }
1102
0
    f->page_size = assertValidPageSize(f, fh.pagesize);
1103
1104
0
    bool result = (f->page_size > 0) && (f->file_size % f->page_size) == 0;
1105
0
    closeFile(f);
1106
0
    return result;
1107
0
  } catch (...) {
1108
0
    closeFile(f);
1109
0
    return false;
1110
0
  }
1111
0
}
1112
1113
// create all implied directories in a path
1114
0
inline void ensureDirExists(const std::string& path) {
1115
0
  hobbes::string::seq ps = hobbes::string::csplit(path, "/");
1116
0
  std::ostringstream pfx;
1117
0
1118
0
  for (const auto& p : ps) {
1119
0
    pfx << p << "/";
1120
0
    if (mkdir(pfx.str().c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == -1 && errno != EEXIST && errno != EISDIR) {
1121
0
      throw std::runtime_error("Failed to make directory '" + pfx.str() + "' with error: " + strerror(errno));
1122
0
    }
1123
0
  }
1124
0
}
1125
1126
// give the first unique filename with some prefix/suffix that satisfies some condition
1127
0
inline std::string withUniqueFilenameBy(const std::string& fprefix, const std::string& fsuffix, const std::function<bool(const std::string&)>& fileOp) {
1128
0
  // the directory that this file is in can be created if necessary
1129
0
  ensureDirExists(hobbes::string::rsplit(fprefix, "/").first);
1130
0
1131
0
  // keep trying for new filenames until we get one that's distinct
1132
0
  size_t inst = 0;
1133
0
  while (true) {
1134
0
    std::ostringstream ss;
1135
0
    ss << fprefix << "-" << inst << fsuffix;
1136
0
    if (fileOp(ss.str())) {
1137
0
      return ss.str();
1138
0
    }
1139
0
    ++inst;
1140
0
  }
1141
0
}
1142
1143
// generate a new file with a given prefix & suffix
1144
0
inline std::string uniqueFilename(const std::string& fprefix, const std::string& fsuffix) {
1145
0
  return withUniqueFilenameBy(fprefix, fsuffix, [](const std::string& newpath) {
1146
0
    int fd = open(newpath.c_str(), O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1147
0
    if (fd >= 0) {
1148
0
      close(fd);
1149
0
      return true;
1150
0
    } else if (errno == EEXIST) {
1151
0
      return false;
1152
0
    } else {
1153
0
      throw std::runtime_error("Failed to generate a log database file with error: " + std::string(strerror(errno)));
1154
0
    }
1155
0
  });
1156
0
}
1157
1158
// move an existing file to a new file with a given prefix & suffix
1159
0
inline std::string moveToUniqueFilename(const std::string& oldpath, const std::string& fprefix, const std::string& fsuffix) {
1160
0
  return withUniqueFilenameBy(fprefix, fsuffix, [&oldpath](const std::string& newpath) {
1161
0
    int rt = link(oldpath.c_str(), newpath.c_str());
1162
0
    if (rt == 0) {
1163
0
      unlink(oldpath.c_str());
1164
0
      return true;
1165
0
    } else if (errno == EEXIST) {
1166
0
      return false;
1167
0
    } else {
1168
0
      throw std::runtime_error("Failed to move to a log database file with error: " + std::string(strerror(errno)));
1169
0
    }
1170
0
  });
1171
0
}
1172
1173
// a file reference is an index into a file where we can find a value of type T
1174
template <typename T>
1175
  struct fileref {
1176
    fileref(uint64_t index = 0) : index(index) { }
1177
    uint64_t index;
1178
    bool operator==(const fileref<T>& rhs) const { return this->index == rhs.index; }
1179
1180
    fileref<T>& operator=(const fileref<T>&) = default;
1181
1182
    T* load(imagefile* f) const {
1183
      return reinterpret_cast<T*>(mapFileData(f, this->index, sizeof(T)));
1184
    }
1185
  };
1186
1187
/***********************
1188
 *
1189
 * type description utilities for storage types
1190
 *
1191
 ***********************/
1192
0
inline ty::desc storedListType(const ty::desc& t, size_t tysize) {
1193
0
  return ty::recursive("x", ty::sum(ty::prim("unit"), ty::tup(0, t, align<size_t>(tysize, sizeof(size_t)), ty::fileRef(ty::var("x")))));
1194
0
}
1195
1196
0
inline ty::desc batchSeqType(const ty::desc& t, size_t batchSize) {
1197
0
  return ty::app(prim("carray", ty::fn("t", "c", ty::rec("avail", 0, ty::prim("long"), "buffer", sizeof(size_t), ty::array(ty::var("t"), ty::var("c"))))), t, ty::nat(batchSize));
1198
0
}
1199
1200
0
inline ty::desc storedSeqType(const ty::desc& t, size_t batchSize) {
1201
0
  // data fseq t c = (^x.(()+((carray t c)@?*x@?)))@?
1202
0
  return ty::app(prim("fseq", ty::fn("t", "c", ty::fileRef(storedListType(ty::fileRef(batchSeqType(t, batchSize)), sizeof(size_t))))), t, ty::nat(batchSize));
1203
0
}
1204
1205
0
inline ty::desc maybeStoredBatchType(const bytes& bs) {
1206
0
  ty::desc dty = ty::decode(bs);
1207
0
1208
0
  if (dty->tid == PRIV_HPPF_TYCTOR_TAPP) {
1209
0
    const auto* p = reinterpret_cast<const ty::App*>(dty.get());
1210
0
    if (p->f->tid == PRIV_HPPF_TYCTOR_PRIM) {
1211
0
      if (reinterpret_cast<const ty::Prim*>(p->f.get())->n == "fseq") {
1212
0
        if (p->args.size() >= 2) {
1213
0
          return p->args[0];
1214
0
        }
1215
0
      }
1216
0
    }
1217
0
  }
1218
0
  return ty::desc();
1219
0
}
1220
1221
0
inline size_t inferBatchSize(const bytes& bs) {
1222
0
  ty::desc dty = ty::decode(bs);
1223
0
1224
0
  if (dty->tid == PRIV_HPPF_TYCTOR_TAPP) {
1225
0
    const auto* p = reinterpret_cast<const ty::App*>(dty.get());
1226
0
    if (p->f->tid == PRIV_HPPF_TYCTOR_PRIM) {
1227
0
      if (reinterpret_cast<const ty::Prim*>(p->f.get())->n == "fseq") {
1228
0
        if (p->args.size() >= 2 && p->args[1]->tid == PRIV_HPPF_TYCTOR_SIZE) {
1229
0
          return reinterpret_cast<const ty::Nat*>(p->args[1].get())->x;
1230
0
        }
1231
0
      }
1232
0
    }
1233
0
  }
1234
0
  throw std::runtime_error("Invalid stored sequence type, can't infer size: " + ty::show(dty));
1235
0
}
1236
1237
// the main interface for type translation into slog data
1238
template <typename T, typename P = void>
1239
  struct store {
1240
  };
1241
1242
template <typename ... Ts>
1243
  struct all_memcpyable { static const bool value = true; };
1244
template <typename T, typename ... Ts>
1245
  struct all_memcpyable<T, Ts...> { static const bool value = store<T>::can_memcpy && all_memcpyable<Ts...>::value; };
1246
1247
// codecs for built-in types
1248
#define PRIV_HFREGION_DEFINE_PRIMTYS(T, n) \
1249
  template <> \
1250
    struct store<T> { \
1251
0
      static ty::desc    storeType()                                  { return ty::prim(n); } \
Unexecuted instantiation: hobbes::fregion::store<bool, void>::storeType()
Unexecuted instantiation: hobbes::fregion::store<unsigned char, void>::storeType()
Unexecuted instantiation: hobbes::fregion::store<char, void>::storeType()
Unexecuted instantiation: hobbes::fregion::store<short, void>::storeType()
Unexecuted instantiation: hobbes::fregion::store<unsigned short, void>::storeType()
Unexecuted instantiation: hobbes::fregion::store<int, void>::storeType()
Unexecuted instantiation: hobbes::fregion::store<unsigned int, void>::storeType()
Unexecuted instantiation: hobbes::fregion::store<long, void>::storeType()
Unexecuted instantiation: hobbes::fregion::store<unsigned long, void>::storeType()
Unexecuted instantiation: hobbes::fregion::store<__int128, void>::storeType()
Unexecuted instantiation: hobbes::fregion::store<float, void>::storeType()
Unexecuted instantiation: hobbes::fregion::store<double, void>::storeType()
1252
      static const bool  can_memcpy = true; \
1253
0
      static size_t      size()                                       { return sizeof(T); } \
Unexecuted instantiation: hobbes::fregion::store<bool, void>::size()
Unexecuted instantiation: hobbes::fregion::store<unsigned char, void>::size()
Unexecuted instantiation: hobbes::fregion::store<char, void>::size()
Unexecuted instantiation: hobbes::fregion::store<short, void>::size()
Unexecuted instantiation: hobbes::fregion::store<unsigned short, void>::size()
Unexecuted instantiation: hobbes::fregion::store<int, void>::size()
Unexecuted instantiation: hobbes::fregion::store<unsigned int, void>::size()
Unexecuted instantiation: hobbes::fregion::store<long, void>::size()
Unexecuted instantiation: hobbes::fregion::store<unsigned long, void>::size()
Unexecuted instantiation: hobbes::fregion::store<__int128, void>::size()
Unexecuted instantiation: hobbes::fregion::store<float, void>::size()
Unexecuted instantiation: hobbes::fregion::store<double, void>::size()
1254
0
      static size_t      alignment()                                  { return sizeof(T); } \
Unexecuted instantiation: hobbes::fregion::store<bool, void>::alignment()
Unexecuted instantiation: hobbes::fregion::store<unsigned char, void>::alignment()
Unexecuted instantiation: hobbes::fregion::store<char, void>::alignment()
Unexecuted instantiation: hobbes::fregion::store<short, void>::alignment()
Unexecuted instantiation: hobbes::fregion::store<unsigned short, void>::alignment()
Unexecuted instantiation: hobbes::fregion::store<int, void>::alignment()
Unexecuted instantiation: hobbes::fregion::store<unsigned int, void>::alignment()
Unexecuted instantiation: hobbes::fregion::store<long, void>::alignment()
Unexecuted instantiation: hobbes::fregion::store<unsigned long, void>::alignment()
Unexecuted instantiation: hobbes::fregion::store<__int128, void>::alignment()
Unexecuted instantiation: hobbes::fregion::store<float, void>::alignment()
Unexecuted instantiation: hobbes::fregion::store<double, void>::alignment()
1255
0
      static void        write(imagefile*, void* p,       const T& x) { *(reinterpret_cast<T*>(p)) = x; } \
Unexecuted instantiation: hobbes::fregion::store<bool, void>::write(hobbes::fregion::imagefile*, void*, bool const&)
Unexecuted instantiation: hobbes::fregion::store<unsigned char, void>::write(hobbes::fregion::imagefile*, void*, unsigned char const&)
Unexecuted instantiation: hobbes::fregion::store<char, void>::write(hobbes::fregion::imagefile*, void*, char const&)
Unexecuted instantiation: hobbes::fregion::store<short, void>::write(hobbes::fregion::imagefile*, void*, short const&)
Unexecuted instantiation: hobbes::fregion::store<unsigned short, void>::write(hobbes::fregion::imagefile*, void*, unsigned short const&)
Unexecuted instantiation: hobbes::fregion::store<int, void>::write(hobbes::fregion::imagefile*, void*, int const&)
Unexecuted instantiation: hobbes::fregion::store<unsigned int, void>::write(hobbes::fregion::imagefile*, void*, unsigned int const&)
Unexecuted instantiation: hobbes::fregion::store<long, void>::write(hobbes::fregion::imagefile*, void*, long const&)
Unexecuted instantiation: hobbes::fregion::store<unsigned long, void>::write(hobbes::fregion::imagefile*, void*, unsigned long const&)
Unexecuted instantiation: hobbes::fregion::store<__int128, void>::write(hobbes::fregion::imagefile*, void*, __int128 const&)
Unexecuted instantiation: hobbes::fregion::store<float, void>::write(hobbes::fregion::imagefile*, void*, float const&)
Unexecuted instantiation: hobbes::fregion::store<double, void>::write(hobbes::fregion::imagefile*, void*, double const&)
1256
0
      static void        read (imagefile*, const void* p, T* x)       { *x = *(reinterpret_cast<const T*>(p)); } \
Unexecuted instantiation: hobbes::fregion::store<bool, void>::read(hobbes::fregion::imagefile*, void const*, bool*)
Unexecuted instantiation: hobbes::fregion::store<unsigned char, void>::read(hobbes::fregion::imagefile*, void const*, unsigned char*)
Unexecuted instantiation: hobbes::fregion::store<char, void>::read(hobbes::fregion::imagefile*, void const*, char*)
Unexecuted instantiation: hobbes::fregion::store<short, void>::read(hobbes::fregion::imagefile*, void const*, short*)
Unexecuted instantiation: hobbes::fregion::store<unsigned short, void>::read(hobbes::fregion::imagefile*, void const*, unsigned short*)
Unexecuted instantiation: hobbes::fregion::store<int, void>::read(hobbes::fregion::imagefile*, void const*, int*)
Unexecuted instantiation: hobbes::fregion::store<unsigned int, void>::read(hobbes::fregion::imagefile*, void const*, unsigned int*)
Unexecuted instantiation: hobbes::fregion::store<long, void>::read(hobbes::fregion::imagefile*, void const*, long*)
Unexecuted instantiation: hobbes::fregion::store<unsigned long, void>::read(hobbes::fregion::imagefile*, void const*, unsigned long*)
Unexecuted instantiation: hobbes::fregion::store<__int128, void>::read(hobbes::fregion::imagefile*, void const*, __int128*)
Unexecuted instantiation: hobbes::fregion::store<float, void>::read(hobbes::fregion::imagefile*, void const*, float*)
Unexecuted instantiation: hobbes::fregion::store<double, void>::read(hobbes::fregion::imagefile*, void const*, double*)
1257
    }
1258
1259
PRIV_HFREGION_DEFINE_PRIMTYS(bool,     "bool");
1260
PRIV_HFREGION_DEFINE_PRIMTYS(uint8_t,  "byte");
1261
PRIV_HFREGION_DEFINE_PRIMTYS(char,     "char");
1262
PRIV_HFREGION_DEFINE_PRIMTYS(int16_t,  "short");
1263
PRIV_HFREGION_DEFINE_PRIMTYS(uint16_t, "short");
1264
PRIV_HFREGION_DEFINE_PRIMTYS(int32_t,  "int");
1265
PRIV_HFREGION_DEFINE_PRIMTYS(uint32_t, "int");
1266
PRIV_HFREGION_DEFINE_PRIMTYS(int64_t,  "long");
1267
PRIV_HFREGION_DEFINE_PRIMTYS(uint64_t, "long");
1268
#if defined(__APPLE__) && defined(__MACH__)
1269
PRIV_HFREGION_DEFINE_PRIMTYS(size_t, "long");
1270
#endif
1271
PRIV_HFREGION_DEFINE_PRIMTYS(__int128, "int128");
1272
PRIV_HFREGION_DEFINE_PRIMTYS(float,    "float");
1273
PRIV_HFREGION_DEFINE_PRIMTYS(double,   "double");
1274
1275
// store unit
1276
template <>
1277
  struct store<unit> {
1278
0
    static ty::desc storeType() { return ty::prim("unit"); }
1279
    static const bool can_memcpy = false;
1280
0
    static size_t size() { return 0; }
1281
0
    static size_t alignment() { return 1; }
1282
0
    static void write(imagefile*, void*, const unit&) { }
1283
0
    static void read(imagefile*, const void*, unit*) { }
1284
  };
1285
1286
// store fixed-length arrays
1287
template <typename T, size_t N>
1288
  struct storeFixedArrayDef {
1289
0
    static ty::desc storeType() { return ty::array(store<T>::storeType(), ty::nat(N)); }
1290
1291
0
    static size_t size()      { return store<T>::size() * N; }
1292
0
    static size_t alignment() { return store<T>::alignment(); }
1293
  };
1294
1295
template <typename T, size_t N>
1296
  struct store<T[N], typename tbool<store<T>::can_memcpy>::type> : public storeFixedArrayDef<T,N> {
1297
    static const bool can_memcpy = true;
1298
    static void write(imagefile*, void* p,       const T (&x)[N]) { memcpy(p, x, N*sizeof(T)); }
1299
    static void read (imagefile*, const void* p, T       (*x)[N]) { memcpy(reinterpret_cast<void*>(x), p, N*sizeof(T)); }
1300
  };
1301
1302
template <typename T, size_t N>
1303
  struct store<T[N], typename tbool<!store<T>::can_memcpy>::type> : public storeFixedArrayDef<T,N> {
1304
    static const bool can_memcpy = false;
1305
    static void write(imagefile* f, void* p, const T (&x)[N]) {
1306
      for (size_t i = 0; i < N; ++i) {
1307
        store<T>::write(f, p, x[i]);
1308
        p = reinterpret_cast<uint8_t*>(p) + store<T>::size();
1309
      }
1310
    }
1311
    static void read (imagefile* f, const void* p, T (*x)[N]) {
1312
      for (size_t i = 0; i < N; ++i) {
1313
        store<T>::read(f, p, &(*x)[i]);
1314
        p = reinterpret_cast<const uint8_t*>(p) + store<T>::size();
1315
      }
1316
    }
1317
  };
1318
1319
template <typename T, size_t N>
1320
  struct store<std::array<T, N>, typename tbool<store<T>::can_memcpy>::type> : public storeFixedArrayDef<T,N> {
1321
    static const bool can_memcpy = true;
1322
    static void write(imagefile*, void* p,       const std::array<T, N>& x) { memcpy(p, &x, N*sizeof(T)); }
1323
    static void read (imagefile*, const void* p, std::array<T, N>*       x) { memcpy(reinterpret_cast<void*>(x),  p, N*sizeof(T)); }
1324
  };
1325
1326
template <typename T, size_t N>
1327
  struct store<std::array<T, N>, typename tbool<!store<T>::can_memcpy>::type> : public storeFixedArrayDef<T,N> {
1328
    static const bool can_memcpy = false;
1329
    static void write(imagefile* f, void* p, const std::array<T, N>& x) {
1330
      for (size_t i = 0; i < N; ++i) {
1331
        store<T>::write(f, p, x[i]);
1332
        p = reinterpret_cast<uint8_t*>(p) + store<T>::size();
1333
      }
1334
    }
1335
    static void read(imagefile* f, const void* p, std::array<T, N>* x) {
1336
      for (size_t i = 0; i < N; ++i) {
1337
        store<T>::read(f, p, &(*x)[i]);
1338
        p = reinterpret_cast<const uint8_t*>(p) + store<T>::size();
1339
      }
1340
    }
1341
  };
1342
1343
// store fixed-capacity variable-length arrays
1344
template <typename T, size_t N>
1345
  struct storeCArrayDef {
1346
    // data carray t n = {avail:long, buffer:[:t|n:]}
1347
    static ty::desc storeType() { return ty::app(prim("carray", ty::fn("t", "c", ty::rec("avail", 0, ty::prim("long"), "buffer", sizeof(size_t), ty::array(ty::var("t"), ty::var("c"))))), store<T>::storeType(), ty::nat(N)); }
1348
1349
    static size_t size()      { static const size_t sz=align(align(sizeof(size_t), alignment())+store<T>::size()*N, alignment()); return sz; }
1350
    static size_t alignment() { static const size_t a =std::max<size_t>(sizeof(size_t), store<T>::alignment()); return a; }
1351
  };
1352
1353
template <typename T, size_t N>
1354
  struct store<carray<T,N>, typename tbool<store<T>::can_memcpy>::type> : public storeCArrayDef<T,N> {
1355
    static const bool can_memcpy = true;
1356
    static void write(imagefile*, void*       p, const carray<T,N>& x) { memcpy(p, &x, storeCArrayDef<T,N>::alignment()+sizeof(T)*x.size); }
1357
    static void read(imagefile*,  const void* p, carray<T,N>*       x) {
1358
      // the stored length comes from the file image and must not exceed the
1359
      // fixed capacity N, or the copy overruns x->data[N]
1360
      const size_t n = *reinterpret_cast<const size_t*>(p);
1361
      if (n > N) {
1362
        throw std::runtime_error("Not a valid structured data file: carray length (" + hobbes::string::from(n) + ") exceeds capacity (" + hobbes::string::from(N) + ")");
1363
      }
1364
      memcpy(reinterpret_cast<void*>(x), p, storeCArrayDef<T,N>::alignment()+sizeof(T)*n);
1365
    }
1366
  };
1367
1368
template <typename T, size_t N>
1369
  struct store<carray<T,N>, typename tbool<!store<T>::can_memcpy>::type> : public storeCArrayDef<T,N> {
1370
    static const bool can_memcpy = false;
1371
    static void write(imagefile* f, void* p, const carray<T,N>& x) {
1372
      *reinterpret_cast<size_t*>(p) = x.size;
1373
      p = reinterpret_cast<uint8_t*>(p) + storeCArrayDef<T,N>::alignment();
1374
      for (size_t i = 0; i < x.size; ++i) {
1375
        store<T>::write(f, p, x.data[i]);
1376
        p = reinterpret_cast<uint8_t*>(p) + store<T>::size();
1377
      }
1378
    }
1379
    static void read(imagefile* f, const void* p, carray<T,N>* x) {
1380
      // the stored length comes from the file image and must not exceed the
1381
      // fixed capacity N, or the loop writes past x->data[N]
1382
      const size_t n = *reinterpret_cast<const size_t*>(p);
1383
      if (n > N) {
1384
        throw std::runtime_error("Not a valid structured data file: carray length (" + hobbes::string::from(n) + ") exceeds capacity (" + hobbes::string::from(N) + ")");
1385
      }
1386
      x->size = n;
1387
      p = reinterpret_cast<const uint8_t*>(p) + storeCArrayDef<T,N>::alignment();
1388
      for (size_t i = 0; i < n; ++i) {
1389
        store<T>::read(f, p, &x->data[i]);
1390
        p = reinterpret_cast<const uint8_t*>(p) + store<T>::size();
1391
      }
1392
    }
1393
  };
1394
1395
// store strings
1396
template <>
1397
  struct store<std::string> {
1398
    static const bool can_memcpy = false;
1399
0
    static ty::desc storeType() { return ty::fileRef(ty::array(ty::prim("char"))); }
1400
0
    static size_t size() { return sizeof(size_t); }
1401
0
    static size_t alignment() { return sizeof(size_t); }
1402
1403
0
    static void write(imagefile* f, void* p, const std::string& x) {
1404
0
      if (!x.empty()) {
1405
0
        auto bc = sizeof(size_t) + x.size();
1406
0
1407
0
        size_t dloc = findSpace(f, pagetype::data, bc, sizeof(size_t));
1408
0
        auto* d = reinterpret_cast<uint8_t*>(mapFileData(f, dloc, bc));
1409
0
        *reinterpret_cast<size_t*>(d) = x.size();
1410
0
        memcpy(d+sizeof(size_t), x.data(), x.size());
1411
0
        unmapFileData(f, d, bc);
1412
0
1413
0
        *reinterpret_cast<size_t*>(p) = dloc;
1414
0
      } else {
1415
0
        *reinterpret_cast<size_t*>(p) = f->empty_array;
1416
0
      }
1417
0
    }
1418
0
    static void read (imagefile* f, const void* p, std::string* x) {
1419
0
      auto     dloc = *reinterpret_cast<const size_t*>(p);
1420
0
      size_t n = 0;
1421
0
      char*  s = reinterpret_cast<char*>(mapStoredSeq(f, dloc, sizeof(char), &n, "stored string"));
1422
0
1423
0
      x->assign(s, s + n);
1424
0
1425
0
      unmapFileData(f, s, n);
1426
0
    }
1427
  };
1428
1429
// store vectors
1430
template <typename T>
1431
  struct storeVectorDef {
1432
    static ty::desc storeType() { return ty::fileRef(ty::array(store<T>::storeType())); }
1433
    static size_t size() { return sizeof(size_t); }
1434
    static size_t alignment() { return sizeof(size_t); }
1435
  };
1436
template <typename T>
1437
  struct store<std::vector<T>, typename tbool<store<T>::can_memcpy>::type> : public storeVectorDef<T> {
1438
    static const bool can_memcpy = false;
1439
    static void write(imagefile* f, void* p, const std::vector<T>& x) {
1440
      if (!x.empty()) {
1441
        auto bc = sizeof(size_t) + sizeof(T)*x.size();
1442
1443
        size_t dloc = findSpace(f, pagetype::data, bc, sizeof(size_t));
1444
        auto* d = reinterpret_cast<uint8_t*>(mapFileData(f, dloc, bc));
1445
        *reinterpret_cast<size_t*>(d) = x.size();
1446
        memcpy(d+sizeof(size_t), &x[0], sizeof(T)*x.size());
1447
        unmapFileData(f, d, bc);
1448
1449
        *reinterpret_cast<size_t*>(p) = dloc;
1450
      } else {
1451
        *reinterpret_cast<size_t*>(p) = f->empty_array;
1452
      }
1453
    }
1454
    static void read(imagefile* f, const void* p, std::vector<T>* x) {
1455
      auto     dloc = *reinterpret_cast<const size_t*>(p);
1456
      size_t n = 0;
1457
      auto* d = mapStoredSeq(f, dloc, sizeof(T), &n, "stored vector");
1458
1459
      x->resize(n);
1460
      if (n > 0) {
1461
        memcpy(reinterpret_cast<void*>(&(*x)[0]), d, n * sizeof(T));
1462
      }
1463
1464
      unmapFileData(f, d, n * sizeof(T));
1465
    }
1466
  };
1467
template <typename T>
1468
  struct store<std::vector<T>, typename tbool<!store<T>::can_memcpy>::type> : public storeVectorDef<T> {
1469
    static const bool can_memcpy = false;
1470
    static void write(imagefile* f, void* p, const std::vector<T>& x) {
1471
      if (!x.empty()) {
1472
        size_t tsz = store<T>::size();
1473
        auto   bc  = sizeof(size_t) + tsz*x.size();
1474
1475
        size_t dloc = findSpace(f, pagetype::data, bc, sizeof(size_t));
1476
        auto* d = reinterpret_cast<uint8_t*>(mapFileData(f, dloc, bc));
1477
        *reinterpret_cast<size_t*>(d) = x.size();
1478
1479
        auto *dc = d + sizeof(size_t);
1480
        for (const auto& xv : x) {
1481
          store<T>::write(f, dc, xv);
1482
          dc += tsz;
1483
        }
1484
        unmapFileData(f, d, bc);
1485
1486
        *reinterpret_cast<size_t*>(p) = dloc;
1487
      } else {
1488
        *reinterpret_cast<size_t*>(p) = f->empty_array;
1489
      }
1490
    }
1491
    static void read(imagefile* f, const void* p, std::vector<T>* x) {
1492
      size_t   tsz  = store<T>::size();
1493
      auto     dloc = *reinterpret_cast<const size_t*>(p);
1494
      size_t n = 0;
1495
      auto* d = mapStoredSeq(f, dloc, tsz, &n, "stored vector");
1496
      uint8_t* dc = d;
1497
1498
      x->resize(n);
1499
      for (size_t i = 0; i < n; ++i) {
1500
        store<T>::read(f, dc, &(*x)[i]);
1501
        dc += tsz;
1502
      }
1503
1504
      unmapFileData(f, d, n * tsz);
1505
    }
1506
  };
1507
1508
// bit vectors wind up being represented differently ...
1509
template <>
1510
  struct store<std::vector<bool>> : public storeVectorDef<bool> {
1511
    static const bool can_memcpy = false;
1512
0
    static void write(imagefile* f, void* p, const std::vector<bool>& x) {
1513
0
      if (!x.empty()) {
1514
0
        auto   bc  = sizeof(size_t) + x.size();
1515
0
1516
0
        size_t dloc = findSpace(f, pagetype::data, bc, sizeof(size_t));
1517
0
        auto* d = reinterpret_cast<uint8_t*>(mapFileData(f, dloc, bc));
1518
0
        *reinterpret_cast<size_t*>(d) = x.size();
1519
0
1520
0
        auto *dc = d + sizeof(size_t);
1521
0
        for (const auto& xv : x) {
1522
0
          store<bool>::write(f, dc, xv);
1523
0
          dc += 1;
1524
0
        }
1525
0
        unmapFileData(f, d, bc);
1526
0
1527
0
        *reinterpret_cast<size_t*>(p) = dloc;
1528
0
      } else {
1529
0
        *reinterpret_cast<size_t*>(p) = f->empty_array;
1530
0
      }
1531
0
    }
1532
0
    static void read(imagefile* f, const void* p, std::vector<bool>* x) {
1533
0
      auto     dloc = *reinterpret_cast<const size_t*>(p);
1534
0
      size_t n = 0;
1535
0
      auto* d = mapStoredSeq(f, dloc, sizeof(uint8_t), &n, "stored bit vector");
1536
0
      uint8_t* dc = d;
1537
0
1538
0
      x->resize(n);
1539
0
      for (size_t i = 0; i < n; ++i) {
1540
0
        bool val=false;
1541
0
        store<bool>::read(f, dc, &val);
1542
0
        (*x)[i] = val;
1543
0
        dc += 1;
1544
0
      }
1545
0
1546
0
      unmapFileData(f, d, n);
1547
0
    }
1548
  };
1549
1550
// store pairs
1551
template <typename U, typename V>
1552
  struct storePairDef {
1553
    static ty::desc storeType() {
1554
      return ty::tup(0, store<U>::storeType(), align<size_t>(store<U>::size(), store<V>::alignment()), store<V>::storeType());
1555
    }
1556
0
    static size_t alignment() { return std::max<size_t>(store<U>::alignment(), store<V>::alignment()); }
1557
0
    static size_t size() { auto usz = store<U>::size(); return align<size_t>(usz + (align<size_t>(usz, store<V>::alignment()) - usz) + store<V>::size(), alignment()); }
1558
  };
1559
1560
template <typename U, typename V>
1561
  struct store<std::pair<U,V>, typename tbool<all_memcpyable<U, V>::value>::type> : public storePairDef<U,V> {
1562
    static const bool can_memcpy = true;
1563
    static void write(imagefile*, void* p,       const std::pair<U,V>& x) { memcpy(p, &x, sizeof(x)); }
1564
0
    static void read (imagefile*, const void* p, std::pair<U,V>* x)       { memcpy(reinterpret_cast<void*>(x), p, sizeof(*x)); }
1565
  };
1566
template <typename U, typename V>
1567
  struct store<std::pair<U,V>, typename tbool<!all_memcpyable<U, V>::value>::type> : public storePairDef<U,V> {
1568
    static const bool can_memcpy = false;
1569
    static void write(imagefile* f, void* p, const std::pair<U,V>& x) {
1570
      store<U>::write(f, p, x.first);
1571
      store<V>::write(f, reinterpret_cast<uint8_t*>(p)+align<size_t>(store<U>::size(), store<V>::alignment()), x.second);
1572
    }
1573
    static void read (imagefile* f, const void* p, std::pair<U,V>* x) {
1574
      store<U>::read(f, p, &x->first);
1575
      store<V>::read(f, reinterpret_cast<const uint8_t*>(p)+align<size_t>(store<U>::size(), store<V>::alignment()), &x->second);
1576
    }
1577
  };
1578
1579
// store tuples
1580
template <size_t i, size_t n, typename ... Ts>
1581
  struct storeTupleDef {
1582
    using H = typename nth<i, Ts...>::type;
1583
    using TT = tuple<Ts...>;
1584
    using offs = typename TT::offs;
1585
    using Recurse = storeTupleDef<i + 1, n, Ts...>;
1586
1587
    static void fieldDefs(size_t offset, ty::Struct::Fields* fs) {
1588
      offset = align(offset, store<H>::alignment());
1589
      fs->push_back(ty::Struct::Field(".f" + hobbes::string::from(i), offset, store<H>::storeType()));
1590
      Recurse::fieldDefs(offset + store<H>::size(), fs);
1591
    }
1592
    static ty::desc storeType() { ty::Struct::Fields fs; fieldDefs(0, &fs); return ty::record(fs); }
1593
1594
    static size_t alignment()                   { static size_t a=std::max<size_t>(store<H>::alignment(), Recurse::alignment()); return a; }
1595
    static size_t tailOffsetFrom(size_t offset) { return Recurse::tailOffsetFrom(align(offset, store<H>::alignment()) + store<H>::size()); }
1596
    static size_t size()                        { static size_t sz=align(tailOffsetFrom(0), alignment()); return sz; }
1597
1598
    static void incrWrite(imagefile* f, void* p, const tuple<Ts...>& x) {
1599
      p = reinterpret_cast<void*>(align(reinterpret_cast<size_t>(p), store<H>::alignment()));
1600
      store<H>::write(f, p, x.template at<i>());
1601
      Recurse::incrWrite(f, reinterpret_cast<uint8_t*>(p) + store<H>::size(), x);
1602
    }
1603
    static void incrRead(imagefile* f, const void* p, tuple<Ts...>* x) {
1604
      p = reinterpret_cast<const void*>(align(reinterpret_cast<size_t>(p), store<H>::alignment()));
1605
      store<H>::read(f, p, &x->template at<i>());
1606
      Recurse::incrRead(f, reinterpret_cast<const uint8_t*>(p) + store<H>::size(), x);
1607
    }
1608
  };
1609
template <size_t n, typename ... Ts>
1610
  struct storeTupleDef<n, n, Ts...> {
1611
    static void fieldDefs(size_t, ty::Struct::Fields*) { }
1612
    static ty::desc storeType()                 { return ty::prim("unit"); }
1613
    static size_t alignment()                   { return  1; }
1614
    static size_t tailOffsetFrom(size_t offset) { return offset; }
1615
    static size_t size()                        { return  0; }
1616
1617
    static void incrWrite(imagefile*, void*, const tuple<Ts...>&) { }
1618
    static void incrRead (imagefile*, const void*, tuple<Ts...>*) { }
1619
  };
1620
1621
template <typename ... Ts>
1622
  struct store<tuple<Ts...>, typename tbool<all_memcpyable<Ts...>::value>::type> : public storeTupleDef<0, sizeof...(Ts), Ts...> {
1623
    static const bool can_memcpy = true;
1624
    static void write(imagefile*, void* p,       const tuple<Ts...>& x) { memcpy(p, &x, sizeof( x)); }
1625
    static void read (imagefile*, const void* p, tuple<Ts...>* x)       { memcpy(reinterpret_cast<void*>(x),  p, sizeof(*x)); }
1626
  };
1627
template <typename ... Ts>
1628
  struct store<tuple<Ts...>, typename tbool<!all_memcpyable<Ts...>::value>::type> : public storeTupleDef<0, sizeof...(Ts), Ts...> {
1629
    static const bool can_memcpy = false;
1630
    using Reflect = storeTupleDef<0, sizeof...(Ts), Ts...>;
1631
1632
    static void write(imagefile* f, void* p, const tuple<Ts...>& x) {
1633
      Reflect::incrWrite(f, p, x);
1634
    }
1635
    static void read (imagefile* f, const void* p, tuple<Ts...>* x) {
1636
      Reflect::incrRead(f, p, x);
1637
    }
1638
  };
1639
1640
// store reflective structs
1641
struct defStructF {
1642
  ty::Struct::Fields* fs;
1643
  size_t offset;
1644
0
  defStructF(ty::Struct::Fields* fs) : fs(fs), offset(0) { }
1645
1646
  template <typename T>
1647
0
    void visit(const char* fname) {
1648
0
      this->offset = align<size_t>(this->offset, store<T>::alignment());
1649
0
      this->fs->push_back(ty::Struct::Field(fname, this->offset, store<T>::storeType()));
1650
0
      this->offset += store<T>::size();
1651
0
    }
Unexecuted instantiation: void hobbes::fregion::defStructF::visit<unsigned char [4088]>(char const*)
Unexecuted instantiation: void hobbes::fregion::defStructF::visit<unsigned long>(char const*)
Unexecuted instantiation: void hobbes::fregion::defStructF::visit<unsigned int>(char const*)
1652
};
1653
1654
struct calcSizeF {
1655
  size_t* sz;
1656
  size_t* maxAlign;
1657
0
  calcSizeF(size_t* sz, size_t* maxAlign) : sz(sz), maxAlign(maxAlign) { *this->sz = 0; }
1658
1659
  template <typename T>
1660
    void visit(const char*) {
1661
      *this->sz       = align<size_t>(*this->sz, store<T>::alignment()) + store<T>::size();
1662
      *this->maxAlign = std::max<size_t>(*this->maxAlign, store<T>::alignment());
1663
    }
1664
};
1665
1666
template <typename T>
1667
  struct storeStructDef {
1668
0
    static ty::desc storeType() {
1669
0
      ty::Struct::Fields fs;
1670
0
      defStructF df(&fs);
1671
0
      T::meta(df);
1672
0
      return ty::record(fs);
1673
0
    }
Unexecuted instantiation: hobbes::fregion::storeStructDef<hobbes::fregion::cbatchseg>::storeType()
Unexecuted instantiation: hobbes::fregion::storeStructDef<hobbes::fregion::cbatch>::storeType()
1674
1675
    static size_t size() {
1676
      size_t sz       = 0;
1677
      size_t maxAlign = 1;
1678
      calcSizeF csF(&sz, &maxAlign);
1679
      T::meta(csF);
1680
      return align<size_t>(sz, maxAlign);
1681
    }
1682
    static size_t alignment() {
1683
      size_t sz       = 0;
1684
      size_t maxAlign = 1;
1685
      calcSizeF csF(&sz, &maxAlign);
1686
      T::meta(csF);
1687
      return maxAlign;
1688
    }
1689
  };
1690
1691
template <typename T>
1692
  struct store<T, typename tbool<T::is_hmeta_struct && store<typename T::as_tuple_type>::can_memcpy>::type> : public storeStructDef<T> {
1693
    static const bool can_memcpy = true;
1694
    static void write(imagefile*, void* p,       const T& x) { memcpy(p, &x, sizeof(T)); }
1695
    static void read (imagefile*, const void* p, T*       x) { memcpy(reinterpret_cast<void*>(x),  p, sizeof(T)); }
1696
  };
1697
1698
struct writeFieldF {
1699
  imagefile*     f;
1700
  uint8_t*       o;
1701
  const uint8_t* i;
1702
  size_t         ooffset, ioffset;
1703
0
  writeFieldF(imagefile* f, uint8_t* o, const uint8_t* i) : f(f), o(o), i(i), ooffset(0), ioffset(0) { }
1704
1705
  template <typename T>
1706
    void visit(const char*) {
1707
      auto ooff = align<size_t>(this->ooffset, store<T>::alignment());
1708
      auto ioff = align<size_t>(this->ioffset, alignof(T));
1709
1710
      store<T>::write(this->f, this->o + ooff, *reinterpret_cast<const T*>(this->i + ioff));
1711
1712
      this->ooffset = ooff + store<T>::size();
1713
      this->ioffset = ioff + sizeof(T);
1714
    }
1715
};
1716
1717
struct readFieldF {
1718
  imagefile*     f;
1719
  const uint8_t* i;
1720
  uint8_t*       o;
1721
  size_t         ooffset, ioffset;
1722
0
  readFieldF(imagefile* f, const uint8_t* i, uint8_t* o) : f(f), i(i), o(o), ooffset(0), ioffset(0) { }
1723
1724
  template <typename T>
1725
    void visit(const char*) {
1726
      auto ioff = align<size_t>(this->ioffset, store<T>::alignment());
1727
      auto ooff = align<size_t>(this->ooffset, alignof(T));
1728
1729
      store<T>::read(this->f, this->i + ioff, reinterpret_cast<T*>(this->o + ooff));
1730
1731
      this->ioffset = ioff + store<T>::size();
1732
      this->ooffset = ooff + sizeof(T);
1733
    }
1734
};
1735
1736
template <typename T>
1737
  struct store<T, typename tbool<T::is_hmeta_struct && !store<typename T::as_tuple_type>::can_memcpy>::type> : public storeStructDef<T> {
1738
    static const bool can_memcpy = false;
1739
    static void write(imagefile* f, void* p, const T& x) {
1740
      writeFieldF wfF(f, reinterpret_cast<uint8_t*>(p), reinterpret_cast<const uint8_t*>(&x));
1741
      T::meta(wfF);
1742
    }
1743
    static void read (imagefile* f, const void* p, T* x) {
1744
      readFieldF rfF(f, reinterpret_cast<const uint8_t*>(p), reinterpret_cast<uint8_t*>(x));
1745
      T::meta(rfF);
1746
    }
1747
  };
1748
1749
// store reflective enumerations
1750
template <typename T>
1751
  struct store<T, typename tbool<T::is_hmeta_enum>::type> {
1752
    static const bool can_memcpy = true;
1753
    
1754
    static ty::desc storeType() {
1755
      return ty::enumdef(store<typename T::rep_t>::storeType(), T::meta());
1756
    }
1757
1758
    static size_t size()      { return sizeof(typename T::rep_t); }
1759
    static size_t alignment() { return sizeof(typename T::rep_t); }
1760
1761
    static void write(imagefile*, void* p,       const T& x) { memcpy(p, &x, sizeof(typename T::rep_t)); }
1762
    static void read (imagefile*, const void* p, T*       x) { memcpy(reinterpret_cast<void*>(x),  p, sizeof(typename T::rep_t)); }
1763
  };
1764
1765
// store variants
1766
template <size_t i, size_t n, typename ... Ts>
1767
  struct storeVariantDef {
1768
    using H = typename nth<i, Ts...>::type;
1769
    using VT = variant<Ts...>;
1770
    using Recurse = storeVariantDef<i + 1, n, Ts...>;
1771
1772
    static void ctorDefs(ty::Variant::Ctors* cs) {
1773
      cs->push_back(ty::Variant::Ctor(".f" + hobbes::string::from(i), static_cast<int>(i), store<H>::storeType()));
1774
      Recurse::ctorDefs(cs);
1775
    }
1776
    static ty::desc storeType() { ty::Variant::Ctors cs; ctorDefs(&cs); return ty::variant(cs); }
1777
1778
    static size_t maxSize()      { return std::max<size_t>(store<H>::size(),      Recurse::maxSize());      }
1779
    static size_t maxAlignment() { return std::max<size_t>(store<H>::alignment(), Recurse::maxAlignment()); }
1780
    static size_t tagOffset()    { return align<size_t>(sizeof(uint32_t), maxAlignment()); }
1781
1782
    static size_t size()      { return align<size_t>(align<size_t>(sizeof(uint32_t), maxAlignment()) + maxSize(), maxAlignment()); }
1783
    static size_t alignment() { return std::max<size_t>(sizeof(uint32_t), maxAlignment()); }
1784
  };
1785
template <size_t n, typename ... Ts>
1786
  struct storeVariantDef<n, n, Ts...> {
1787
    static void ctorDefs(ty::Variant::Ctors*) { }
1788
    static ty::desc storeType()   { return ty::prim("void"); }
1789
    static size_t maxSize()       { return 0;  }
1790
    static size_t maxAlignment()  { return 1;  }
1791
  };
1792
1793
template <typename ... Ts>
1794
  struct store<variant<Ts...>, typename tbool<all_memcpyable<Ts...>::value>::type> : public storeVariantDef<0, sizeof...(Ts), Ts...> {
1795
    static const bool can_memcpy = true;
1796
    static void write(imagefile*, void* p,       const variant<Ts...>& x) { memcpy(p, &x, sizeof( x)); }
1797
    static void read (imagefile*, const void* p, variant<Ts...>* x)       { memcpy(reinterpret_cast<void*>(x),  p, sizeof(*x)); }
1798
  };
1799
1800
template <size_t tag, typename T, typename M>
1801
  struct variantGenWrite {
1802
    static void fn(T* vd, imagefile* f, void* p, size_t tagOffset) {
1803
      *reinterpret_cast<uint32_t*>(p) = tag;
1804
      store<T>::write(f, reinterpret_cast<uint8_t*>(p) + tagOffset, *vd);
1805
    }
1806
  };
1807
template <size_t tag, typename T, typename M>
1808
  struct variantGenRead {
1809
    static void fn(T* vd, imagefile* f, const void* p, size_t tagOffset) {
1810
      new (vd) T();
1811
      store<T>::read(f, reinterpret_cast<const uint8_t*>(p) + tagOffset, vd);
1812
    }
1813
  };
1814
template <typename ... Ts>
1815
  struct store<variant<Ts...>, typename tbool<!all_memcpyable<Ts...>::value>::type> : public storeVariantDef<0, sizeof...(Ts), Ts...> {
1816
    static const bool can_memcpy = false;
1817
    using Reflect = storeVariantDef<0, sizeof...(Ts), Ts...>;
1818
1819
    static void write(imagefile* f, void* p, const variant<Ts...>& x) {
1820
      x.template apply<void, variantGenWrite, void, imagefile*, void*, size_t>(f, p, Reflect::tagOffset());
1821
    }
1822
    static void read (imagefile* f, const void* p, variant<Ts...>* x) {
1823
      x->unsafeTag() = *reinterpret_cast<const uint32_t*>(p);
1824
      variantApp<void, variantGenRead, void, tuple<Ts...>, imagefile*, const void*, size_t>::apply(x->unsafeTag(), x->unsafePayload(), f, p, Reflect::tagOffset());
1825
    }
1826
  };
1827
1828
// store variants with named constructors
1829
struct printVF {
1830
  std::ostringstream* ss;
1831
0
  printVF(std::ostringstream* ss) : ss(ss) { }
1832
  template <typename T>
1833
  void visit(const char* n, int, const T& x) {
1834
    *this->ss << n << "=" << x;
1835
  }
1836
};
1837
template <typename T>
1838
  inline std::string showV(const T& x) {
1839
    std::ostringstream ss;
1840
    ss << "|";
1841
    printVF pvf(&ss);
1842
    x.gvisit(pvf);
1843
    ss << "|";
1844
    return ss.str();
1845
  }
1846
1847
struct descVariantF {
1848
  ty::Variant::Ctors* ctors;
1849
0
  descVariantF(ty::Variant::Ctors* ctors) : ctors(ctors) { }
1850
1851
  template <typename T>
1852
  void ctor(const char* n, int id) {
1853
    this->ctors->push_back(ty::Variant::Ctor(n, id, store<T>::storeType()));
1854
  }
1855
};
1856
1857
struct calcVSizeF {
1858
  size_t* maxSize;
1859
  size_t* maxAlign;
1860
0
  calcVSizeF(size_t* maxSize, size_t* maxAlign) : maxSize(maxSize), maxAlign(maxAlign) { *this->maxSize = 0; *this->maxAlign = sizeof(uint32_t); }
1861
1862
  template <typename T>
1863
  void ctor(const char*, int) {
1864
    *this->maxSize  = std::max<size_t>(*this->maxSize,  store<T>::size());
1865
    *this->maxAlign = std::max<size_t>(*this->maxAlign, store<T>::alignment());
1866
  }
1867
};
1868
1869
template <typename T>
1870
  struct store<T, typename tbool<T::is_hmeta_variant>::type> {
1871
    using VT = typename T::as_variant_type;
1872
    static const bool can_memcpy = store<VT>::can_memcpy;
1873
1874
    static ty::desc storeType() {
1875
      ty::Variant::Ctors cs;
1876
      descVariantF f(&cs);
1877
      T::meta(f);
1878
      return ty::variant(cs);
1879
    }
1880
1881
    static void write(imagefile* f, void* p,       const T& x) { store<VT>::write(f, p, *reinterpret_cast<const VT*>(&x)); }
1882
    static void read (imagefile* f, const void* p, T*       x) { store<VT>::read (f, p, reinterpret_cast<VT*>(x)); }
1883
1884
    static size_t size() { return store<VT>::size(); }
1885
    static size_t alignment() { return store<VT>::alignment(); }
1886
  };
1887
1888
// store opaque type aliases
1889
template <typename T>
1890
  struct store<T, typename tbool<T::is_hmeta_alias>::type> {
1891
    using RT = typename T::type;
1892
1893
    static ty::desc storeType() { return ty::prim(T::name(), store<RT>::storeType()); ; }
1894
1895
    static const bool  can_memcpy = store<RT>::can_memcpy;
1896
    static size_t      size()                                         { return store<RT>::size(); }
1897
    static size_t      alignment()                                    { return store<RT>::alignment(); }
1898
    static void        write(imagefile* f, void* p,       const T& x) { store<RT>::write(f, p, x.value); }
1899
    static void        read (imagefile* f, const void* p, T* x)       { store<RT>::read(f, p, &x->value); }
1900
  };
1901
1902
/***********************
1903
 *
1904
 * user API on top of type translation and low-level file access
1905
 *
1906
 ***********************/
1907
1908
struct seriesi {
1909
  virtual ~seriesi() = default;
1910
  virtual const ty::desc& typeDef() const = 0;
1911
};
1912
1913
// how much space does a stored array batch use?
1914
template <typename T>
1915
0
  inline size_t batchByteCount(size_t batchSize) {
1916
0
    return sizeof(size_t) + align<size_t>(store<T>::size()*batchSize, sizeof(size_t));
1917
0
  }
1918
1919
// interface to incrementally write into a stored series
1920
template <typename T>
1921
  class wseries : public seriesi {
1922
  public:
1923
    wseries(imagefile* f, const std::string& seqname, size_t batchSize, const ty::desc& tdef = store<T>::storeType()) : tdef(tdef), f(f), seqname(seqname), batchSize(batchSize), writeCB([](uint64_t){}) {
1924
      // determine sequence types
1925
      this->stdef = storedSeqType(this->tdef, this->batchSize);
1926
1927
      // allocate space for this sequence and prepare to write
1928
      auto b = this->f->bindings.find(this->seqname);
1929
      if (b == this->f->bindings.end()) {
1930
        // this sequence is not yet defined
1931
1932
        // allocate the root node with a fresh batch
1933
        size_t dloc = findSpace(this->f, pagetype::data, sizeof(size_t), sizeof(size_t));
1934
        auto* rootRef = reinterpret_cast<uint64_t*>(mapFileData(this->f, dloc, sizeof(uint64_t)));
1935
        *rootRef = initSeqNode();
1936
        unmapFileData(this->f, rootRef, sizeof(uint64_t));
1937
1938
        // define the sequence variable with this type/location
1939
        addBinding(this->f, this->seqname, ty::encoding(this->stdef), dloc);
1940
      } else {
1941
        // the sequence is already defined, make sure it has the right type def and then resume writing to it
1942
        if (!equivModOffset(ty::decode(b->second.type), this->stdef)) {
1943
          throw std::runtime_error(
1944
            "File defines series '" + seqname + "' with inconsistent type:\n" + 
1945
            "  Expected: " + ty::show(this->stdef) + "\n" +
1946
            "  Actual:   " + ty::show(ty::decode(b->second.type))
1947
          );
1948
        } else {
1949
          const auto* rootRef = reinterpret_cast<const uint64_t*>(mapFileData(this->f, b->second.offset, sizeof(uint64_t)));
1950
          initFromSeqNode(*rootRef);
1951
          unmapFileData(this->f, rootRef, sizeof(uint64_t));
1952
        }
1953
      }
1954
    }
1955
    ~wseries() = default;
1956
1957
    const ty::desc&    typeDef()  const override { return this->tdef; }
1958
    const std::string& name()     const { return this->seqname; }
1959
    imagefile*         file()     const { return this->f; }
1960
1961
    void operator()(const T& x) {
1962
      store<T>::write(this->f, this->batchHead, x);
1963
      this->writeCB(this->batchDataRef+(this->batchHead-reinterpret_cast<uint8_t*>(this->batchCount)));
1964
      this->batchHead += store<T>::size();
1965
      if (++(*this->batchCount) == this->batchSize) {
1966
        unmapFileData(this->f, this->batchCount, batchByteCount<T>(this->batchSize));
1967
        promoteNullNode(this->batchNextRef);
1968
      }
1969
    }
1970
  public:
1971
    void setWriteCB(const std::function<void(uint64_t)>& f) {
1972
      if (this->writeCB) {
1973
        auto tmpCB = this->writeCB;
1974
        this->writeCB = [=](uint64_t fpos) { tmpCB(fpos); f(fpos); };
1975
        return;
1976
      }
1977
      this->writeCB = f;
1978
    }
1979
  private:
1980
    ty::desc tdef;  // the type for a single sequence value
1981
    ty::desc stdef; // the type for the whole sequence
1982
1983
    imagefile*                    f;          // the file we're writing into
1984
    std::string                   seqname;    // the name of this sequence in the file
1985
    size_t                        batchSize;  // the size of each batch of values within a node
1986
    std::function<void(uint64_t)> writeCB;    // post-write logic (e.g. for sequencing writes across multiple series)
1987
1988
    struct batchdef {
1989
      uint64_t varCtor;  // the 'variant tag' for this batch, by the earlier type description: 0=null, 1=batch*link pair
1990
      uint64_t batchRef; // ref to batch array data
1991
      uint64_t nextRef;  // link to next batchdef
1992
    };
1993
1994
    uint64_t  batchDataRef; // file-pointer to batch array start
1995
    uint64_t* batchCount;   // the count of values written in the current batch
1996
    uint8_t*  batchHead;    // pointer to the next location to write a value
1997
    uint64_t  batchNextRef; // file-pointer to the next batch node (should always point to a null node in the writer)
1998
1999
    // allocate an initial node (just used when defining a sequence variable for the first time)
2000
    uint64_t initSeqNode() {
2001
      auto r = allocNullNode();
2002
      promoteNullNode(r);
2003
      return r;
2004
    }
2005
2006
    // does a node ref designate a null (uninitialized) node?
2007
    bool isNullNode(uint64_t r) {
2008
      auto* n   = reinterpret_cast<batchdef*>(mapFileData(this->f, r, sizeof(batchdef)));
2009
      bool  ret = n->varCtor == 0;
2010
      unmapFileData(this->f, n, sizeof(batchdef));
2011
      return ret;
2012
    }
2013
2014
    // what node follows another?
2015
    uint64_t nextNodeRef(uint64_t r) {
2016
      auto* n   = reinterpret_cast<batchdef*>(mapFileData(this->f, r, sizeof(batchdef)));
2017
      auto  ret = n->nextRef;
2018
      unmapFileData(this->f, n, sizeof(batchdef));
2019
      return ret;
2020
    }
2021
2022
    // load initial state from existing persisted sequence
2023
    void initFromSeqNode(uint64_t r) {
2024
      while (!isNullNode(r)) {
2025
        auto s = nextNodeRef(r);
2026
        if (!isNullNode(s)) {
2027
          r = s;
2028
        } else {
2029
          // now 'r' must be the last node with data
2030
          // initialize local state from it
2031
          auto* n = reinterpret_cast<batchdef*>(mapFileData(this->f, r, sizeof(batchdef)));
2032
2033
          this->batchDataRef = n->batchRef;
2034
          this->batchCount   = reinterpret_cast<uint64_t*>(mapFileData(this->f, this->batchDataRef, batchByteCount<T>(this->batchSize)));
2035
          this->batchHead    = reinterpret_cast<uint8_t*>(this->batchCount) + sizeof(uint64_t);
2036
          this->batchNextRef = n->nextRef;
2037
2038
          unmapFileData(this->f, n, sizeof(batchdef));
2039
2040
          // just if this local state leaves us at a full batch, then we'd need to jump to the next batch
2041
          if (*this->batchCount < this->batchSize) {
2042
            this->batchHead += *this->batchCount * store<T>::size();
2043
            return;
2044
          } else {
2045
            unmapFileData(this->f, this->batchCount, batchByteCount<T>(this->batchSize));
2046
            r = s;
2047
            break;
2048
          }
2049
        }
2050
      }
2051
2052
      promoteNullNode(r);
2053
    }
2054
2055
    // initially allocate a '()+((carray T n)*x@?)' node with the null '()' case
2056
    uint64_t allocNullNode() {
2057
      return findSpace(this->f, pagetype::data, sizeof(batchdef), alignof(uint64_t));
2058
    }
2059
2060
    // mutate a stored '()+((carray T n)*x@?)' value from the left '()' case to the right '(carray T n)*x@?' case
2061
    void promoteNullNode(uint64_t nodeRef) {
2062
      auto bsz = batchByteCount<T>(this->batchSize);
2063
2064
      auto* n = reinterpret_cast<batchdef*>(mapFileData(this->f, nodeRef, sizeof(batchdef)));
2065
      n->batchRef = findSpace(this->f, pagetype::data, bsz, alignof(uint64_t));
2066
      n->nextRef  = allocNullNode();
2067
      n->varCtor  = 1;
2068
2069
      this->batchDataRef = n->batchRef;
2070
      this->batchCount   = reinterpret_cast<uint64_t*>(mapFileData(this->f, this->batchDataRef, bsz));
2071
      this->batchHead    = reinterpret_cast<uint8_t*>(this->batchCount) + sizeof(uint64_t);
2072
      this->batchNextRef = n->nextRef;
2073
2074
      unmapFileData(this->f, n, sizeof(batchdef));
2075
    }
2076
  };
2077
template <typename ... Wss>
2078
  struct seqVar {
2079
    static void stepEnc(ty::Variant::Ctors*, size_t*, const Wss& ...) { }
2080
    static ty::desc storeType(imagefile*, const Wss& ...) { return ty::prim("unit"); }
2081
    static void setWriteCBs(uint32_t, const std::function<void(uint32_t,uint64_t)>&, Wss& ...) { }
2082
  };
2083
template <typename Ws, typename ... Wss>
2084
  struct seqVar<Ws, Wss...> {
2085
    static void stepEnc(ty::Variant::Ctors* cs, size_t* c, const Ws& s, const Wss& ... wss) {
2086
      cs->push_back(ty::Variant::Ctor(s.name(), *c, ty::fileRef(s.typeDef())));
2087
      ++*c;
2088
      seqVar<Wss...>::stepEnc(cs, c, wss...);
2089
    }
2090
    static ty::desc storeType(imagefile* f, const Ws& s, const Wss& ... wss) {
2091
      if (f != s.file()) {
2092
        throw std::runtime_error("Error, can't sequence series stored in different files");
2093
      }
2094
      ty::Variant::Ctors cs;
2095
      size_t c = 0;
2096
      stepEnc(&cs, &c, s, wss...);
2097
      return ty::variant(cs);
2098
    }
2099
    static void setWriteCBs(uint32_t id, const std::function<void(uint32_t,uint64_t)>& fn, Ws& s, Wss& ... wss) {
2100
      s.setWriteCB([id,fn](uint64_t p) { fn(id, p); });
2101
      seqVar<Wss...>::setWriteCBs(id+1, fn, wss...);
2102
    }
2103
  };
2104
template <typename ... Wss>
2105
  class wsseq : public seriesi {
2106
  public:
2107
    wsseq(imagefile* f, const std::string& n, Wss& ... wss) : log(f, n, 10000, seqVar<Wss...>::storeType(f, wss...)) {
2108
      seqVar<Wss...>::setWriteCBs(0, [this](uint32_t id, uint64_t p){this->log(std::pair<uint32_t,uint64_t>(id,p));}, wss...);
2109
    }
2110
    ~wsseq() = default;
2111
    const ty::desc& typeDef()  const override { return this->log.typeDef(); }
2112
  private:
2113
    wseries<std::pair<uint32_t,size_t>> log;
2114
  };
2115
2116
// a structured data file opened for output
2117
class writer {
2118
public:
2119
0
  writer(imagefile* f) : f(f) {
2120
0
  }
2121
0
  writer(const std::string& fname) : f(openFile(fname, false)) {
2122
0
  }
2123
0
  ~writer() {
2124
0
    closeFile(this->f);
2125
0
    for (const auto& s : this->ss) {
2126
0
      delete s.second;
2127
0
    }
2128
0
  }
2129
2130
  template <typename T>
2131
    wseries<T>& series(const std::string& n, size_t batchSize = 10000) {
2132
      auto s = this->ss.find(n);
2133
      if (s != this->ss.end()) {
2134
        if (s->second->typeDef() == store<T>::storeType()) {
2135
          return *reinterpret_cast<wseries<T>*>(s->second);
2136
        } else {
2137
          throw std::runtime_error("Inconsistent usage of '" + n + "' as type " + ty::show(store<T>::storeType()) + " (but declared as type " + ty::show(s->second->typeDef()) + ")");
2138
        }
2139
      } else {
2140
        auto r = new wseries<T>(this->f, n, batchSize);
2141
        this->ss[n] = r;
2142
        return *r;
2143
      }
2144
    }
2145
2146
  template <typename ... Wss>
2147
    void recordOrdering(const std::string& n, Wss& ... wss) {
2148
      auto s = this->ss.find(n);
2149
      if (s != this->ss.end()) {
2150
        ty::desc tdesc = seqVar<Wss...>::storeType(this->f, wss...);
2151
2152
        if (s->second->typeDef() != tdesc) {
2153
          throw std::runtime_error("Inconsistent usage of '" + n + "' as type " + ty::show(tdesc) + " (but declared as type " + ty::show(s->second->typeDef()) + ")");
2154
        }
2155
      } else {
2156
        this->ss[n] = new wsseq<Wss...>(this->f, n, wss...);
2157
      }
2158
    }
2159
2160
  template <typename T, typename P = typename std::enable_if<store<T>::can_memcpy>::type>
2161
    T* define(const std::string& n) {
2162
      // is this binding defined already?
2163
      auto b = this->f->bindings.find(n);
2164
      if (b == this->f->bindings.end()) {
2165
        // binding not defined, now is the time to do it
2166
        size_t dloc = findSpace(this->f, pagetype::data, sizeof(T), alignof(T));
2167
        addBinding(this->f, n, ty::encoding(store<T>::storeType()), dloc);
2168
        return reinterpret_cast<T*>(mapFileData(this->f, dloc, sizeof(T)));
2169
      } else {
2170
        // binding already defined, just make sure that the types match
2171
        if (b->second.type != ty::encoding(store<T>::storeType())) {
2172
          throw std::runtime_error(
2173
            "File already defines '" + n + "' but with an inconsistent type.\n" +
2174
            "  Expected: " + ty::show(store<T>::storeType()) + "\n"
2175
            "  Actual:   " + ty::show(ty::decode(b->second.type))
2176
          );
2177
        }
2178
        return reinterpret_cast<T*>(mapFileData(this->f, b->second.offset, sizeof(T)));
2179
      }
2180
    }
2181
2182
  template <typename T>
2183
    void define(const std::string& n, const T& x) {
2184
      // is this binding defined already?
2185
      auto b = this->f->bindings.find(n);
2186
      if (b == this->f->bindings.end()) {
2187
        // binding not defined, now is the time to do it
2188
        size_t dloc = findSpace(this->f, pagetype::data, store<T>::size(), store<T>::alignment());
2189
        addBinding(this->f, n, ty::encoding(store<T>::storeType()), dloc);
2190
        auto* p = reinterpret_cast<uint8_t*>(mapFileData(this->f, dloc, store<T>::size()));
2191
        store<T>::write(this->f, p, x);
2192
        unmapFileData(this->f, p, store<T>::size());
2193
      } else {
2194
        // binding already defined, just make sure that the types match
2195
        if (b->second.type != ty::encoding(store<T>::storeType())) {
2196
          throw std::runtime_error(
2197
            "File already defines '" + n + "' but with an inconsistent type.\n" +
2198
            "  Expected: " + ty::show(store<T>::storeType()) + "\n"
2199
            "  Actual:   " + ty::show(ty::decode(b->second.type))
2200
          );
2201
        }
2202
        auto* p = reinterpret_cast<uint8_t*>(mapFileData(this->f, b->second.offset, store<T>::size()));
2203
        store<T>::write(this->f, p, x);
2204
        unmapFileData(this->f, p, store<T>::size());
2205
      }
2206
    }
2207
2208
  template <typename T, typename P = typename std::enable_if<store<T>::can_memcpy>::type>
2209
    array<T>* defineArray(const std::string& n, size_t len) {
2210
      // is this binding defined already?
2211
      auto b = this->f->bindings.find(n);
2212
      if (b == this->f->bindings.end()) {
2213
        // binding not defined, now is the time to do it
2214
        size_t dloc = findSpace(this->f, pagetype::data, sizeof(size_t)+len*sizeof(T), std::max<size_t>(sizeof(size_t), alignof(T)));
2215
        addBinding(this->f, n, ty::encoding(ty::array(store<T>::storeType())), dloc);
2216
        
2217
        auto* result = reinterpret_cast<array<T>*>(mapFileData(this->f, dloc, sizeof(size_t)+len*sizeof(T)));
2218
        result->size = len;
2219
        return result;
2220
      } else {
2221
        // binding already defined, make sure that the types match
2222
        if (b->second.type != ty::encoding(ty::array(store<T>::storeType()))) {
2223
          throw std::runtime_error(
2224
            "File already defines '" + n + "' but with an inconsistent type.\n" +
2225
            "  Expected: " + ty::show(ty::array(store<T>::storeType())) + "\n"
2226
            "  Actual:   " + ty::show(ty::decode(b->second.type))
2227
          );
2228
        }
2229
2230
        // optimistically map as much as needed for the requested array length
2231
        // but make sure that the array is stored with the expected length
2232
        auto* result = reinterpret_cast<array<T>*>(mapFileData(this->f, b->second.offset, sizeof(size_t)+len*sizeof(T)));
2233
2234
        if (result->size != len) {
2235
          unmapFileData(this->f, result, sizeof(size_t)+len*sizeof(T));
2236
2237
          throw std::runtime_error(
2238
            "File defines '" + n + "' with type " + ty::show(ty::array(store<T>::storeType())) +
2239
            " but with length=" + hobbes::string::from(result->size) +
2240
            " though expected length=" + hobbes::string::from(len)
2241
          );
2242
        }
2243
2244
        return result;
2245
      }
2246
    }
2247
2248
0
  void signal() { 
2249
0
    seekAbs(this->f, 0);
2250
0
    write(this->f, static_cast<uint8_t>(0x0d));
2251
0
  }
2252
2253
0
  imagefile* fileData() { return this->f; }
2254
0
  const imagefile* fileData() const { return this->f; }
2255
private:
2256
  using wseriess = std::map<std::string, seriesi *>;
2257
  imagefile* f;
2258
  wseriess   ss;
2259
};
2260
2261
// utility to wait for updates to a file (per platform)
2262
0
inline long fsWaitTickMS() {
2263
0
  struct timeval t;
2264
0
  if (gettimeofday(&t, nullptr) == 0) {
2265
0
    return (t.tv_sec*1000)+(t.tv_usec/1000);
2266
0
  } else {
2267
0
    return 0;
2268
0
  }
2269
0
}
2270
2271
#if defined(__APPLE__) && defined(__MACH__)
2272
2273
// macOS uses 'kqueue' to wait for filesystem events
2274
2275
class file_watch {
2276
public:
2277
  file_watch(const std::string&, int wfd) {
2278
    this->kq = kqueue();
2279
    if (this->kq < 0) {
2280
      throw std::runtime_error("Failed to allocate kqueue: " + std::string(strerror(errno)));
2281
    }
2282
2283
    struct kevent ke;
2284
    EV_SET(&ke, wfd, EVFILT_VNODE, EV_ADD, NOTE_DELETE | NOTE_WRITE, 0, 0);
2285
    if (kevent(this->kq, &ke, 1, 0, 0, 0) == -1) {
2286
      throw std::runtime_error("Failed to add FD to kqueue: " + std::string(strerror(errno)));
2287
    }
2288
  }
2289
  ~file_watch() {
2290
    close(this->kq);
2291
  }
2292
2293
  int wait(int maxWaitMS) {
2294
    if (maxWaitMS == 0) {
2295
      return 0;
2296
    } else if (maxWaitMS < 0) {
2297
      struct kevent evts[64];
2298
      kevent(this->kq, 0, 0, evts, sizeof(evts)/sizeof(evts[0]), 0);
2299
      return maxWaitMS;
2300
    } else {
2301
      auto t0 = fsWaitTickMS();
2302
2303
      struct timespec timeout;
2304
      timeout.tv_sec  = maxWaitMS / 1000;
2305
      timeout.tv_nsec = (maxWaitMS % 1000) * 1000000UL;
2306
2307
      struct kevent evts[64];
2308
      kevent(this->kq, 0, 0, evts, sizeof(evts)/sizeof(evts[0]), &timeout);
2309
2310
      int r = maxWaitMS - (fsWaitTickMS() - t0);
2311
      return r > 0 ? r : 0;
2312
    }
2313
  }
2314
private:
2315
  int kq;
2316
};
2317
2318
#else
2319
2320
// Linux uses inotify to signal filesystem events and e.g. epoll to wait for updates
2321
2322
class file_watch {
2323
public:
2324
0
  file_watch(const std::string& path, int) {
2325
0
    this->ifd = inotify_init();
2326
0
    if (this->ifd < 0) {
2327
0
      throw std::runtime_error("Failed to initialize inotify (" + std::string(strerror(errno)) + ")");
2328
0
    }
2329
0
    if (inotify_add_watch(this->ifd, path.c_str(), IN_MODIFY | IN_CREATE | IN_DELETE) < 0) {
2330
0
      throw std::runtime_error("failed to watch file: " + path + " (" + strerror(errno) + ")");
2331
0
    }
2332
0
2333
0
    this->ep = epoll_create(1);
2334
0
    if (this->ep < 0) {
2335
0
      throw std::runtime_error("Failed to allocate epoll FD: " + std::string(strerror(errno)));
2336
0
    }
2337
0
2338
0
    struct epoll_event evt;
2339
0
    memset(&evt, 0, sizeof(evt));
2340
0
    evt.events   = EPOLLIN | EPOLLPRI | EPOLLERR;
2341
0
    evt.data.fd  = this->ifd;
2342
0
    evt.data.ptr = nullptr;
2343
0
2344
0
    if (epoll_ctl(this->ep, EPOLL_CTL_ADD, this->ifd, &evt) != 0) {
2345
0
      throw std::runtime_error("Failed to add inotify FD to epoll set: " + std::string(strerror(errno)));
2346
0
    }
2347
0
  }
2348
0
  ~file_watch() {
2349
0
    close(this->ep);
2350
0
    close(this->ifd);
2351
0
  }
2352
2353
0
  int wait(int maxWaitMS) const {
2354
0
    if (maxWaitMS == 0) {
2355
0
      return 0;
2356
0
    } else if (maxWaitMS < 0) {
2357
0
      struct epoll_event evts[64];
2358
0
      epoll_wait(this->ep, evts, sizeof(evts)/sizeof(evts[0]), -1);
2359
0
      return maxWaitMS;
2360
0
    } else {
2361
0
      auto t0 = fsWaitTickMS();
2362
0
2363
0
      struct epoll_event evts[64];
2364
0
      int fds = epoll_wait(this->ep, evts, sizeof(evts)/sizeof(evts[0]), maxWaitMS);
2365
0
      if (fds < 0) {
2366
0
        return 0;
2367
0
      }
2368
0
2369
0
      int r = maxWaitMS - (fsWaitTickMS() - t0);
2370
0
      return r > 0 ? r : 0;
2371
0
    }
2372
0
  }
2373
private:
2374
  int ep;
2375
  int ifd;
2376
};
2377
#endif
2378
2379
// interface to incrementally read a stored series
2380
template <typename T>
2381
  class rseries : public seriesi {
2382
  public:
2383
    rseries(imagefile* f, const std::string& seqname, const ty::desc& tdef, const binding& b) : tdef(tdef), f(f), fwatch(f->path, f->fd), batchSize(inferBatchSize(b.type)) {
2384
      // determine value and sequence types
2385
      this->stdef = storedSeqType(this->tdef, this->batchSize);
2386
2387
      if (!equivModOffset(ty::decode(b.type), this->stdef)) {
2388
        throw std::runtime_error(
2389
          "File defines series '" + seqname + "' with inconsistent type:\n" + 
2390
          "  Expected: " + ty::show(this->stdef) + "\n" +
2391
          "  Actual:   " + ty::show(ty::decode(b.type))
2392
        );
2393
      }
2394
2395
      // load the initial batch
2396
      auto* n = reinterpret_cast<uint64_t*>(mapFileData(this->f, b.offset, sizeof(size_t)));
2397
      loadReadState(*n);
2398
      unmapFileData(this->f, n, sizeof(size_t));
2399
    }
2400
    rseries(imagefile* f, const std::string& seqname) : rseries(f, seqname, store<T>::storeType(), loadBinding(f, seqname)) {
2401
    }
2402
    ~rseries() = default;
2403
2404
0
    const ty::desc& typeDef() const override { return this->tdef; }
2405
0
    imagefile*      file()    const { return this->f; }
2406
2407
0
    bool next(T* x, int maxWaitMS = 0 /* <0 : infinite wait, 0 : no wait, >0 : wait up to milliseconds */) {
2408
0
      if (!ensureReadability(maxWaitMS)) {
2409
0
        return false;
2410
0
      }
2411
0
2412
0
      store<T>::read(this->f, this->head, x);
2413
0
      this->head += store<T>::size();
2414
0
      ++this->headIndex;
2415
0
      return true;
2416
0
    }
2417
  private:
2418
    ty::desc tdef;  // the type for a single sequence value
2419
    ty::desc stdef; // the type for the whole sequence
2420
2421
    imagefile* f;
2422
    file_watch fwatch;
2423
    size_t     batchSize;
2424
2425
    const uint64_t* headLen = nullptr;   // the mapped array count
2426
    const uint8_t*  head;      // pointer into mapped array data (advanced as we read)
2427
    size_t          headIndex; // read index into mapped array
2428
2429
    uint64_t curNodeRef;  // the batch node we're currently reading
2430
    uint64_t nextNodeRef; // the next batch node after this one
2431
2432
    static const binding& loadBinding(imagefile* f, const std::string& seqname) {
2433
      auto b = f->bindings.find(seqname);
2434
      if (b == f->bindings.end()) {
2435
        throw std::runtime_error("File does not define series '" + seqname + "'");
2436
      }
2437
      return b->second;
2438
    }
2439
2440
0
    void loadReadState(uint64_t n) {
2441
0
      // unload the current batch if necessary
2442
0
      auto bsz = batchByteCount<T>(this->batchSize);
2443
0
      if (this->headLen) {
2444
0
        unmapFileData(this->f, this->headLen, bsz);
2445
0
      }
2446
0
2447
0
      // reset the read position to 0 for this batch
2448
0
      this->curNodeRef = n;
2449
0
      this->headIndex  = 0;
2450
0
2451
0
      // load the node data
2452
0
      // each node looks like '()+((carray T n) * x@?)'
2453
0
      const auto* d = reinterpret_cast<const uint64_t*>(mapFileDataInFile(this->f, n, 3*sizeof(uint64_t), "series node"));
2454
0
2455
0
      // if we're in the left '()' case, our state is "null"
2456
0
      // else our state can be set to load the 'carray T n' batch in this node
2457
0
      if (d[0] != 0u) {
2458
0
        uint64_t batchRef = d[1];
2459
0
        uint64_t nextRef  = d[2];
2460
0
        unmapFileData(this->f, d, 3*sizeof(uint64_t));
2461
0
2462
0
        this->headLen     = reinterpret_cast<const uint64_t*>(mapFileDataInFile(this->f, batchRef, bsz, "series batch"));
2463
0
        this->head        = reinterpret_cast<const uint8_t*>(this->headLen) + sizeof(uint64_t);
2464
0
        this->nextNodeRef = nextRef;
2465
0
2466
0
        // the count stored with a batch says how much of it the writer has
2467
0
        // filled, and it is only ever compared against the read index, so a
2468
0
        // count larger than the batch read past the batch and on out of the
2469
0
        // file. A batch holds batchSize values and no more.
2470
0
        if (*this->headLen > this->batchSize) {
2471
0
          uint64_t claimed = *this->headLen;
2472
0
          unmapFileData(this->f, this->headLen, bsz);
2473
0
          this->headLen = nullptr;
2474
0
          this->head    = nullptr;
2475
0
          throw std::runtime_error(
2476
0
            "series batch claims " + hobbes::string::from(claimed) +
2477
0
            " values out of a batch of " + hobbes::string::from(this->batchSize)
2478
0
          );
2479
0
        }
2480
0
        return;
2481
0
      } else {
2482
0
        this->headLen     = nullptr;
2483
0
        this->head        = nullptr;
2484
0
        this->nextNodeRef = 0;
2485
0
      }
2486
0
2487
0
      unmapFileData(this->f, d, 3*sizeof(uint64_t));
2488
0
    }
2489
2490
    // is a stored node the left '()' case of '()+((carray T n) * x@?)'?
2491
0
    bool isNullNode(uint64_t n) {
2492
0
      const auto* d = reinterpret_cast<const uint64_t*>(mapFileDataInFile(this->f, n, 3*sizeof(uint64_t), "series node"));
2493
0
      bool r = d[0] == 0;
2494
0
      unmapFileData(this->f, d, 3*sizeof(uint64_t));
2495
0
      return r;
2496
0
    }
2497
2498
    // easy check to determine if we're in a state to read at least one value
2499
0
    bool canRead() const {
2500
0
      return this->headLen && this->headIndex < *this->headLen;
2501
0
    }
2502
2503
    // ensure that the current state allows reading a value from the underlying stored sequence
2504
    // this is 'true' when the current head index hasn't advanced past the current batch length
2505
    // if we're at the end of the current batch, try to move to the next batch
2506
    // if at the end of sequence, we may try to wait until the writer advances
2507
0
    bool ensureReadability(int maxWaitMS) {
2508
0
      do {
2509
0
        // easy exit
2510
0
        if (canRead()) {
2511
0
          return true;
2512
0
        }
2513
0
        
2514
0
        // if we're in a null node (ie: the root node is null), try to reload it
2515
0
        if (!this->headLen) {
2516
0
          loadReadState(this->curNodeRef);
2517
0
          if (canRead()) {
2518
0
            return true;
2519
0
          }
2520
0
        } else {
2521
0
          // we can't advance in the current batch
2522
0
          // so load the next batch if possible
2523
0
          // then ensure readability in this successor batch
2524
0
          // (followed for the edge case of a succession of empty batches)
2525
0
          //
2526
0
          // a node's successor is a stored offset like any other, so a node
2527
0
          // that points at itself -- or at any node already visited -- is a
2528
0
          // list with no end, and this walked it forever. A node occupies at
2529
0
          // least three words, so a file cannot hold more distinct nodes than
2530
0
          // that; past this many steps the links cannot all be to distinct
2531
0
          // nodes, whatever order they are in.
2532
0
          size_t steps = 0;
2533
0
          const size_t maxSteps = 1 + (this->f->file_size / (3*sizeof(uint64_t)));
2534
0
2535
0
          while (this->nextNodeRef && !isNullNode(this->nextNodeRef)) {
2536
0
            if (++steps > maxSteps) {
2537
0
              throw std::runtime_error("series node links form a cycle");
2538
0
            }
2539
0
            loadReadState(this->nextNodeRef);
2540
0
            if (canRead()) {
2541
0
              return true;
2542
0
            }
2543
0
          }
2544
0
        }
2545
0
      }
2546
0
      while ((maxWaitMS = fwatch.wait(maxWaitMS)) != 0);
2547
0
2548
0
      // we just couldn't get there
2549
0
      return false;
2550
0
    }
2551
  };
2552
class rordering {
2553
public:
2554
0
  rordering(imagefile* f, const std::string& seqname) : logDef(loadLogDef(f, seqname)), log(f, seqname, logDef.tdesc, *logDef.b) {
2555
0
  }
2556
  template <typename T>
2557
    void match(const std::string& n, const std::function<void(const T&)>& cfn) {
2558
      auto v = this->logDef.varDef.find(n);
2559
      if (v == this->logDef.varDef.end()) {
2560
        throw std::runtime_error("Constructor undefined in ordering: " + std::string(n));
2561
      }
2562
      if (ty::encoding(v->second.second) != ty::encoding(ty::fileRef(store<T>::storeType()))) {
2563
        throw std::runtime_error(
2564
          "Constructor '" + n + "' defined in ordering with inconsistent type.\n" + 
2565
          "  Expected: " + ty::show(store<T>::storeType()) + "\n" +
2566
          "  Actual:   " + ty::show(v->second.second)
2567
        );
2568
      }
2569
2570
      // bind a function to process values out of this ordering
2571
      this->logDef.varBindings[v->second.first] = [cfn](imagefile* f, uint64_t offset) {
2572
        void* d = mapFileData(f, offset, store<T>::size());
2573
        T     t;
2574
2575
        store<T>::read(f, d, &t);
2576
        cfn(t);
2577
        
2578
        unmapFileData(f, d, store<T>::size());
2579
      };
2580
    }
2581
0
  bool next() {
2582
0
    std::pair<uint32_t, uint64_t> cp;
2583
0
    while (this->log.next(&cp)) {
2584
0
      auto bf = this->logDef.varBindings.find(cp.first);
2585
0
      if (bf != this->logDef.varBindings.end()) {
2586
0
        bf->second(this->log.file(), cp.second);
2587
0
        return true;
2588
0
      }
2589
0
    }
2590
0
    return false;
2591
0
  }
2592
private:
2593
  using VarDef = std::map<std::string, std::pair<uint32_t, ty::desc>>;
2594
  using VarCtorBindings = std::unordered_map<uint32_t, std::function<void (imagefile *, uint64_t)>>;
2595
  struct LogDef {
2596
    // the encoded ordering variant type and seq binding
2597
    ty::desc       tdesc;
2598
    const binding* b;
2599
    
2600
    // the parsed ordering variant definition
2601
    VarDef varDef;
2602
2603
    // bindings to specific cases of the ordering variant
2604
    VarCtorBindings varBindings;
2605
  };
2606
  LogDef logDef;
2607
2608
0
  static LogDef loadLogDef(imagefile* f, const std::string& seqname) {
2609
0
    auto b = f->bindings.find(seqname);
2610
0
    if (b == f->bindings.end()) {
2611
0
      throw std::runtime_error("File does not define series '" + seqname + "'");
2612
0
    }
2613
0
    
2614
0
    LogDef r;
2615
0
    r.tdesc = maybeStoredBatchType(b->second.type);
2616
0
    r.b     = &b->second;
2617
0
2618
0
    if (!r.tdesc) {
2619
0
      throw std::runtime_error("File does not define '" + seqname + "' as a series.");
2620
0
    }
2621
0
2622
0
    if (r.tdesc->tid == PRIV_HPPF_TYCTOR_VARIANT) {
2623
0
      for (const auto& ctor : reinterpret_cast<ty::Variant*>(r.tdesc.get())->ctors) {
2624
0
        r.varDef[ctor.at<0>()] = std::pair<uint32_t, ty::desc>(ctor.at<1>(), ctor.at<2>());
2625
0
      }
2626
0
    }
2627
0
    return r;
2628
0
  }
2629
2630
  rseries<std::pair<uint32_t, uint64_t>> log;
2631
};
2632
2633
// a structured data file opened for input
2634
class reader {
2635
public:
2636
0
  reader(imagefile* f) : f(f) {
2637
0
  }
2638
1.43k
  reader(const std::string& fname) : f(openFile(fname, true)) {
2639
1.43k
  }
2640
46
  ~reader() {
2641
46
    closeFile(this->f);
2642
46
    for (const auto& s : this->ss) {
2643
0
      delete s.second;
2644
0
    }
2645
46
  }
2646
2647
  template <typename T>
2648
    rseries<T>& series(const std::string& name) {
2649
      auto s = this->ss.find(name);
2650
      if (s != this->ss.end()) {
2651
        ty::desc tdesc = store<T>::storeType();
2652
2653
        if (s->second->typeDef() == tdesc) {
2654
          return *reinterpret_cast<rseries<T>*>(s->second);
2655
        } else {
2656
          throw std::runtime_error("Inconsistent usage of '" + name + "' as type " + ty::show(tdesc) + " (but declared as type " + ty::show(s->second->typeDef()) + ")");
2657
        }
2658
      } else {
2659
        auto r = new rseries<T>(this->f, name);
2660
        this->ss[name] = r;
2661
        return *r;
2662
      }
2663
    }
2664
2665
0
    rordering ordering(const std::string& name) {
2666
0
      return rordering(this->f, name);
2667
0
    }
2668
2669
  template <typename T, typename P = typename std::enable_if<store<T>::can_memcpy>::type>
2670
    const T* definition(const std::string& n) {
2671
      // is this binding defined?
2672
      auto b = this->f->bindings.find(n);
2673
      if (b == this->f->bindings.end()) {
2674
        // binding not defined
2675
        throw std::runtime_error(
2676
          "Expected definition of '" + n + "' in file, but none was found"
2677
        );
2678
      } else {
2679
        // binding defined, just make sure that the types match
2680
        if (b->second.type != ty::encoding(store<T>::storeType())) {
2681
          throw std::runtime_error(
2682
            "File defines '" + n + "' but with an inconsistent type.:\n" +
2683
            "  Expected: " + ty::show(store<T>::storeType()) + "\n"
2684
            "  Actual:   " + ty::show(ty::decode(b->second.type))
2685
          );
2686
        }
2687
        return reinterpret_cast<const T*>(mapFileData(this->f, b->second.offset, sizeof(T)));
2688
      }
2689
    }
2690
2691
  template <typename T>
2692
    void definition(const std::string& n, T* x) {
2693
      // is this binding defined?
2694
      auto b = this->f->bindings.find(n);
2695
      if (b == this->f->bindings.end()) {
2696
        // binding not defined
2697
        throw std::runtime_error(
2698
          "Expected definition of '" + n + "' in file, but none was found"
2699
        );
2700
      } else {
2701
        // binding defined, just make sure that the types match
2702
        if (b->second.type != ty::encoding(store<T>::storeType())) {
2703
          throw std::runtime_error(
2704
            "File defines '" + n + "' but with an inconsistent type.:\n" +
2705
            "  Expected: " + ty::show(store<T>::storeType()) + "\n"
2706
            "  Actual:   " + ty::show(ty::decode(b->second.type))
2707
          );
2708
        }
2709
        const auto* p = reinterpret_cast<const uint8_t*>(mapFileData(this->f, b->second.offset, store<T>::size()));
2710
        store<T>::read(this->f, p, x);
2711
        unmapFileData(f, p, store<T>::size());
2712
      }
2713
    }
2714
2715
  template <typename T, typename P = typename std::enable_if<store<T>::can_memcpy>::type>
2716
    const array<T>* arrayDefinition(const std::string& n) {
2717
      // is this binding defined?
2718
      auto b = this->f->bindings.find(n);
2719
      if (b == this->f->bindings.end()) {
2720
        // binding not defined
2721
        throw std::runtime_error(
2722
          "Expected definition of '" + n + "' in file, but none was found"
2723
        );
2724
      } else if (b->second.type == ty::encoding(ty::array(store<T>::storeType()))) {
2725
        // map the array length to determine how much to map for the array
2726
        const auto* lenp = reinterpret_cast<const size_t*>(mapFileData(this->f, b->second.offset, sizeof(size_t)));
2727
        size_t        len  = *lenp;
2728
2729
        // now that we know the stored length, we can map the array contents
2730
        const auto* result = reinterpret_cast<const array<T>*>(mapFileData(this->f, b->second.offset, sizeof(size_t)+len*sizeof(T)));
2731
2732
        // and we can get rid of the map segment for the len
2733
        unmapFileData(this->f, lenp, sizeof(size_t));
2734
2735
        return result;
2736
      } else if (b->second.type == ty::encoding(ty::fileRef(ty::array(store<T>::storeType())))) {
2737
        const auto* offp = reinterpret_cast<const size_t*>(mapFileData(this->f, b->second.offset, sizeof(size_t)));
2738
        size_t off = *offp;
2739
        unmapFileData(this->f, offp, sizeof(size_t));
2740
2741
        const auto*   lenp   = reinterpret_cast<const size_t*>(mapFileData(this->f, off, sizeof(size_t)));
2742
        size_t          len    = *lenp;
2743
        const auto* result = reinterpret_cast<const array<T>*>(mapFileData(this->f, off, sizeof(size_t)+len*sizeof(T)));
2744
        unmapFileData(this->f, lenp, sizeof(size_t));
2745
        return result;
2746
      } else {
2747
        // binding defined, make sure that the types match
2748
        throw std::runtime_error(
2749
          "File defines '" + n + "' but with an inconsistent type.\n" +
2750
          "  Expected: " + ty::show(ty::array(store<T>::storeType())) + "\n"
2751
          "  Actual:   " + ty::show(ty::decode(b->second.type))
2752
        );
2753
      }
2754
    }
2755
2756
0
  imagefile* fileData() { return this->f; }
2757
0
  const imagefile* fileData() const { return this->f; }
2758
private:
2759
  using rseriess = std::map<std::string, seriesi *>;
2760
  imagefile* f;
2761
  rseriess   ss;
2762
};
2763
2764
}}
2765
2766
#endif
2767