1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
/*
* bstack — persistent, fsync-durable binary stack backed by a single file.
*
* File format (16-byte header followed by payload):
* [0..8) magic: "BSTK" + major(1) + minor(1) + patch(2) + reserved(1)
* [8..16) committed payload length, little-endian uint64
* [16..) payload bytes
*
* All logical offsets are 0-based from the start of the payload region.
*
* Error handling
* --------------
* bstack_open returns NULL on failure; errno is set by the failing syscall,
* or to EINVAL for bad/short headers, or to EWOULDBLOCK when
* another process holds the exclusive lock.
* All other functions return 0 on success, -1 on failure with errno set.
*
* Thread safety
* -------------
* On Unix a pthread_rwlock protects each handle; on Windows an SRWLOCK is
* used. bstack_push / bstack_extend / bstack_pop / bstack_discard /
* bstack_set / bstack_zero / bstack_atrunc / bstack_splice /
* bstack_try_extend / bstack_try_discard(s, n>0) / bstack_swap / bstack_cas /
* bstack_replace / bstack_process
* hold a write lock. bstack_try_discard(s, 0) holds a read lock.
* bstack_peek / bstack_get / bstack_len hold a read lock and may run
* concurrently with each other on both platforms.
*
* Multi-process safety
* --------------------
* bstack_open acquires an exclusive advisory lock on the file:
* Unix — flock(LOCK_EX|LOCK_NB)
* Windows — LockFileEx(LOCKFILE_EXCLUSIVE_LOCK|LOCKFILE_FAIL_IMMEDIATELY)
* The lock is released when bstack_close is called (fd / HANDLE is closed).
*
* Feature flags
* -------------
* Compile with -DBSTACK_FEATURE_SET to enable bstack_set and bstack_zero.
* Compile with -DBSTACK_FEATURE_ATOMIC to enable bstack_atrunc, bstack_splice,
* bstack_try_extend, bstack_try_discard, and bstack_replace. Both flags
* together also enable bstack_swap, bstack_cas, and bstack_process.
*/
typedef struct bstack bstack_t;
extern "C" BSTACK_FEATURE_SET
/*
* Overwrite len bytes in place starting at logical offset.
* The file size is never changed. An empty slice is a valid no-op.
* Returns EINVAL if offset + len would exceed the payload size or overflow
* uint64_t.
*
* Only available when compiled with -DBSTACK_FEATURE_SET.
*/
int ;
/*
* Overwrite n bytes with zeros in place starting at logical offset.
* The file size is never changed. n = 0 is a valid no-op.
* Returns EINVAL if offset + n would exceed the payload size or overflow
* uint64_t.
*
* Only available when compiled with -DBSTACK_FEATURE_SET.
*/
int ;
/* BSTACK_FEATURE_SET */
/*
* Atomically cut n bytes off the tail then append buf_len bytes from buf.
*
* The write ordering is chosen for crash safety: when buf_len > n (net
* extension) the file is extended before writing buf so a crash before the
* committed-length update cleanly rolls back to the original state; when
* buf_len <= n (net truncation or same size) buf is written first, then the
* file is truncated, so a crash after truncation is committed by recovery.
*
* n = 0 with buf_len = 0 is a valid no-op.
* Returns EINVAL if n exceeds the current payload size.
*
* Only available when compiled with -DBSTACK_FEATURE_ATOMIC.
*/
int ;
/*
* Atomically pop n bytes from the tail into removed, then append new_len
* bytes from new_buf.
*
* removed must point to at least n bytes of caller-allocated storage;
* it may be NULL when n == 0. Uses the same two-path ordering strategy as
* bstack_atrunc.
*
* Returns EINVAL if n exceeds the current payload size.
*
* Only available when compiled with -DBSTACK_FEATURE_ATOMIC.
*/
int ;
/*
* Append buf_len bytes from buf only if the current logical payload size
* equals s.
*
* *ok (if non-NULL) is set to 1 when the condition matched and the append was
* performed, or 0 when the size did not match (no-op).
* Returns 0 on success (condition-matched or not), -1 on I/O error.
*
* Only available when compiled with -DBSTACK_FEATURE_ATOMIC.
*/
int ;
/*
* Discard n bytes only if the current logical payload size equals s.
*
* *ok (if non-NULL) is set to 1 when the condition matched and n bytes were
* removed, or 0 when the size did not match (no-op).
* When n == 0 only the read lock is taken; the file is not modified.
* Returns EINVAL if n exceeds the current payload size (only checked when
* the size condition matches).
*
* Only available when compiled with -DBSTACK_FEATURE_ATOMIC.
*/
int ;
/*
* Pop n bytes from the tail, pass them read-only to the callback, then write
* whatever the callback produces as the new tail.
*
* The callback signature is:
* int cb(const uint8_t *old, size_t old_len,
* uint8_t **new_buf, size_t *new_len, void *ctx)
*
* The callback must set *new_buf to a malloc'd buffer (or NULL when
* *new_len == 0) and *new_len to its byte length, then return 0 on success.
* bstack calls free(*new_buf) after writing; the caller must not free it.
* If the callback returns -1 the operation is aborted (errno set by the
* callback); *new_buf is not freed by bstack in that case.
*
* The file may grow or shrink according to *new_len; the same two-path
* crash-safe ordering as bstack_atrunc is used. n = 0 is valid (old is
* NULL and old_len is 0). Returns EINVAL if n exceeds the payload size.
*
* Only available when compiled with -DBSTACK_FEATURE_ATOMIC.
*/
int ;
/* BSTACK_FEATURE_ATOMIC */
/*
* Atomically read len bytes at logical offset into old_buf and overwrite
* them with new_buf. The file size is never changed.
*
* old_buf and new_buf must each point to at least len bytes; they may overlap
* only if old_buf == new_buf (a no-op swap).
* len == 0 is a valid no-op.
* Returns EINVAL if offset + len would exceed the payload size or overflow
* uint64_t.
*
* Only available when compiled with both -DBSTACK_FEATURE_SET and
* -DBSTACK_FEATURE_ATOMIC.
*/
int ;
/*
* Compare-and-exchange: read len bytes at logical offset and, if they equal
* old_buf, overwrite them with new_buf.
*
* *ok (if non-NULL) is set to 1 if the exchange was performed, 0 if the
* bytes at offset differed from old_buf (no write is performed).
* len == 0 always succeeds with *ok = 1.
* Returns EINVAL if offset + len would exceed the payload size or overflow
* uint64_t.
*
* Only available when compiled with both -DBSTACK_FEATURE_SET and
* -DBSTACK_FEATURE_ATOMIC.
*/
int ;
/*
* Read bytes in the half-open logical range [start, end), pass the mutable
* buffer to the callback for in-place modification, then write it back.
*
* The callback signature is:
* int cb(uint8_t *buf, size_t len, void *ctx)
*
* The callback receives a writable buffer of length (end - start), mutates
* it in place, and returns 0 on success or -1 on failure. The file size is
* never changed. start == end is a valid no-op (callback invoked with
* buf == NULL and len == 0). Returns EINVAL if end < start or end exceeds
* the payload size.
*
* Only available when compiled with both -DBSTACK_FEATURE_SET and
* -DBSTACK_FEATURE_ATOMIC.
*/
int ;
/* BSTACK_FEATURE_ATOMIC && BSTACK_FEATURE_SET */
}
/* BSTACK_H */