ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
MappedRegion.h
Go to the documentation of this file.
1/**
2 * @file MappedRegion.h
3 * @brief File-backed memory region with two interchangeable backends.
4 *
5 * A @c MappedRegion owns a backing file and a base pointer to @c length()
6 * bytes of it, with @c map() / @c remap() / @c sync() / @c close(). It is
7 * the single storage primitive under @c MMappedVector and
8 * @c MMappedUUIDHashTable.
9 *
10 * Multi-process build: the region is a shared (@c MAP_SHARED) @c mmap of
11 * the file. The kernel keeps the mapping coherent across the backends and
12 * the worker and flushes dirty pages, so a backend's writes are visible to
13 * the others through the same file.
14 *
15 * Single-process build (@c PROVSQL_INPROCESS_STORE): the region is a heap
16 * buffer loaded from the file on @c map() and written back explicitly on
17 * @c sync() / @c close(). Emscripten does not support @c MAP_SHARED
18 * write-back, and with a single process a shared mapping has no purpose;
19 * the file still lives under @c $PGDATA, so PGlite persists it. Write-back
20 * timing is the caller's responsibility (the store registers an
21 * @c on_proc_exit hook so a backend flushes before it exits).
22 */
23#ifndef MAPPED_REGION_H
24#define MAPPED_REGION_H
25
26#include <cerrno>
27#include <cstddef>
28#include <cstdlib>
29#include <cstring>
30#include <stdexcept>
31
32#include <fcntl.h>
33#include <unistd.h>
34
35#include "provsql_config.h"
36
37#ifndef PROVSQL_INPROCESS_STORE
38#include <sys/mman.h>
39#endif
40
42int fd_ = -1; ///< Backing file descriptor
43void *base_ = nullptr; ///< Base of the mapped region / heap buffer
44std::size_t length_ = 0; ///< Current region length in bytes
45bool read_only_ = false; ///< Opened read-only (no write-back)
46
47public:
48MappedRegion() = default;
49MappedRegion(const MappedRegion &) = delete;
51
52/**
53 * @brief Open (creating if absent) the backing file.
54 * @return The file's current size in bytes (0 if newly created).
55 */
56std::size_t openFile(const char *filename, bool read_only) {
57 read_only_ = read_only;
58 fd_ = open(filename, O_CREAT | (read_only ? O_RDONLY : O_RDWR), 0600); // flawfinder: ignore
59 if(fd_ == -1)
60 throw std::runtime_error(strerror(errno));
61 auto size = lseek(fd_, 0, SEEK_END);
62 lseek(fd_, 0, SEEK_SET);
63 return static_cast<std::size_t>(size);
64}
65
66/** @brief Set the backing file's size.
67 *
68 * The shared-mmap backend must pre-size the file (mmap maps file-backed
69 * pages). The heap-buffer backend does not: it allocates the buffer and
70 * @c sync() extends the file with @c pwrite. Crucially, leaving the file
71 * unsized until the first @c sync() means a fresh file that is never
72 * synced (e.g. a backend that aborts before write-back) stays empty on
73 * disk and is re-initialised cleanly on reopen, rather than persisting as
74 * a full-size, never-written file whose zero header fails magic
75 * validation. */
76void resizeFile(std::size_t length) {
77#ifdef PROVSQL_INPROCESS_STORE
78 (void) length;
79#else
80 if(ftruncate(fd_, length))
81 throw std::runtime_error(strerror(errno));
82#endif
83}
84
85/** @brief Establish the initial region of @p length bytes over the file. */
86void map(std::size_t length) {
87#ifdef PROVSQL_INPROCESS_STORE
88 base_ = malloc(length);
89 if(!base_)
90 throw std::runtime_error("ProvSQL: out of memory mapping region");
91 ssize_t r = pread(fd_, base_, length, 0); // flawfinder: ignore
92 if(r < 0)
93 throw std::runtime_error(strerror(errno));
94 if(static_cast<std::size_t>(r) < length)
95 memset(static_cast<char *>(base_) + r, 0, length - static_cast<std::size_t>(r));
96#else
97 base_ = ::mmap(nullptr, length, PROT_READ | (read_only_ ? 0 : PROT_WRITE),
98 MAP_SHARED, fd_, 0);
99 if(base_ == MAP_FAILED)
100 throw std::runtime_error(strerror(errno));
101#endif
102 length_ = length;
103}
104
105/** @brief Grow the region to @p new_length, preserving existing content. */
106void remap(std::size_t new_length) {
107#ifdef PROVSQL_INPROCESS_STORE
108 resizeFile(new_length);
109 void *p = realloc(base_, new_length);
110 if(!p)
111 throw std::runtime_error("ProvSQL: out of memory growing region");
112 base_ = p;
113 if(new_length > length_)
114 memset(static_cast<char *>(base_) + length_, 0, new_length - length_);
115#else
116 if(::munmap(base_, length_))
117 throw std::runtime_error(strerror(errno));
118 resizeFile(new_length);
119 base_ = ::mmap(nullptr, new_length, PROT_READ | (read_only_ ? 0 : PROT_WRITE),
120 MAP_SHARED, fd_, 0);
121 if(base_ == MAP_FAILED)
122 throw std::runtime_error(strerror(errno));
123#endif
124 length_ = new_length;
125}
126
127/** @brief Flush the region to the backing file (no-op when read-only). */
128void sync() {
129 if(read_only_ || !base_)
130 return;
131#ifdef PROVSQL_INPROCESS_STORE
132 if(pwrite(fd_, base_, length_, 0) < 0) // flawfinder: ignore
133 throw std::runtime_error(strerror(errno));
134#else
135 msync(base_, length_, MS_SYNC);
136#endif
137}
138
139/** @brief Write back (if writable) and release the region and file. */
140void close() {
141 if(base_) {
142#ifdef PROVSQL_INPROCESS_STORE
143 sync();
144 free(base_);
145#else
146 ::munmap(base_, length_);
147#endif
148 base_ = nullptr;
149 }
150 if(fd_ != -1) {
151 ::close(fd_);
152 fd_ = -1;
153 }
154}
155
156void *base() const { return base_; }
157std::size_t length() const { return length_; }
158};
159
160#endif /* MAPPED_REGION_H */
void close()
Write back (if writable) and release the region and file.
std::size_t openFile(const char *filename, bool read_only)
Open (creating if absent) the backing file.
void map(std::size_t length)
Establish the initial region of length bytes over the file.
void remap(std::size_t new_length)
Grow the region to new_length, preserving existing content.
bool read_only_
Opened read-only (no write-back).
MappedRegion(const MappedRegion &)=delete
std::size_t length() const
std::size_t length_
Current region length in bytes.
int fd_
Backing file descriptor.
void * base_
Base of the mapped region / heap buffer.
MappedRegion()=default
void sync()
Flush the region to the backing file (no-op when read-only).
MappedRegion & operator=(const MappedRegion &)=delete
void resizeFile(std::size_t length)
Set the backing file's size.
void * base() const
Build-configuration switches shared across the C and C++ sources.