File Coverage

graph.h
Criterion Covered Total %
statement 258 287 89.9
branch 105 238 44.1
condition n/a
subroutine n/a
pod n/a
total 363 525 69.1


line stmt bran cond sub pod time code
1             /*
2             * graph.h -- Shared-memory directed weighted graph for Linux
3             *
4             * Nodes allocated from a bitmap pool. Edges stored as adjacency lists
5             * in a separate edge pool. Mutex-protected mutations.
6             *
7             * Node: int64_t data
8             * Edge: uint32_t dst, int64_t weight, uint32_t next (linked list)
9             */
10              
11             #ifndef GRAPH_H
12             #define GRAPH_H
13              
14             #include
15             #include
16             #include
17             #include
18             #include
19             #include
20             #include
21             #include
22             #include
23             #include
24             #include
25             #include
26             #include
27             #include
28             #include
29             #include
30              
31             #define GRAPH_MAGIC 0x47525031U /* "GRP1" */
32             #define GRAPH_VERSION 1
33             #define GRAPH_ERR_BUFLEN 256
34             #define GRAPH_NONE UINT32_MAX
35              
36             #define GRAPH_MUTEX_BIT 0x80000000U
37             #define GRAPH_MUTEX_PID 0x7FFFFFFFU
38              
39             /* ================================================================
40             * Layout
41             *
42             * Header (128 bytes)
43             * Node data: max_nodes * sizeof(int64_t) — node values
44             * Node heads: max_nodes * sizeof(uint32_t) — first-edge index per node
45             * Node bitmap: ceil(max_nodes/64) * 8 — allocation bitmap
46             * Edges: max_edges * sizeof(GraphEdge) — edge pool
47             * Edge bitmap: ceil(max_edges/64) * 8 — edge allocation bitmap
48             * ================================================================ */
49              
50             typedef struct {
51             uint32_t dst;
52             uint32_t next; /* index of next edge in adjacency list, GRAPH_NONE = end */
53             int64_t weight;
54             } GraphEdge;
55              
56             typedef struct {
57             uint32_t magic;
58             uint32_t version;
59             uint32_t max_nodes;
60             uint32_t max_edges;
61             uint64_t total_size;
62             uint64_t node_data_off;
63             uint64_t node_heads_off;
64             uint64_t node_bitmap_off;
65             uint64_t edge_data_off;
66             uint64_t edge_bitmap_off;
67              
68             uint32_t node_count; /* 64 */
69             uint32_t edge_count; /* 68 */
70             uint32_t mutex; /* 72 */
71             uint32_t mutex_waiters; /* 76 */
72             uint64_t stat_ops; /* 80 */
73             uint8_t _pad1[40]; /* 88-127 */
74             } GraphHeader;
75              
76             #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
77             _Static_assert(sizeof(GraphHeader) == 128, "GraphHeader must be 128 bytes");
78             #endif
79              
80             typedef struct {
81             GraphHeader *hdr;
82             int64_t *node_data;
83             uint32_t *node_heads;
84             uint64_t *node_bitmap;
85             GraphEdge *edges;
86             uint64_t *edge_bitmap;
87             uint32_t node_bwords;
88             uint32_t edge_bwords;
89             size_t mmap_size;
90             char *path;
91             int notify_fd;
92             int backing_fd;
93             } GraphHandle;
94              
95             /* ================================================================
96             * Mutex (same pattern as Heap)
97             * ================================================================ */
98              
99             static const struct timespec graph_lock_timeout = { 2, 0 };
100              
101 0           static inline int graph_pid_alive(uint32_t pid) {
102 0 0         if (pid == 0) return 1;
103 0 0         return !(kill((pid_t)pid, 0) == -1 && errno == ESRCH);
    0          
104             }
105              
106 110           static inline void graph_mutex_lock(GraphHeader *hdr) {
107 110           uint32_t mypid = GRAPH_MUTEX_BIT | ((uint32_t)getpid() & GRAPH_MUTEX_PID);
108 110           for (int spin = 0; ; spin++) {
109 110           uint32_t expected = 0;
110 110 50         if (__atomic_compare_exchange_n(&hdr->mutex, &expected, mypid,
111             1, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
112 110           return;
113 0 0         if (spin < 32) {
114             #if defined(__x86_64__) || defined(__i386__)
115 0           __asm__ volatile("pause" ::: "memory");
116             #elif defined(__aarch64__)
117             __asm__ volatile("yield" ::: "memory");
118             #endif
119 0           continue;
120             }
121 0           __atomic_add_fetch(&hdr->mutex_waiters, 1, __ATOMIC_RELAXED);
122 0           uint32_t cur = __atomic_load_n(&hdr->mutex, __ATOMIC_RELAXED);
123 0 0         if (cur != 0) {
124 0           long rc = syscall(SYS_futex, &hdr->mutex, FUTEX_WAIT, cur,
125             &graph_lock_timeout, NULL, 0);
126 0 0         if (rc == -1 && errno == ETIMEDOUT && cur >= GRAPH_MUTEX_BIT) {
    0          
    0          
127 0           uint32_t pid = cur & GRAPH_MUTEX_PID;
128 0 0         if (!graph_pid_alive(pid) &&
129 0 0         __atomic_compare_exchange_n(&hdr->mutex, &cur, 0,
130             0, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)) {
131             /* Recovered — wake one waiter so it can proceed. */
132 0           syscall(SYS_futex, &hdr->mutex, FUTEX_WAKE, 1, NULL, NULL, 0);
133             }
134             }
135             }
136 0           __atomic_sub_fetch(&hdr->mutex_waiters, 1, __ATOMIC_RELAXED);
137 0           spin = 0;
138             }
139             }
140              
141 110           static inline void graph_mutex_unlock(GraphHeader *hdr) {
142 110           __atomic_store_n(&hdr->mutex, 0, __ATOMIC_RELEASE);
143 110 50         if (__atomic_load_n(&hdr->mutex_waiters, __ATOMIC_RELAXED) > 0)
144 0           syscall(SYS_futex, &hdr->mutex, FUTEX_WAKE, 1, NULL, NULL, 0);
145 110           }
146              
147             /* ================================================================
148             * Bitmap helpers
149             * ================================================================ */
150              
151 80           static inline int graph_bit_set(uint64_t *bm, uint32_t idx) {
152 80           uint64_t word = __atomic_load_n(&bm[idx / 64], __ATOMIC_ACQUIRE);
153 80           return (word >> (idx % 64)) & 1;
154             }
155              
156 90           static inline int32_t graph_bit_alloc(uint64_t *bm, uint32_t bwords, uint32_t max) {
157 90 50         for (uint32_t w = 0; w < bwords; w++) {
158 90           uint64_t word = __atomic_load_n(&bm[w], __ATOMIC_RELAXED);
159 90 50         if (word == ~(uint64_t)0) continue;
160 90           int bit = __builtin_ctzll(~word);
161 90           uint32_t idx = w * 64 + bit;
162 90 50         if (idx >= max) return -1;
163 90           __atomic_fetch_or(&bm[w], (uint64_t)1 << bit, __ATOMIC_RELEASE);
164 90           return (int32_t)idx;
165             }
166 0           return -1;
167             }
168              
169 9           static inline void graph_bit_free(uint64_t *bm, uint32_t idx) {
170 9           __atomic_fetch_and(&bm[idx / 64], ~((uint64_t)1 << (idx % 64)), __ATOMIC_RELEASE);
171 9           }
172              
173             /* ================================================================
174             * Graph operations (must hold mutex)
175             * ================================================================ */
176              
177 80           static inline int32_t graph_add_node_locked(GraphHandle *h, int64_t data) {
178 80           int32_t idx = graph_bit_alloc(h->node_bitmap, h->node_bwords, h->hdr->max_nodes);
179 80 50         if (idx < 0) return -1;
180 80           h->node_data[idx] = data;
181 80           h->node_heads[idx] = GRAPH_NONE;
182 80           __atomic_fetch_add(&h->hdr->node_count, 1, __ATOMIC_RELAXED);
183 80           return idx;
184             }
185              
186 11           static inline int graph_add_edge_locked(GraphHandle *h, uint32_t src, uint32_t dst, int64_t weight) {
187 11 50         if (src >= h->hdr->max_nodes || dst >= h->hdr->max_nodes) return 0;
    50          
188 11 100         if (!graph_bit_set(h->node_bitmap, src) || !graph_bit_set(h->node_bitmap, dst))
    50          
189 1           return 0;
190 10           int32_t eidx = graph_bit_alloc(h->edge_bitmap, h->edge_bwords, h->hdr->max_edges);
191 10 50         if (eidx < 0) return 0;
192 10           h->edges[eidx].dst = dst;
193 10           h->edges[eidx].weight = weight;
194 10           h->edges[eidx].next = h->node_heads[src];
195 10           h->node_heads[src] = (uint32_t)eidx;
196 10           __atomic_fetch_add(&h->hdr->edge_count, 1, __ATOMIC_RELAXED);
197 10           return 1;
198             }
199              
200 5           static inline int graph_remove_node_locked(GraphHandle *h, uint32_t node) {
201 5 50         if (node >= h->hdr->max_nodes) return 0;
202 5 50         if (!graph_bit_set(h->node_bitmap, node)) return 0;
203             /* free all outgoing edges */
204 5           uint32_t eidx = h->node_heads[node];
205 7 100         while (eidx != GRAPH_NONE) {
206 2           uint32_t next = h->edges[eidx].next;
207 2           graph_bit_free(h->edge_bitmap, eidx);
208 2           __atomic_fetch_sub(&h->hdr->edge_count, 1, __ATOMIC_RELAXED);
209 2           eidx = next;
210             }
211 5           h->node_heads[node] = GRAPH_NONE;
212 5           graph_bit_free(h->node_bitmap, node);
213 5           __atomic_fetch_sub(&h->hdr->node_count, 1, __ATOMIC_RELAXED);
214 5           return 1;
215             }
216              
217             /* Like remove_node_locked, but also splices every other node's adjacency
218             * list to drop edges pointing TO `node` (incoming edges). O(N+E). */
219 4           static inline int graph_remove_node_full_locked(GraphHandle *h, uint32_t node) {
220 4 100         if (node >= h->hdr->max_nodes) return 0;
221 3 50         if (!graph_bit_set(h->node_bitmap, node)) return 0;
222 3           uint32_t max_n = h->hdr->max_nodes;
223 23 100         for (uint32_t src = 0; src < max_n; src++) {
224 20 100         if (src == node) continue;
225 17 100         if (!graph_bit_set(h->node_bitmap, src)) continue;
226 4           uint32_t *slot = &h->node_heads[src];
227 4           uint32_t eidx = *slot;
228 7 100         while (eidx != GRAPH_NONE) {
229 3           uint32_t next = h->edges[eidx].next;
230 3 100         if (h->edges[eidx].dst == node) {
231 2           *slot = next;
232 2           graph_bit_free(h->edge_bitmap, eidx);
233 2           __atomic_fetch_sub(&h->hdr->edge_count, 1, __ATOMIC_RELAXED);
234             } else {
235 1           slot = &h->edges[eidx].next;
236             }
237 3           eidx = next;
238             }
239             }
240 3           return graph_remove_node_locked(h, node);
241             }
242              
243             /* ================================================================
244             * Public API (lock + operation + unlock)
245             * ================================================================ */
246              
247 80           static inline int32_t graph_add_node(GraphHandle *h, int64_t data) {
248 80           graph_mutex_lock(h->hdr);
249 80           int32_t r = graph_add_node_locked(h, data);
250 80           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
251 80           graph_mutex_unlock(h->hdr);
252 80           return r;
253             }
254              
255 11           static inline int graph_add_edge(GraphHandle *h, uint32_t src, uint32_t dst, int64_t weight) {
256 11           graph_mutex_lock(h->hdr);
257 11           int r = graph_add_edge_locked(h, src, dst, weight);
258 11           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
259 11           graph_mutex_unlock(h->hdr);
260 11           return r;
261             }
262              
263 2           static inline int graph_remove_node(GraphHandle *h, uint32_t node) {
264 2           graph_mutex_lock(h->hdr);
265 2           int r = graph_remove_node_locked(h, node);
266 2           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
267 2           graph_mutex_unlock(h->hdr);
268 2           return r;
269             }
270              
271 4           static inline int graph_remove_node_full(GraphHandle *h, uint32_t node) {
272 4           graph_mutex_lock(h->hdr);
273 4           int r = graph_remove_node_full_locked(h, node);
274 4           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
275 4           graph_mutex_unlock(h->hdr);
276 4           return r;
277             }
278              
279 22           static inline int graph_has_node(GraphHandle *h, uint32_t node) {
280 22 100         if (node >= h->hdr->max_nodes) return 0;
281 21           return graph_bit_set(h->node_bitmap, node);
282             }
283              
284             /* Caller must hold graph_mutex and have verified node is live via bitmap. */
285 8           static inline uint32_t graph_degree(GraphHandle *h, uint32_t node) {
286 8 50         if (node >= h->hdr->max_nodes) return 0;
287 8           uint32_t count = 0;
288 8           uint32_t eidx = h->node_heads[node];
289 19 100         while (eidx != GRAPH_NONE) {
290 11           count++;
291 11           eidx = h->edges[eidx].next;
292             }
293 8           return count;
294             }
295              
296             /* ================================================================
297             * Create / Open / Close
298             * ================================================================ */
299              
300             #define GRAPH_ERR(fmt, ...) do { if (errbuf) snprintf(errbuf, GRAPH_ERR_BUFLEN, fmt, ##__VA_ARGS__); } while(0)
301              
302 14           static inline uint64_t graph_total_size(uint32_t max_nodes, uint32_t max_edges) {
303 14           uint32_t nb = (max_nodes + 63) / 64;
304 14           uint32_t eb = (max_edges + 63) / 64;
305 14           uint64_t node_data_off = sizeof(GraphHeader);
306 14           uint64_t node_heads_off = node_data_off + (uint64_t)max_nodes * sizeof(int64_t);
307 14           uint64_t node_bitmap_off = node_heads_off + (uint64_t)max_nodes * sizeof(uint32_t);
308 14           uint64_t edge_data_off = node_bitmap_off + (uint64_t)nb * 8;
309 14           uint64_t edge_bitmap_off = edge_data_off + (uint64_t)max_edges * sizeof(GraphEdge);
310 14           return edge_bitmap_off + (uint64_t)eb * 8;
311             }
312              
313 11           static inline void graph_init_header(void *base, uint32_t max_nodes, uint32_t max_edges,
314             uint64_t total) {
315 11           uint32_t nb = (max_nodes + 63) / 64;
316 11           uint64_t node_data_off = sizeof(GraphHeader);
317 11           uint64_t node_heads_off = node_data_off + (uint64_t)max_nodes * sizeof(int64_t);
318 11           uint64_t node_bitmap_off = node_heads_off + (uint64_t)max_nodes * sizeof(uint32_t);
319 11           uint64_t edge_data_off = node_bitmap_off + (uint64_t)nb * 8;
320 11           uint64_t edge_bitmap_off = edge_data_off + (uint64_t)max_edges * sizeof(GraphEdge);
321              
322 11           GraphHeader *hdr = (GraphHeader *)base;
323 11           memset(base, 0, (size_t)total);
324 11           hdr->magic = GRAPH_MAGIC;
325 11           hdr->version = GRAPH_VERSION;
326 11           hdr->max_nodes = max_nodes;
327 11           hdr->max_edges = max_edges;
328 11           hdr->total_size = total;
329 11           hdr->node_data_off = node_data_off;
330 11           hdr->node_heads_off = node_heads_off;
331 11           hdr->node_bitmap_off = node_bitmap_off;
332 11           hdr->edge_data_off = edge_data_off;
333 11           hdr->edge_bitmap_off = edge_bitmap_off;
334 11           uint32_t *heads = (uint32_t *)((uint8_t *)base + node_heads_off);
335 217 100         for (uint32_t i = 0; i < max_nodes; i++) heads[i] = GRAPH_NONE;
336 11           __atomic_thread_fence(__ATOMIC_SEQ_CST);
337 11           }
338              
339 13           static inline GraphHandle *graph_setup(void *base, size_t map_size,
340             const char *path, int backing_fd) {
341 13           GraphHeader *hdr = (GraphHeader *)base;
342 13           GraphHandle *h = (GraphHandle *)calloc(1, sizeof(GraphHandle));
343 13 50         if (!h) {
344 0           munmap(base, map_size);
345 0 0         if (backing_fd >= 0) close(backing_fd);
346 0           return NULL;
347             }
348 13           h->hdr = hdr;
349 13           h->node_data = (int64_t *)((uint8_t *)base + hdr->node_data_off);
350 13           h->node_heads = (uint32_t *)((uint8_t *)base + hdr->node_heads_off);
351 13           h->node_bitmap = (uint64_t *)((uint8_t *)base + hdr->node_bitmap_off);
352 13           h->edges = (GraphEdge *)((uint8_t *)base + hdr->edge_data_off);
353 13           h->edge_bitmap = (uint64_t *)((uint8_t *)base + hdr->edge_bitmap_off);
354 13           h->node_bwords = (hdr->max_nodes + 63) / 64;
355 13           h->edge_bwords = (hdr->max_edges + 63) / 64;
356 13           h->mmap_size = map_size;
357 13 100         h->path = path ? strdup(path) : NULL;
358 13           h->notify_fd = -1;
359 13           h->backing_fd = backing_fd;
360 13           return h;
361             }
362              
363             /* Validate a mapped header (shared by graph_create reopen and graph_open_fd). */
364 2           static inline int graph_validate_header(const GraphHeader *hdr, uint64_t file_size) {
365 2 50         if (hdr->magic != GRAPH_MAGIC) return 0;
366 2 50         if (hdr->version != GRAPH_VERSION) return 0;
367 2 50         if (hdr->max_nodes == 0 || hdr->max_edges == 0) return 0;
    50          
368 2 50         if (hdr->max_nodes > 0x7FFFFFFFu || hdr->max_edges > 0x7FFFFFFFu) return 0;
    50          
369 2 50         if (hdr->total_size != file_size) return 0;
370 2 50         if (hdr->total_size != graph_total_size(hdr->max_nodes, hdr->max_edges)) return 0;
371              
372 2           uint32_t nb = (hdr->max_nodes + 63) / 64;
373 2           uint64_t exp_node_data = sizeof(GraphHeader);
374 2           uint64_t exp_node_heads = exp_node_data + (uint64_t)hdr->max_nodes * sizeof(int64_t);
375 2           uint64_t exp_node_bitmap = exp_node_heads + (uint64_t)hdr->max_nodes * sizeof(uint32_t);
376 2           uint64_t exp_edge_data = exp_node_bitmap + (uint64_t)nb * 8;
377 2           uint64_t exp_edge_bitmap = exp_edge_data + (uint64_t)hdr->max_edges * sizeof(GraphEdge);
378 2 50         if (hdr->node_data_off != exp_node_data) return 0;
379 2 50         if (hdr->node_heads_off != exp_node_heads) return 0;
380 2 50         if (hdr->node_bitmap_off != exp_node_bitmap) return 0;
381 2 50         if (hdr->edge_data_off != exp_edge_data) return 0;
382 2 50         if (hdr->edge_bitmap_off != exp_edge_bitmap) return 0;
383 2           return 1;
384             }
385              
386 10           static GraphHandle *graph_create(const char *path, uint32_t max_nodes, uint32_t max_edges,
387             char *errbuf) {
388 10 50         if (errbuf) errbuf[0] = '\0';
389 10 50         if (max_nodes == 0) { GRAPH_ERR("max_nodes must be > 0"); return NULL; }
    0          
390 10 50         if (max_edges == 0) { GRAPH_ERR("max_edges must be > 0"); return NULL; }
    0          
391 10 50         if (max_nodes > UINT32_MAX - 63) { GRAPH_ERR("max_nodes too large"); return NULL; }
    0          
392 10 50         if (max_edges > UINT32_MAX - 63) { GRAPH_ERR("max_edges too large"); return NULL; }
    0          
393              
394 10           uint64_t total = graph_total_size(max_nodes, max_edges);
395 10           int anonymous = (path == NULL);
396 10           int fd = -1;
397             size_t map_size;
398             void *base;
399              
400 10 100         if (anonymous) {
401 7           map_size = (size_t)total;
402 7           base = mmap(NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
403 7 50         if (base == MAP_FAILED) { GRAPH_ERR("mmap: %s", strerror(errno)); return NULL; }
    0          
404             } else {
405 3           fd = open(path, O_RDWR|O_CREAT, 0666);
406 4 50         if (fd < 0) { GRAPH_ERR("open: %s", strerror(errno)); return NULL; }
    0          
407 3 50         if (flock(fd, LOCK_EX) < 0) { GRAPH_ERR("flock: %s", strerror(errno)); close(fd); return NULL; }
    0          
408             struct stat st;
409 3 50         if (fstat(fd, &st) < 0) { GRAPH_ERR("fstat: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL; }
    0          
410 3           int is_new = (st.st_size == 0);
411 3 100         if (!is_new && (uint64_t)st.st_size < sizeof(GraphHeader)) {
    50          
412 0 0         GRAPH_ERR("%s: file too small (%lld)", path, (long long)st.st_size);
413 0           flock(fd, LOCK_UN); close(fd); return NULL;
414             }
415 3 100         if (is_new && ftruncate(fd, (off_t)total) < 0) {
    50          
416 0 0         GRAPH_ERR("ftruncate: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL;
417             }
418 3 100         map_size = is_new ? (size_t)total : (size_t)st.st_size;
419 3           base = mmap(NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
420 3 50         if (base == MAP_FAILED) { GRAPH_ERR("mmap: %s", strerror(errno)); flock(fd, LOCK_UN); close(fd); return NULL; }
    0          
421 3 100         if (!is_new) {
422 1 50         if (!graph_validate_header((GraphHeader *)base, (uint64_t)st.st_size)) {
423 0 0         GRAPH_ERR("invalid graph file"); munmap(base, map_size); flock(fd, LOCK_UN); close(fd); return NULL;
424             }
425 1           flock(fd, LOCK_UN); close(fd);
426 1           return graph_setup(base, map_size, path, -1);
427             }
428             }
429 9           graph_init_header(base, max_nodes, max_edges, total);
430 9 100         if (fd >= 0) { flock(fd, LOCK_UN); close(fd); }
431 9           return graph_setup(base, map_size, path, -1);
432             }
433              
434 2           static GraphHandle *graph_create_memfd(const char *name, uint32_t max_nodes, uint32_t max_edges,
435             char *errbuf) {
436 2 50         if (errbuf) errbuf[0] = '\0';
437 2 50         if (max_nodes == 0) { GRAPH_ERR("max_nodes must be > 0"); return NULL; }
    0          
438 2 50         if (max_edges == 0) { GRAPH_ERR("max_edges must be > 0"); return NULL; }
    0          
439 2 50         if (max_nodes > UINT32_MAX - 63 || max_edges > UINT32_MAX - 63) {
    50          
440 0 0         GRAPH_ERR("max_nodes/max_edges too large"); return NULL;
441             }
442 2           uint64_t total = graph_total_size(max_nodes, max_edges);
443 2 50         int fd = memfd_create(name ? name : "graph", MFD_CLOEXEC | MFD_ALLOW_SEALING);
444 2 50         if (fd < 0) { GRAPH_ERR("memfd_create: %s", strerror(errno)); return NULL; }
    0          
445 2 50         if (ftruncate(fd, (off_t)total) < 0) {
446 0 0         GRAPH_ERR("ftruncate: %s", strerror(errno)); close(fd); return NULL;
447             }
448 2           (void)fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW);
449 2           void *base = mmap(NULL, (size_t)total, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
450 2 50         if (base == MAP_FAILED) { GRAPH_ERR("mmap: %s", strerror(errno)); close(fd); return NULL; }
    0          
451 2           graph_init_header(base, max_nodes, max_edges, total);
452 2           return graph_setup(base, (size_t)total, NULL, fd);
453             }
454              
455 1           static GraphHandle *graph_open_fd(int fd, char *errbuf) {
456 1 50         if (errbuf) errbuf[0] = '\0';
457             struct stat st;
458 1 50         if (fstat(fd, &st) < 0) { GRAPH_ERR("fstat: %s", strerror(errno)); return NULL; }
    0          
459 1 50         if ((uint64_t)st.st_size < sizeof(GraphHeader)) { GRAPH_ERR("too small"); return NULL; }
    0          
460 1           size_t ms = (size_t)st.st_size;
461 1           void *base = mmap(NULL, ms, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
462 1 50         if (base == MAP_FAILED) { GRAPH_ERR("mmap: %s", strerror(errno)); return NULL; }
    0          
463 1 50         if (!graph_validate_header((GraphHeader *)base, (uint64_t)st.st_size)) {
464 0 0         GRAPH_ERR("invalid graph"); munmap(base, ms); return NULL;
465             }
466 1           int myfd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
467 1 50         if (myfd < 0) { GRAPH_ERR("fcntl: %s", strerror(errno)); munmap(base, ms); return NULL; }
    0          
468 1           return graph_setup(base, ms, NULL, myfd);
469             }
470              
471 13           static void graph_destroy(GraphHandle *h) {
472 13 50         if (!h) return;
473 13 100         if (h->notify_fd >= 0) close(h->notify_fd);
474 13 100         if (h->backing_fd >= 0) close(h->backing_fd);
475 13 50         if (h->hdr) munmap(h->hdr, h->mmap_size);
476 13           free(h->path);
477 13           free(h);
478             }
479              
480 1           static inline int graph_msync(GraphHandle *h) {
481 1 50         if (!h || !h->hdr) return 0;
    50          
482 1           return msync(h->hdr, h->mmap_size, MS_SYNC);
483             }
484              
485 2           static int graph_create_eventfd(GraphHandle *h) {
486 2 50         if (h->notify_fd >= 0) return h->notify_fd;
487 2           int efd = eventfd(0, EFD_NONBLOCK|EFD_CLOEXEC);
488 2 50         if (efd < 0) return -1;
489 2           h->notify_fd = efd;
490 2           return efd;
491             }
492              
493 3           static int graph_notify(GraphHandle *h) {
494 3 50         if (h->notify_fd < 0) return 0;
495 3           uint64_t v = 1;
496 3           return write(h->notify_fd, &v, sizeof(v)) == sizeof(v);
497             }
498              
499 2           static int64_t graph_eventfd_consume(GraphHandle *h) {
500 2 50         if (h->notify_fd < 0) return -1;
501 2           uint64_t v = 0;
502 2 50         if (read(h->notify_fd, &v, sizeof(v)) != sizeof(v)) return -1;
503 2           return (int64_t)v;
504             }
505              
506             #endif /* GRAPH_H */