ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/TDB_FileX/TDB_FileX.xs
Revision: 1.14
Committed: Mon May 5 10:45:21 2025 UTC (16 months, 1 week ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.13: +1 -0 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 #include "EXTERN.h"
2 #include "perl.h"
3 #include "XSUB.h"
4
5 #if __linux__
6 #include <sys/ioctl.h>
7 #include <linux/fs.h>
8 #endif
9
10 #include <sys/types.h>
11 #include <tdb.h>
12
13 #define XXH_STATIC_LINKING_ONLY
14 #define XXH_IMPLEMENTATION
15 #define XXH_INLINE_ALL
16 #include "xxHash/xxhash.h"
17
18 static void
19 set_nocow (const char *path, int flags, int mode)
20 {
21 #if __linux__ && FS_NOCOW_FL && O_CLOEXEC
22 int fd = open (path, flags, mode | O_CLOEXEC);
23
24 if (fd >= 0)
25 {
26 int flags;
27 if (!ioctl (fd, FS_IOC_GETFLAGS, &flags))
28 {
29 flags |= FS_NOCOW_FL;
30 ioctl (fd, FS_IOC_SETFLAGS, &flags);
31 }
32
33 close (fd);
34 }
35 #endif
36 }
37
38 static void
39 log_func_cb (TDB_CONTEXT *tdb, enum tdb_debug_level level, const char *fmt, ...)
40 {
41 va_list ap;
42 bool xfalse = FALSE;
43 SV *cb = (SV *)tdb_get_logging_private (tdb);
44
45 if (!cb)
46 return;
47
48 dSP;
49 dXSTARG;
50
51 ENTER;
52 SAVETMPS;
53
54 va_start (ap, fmt);
55
56 SV *sv = sv_newmortal ();
57 sv_vsetpvfn (sv, fmt, strlen (fmt), &ap, NULL, 0, &xfalse);
58
59 va_end (ap);
60
61 PUSHMARK (SP);
62 XPUSHi (level);
63 XPUSHs (sv);
64
65 PUTBACK;
66 call_sv (cb, G_VOID | G_DISCARD);
67
68 FREETMPS;
69 LEAVE;
70 }
71
72 static int
73 traverse_cb (TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA data, void *private_data)
74 {
75 dSP;
76
77 ENTER;
78 SAVETMPS;
79
80 PUSHMARK (SP);
81 XPUSHs (sv_2mortal (newSVpv (key .dptr, key .dsize)));
82 XPUSHs (sv_2mortal (newSVpv (data.dptr, data.dsize)));
83 PUTBACK;
84
85 int count = call_sv ((SV *)private_data, G_SCALAR);
86
87 SPAGAIN;
88
89 if (count != 1)
90 croak ("tdb_traverse callback returned %d args\n", count);
91
92 SV *retval = POPs;
93 int ret = !SvTRUE (retval);
94
95 PUTBACK;
96 FREETMPS;
97 LEAVE;
98
99 return ret;
100 }
101
102 static int
103 check_cb (TDB_DATA key, TDB_DATA data, void *private_data)
104 {
105 dSP;
106
107 ENTER;
108 SAVETMPS;
109
110 PUSHMARK (SP);
111 XPUSHs (sv_2mortal (newSVpv (key .dptr, key .dsize)));
112 XPUSHs (sv_2mortal (newSVpv (data.dptr, data.dsize)));
113
114 PUTBACK;
115 int count = call_sv ((SV *)private_data, G_SCALAR);
116 SPAGAIN;
117
118 if (count != 1)
119 croak ("tdb_check callback returned %d args\n", count);
120
121 SV *retval = POPs;
122 int ret = SvTRUE (retval) ? 0 : -1;
123
124 PUTBACK;
125 FREETMPS;
126 LEAVE;
127
128 return ret;
129 }
130
131 static void
132 rescue_cb (TDB_DATA key, TDB_DATA data, void *private_data)
133 {
134 dSP;
135
136 ENTER;
137 SAVETMPS;
138
139 PUSHMARK (SP);
140 XPUSHs (sv_2mortal (newSVpv (key .dptr, key .dsize)));
141 XPUSHs (sv_2mortal (newSVpv (data.dptr, data.dsize)));
142
143 PUTBACK;
144 call_sv ((SV *)private_data, G_VOID | G_DISCARD);
145
146 FREETMPS;
147 LEAVE;
148 }
149
150 // https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
151 // https://github.com/skeeto/hash-prospector/issues/19
152 static unsigned int
153 fnv1ax_hash (TDB_DATA *key)
154 {
155 unsigned int x = sizeof (unsigned int) < 8 ? 0x811c9dc5U : 0xcbf29ce484222325U;
156
157 for (size_t i = 0; i < key->dsize; ++i)
158 x = (x ^ key->dptr[i]) * (sizeof (unsigned int) < 8 ? 0x01000193U : 0x00000100000001b3U);
159
160 x ^= x >> 16; x *= 0x21f0aaadU;
161 x ^= x >> 15; x *= 0x735a2d97U;
162 x ^= x >> 15;
163
164 return x;
165 }
166
167 static unsigned int
168 xxh3_hash (TDB_DATA *key)
169 {
170 return XXH3_64bits (key->dptr, key->dsize);
171 }
172
173 static unsigned int
174 custom_hash (TDB_DATA *key, SV *cb)
175 {
176 dSP;
177
178 ENTER;
179 SAVETMPS;
180
181 PUSHMARK (SP);
182 XPUSHs (sv_2mortal (newSVpv (key->dptr, key->dsize)));
183
184 PUTBACK;
185 int count = call_sv (cb, G_SCALAR);
186 SPAGAIN;
187
188 if (count != 1)
189 croak ("hash callback returned %d vaslues, expecte4d exactly 1", count);
190
191 unsigned int hash = POPu;
192
193 PUTBACK;
194 FREETMPS;
195 LEAVE;
196
197 return hash;
198 }
199
200 static void
201 croak_on_error_slowpath (TDB_CONTEXT *tdb)
202 {
203 errno = tdb_error (tdb);
204 croak_sv (sv_2mortal (newSVpv (tdb_errorstr (tdb), 0)));
205 }
206
207 #define croak_on_error(res) do { if ((res) < 0) croak_on_error_slowpath (tdb); } while (0)
208
209 #define NUM_HASH_FUNC 4
210
211 static SV *custom_hash_cb[NUM_HASH_FUNC];
212
213 static unsigned int custom_hash_1 (TDB_DATA *key) { return custom_hash (key, custom_hash_cb[0]); }
214 static unsigned int custom_hash_2 (TDB_DATA *key) { return custom_hash (key, custom_hash_cb[1]); }
215 static unsigned int custom_hash_3 (TDB_DATA *key) { return custom_hash (key, custom_hash_cb[2]); }
216 static unsigned int custom_hash_4 (TDB_DATA *key) { return custom_hash (key, custom_hash_cb[3]); }
217
218 static int
219 fetch_parser (TDB_DATA key, TDB_DATA data, void *private_data)
220 {
221 SV **svp = (SV **)private_data;
222 *svp = newSVpvn (data.dptr, data.dsize);
223 return 0;
224 }
225
226 typedef int mone_on_fail;
227
228 MODULE = TDB_FileX PACKAGE = TDB_FileX PREFIX = tdb_
229
230 PROTOTYPES: DISABLE
231
232 BOOT:
233 {
234 HV *stash = gv_stashpv ("TDB_FileX", 1);
235
236 static const struct {
237 const char *name;
238 IV iv;
239 } *civ, const_iv[] = {
240 # define const_iv(name) { # name, (IV) TDB_ ## name },
241 const_iv (ALLOW_NESTING)
242 const_iv (BIGENDIAN)
243 const_iv (CLEAR_IF_FIRST)
244 const_iv (CONVERT)
245 const_iv (DEFAULT)
246 const_iv (DISALLOW_NESTING)
247 const_iv (INCOMPATIBLE_HASH)
248 const_iv (INSERT)
249 const_iv (INTERNAL)
250 const_iv (MODIFY)
251 const_iv (MUTEX_LOCKING)
252 const_iv (NOLOCK)
253 const_iv (NOMMAP)
254 const_iv (NOSYNC)
255 const_iv (REPLACE)
256 const_iv (SEQNUM)
257 const_iv (VOLATILE)
258
259 const_iv (ERR_CORRUPT)
260 const_iv (ERR_IO)
261 const_iv (ERR_LOCK)
262 const_iv (ERR_OOM)
263 const_iv (ERR_EXISTS)
264 const_iv (ERR_NOLOCK)
265 const_iv (ERR_LOCK_TIMEOUT)
266 const_iv (ERR_NOEXIST)
267 const_iv (ERR_EINVAL)
268 const_iv (ERR_RDONLY)
269 const_iv (SUCCESS)
270
271 const_iv (DEBUG_FATAL)
272 const_iv (DEBUG_ERROR)
273 const_iv (DEBUG_WARNING)
274 const_iv (DEBUG_TRACE)
275 };
276
277 for (civ = const_iv + sizeof (const_iv) / sizeof (const_iv [0]); civ > const_iv; civ--)
278 newCONSTSUB (stash, (char *)civ[-1].name, newSViv (civ[-1].iv));
279
280 tdb_runtime_check_for_robust_mutexes ();
281 }
282
283 int
284 register_hash_function (SV *cb)
285 PROTOTYPE: $
286 CODE:
287 {
288 int idx;
289
290 for (idx = 0; ; ++idx)
291 {
292 if (idx >= NUM_HASH_FUNC)
293 croak ("register_hash_function: no free slot left");
294
295 if (!custom_hash_cb[idx])
296 {
297 custom_hash_cb[idx] = newSVsv (cb);
298 RETVAL = idx + 1;
299 break;
300 }
301 }
302 }
303 OUTPUT: RETVAL
304
305 void
306 unregister_hash_function (int idx)
307 CODE:
308 --idx;
309 if (idx <= 0 || idx > NUM_HASH_FUNC || !custom_hash_cb[idx])
310 croak ("unregister_hash_function: illegal hash function id");
311 SvREFCNT_dec_NN (custom_hash_cb[idx]);
312 custom_hash_cb[idx] = 0;
313
314 void
315 DESTROY (TDB_CONTEXT *tdb)
316 CODE:
317 if (tdb)
318 {
319 SvREFCNT_dec ((SV *)tdb_get_logging_private (tdb));
320 tdb_close (tdb);
321 }
322
323 NO_OUTPUT int
324 tdb_delete (TDB_CONTEXT *tdb, TDB_DATA key)
325 ALIAS:
326 DELETE = 0
327 POSTCALL:
328 croak_on_error (RETVAL);
329
330 NO_OUTPUT int
331 tdb_wipe_all (TDB_CONTEXT *tdb)
332 ALIAS:
333 CLEAR = 0
334 CODE:
335 tdb_wipe_all (tdb);
336 POSTCALL:
337 croak_on_error (RETVAL);
338
339 void
340 tdb_dump_all (TDB_CONTEXT *tdb)
341
342 enum TDB_ERROR
343 tdb_error (TDB_CONTEXT *tdb)
344
345 const char *
346 tdb_errorstr (TDB_CONTEXT *tdb)
347
348 SV *
349 tdb_exists (TDB_CONTEXT *tdb, TDB_DATA key)
350 ALIAS:
351 EXISTS = 0
352 CODE:
353 RETVAL = tdb_exists (tdb, key) ? &PL_sv_yes : &PL_sv_no;
354 OUTPUT: RETVAL
355
356 SV *
357 tdb_fetch (TDB_CONTEXT *tdb, TDB_DATA key)
358 ALIAS:
359 FETCH = 0
360 CODE:
361 /* tdb_parse_record is faster than tdb_fetch, due to one copy saved */
362 /* should use tdb_fetch with perlmulticore */
363 RETVAL = 0;
364 int res = tdb_parse_record (tdb, key, fetch_parser, &RETVAL);
365 if (res < 0)
366 {
367 SvREFCNT_dec (RETVAL);
368 if (tdb_error (tdb) == TDB_ERR_NOEXIST)
369 XSRETURN_UNDEF;
370 croak_on_error (res);
371 }
372 OUTPUT: RETVAL
373
374 NO_OUTPUT int
375 tdb_store (TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, int flag = TDB_REPLACE)
376 ALIAS:
377 STORE = 0
378 POSTCALL:
379 croak_on_error (RETVAL);
380
381 NO_OUTPUT int
382 tdb_append (TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf)
383 POSTCALL:
384 croak_on_error (RETVAL);
385
386 TDB_DATA
387 tdb_firstkey (TDB_CONTEXT *tdb)
388 ALIAS:
389 FIRSTKEY = 0
390
391 NO_OUTPUT int
392 tdb_context_only_and_mone_on_fail (TDB_CONTEXT *tdb)
393 INTERFACE:
394 tdb_lockall
395 tdb_unlockall
396 tdb_lockall_read
397 tdb_unlockall_read
398 tdb_lockall_mark
399 tdb_lockall_unmark
400 tdb_transaction_start
401 tdb_transaction_cancel
402 tdb_transaction_commit
403 tdb_transaction_prepare_commit
404 tdb_repack
405 POSTCALL:
406 croak_on_error (RETVAL);
407
408 bool
409 tdb_context_only_and_mone_on_fail_nonblock (TDB_CONTEXT *tdb)
410 INTERFACE:
411 tdb_lockall_nonblock
412 tdb_lockall_read_nonblock
413 tdb_transaction_start_nonblock
414 POSTCALL:
415 if (RETVAL < 0)
416 {
417 if (tdb_error (tdb) != TDB_ERR_LOCK)
418 croak_on_error (RETVAL);
419
420 RETVAL = 0;
421 }
422 else
423 RETVAL = 1;
424
425 bool tdb_transaction_active (TDB_CONTEXT *tdb)
426
427 void tdb_enable_seqnum (TDB_CONTEXT *tdb)
428
429 U32 tdb_get_seqnum (TDB_CONTEXT *tdb)
430
431 void tdb_increment_seqnum_nonblock (TDB_CONTEXT *tdb)
432
433 int tdb_hash_size (TDB_CONTEXT *tdb)
434
435 size_t tdb_map_size (TDB_CONTEXT *tdb)
436
437 int tdb_get_flags (TDB_CONTEXT *tdb)
438
439 void tdb_add_flags (TDB_CONTEXT *tdb, unsigned int flag)
440
441 void tdb_remove_flags (TDB_CONTEXT *tdb, unsigned int flag)
442
443 void tdb_set_max_dead (TDB_CONTEXT *tdb, int max_dead)
444
445 bool tdb_runtime_check_for_robust_mutexes ()
446
447 void
448 tdb_set_logging_function (TDB_CONTEXT *tdb, SV *cb)
449 CODE:
450 struct tdb_logging_context ctx;
451 ctx.log_fn = log_func_cb;
452 ctx.log_private = (SV *)newSVsv (cb);
453 SvREFCNT_dec ((SV *)tdb_get_logging_private (tdb));
454 tdb_set_logging_function (tdb, &ctx);
455
456 TDB_DATA
457 tdb_nextkey (TDB_CONTEXT *tdb, TDB_DATA key)
458 ALIAS:
459 NEXTKEY = 0
460
461 TDB_CONTEXT *
462 tdb_open (char *class, char *path, ...)
463 ALIAS:
464 TIEHASH = 0
465 CODE:
466 tdb_hash_func hash_func = 0;
467 struct tdb_logging_context ctx = { 0 };
468 int tdb_flags = TDB_DEFAULT;
469 int open_flags = O_RDWR | O_CREAT;
470 mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
471 int hash_size = 0;
472 int nocow = 0;
473
474 for (int i = 2; i < items - 1; i += 2)
475 {
476 const char *k = SvPVbyte_nolen (ST (i));
477 SV *v = ST (i + 1);
478
479 if (strEQ (k, "tdb_flags" )) tdb_flags = SvIV (v);
480 else if (strEQ (k, "open_flags")) open_flags = SvIV (v);
481 else if (strEQ (k, "mode" )) mode = SvUV (v);
482 else if (strEQ (k, "hash_size" )) hash_size = SvIV (v);
483 else if (strEQ (k, "log_cb"))
484 {
485 SvREFCNT_dec (ctx.log_private);
486 ctx.log_fn = 0;
487 ctx.log_private = 0;
488
489 if (SvOK (v))
490 {
491 ctx.log_fn = log_func_cb;
492 ctx.log_private = (void *)newSVsv (v);
493 }
494 }
495 else if (strEQ (k, "hash"))
496 {
497 const char *f = SvPVbyte_nolen (v);
498
499 if (!SvOK (v) ) hash_func = 0;
500 else if (strEQ (f, "default")) hash_func = 0;
501 else if (strEQ (f, "jenkins")) hash_func = tdb_jenkins_hash;
502 else if (strEQ (f, "fnv1ax" )) hash_func = fnv1ax_hash;
503 else if (strEQ (f, "xxh3" )) hash_func = xxh3_hash;
504 else if (strEQ (f, "1" )) hash_func = custom_hash_1;
505 else if (strEQ (f, "2" )) hash_func = custom_hash_2;
506 else if (strEQ (f, "3" )) hash_func = custom_hash_3;
507 else if (strEQ (f, "4" )) hash_func = custom_hash_4;
508 else
509 croak ("%s: not a known hash function", f);
510 }
511 else if (strEQ (k, "mutex"))
512 {
513 if (SvTRUE (v))
514 {
515 if (tdb_runtime_check_for_robust_mutexes ())
516 tdb_flags |= TDB_MUTEX_LOCKING;
517 }
518 else
519 tdb_flags &= ~TDB_MUTEX_LOCKING;
520 }
521 else if (strEQ (k, "nocow"))
522 nocow = SvTRUE (v);
523 else
524 croak ("%s: not a known parameter name", k);
525 }
526
527 if (nocow && (open_flags & O_CREAT) && !(tdb_flags & TDB_INTERNAL))
528 set_nocow (path, open_flags, mode);
529
530 RETVAL = tdb_open_ex (path, hash_size, tdb_flags, open_flags, mode, ctx.log_fn ? &ctx : 0, hash_func);
531
532 if (!RETVAL)
533 {
534 SvREFCNT_dec ((SV *)ctx.log_private);
535 XSRETURN_UNDEF;
536 }
537 OUTPUT: RETVAL
538
539 void
540 tdb_reopen (TDB_CONTEXT *tdb)
541 PROTOTYPE:
542 CODE:
543 void *logcb = tdb_get_logging_private (tdb);
544
545 int res = tdb_reopen (tdb);
546
547 if (res < 0)
548 {
549 /* tdb_reopen frees the TDB_CONTEXT on failure,
550 * so set scalar value to 0 to avoid double free on DESTROY */
551 sv_setiv ((SV*)SvRV (ST (0)), 0);
552 SvREFCNT_dec ((SV *)logcb);
553 croak ("tdb_reopen failed");
554 }
555
556 # FIXME: if this fails, we need to undef $tdb or something
557 # .. which we can't do - cos we don't know where it failed :(
558 # maybe reimplement this ourselves?
559 NO_OUTPUT int
560 tdb_reopen_all (int parent_longlived = 0)
561 POSTCALL:
562 if (RETVAL < 0)
563 croak ("tdb_reopen_all failed");
564
565 int
566 tdb_fd (TDB_CONTEXT *tdb)
567
568 const char *
569 tdb_name (TDB_CONTEXT *tdb)
570
571 int
572 tdb_traverse (TDB_CONTEXT *tdb, SV *fn = &PL_sv_undef)
573 ALIAS:
574 tdb_traverse_read = 1
575 CODE:
576 RETVAL = (ix ? tdb_traverse_read : tdb_traverse) (tdb, SvOK (fn) ? traverse_cb: 0, fn);
577 croak_on_error (RETVAL);
578 OUTPUT: RETVAL
579
580 mone_on_fail
581 tdb_check (TDB_CONTEXT *tdb, SV *fn = &PL_sv_undef)
582 CODE:
583 RETVAL = tdb_check (tdb, SvOK (fn) ? check_cb : 0, fn);
584 OUTPUT: RETVAL
585
586 mone_on_fail
587 tdb_rescue (TDB_CONTEXT *tdb, SV *fn)
588 CODE:
589 RETVAL = tdb_rescue (tdb, rescue_cb, fn);
590 OUTPUT: RETVAL
591
592 char *
593 tdb_summary (TDB_CONTEXT *tdb)
594 CLEANUP:
595 free (RETVAL);
596
597 int
598 tdb_validate_freelist (TDB_CONTEXT *tdb)
599 CODE:
600 int ret = tdb_validate_freelist (tdb, &RETVAL);
601 croak_on_error (ret);
602 OUTPUT: RETVAL
603
604 void
605 tdb_printfreelist (TDB_CONTEXT *tdb)
606
607 int
608 tdb_freelist_size (TDB_CONTEXT *tdb)
609
610 unsigned int
611 fnv1ax (TDB_DATA key)
612 ALIAS:
613 xxh3 = 1
614 CODE:
615 RETVAL = (ix ? xxh3_hash : fnv1ax_hash) (&key);
616 OUTPUT: RETVAL
617