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

# User Rev Content
1 root 1.1 #include "EXTERN.h"
2     #include "perl.h"
3     #include "XSUB.h"
4    
5 root 1.4 #if __linux__
6     #include <sys/ioctl.h>
7     #include <linux/fs.h>
8     #endif
9    
10 root 1.1 #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 root 1.4 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 root 1.1 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 root 1.3
65 root 1.1 PUTBACK;
66     call_sv (cb, G_VOID | G_DISCARD);
67    
68     FREETMPS;
69     LEAVE;
70     }
71    
72     static int
73 root 1.3 traverse_cb (TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA data, void *private_data)
74 root 1.1 {
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 root 1.3 int count = call_sv ((SV *)private_data, G_SCALAR);
86 root 1.1
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 root 1.3 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 root 1.1 // 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 root 1.3
184 root 1.1 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 root 1.2 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 root 1.10 #define NUM_HASH_FUNC 4
210    
211     static SV *custom_hash_cb[NUM_HASH_FUNC];
212 root 1.1
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 root 1.8 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 root 1.1 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 root 1.10 int
284     register_hash_function (SV *cb)
285     PROTOTYPE: $
286 root 1.1 CODE:
287     {
288 root 1.10 int idx;
289 root 1.1
290 root 1.10 for (idx = 0; ; ++idx)
291     {
292     if (idx >= NUM_HASH_FUNC)
293     croak ("register_hash_function: no free slot left");
294 root 1.1
295 root 1.10 if (!custom_hash_cb[idx])
296     {
297     custom_hash_cb[idx] = newSVsv (cb);
298     RETVAL = idx + 1;
299     break;
300     }
301 root 1.1 }
302     }
303     OUTPUT: RETVAL
304    
305     void
306 root 1.10 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 root 1.1 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 root 1.2 NO_OUTPUT int
324 root 1.1 tdb_delete (TDB_CONTEXT *tdb, TDB_DATA key)
325     ALIAS:
326     DELETE = 0
327 root 1.2 POSTCALL:
328     croak_on_error (RETVAL);
329 root 1.1
330 root 1.2 NO_OUTPUT int
331 root 1.1 tdb_wipe_all (TDB_CONTEXT *tdb)
332     ALIAS:
333     CLEAR = 0
334     CODE:
335     tdb_wipe_all (tdb);
336 root 1.2 POSTCALL:
337     croak_on_error (RETVAL);
338 root 1.1
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 root 1.2 SV *
349 root 1.1 tdb_exists (TDB_CONTEXT *tdb, TDB_DATA key)
350     ALIAS:
351     EXISTS = 0
352 root 1.2 CODE:
353     RETVAL = tdb_exists (tdb, key) ? &PL_sv_yes : &PL_sv_no;
354     OUTPUT: RETVAL
355 root 1.1
356 root 1.8 SV *
357 root 1.1 tdb_fetch (TDB_CONTEXT *tdb, TDB_DATA key)
358     ALIAS:
359     FETCH = 0
360 root 1.8 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 root 1.1
374 root 1.2 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 root 1.1 tdb_append (TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf)
383 root 1.2 POSTCALL:
384     croak_on_error (RETVAL);
385 root 1.1
386     TDB_DATA
387     tdb_firstkey (TDB_CONTEXT *tdb)
388     ALIAS:
389     FIRSTKEY = 0
390    
391 root 1.2 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 root 1.1
408 root 1.6 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 root 1.12 bool tdb_transaction_active (TDB_CONTEXT *tdb)
426 root 1.1
427 root 1.12 void tdb_enable_seqnum (TDB_CONTEXT *tdb)
428 root 1.1
429 root 1.12 U32 tdb_get_seqnum (TDB_CONTEXT *tdb)
430 root 1.1
431 root 1.12 void tdb_increment_seqnum_nonblock (TDB_CONTEXT *tdb)
432 root 1.1
433 root 1.12 int tdb_hash_size (TDB_CONTEXT *tdb)
434 root 1.1
435 root 1.12 size_t tdb_map_size (TDB_CONTEXT *tdb)
436 root 1.1
437 root 1.12 int tdb_get_flags (TDB_CONTEXT *tdb)
438 root 1.1
439 root 1.12 void tdb_add_flags (TDB_CONTEXT *tdb, unsigned int flag)
440 root 1.1
441 root 1.12 void tdb_remove_flags (TDB_CONTEXT *tdb, unsigned int flag)
442 root 1.1
443 root 1.12 void tdb_set_max_dead (TDB_CONTEXT *tdb, int max_dead)
444 root 1.9
445 root 1.12 bool tdb_runtime_check_for_robust_mutexes ()
446 root 1.1
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 root 1.4 int nocow = 0;
473 root 1.1
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 root 1.4 else if (strEQ (k, "nocow"))
522     nocow = SvTRUE (v);
523 root 1.1 else
524     croak ("%s: not a known parameter name", k);
525     }
526    
527 root 1.5 if (nocow && (open_flags & O_CREAT) && !(tdb_flags & TDB_INTERNAL))
528 root 1.4 set_nocow (path, open_flags, mode);
529    
530 root 1.1 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 root 1.2 int res = tdb_reopen (tdb);
546 root 1.1
547 root 1.2 if (res < 0)
548 root 1.1 {
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 root 1.2 croak ("tdb_reopen failed");
554 root 1.1 }
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 root 1.2 NO_OUTPUT int
560 root 1.1 tdb_reopen_all (int parent_longlived = 0)
561 root 1.2 POSTCALL:
562     if (RETVAL < 0)
563     croak ("tdb_reopen_all failed");
564 root 1.1
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 root 1.2 RETVAL = (ix ? tdb_traverse_read : tdb_traverse) (tdb, SvOK (fn) ? traverse_cb: 0, fn);
577     croak_on_error (RETVAL);
578 root 1.1 OUTPUT: RETVAL
579    
580 root 1.3 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 root 1.9 char *
593     tdb_summary (TDB_CONTEXT *tdb)
594     CLEANUP:
595     free (RETVAL);
596    
597 root 1.11 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 root 1.9 void
605     tdb_printfreelist (TDB_CONTEXT *tdb)
606    
607     int
608     tdb_freelist_size (TDB_CONTEXT *tdb)
609    
610 root 1.13 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 root 1.14