ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/TDB_FileX/TDB_FileX.xs
Revision: 1.3
Committed: Tue Apr 29 21:56:12 2025 UTC (16 months, 4 weeks ago) by root
Branch: MAIN
Changes since 1.2: +64 -5 lines
Log Message:
*** empty log message ***

File Contents

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