xapian-core  2.0.0
honey_version.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2006,2007,2008,2009,2010,2013,2014,2015,2016,2017,2018 Olly Betts
5  * Copyright (C) 2011 Dan Colish
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see
19  * <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 
24 #include "honey_version.h"
25 
26 #include "debuglog.h"
27 #include "fd.h"
28 #include "honey_defs.h"
29 #include "io_utils.h"
30 #include "min_non_zero.h"
31 #include "omassert.h"
32 #include "pack.h"
33 #include "posixy_wrapper.h"
34 #include "stringutils.h" // For STRINGIZE() and CONST_STRLEN().
35 
36 #include <cerrno>
37 #include <cstring> // For memcmp().
38 #include <string>
39 #include <sys/types.h>
40 #include "safesysstat.h"
41 #include "safefcntl.h"
42 #include "safeunistd.h"
43 #include "str.h"
44 #include "stringutils.h"
45 
46 #include "backends/uuids.h"
47 
48 #include "xapian/constants.h"
49 #include "xapian/error.h"
50 
51 using namespace std;
52 
54 #define HONEY_FORMAT_VERSION DATE_TO_VERSION(2018,4,3)
55 // 2018,4,3 2.0.0 outlaw mixed-wdf terms
56 // 2018,3,28 don't special case first entry in SSTable
57 // 2018,3,27 new key format for value stats, value chunks, doclen chunks
58 // 2018,3,26 use known suffix from spelling B and T keys
59 // 2018,3,25 use known prefix from spelling B and H keys
60 // 2018,3,15 avoid storing flat wdf
61 // 2018,3,14 store per term wdf_max
62 // 2018,3,12 binary chop index
63 // 2018,3,11 spelling key encoding changed
64 // 2018,2,22 index valuestream chunks by last docid in chunk
65 // 2018,2,21 index doclen chunks by last docid in chunk
66 // 2018,2,20 implement array index
67 // 2018,2,19 allow 1,2,3 as well as 4 byte doc length width
68 // 2018,2,2 special case tf=2; first_wdf = floor(collfreq/2)
69 // 2018,2,1 pack_uint for postlist data
70 // 2018,1,31 Special case postlist when termfreq==2
71 // 2018,1,30 More compact postlist chunk headers
72 // 2018,1,23 Elide last-first for single occurrence terms
73 // 2018,1,4 Merge values used and terms used
74 // 2018,1,3 Table start offset in RootInfo
75 // 2017,12,30 Value stats key changes
76 // 2017,12,29 User metadata key changes
77 // 2017,12,5 New Honey backend
78 
80 #define DATE_TO_VERSION(Y,M,D) \
81  ((unsigned(Y) - 2014) << 9 | unsigned(M) << 5 | unsigned(D))
82 #define VERSION_TO_YEAR(V) ((unsigned(V) >> 9) + 2014)
83 #define VERSION_TO_MONTH(V) ((unsigned(V) >> 5) & 0x0f)
84 #define VERSION_TO_DAY(V) (unsigned(V) & 0x1f)
85 
86 #define HONEY_VERSION_MAGIC_LEN 14
87 #define HONEY_VERSION_MAGIC_AND_VERSION_LEN 16
88 
90  '\x0f', '\x0d', 'X', 'a', 'p', 'i', 'a', 'n', ' ', 'H', 'o', 'n', 'e', 'y',
91  char((HONEY_FORMAT_VERSION >> 8) & 0xff), char(HONEY_FORMAT_VERSION & 0xff)
92 };
93 
95  : fd(fd_), db_dir()
96 {
97  offset = lseek(fd, 0, SEEK_CUR);
98  if (rare(offset < 0)) {
99  string msg = "lseek failed on file descriptor ";
100  msg += str(fd);
101  throw Xapian::DatabaseOpeningError(msg, errno);
102  }
103 }
104 
106 {
107  // Either this is a single-file database, or this fd is from opening a new
108  // version file in write(), but sync() was never called.
109  if (fd != -1)
110  (void)::close(fd);
111 }
112 
113 void
115 {
116  LOGCALL_VOID(DB, "HoneyVersion::read", NO_ARGS);
117  FD close_fd(-1);
118  int fd_in;
119  if (single_file()) {
120  if (rare(lseek(fd, offset, SEEK_SET) < 0)) {
121  string msg = "Failed to rewind file descriptor ";
122  msg += str(fd);
123  throw Xapian::DatabaseOpeningError(msg, errno);
124  }
125  fd_in = fd;
126  } else {
127  string filename = db_dir;
128  filename += "/iamhoney";
129  fd_in = posixy_open(filename.c_str(), O_RDONLY|O_BINARY);
130  if (rare(fd_in < 0)) {
131  string msg = filename;
132  msg += ": Failed to open honey revision file for reading";
133  if (errno == ENOENT || errno == ENOTDIR) {
134  throw Xapian::DatabaseNotFoundError(msg, errno);
135  }
136  throw Xapian::DatabaseOpeningError(msg, errno);
137  }
138  close_fd = fd_in;
139  }
140 
141  char buf[256];
142 
143  const char* p = buf;
144  const char* end = p + io_read(fd_in, buf, sizeof(buf), 33);
145 
146  if (memcmp(buf, HONEY_VERSION_MAGIC, HONEY_VERSION_MAGIC_LEN) != 0)
147  throw Xapian::DatabaseCorruptError("Rev file magic incorrect");
148 
149  unsigned version;
150  version = static_cast<unsigned char>(buf[HONEY_VERSION_MAGIC_LEN]);
151  version <<= 8;
152  version |= static_cast<unsigned char>(buf[HONEY_VERSION_MAGIC_LEN + 1]);
153  if (version != HONEY_FORMAT_VERSION) {
154  string msg;
155  if (!single_file()) {
156  msg = db_dir;
157  msg += ": ";
158  }
159  msg += "Database is format version ";
160  msg += str(VERSION_TO_YEAR(version) * 10000 +
161  VERSION_TO_MONTH(version) * 100 +
162  VERSION_TO_DAY(version));
163  msg += " but I only understand ";
164  msg += str(VERSION_TO_YEAR(HONEY_FORMAT_VERSION) * 10000 +
167  throw Xapian::DatabaseVersionError(msg);
168  }
169 
171  uuid.assign(p);
172  p += uuid.BINARY_SIZE;
173 
174  if (!unpack_uint(&p, end, &rev)) {
175  throw Xapian::DatabaseCorruptError("Rev file failed to decode "
176  "revision");
177  }
178 
179  for (unsigned table_no = 0; table_no < Honey::MAX_; ++table_no) {
180  if (!root[table_no].unserialise(&p, end)) {
181  throw Xapian::DatabaseCorruptError("Rev file root_info missing");
182  }
183  old_root[table_no] = root[table_no];
184  }
185 
186  // For a single-file database, this will assign extra data. We read
187  // sizeof(buf) above, then skip HONEY_VERSION_MAGIC_AND_VERSION_LEN,
188  // then 16, then the size of the serialised root info.
189  serialised_stats.assign(p, end);
191 }
192 
193 void
195 {
196  serialised_stats.resize(0);
198  // last_docid must always be >= doccount.
202  // doclen_ubound should always be >= wdf_ubound, so we store the
203  // difference as it may encode smaller. wdf_ubound is likely to
204  // be larger than doclen_lbound.
209  // If total_doclen == 0 then unique_terms is always zero (or there are no
210  // documents at all) so storing these just complicates things because
211  // uniq_terms_lbound could legitimately be zero.
212  if (total_doclen != 0) {
213  // We rely on uniq_terms_lbound being non-zero to detect if it's present
214  // for a single file DB.
218  }
219 }
220 
221 void
223 {
224  const char* p = serialised_stats.data();
225  const char* end = p + serialised_stats.size();
226  if (p == end) {
227  doccount = 0;
228  total_doclen = 0;
229  last_docid = 0;
230  doclen_lbound = 0;
231  doclen_ubound = 0;
232  wdf_ubound = 0;
233  oldest_changeset = 0;
235  uniq_terms_lbound = 0;
236  uniq_terms_ubound = 0;
237  return;
238  }
239 
240  if (!unpack_uint(&p, end, &doccount) ||
241  !unpack_uint(&p, end, &last_docid) ||
242  !unpack_uint(&p, end, &doclen_lbound) ||
243  !unpack_uint(&p, end, &wdf_ubound) ||
244  !unpack_uint(&p, end, &doclen_ubound) ||
245  !unpack_uint(&p, end, &oldest_changeset) ||
246  !unpack_uint(&p, end, &total_doclen) ||
248  const char* m = p ?
249  "Bad serialised DB stats (overflowed)" :
250  "Bad serialised DB stats (out of data)";
252  }
253 
254  // last_docid must always be >= doccount.
255  last_docid += doccount;
256  // doclen_ubound should always be >= wdf_ubound, so we store the
257  // difference as it may encode smaller. wdf_ubound is likely to
258  // be larger than doclen_lbound.
260 
261  // We don't check if there's undecoded data between p and end - in the
262  // single-file DB case there will be extra zero bytes in serialised_stats,
263  // and more generally it's useful to be able to add new stats when it is
264  // safe for old versions to just ignore them and there are sensible values
265  // to use when a new version reads an old database.
266 
267  // Read bounds on unique_terms if stored. This test relies on the first
268  // byte of pack_uint(x) being zero if and only if x is zero, and on
269  // uniq_terms_lbound being non-zero.
270  if (p == end || *p == '\0') {
271  // No bounds stored so use weak bounds based on other stats.
272  if (total_doclen == 0) {
274  } else {
275  Assert(doclen_lbound != 0);
276  Assert(wdf_ubound != 0);
279  }
280  } else if (!unpack_uint(&p, end, &uniq_terms_lbound) ||
281  !unpack_uint(&p, end, &uniq_terms_ubound)) {
282  const char* m = p ?
283  "Bad serialised unique_terms bounds (overflowed)" :
284  "Bad serialised unique_terms bounds (out of data)";
286  }
287 }
288 
289 void
291 {
292  doccount += o.get_doccount();
293  if (doccount < o.get_doccount()) {
294  throw Xapian::DatabaseError("doccount overflowed!");
295  }
296 
301  if (total_doclen < o.get_total_doclen()) {
302  throw Xapian::DatabaseError("Total document length overflowed!");
303  }
304 
305  // The upper bounds might be on the same word, so we must sum them.
307 
312 }
313 
314 void
316  Xapian::termcount o_doclen_lbound,
317  Xapian::termcount o_doclen_ubound,
318  Xapian::termcount o_wdf_ubound,
319  Xapian::totallength o_total_doclen,
320  Xapian::termcount o_spelling_wordfreq_ubound,
321  Xapian::termcount o_uniq_terms_lbound,
322  Xapian::termcount o_uniq_terms_ubound)
323 {
324  doccount += o_doccount;
325  if (doccount < o_doccount) {
326  throw Xapian::DatabaseError("doccount overflowed!");
327  }
328 
329  doclen_lbound = min_non_zero(doclen_lbound, o_doclen_lbound);
330  doclen_ubound = max(doclen_ubound, o_doclen_ubound);
331  wdf_ubound = max(wdf_ubound, o_wdf_ubound);
332  total_doclen += o_total_doclen;
333  if (total_doclen < o_total_doclen) {
334  throw Xapian::DatabaseError("Total document length overflowed!");
335  }
336 
337  // The upper bounds might be on the same word, so we must sum them.
338  spelling_wordfreq_ubound += o_spelling_wordfreq_ubound;
339 
340  uniq_terms_lbound = min_non_zero(uniq_terms_lbound, o_uniq_terms_lbound);
341  uniq_terms_ubound = max(uniq_terms_ubound, o_uniq_terms_ubound);
342 }
343 
344 void
346 {
347  LOGCALL_VOID(DB, "HoneyVersion::cancel", NO_ARGS);
348  for (unsigned table_no = 0; table_no < Honey::MAX_; ++table_no) {
349  root[table_no] = old_root[table_no];
350  }
352 }
353 
354 const string
356 {
357  LOGCALL(DB, const string, "HoneyVersion::write", new_rev|flags);
358 
360  s.append(uuid.data(), uuid.BINARY_SIZE);
361 
362  pack_uint(s, new_rev);
363 
364  for (unsigned table_no = 0; table_no < Honey::MAX_; ++table_no) {
365  root[table_no].serialise(s);
366  }
367 
368  // Serialise database statistics.
369  serialise_stats();
370  s += serialised_stats;
371 
372  string tmpfile;
373  if (!single_file()) {
374  tmpfile = db_dir;
375  // In dangerous mode, just write the new version file in place.
376  if (flags & Xapian::DB_DANGEROUS)
377  tmpfile += "/iamhoney";
378  else
379  tmpfile += "/v.tmp";
380 
381  int open_flags = O_CREAT|O_TRUNC|O_WRONLY|O_BINARY;
382  fd = posixy_open(tmpfile.c_str(), open_flags, 0666);
383  if (rare(fd < 0)) {
384  string msg = "Couldn't write new rev file: ";
385  msg += tmpfile;
386  throw Xapian::DatabaseOpeningError(msg, errno);
387  }
388 
389  if (flags & Xapian::DB_DANGEROUS)
390  tmpfile = string();
391  }
392 
393  try {
394  io_write(fd, s.data(), s.size());
395  } catch (...) {
396  if (!single_file())
397  (void)close(fd);
398  throw;
399  }
400 
401  RETURN(tmpfile);
402 }
403 
404 bool
405 HoneyVersion::sync(const string& tmpfile,
406  honey_revision_number_t new_rev, int flags)
407 {
408  Assert(new_rev > rev || rev == 0);
409 
410  if (single_file()) {
411  if ((flags & Xapian::DB_NO_SYNC) == 0 &&
412  ((flags & Xapian::DB_FULL_SYNC) ?
413  !io_full_sync(fd) :
414  !io_sync(fd))) {
415  // FIXME what to do?
416  }
417  } else {
418  int fd_to_close = fd;
419  fd = -1;
420  if ((flags & Xapian::DB_NO_SYNC) == 0 &&
421  ((flags & Xapian::DB_FULL_SYNC) ?
422  !io_full_sync(fd_to_close) :
423  !io_sync(fd_to_close))) {
424  int save_errno = errno;
425  (void)close(fd_to_close);
426  if (!tmpfile.empty())
427  (void)unlink(tmpfile.c_str());
428  errno = save_errno;
429  return false;
430  }
431 
432  if (close(fd_to_close) != 0) {
433  if (!tmpfile.empty()) {
434  int save_errno = errno;
435  (void)unlink(tmpfile.c_str());
436  errno = save_errno;
437  }
438  return false;
439  }
440 
441  if (!tmpfile.empty()) {
442  if (!io_tmp_rename(tmpfile, db_dir + "/iamhoney")) {
443  return false;
444  }
445  }
446  }
447 
448  for (unsigned table_no = 0; table_no < Honey::MAX_; ++table_no) {
449  old_root[table_no] = root[table_no];
450  }
451 
452  rev = new_rev;
453  return true;
454 }
455 
456 /* Only try to compress tags strictly longer than this many bytes.
457  *
458  * This can theoretically usefully be set as low as 4, but in practical terms
459  * zlib can't compress in very many cases for short inputs and even when it can
460  * the savings are small, so we default to a higher threshold to save CPU time
461  * for marginal size reductions.
462  */
463 const size_t COMPRESS_MIN = 18;
464 
465 static const uint4 compress_min_tab[] = {
466  0, // POSTLIST
467  COMPRESS_MIN, // DOCDATA
468  COMPRESS_MIN, // TERMLIST
469  0, // POSITION
470  COMPRESS_MIN, // SPELLING
471  COMPRESS_MIN // SYNONYM
472 };
473 
474 void
476 {
477  uuid.generate();
478  for (unsigned table_no = 0; table_no < Honey::MAX_; ++table_no) {
479  root[table_no].init(compress_min_tab[table_no]);
480  }
481 }
482 
483 namespace Honey {
484 
485 void
486 RootInfo::init(uint4 compress_min_)
487 {
488  offset = 0;
489  root = 0;
490  num_entries = 0;
491  compress_min = compress_min_;
492  fl_serialised.resize(0);
493 }
494 
495 void
496 RootInfo::serialise(string& s) const
497 {
498  AssertRel(offset, >=, 0);
499  std::make_unsigned_t<off_t> uoffset = offset;
500  AssertRel(root, >=, offset);
501  pack_uint(s, uoffset);
502  pack_uint(s, root - uoffset);
503  pack_uint(s, 0u);
505  pack_uint(s, 2048u >> 11);
508 }
509 
510 bool
511 RootInfo::unserialise(const char** p, const char* end)
512 {
513  std::make_unsigned_t<off_t> uoffset, uroot;
514  unsigned dummy_val;
515  unsigned dummy_blocksize;
516  if (!unpack_uint(p, end, &uoffset) ||
517  !unpack_uint(p, end, &uroot) ||
518  !unpack_uint(p, end, &dummy_val) ||
519  !unpack_uint(p, end, &num_entries) ||
520  !unpack_uint(p, end, &dummy_blocksize) ||
521  !unpack_uint(p, end, &compress_min) ||
522  !unpack_string(p, end, fl_serialised)) return false;
523  offset = uoffset;
524  root = uoffset + uroot;
525  // Not meaningful, but still there so that existing honey databases
526  // continue to work.
527  (void)dummy_val;
528  (void)dummy_blocksize;
529  // Map old default to new default.
530  if (compress_min == 4) {
532  }
533  return true;
534 }
535 
536 }
Definition: fd.h:30
The HoneyVersion class manages the revision files.
Definition: honey_version.h:79
void create()
Create the version file.
Xapian::termcount get_spelling_wordfreq_upper_bound() const
Xapian::doccount doccount
The number of documents in the database.
Xapian::termcount doclen_lbound
A lower bound on the smallest document length in this database.
honey_revision_number_t oldest_changeset
Oldest changeset removed when max_changesets is set.
Xapian::termcount doclen_ubound
An upper bound on the greatest document length in this database.
void merge_stats(const HoneyVersion &o)
Merge the database stats.
off_t offset
Offset into the file at which the version data starts.
bool sync(const std::string &tmpfile, honey_revision_number_t new_rev, int flags)
Xapian::termcount get_doclength_lower_bound() const
const std::string write(honey_revision_number_t new_rev, int flags)
Xapian::totallength get_total_doclen() const
Xapian::termcount get_unique_terms_upper_bound() const
honey_revision_number_t rev
Definition: honey_version.h:80
std::string db_dir
The database directory.
Xapian::docid last_docid
Greatest document id ever used in this database.
Xapian::totallength total_doclen
The total of the lengths of all documents in the database.
int fd
File descriptor.
Definition: honey_version.h:96
Honey::RootInfo old_root[Honey::MAX_]
Definition: honey_version.h:83
HoneyVersion(std::string_view db_dir_)
Xapian::termcount spelling_wordfreq_ubound
An upper bound on the spelling wordfreq in this database.
Xapian::termcount get_unique_terms_lower_bound() const
void unserialise_stats()
void read()
Read the version file and check it's a version we understand.
Xapian::termcount get_wdf_upper_bound() const
bool single_file() const
std::string serialised_stats
The serialised database stats.
void serialise_stats()
Honey::RootInfo root[Honey::MAX_]
Definition: honey_version.h:82
Xapian::doccount get_doccount() const
Xapian::termcount uniq_terms_ubound
An upper bound on the number of unique terms in a document in this database.
Xapian::termcount uniq_terms_lbound
A lower bound on the number of unique terms in a document in this database.
Xapian::termcount get_doclength_upper_bound() const
Xapian::termcount wdf_ubound
An upper bound on the greatest wdf in this database.
Uuid uuid
The UUID of this database.
Definition: honey_version.h:86
void init(uint4 compress_min_)
bool unserialise(const char **p, const char *end)
uint4 compress_min
Should be >= 4 or 0 for no compression.
Definition: honey_version.h:45
std::string fl_serialised
Definition: honey_version.h:46
void serialise(std::string &s) const
honey_tablesize_t num_entries
Definition: honey_version.h:43
void generate()
Definition: uuids.cc:63
static constexpr unsigned BINARY_SIZE
The size of a UUID in bytes.
Definition: uuids.h:31
void assign(const char *p)
Definition: uuids.h:64
const char * data() const
Definition: uuids.h:60
DatabaseCorruptError indicates database corruption was detected.
Definition: error.h:397
DatabaseError indicates some sort of database related error.
Definition: error.h:355
Indicates an attempt to access a database not present.
Definition: error.h:1043
DatabaseOpeningError indicates failure to open a database.
Definition: error.h:569
DatabaseVersionError indicates that a database is in an unsupported format.
Definition: error.h:620
#define rare(COND)
Definition: config.h:607
Constants in the Xapian namespace.
PositionList * p
Debug logging macros.
#define RETURN(...)
Definition: debuglog.h:484
#define LOGCALL(CATEGORY, TYPE, FUNC, PARAMS)
Definition: debuglog.h:478
#define LOGCALL_VOID(CATEGORY, FUNC, PARAMS)
Definition: debuglog.h:479
Hierarchy of classes which Xapian can throw as exceptions.
Wrapper class around a file descriptor to avoid leaks.
int close(FD &fd)
Definition: fd.h:63
Definitions, types, etc for use inside honey.
uint4 honey_revision_number_t
The revision number of a honey database.
Definition: honey_defs.h:104
const size_t COMPRESS_MIN
#define VERSION_TO_MONTH(V)
#define HONEY_VERSION_MAGIC_LEN
static const char HONEY_VERSION_MAGIC[HONEY_VERSION_MAGIC_AND_VERSION_LEN]
#define HONEY_FORMAT_VERSION
Honey format version (date of change):
#define VERSION_TO_DAY(V)
#define VERSION_TO_YEAR(V)
#define HONEY_VERSION_MAGIC_AND_VERSION_LEN
static const uint4 compress_min_tab[]
HoneyVersion class.
uint32_t uint4
Definition: internaltypes.h:31
void io_write(int fd, const char *p, size_t n)
Write n bytes from block pointed to by p to file descriptor fd.
Definition: io_utils.cc:263
size_t io_read(int fd, char *p, size_t n, size_t min)
Read n bytes (or until EOF) into block pointed to by p from file descriptor fd.
Definition: io_utils.cc:241
bool io_tmp_rename(const std::string &tmp_file, const std::string &real_file)
Rename a temporary file to its final position.
Definition: io_utils.cc:573
Wrappers for low-level POSIX I/O routines.
bool io_sync(int fd)
Ensure all data previously written to file descriptor fd has been written to disk.
Definition: io_utils.h:107
bool io_full_sync(int fd)
Definition: io_utils.h:122
Return the smaller of two numbers which isn't zero.
constexpr std::enable_if_t< std::is_unsigned_v< T >, T > min_non_zero(const T &a, const T &b)
Return the smaller of two unsigned integers which isn't zero.
Definition: min_non_zero.h:39
@ MAX_
Definition: honey_defs.h:75
string str(int value)
Convert int to std::string.
Definition: str.cc:91
unsigned XAPIAN_TERMCOUNT_BASE_TYPE termcount
A counts of terms.
Definition: types.h:64
const int DB_NO_SYNC
Don't attempt to ensure changes have hit disk.
Definition: constants.h:65
unsigned XAPIAN_DOCID_BASE_TYPE doccount
A count of documents.
Definition: types.h:37
const int DB_FULL_SYNC
Try to ensure changes are really written to disk.
Definition: constants.h:82
XAPIAN_TOTALLENGTH_TYPE totallength
The total length of all documents in a database.
Definition: types.h:114
const int DB_DANGEROUS
Update the database in-place.
Definition: constants.h:102
Various assertion macros.
#define AssertRel(A, REL, B)
Definition: omassert.h:123
#define Assert(COND)
Definition: omassert.h:122
Pack types into strings and unpack them again.
bool unpack_string(const char **p, const char *end, std::string &result)
Decode a std::string from a string.
Definition: pack.h:468
bool unpack_uint(const char **p, const char *end, U *result)
Decode an unsigned integer from a string.
Definition: pack.h:346
void pack_uint(std::string &s, U value)
Append an encoded unsigned integer to a string.
Definition: pack.h:315
void pack_string(std::string &s, std::string_view value)
Append an encoded std::string to a string.
Definition: pack.h:442
Provides wrappers with POSIXy semantics.
#define posixy_open
include <fcntl.h>, but working around broken platforms.
#define O_BINARY
Definition: safefcntl.h:80
include <sys/stat.h> with portability enhancements
<unistd.h>, but with compat.
Convert types to std::string.
Various handy string-related helpers.
Class for handling UUIDs.