ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/TDB_FileX/TDB_FileX.xs
Revision: 1.6
Committed: Fri May 2 20:47:43 2025 UTC (16 months, 1 week ago) by root
Branch: MAIN
Changes since 1.5: +17 -3 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.1 static SV *custom_hash_cb[4];
210    
211     static unsigned int custom_hash_1 (TDB_DATA *key) { return custom_hash (key, custom_hash_cb[0]); }
212     static unsigned int custom_hash_2 (TDB_DATA *key) { return custom_hash (key, custom_hash_cb[1]); }
213     static unsigned int custom_hash_3 (TDB_DATA *key) { return custom_hash (key, custom_hash_cb[2]); }
214     static unsigned int custom_hash_4 (TDB_DATA *key) { return custom_hash (key, custom_hash_cb[3]); }
215    
216     typedef int mone_on_fail;
217    
218     MODULE = TDB_FileX PACKAGE = TDB_FileX PREFIX = tdb_
219    
220     PROTOTYPES: DISABLE
221    
222     BOOT:
223     {
224     HV *stash = gv_stashpv ("TDB_FileX", 1);
225    
226     static const struct {
227     const char *name;
228     IV iv;
229     } *civ, const_iv[] = {
230     # define const_iv(name) { # name, (IV) TDB_ ## name },
231     const_iv (ALLOW_NESTING)
232     const_iv (BIGENDIAN)
233     const_iv (CLEAR_IF_FIRST)
234     const_iv (CONVERT)
235     const_iv (DEFAULT)
236     const_iv (DISALLOW_NESTING)
237     const_iv (INCOMPATIBLE_HASH)
238     const_iv (INSERT)
239     const_iv (INTERNAL)
240     const_iv (MODIFY)
241     const_iv (MUTEX_LOCKING)
242     const_iv (NOLOCK)
243     const_iv (NOMMAP)
244     const_iv (NOSYNC)
245     const_iv (REPLACE)
246     const_iv (SEQNUM)
247     const_iv (VOLATILE)
248    
249     const_iv (ERR_CORRUPT)
250     const_iv (ERR_IO)
251     const_iv (ERR_LOCK)
252     const_iv (ERR_OOM)
253     const_iv (ERR_EXISTS)
254     const_iv (ERR_NOLOCK)
255     const_iv (ERR_LOCK_TIMEOUT)
256     const_iv (ERR_NOEXIST)
257     const_iv (ERR_EINVAL)
258     const_iv (ERR_RDONLY)
259     const_iv (SUCCESS)
260    
261     const_iv (DEBUG_FATAL)
262     const_iv (DEBUG_ERROR)
263     const_iv (DEBUG_WARNING)
264     const_iv (DEBUG_TRACE)
265     };
266    
267     for (civ = const_iv + sizeof (const_iv) / sizeof (const_iv [0]); civ > const_iv; civ--)
268     newCONSTSUB (stash, (char *)civ[-1].name, newSViv (civ[-1].iv));
269    
270     tdb_runtime_check_for_robust_mutexes ();
271     }
272    
273     SV *
274     set_hash_function (int idx, SV *cb = 0)
275     PROTOTYPE: $;$
276     CODE:
277     {
278     if (idx < 1 || idx > 4)
279     croak ("hash function index must be between 1 and 4");
280    
281     SV **cbp = &custom_hash_cb[idx - 1];
282    
283     RETVAL = *cbp ? newSVsv (*cbp) : &PL_sv_undef;
284    
285     if (cb)
286     {
287     SvREFCNT_dec (*cbp);
288     *cbp = SvOK (cb) ? newSVsv (cb) : 0;
289     }
290     }
291     OUTPUT: RETVAL
292    
293     void
294     DESTROY (TDB_CONTEXT *tdb)
295     CODE:
296     if (tdb)
297     {
298     SvREFCNT_dec ((SV *)tdb_get_logging_private (tdb));
299     tdb_close (tdb);
300     }
301    
302 root 1.2 NO_OUTPUT int
303 root 1.1 tdb_delete (TDB_CONTEXT *tdb, TDB_DATA key)
304     ALIAS:
305     DELETE = 0
306 root 1.2 POSTCALL:
307     croak_on_error (RETVAL);
308 root 1.1
309 root 1.2 NO_OUTPUT int
310 root 1.1 tdb_wipe_all (TDB_CONTEXT *tdb)
311     ALIAS:
312     CLEAR = 0
313     CODE:
314     tdb_wipe_all (tdb);
315 root 1.2 POSTCALL:
316     croak_on_error (RETVAL);
317 root 1.1
318     void
319     tdb_dump_all (TDB_CONTEXT *tdb)
320    
321     enum TDB_ERROR
322     tdb_error (TDB_CONTEXT *tdb)
323    
324     const char *
325     tdb_errorstr (TDB_CONTEXT *tdb)
326    
327 root 1.2 SV *
328 root 1.1 tdb_exists (TDB_CONTEXT *tdb, TDB_DATA key)
329     ALIAS:
330     EXISTS = 0
331 root 1.2 CODE:
332     RETVAL = tdb_exists (tdb, key) ? &PL_sv_yes : &PL_sv_no;
333     OUTPUT: RETVAL
334 root 1.1
335     TDB_DATA
336     tdb_fetch (TDB_CONTEXT *tdb, TDB_DATA key)
337     ALIAS:
338     FETCH = 0
339    
340 root 1.2 NO_OUTPUT int
341     tdb_store (TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, int flag = TDB_REPLACE)
342     ALIAS:
343     STORE = 0
344     POSTCALL:
345     croak_on_error (RETVAL);
346    
347     NO_OUTPUT int
348 root 1.1 tdb_append (TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf)
349 root 1.2 POSTCALL:
350     croak_on_error (RETVAL);
351 root 1.1
352     TDB_DATA
353     tdb_firstkey (TDB_CONTEXT *tdb)
354     ALIAS:
355     FIRSTKEY = 0
356    
357 root 1.2 NO_OUTPUT int
358     tdb_context_only_and_mone_on_fail (TDB_CONTEXT *tdb)
359     INTERFACE:
360     tdb_lockall
361     tdb_unlockall
362     tdb_lockall_read
363     tdb_unlockall_read
364     tdb_lockall_mark
365     tdb_lockall_unmark
366     tdb_transaction_start
367     tdb_transaction_cancel
368     tdb_transaction_commit
369     tdb_transaction_prepare_commit
370     tdb_repack
371     POSTCALL:
372     croak_on_error (RETVAL);
373 root 1.1
374 root 1.6 bool
375     tdb_context_only_and_mone_on_fail_nonblock (TDB_CONTEXT *tdb)
376     INTERFACE:
377     tdb_lockall_nonblock
378     tdb_lockall_read_nonblock
379     tdb_transaction_start_nonblock
380     POSTCALL:
381     if (RETVAL < 0)
382     {
383     if (tdb_error (tdb) != TDB_ERR_LOCK)
384     croak_on_error (RETVAL);
385    
386     RETVAL = 0;
387     }
388     else
389     RETVAL = 1;
390    
391 root 1.1 bool tdb_transaction_active (TDB_CONTEXT *tdb)
392    
393     void tdb_enable_seqnum (TDB_CONTEXT *tdb)
394    
395     int tdb_get_seqnum (TDB_CONTEXT *tdb)
396    
397     void tdb_increment_seqnum_nonblock (TDB_CONTEXT *tdb)
398    
399     int tdb_hash_size (TDB_CONTEXT *tdb)
400    
401     size_t tdb_map_size (TDB_CONTEXT *tdb)
402    
403     int tdb_get_flags (TDB_CONTEXT *tdb)
404    
405     void tdb_add_flags (TDB_CONTEXT *tdb, unsigned int flag)
406    
407     void tdb_remove_flags (TDB_CONTEXT *tdb, unsigned int flag)
408    
409     bool tdb_runtime_check_for_robust_mutexes ()
410    
411     void
412     tdb_set_logging_function (TDB_CONTEXT *tdb, SV *cb)
413     CODE:
414     struct tdb_logging_context ctx;
415     ctx.log_fn = log_func_cb;
416     ctx.log_private = (SV *)newSVsv (cb);
417     SvREFCNT_dec ((SV *)tdb_get_logging_private (tdb));
418     tdb_set_logging_function (tdb, &ctx);
419    
420     TDB_DATA
421     tdb_nextkey (TDB_CONTEXT *tdb, TDB_DATA key)
422     ALIAS:
423     NEXTKEY = 0
424    
425     TDB_CONTEXT *
426     tdb_open (char *class, char *path, ...)
427     ALIAS:
428     TIEHASH = 0
429     CODE:
430     tdb_hash_func hash_func = 0;
431     struct tdb_logging_context ctx = { 0 };
432     int tdb_flags = TDB_DEFAULT;
433     int open_flags = O_RDWR | O_CREAT;
434     mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
435     int hash_size = 0;
436 root 1.4 int nocow = 0;
437 root 1.1
438     for (int i = 2; i < items - 1; i += 2)
439     {
440     const char *k = SvPVbyte_nolen (ST (i));
441     SV *v = ST (i + 1);
442    
443     if (strEQ (k, "tdb_flags" )) tdb_flags = SvIV (v);
444     else if (strEQ (k, "open_flags")) open_flags = SvIV (v);
445     else if (strEQ (k, "mode" )) mode = SvUV (v);
446     else if (strEQ (k, "hash_size" )) hash_size = SvIV (v);
447     else if (strEQ (k, "log_cb"))
448     {
449     SvREFCNT_dec (ctx.log_private);
450     ctx.log_fn = 0;
451     ctx.log_private = 0;
452    
453     if (SvOK (v))
454     {
455     ctx.log_fn = log_func_cb;
456     ctx.log_private = (void *)newSVsv (v);
457     }
458     }
459     else if (strEQ (k, "hash"))
460     {
461     const char *f = SvPVbyte_nolen (v);
462    
463     if (!SvOK (v) ) hash_func = 0;
464     else if (strEQ (f, "default")) hash_func = 0;
465     else if (strEQ (f, "jenkins")) hash_func = tdb_jenkins_hash;
466     else if (strEQ (f, "fnv1ax" )) hash_func = fnv1ax_hash;
467     else if (strEQ (f, "xxh3" )) hash_func = xxh3_hash;
468     else if (strEQ (f, "1" )) hash_func = custom_hash_1;
469     else if (strEQ (f, "2" )) hash_func = custom_hash_2;
470     else if (strEQ (f, "3" )) hash_func = custom_hash_3;
471     else if (strEQ (f, "4" )) hash_func = custom_hash_4;
472     else
473     croak ("%s: not a known hash function", f);
474     }
475     else if (strEQ (k, "mutex"))
476     {
477     if (SvTRUE (v))
478     {
479     if (tdb_runtime_check_for_robust_mutexes ())
480     tdb_flags |= TDB_MUTEX_LOCKING;
481     }
482     else
483     tdb_flags &= ~TDB_MUTEX_LOCKING;
484     }
485 root 1.4 else if (strEQ (k, "nocow"))
486     nocow = SvTRUE (v);
487 root 1.1 else
488     croak ("%s: not a known parameter name", k);
489     }
490    
491 root 1.5 if (nocow && (open_flags & O_CREAT) && !(tdb_flags & TDB_INTERNAL))
492 root 1.4 set_nocow (path, open_flags, mode);
493    
494 root 1.1 RETVAL = tdb_open_ex (path, hash_size, tdb_flags, open_flags, mode, ctx.log_fn ? &ctx : 0, hash_func);
495    
496     if (!RETVAL)
497     {
498     SvREFCNT_dec ((SV *)ctx.log_private);
499     XSRETURN_UNDEF;
500     }
501     OUTPUT: RETVAL
502    
503     void
504     tdb_printfreelist (TDB_CONTEXT *tdb)
505    
506 root 1.2 void
507 root 1.1 tdb_reopen (TDB_CONTEXT *tdb)
508     PROTOTYPE:
509     CODE:
510     void *logcb = tdb_get_logging_private (tdb);
511    
512 root 1.2 int res = tdb_reopen (tdb);
513 root 1.1
514 root 1.2 if (res < 0)
515 root 1.1 {
516     /* tdb_reopen frees the TDB_CONTEXT on failure,
517     * so set scalar value to 0 to avoid double free on DESTROY */
518     sv_setiv ((SV*)SvRV (ST (0)), 0);
519     SvREFCNT_dec ((SV *)logcb);
520 root 1.2 croak ("tdb_reopen failed");
521 root 1.1 }
522    
523     # FIXME: if this fails, we need to undef $tdb or something
524     # .. which we can't do - cos we don't know where it failed :(
525     # maybe reimplement this ourselves?
526 root 1.2 NO_OUTPUT int
527 root 1.1 tdb_reopen_all (int parent_longlived = 0)
528 root 1.2 POSTCALL:
529     if (RETVAL < 0)
530     croak ("tdb_reopen_all failed");
531 root 1.1
532     int
533     tdb_fd (TDB_CONTEXT *tdb)
534    
535     const char *
536     tdb_name (TDB_CONTEXT *tdb)
537    
538     int
539     tdb_traverse (TDB_CONTEXT *tdb, SV *fn = &PL_sv_undef)
540     ALIAS:
541     tdb_traverse_read = 1
542     CODE:
543 root 1.2 RETVAL = (ix ? tdb_traverse_read : tdb_traverse) (tdb, SvOK (fn) ? traverse_cb: 0, fn);
544     croak_on_error (RETVAL);
545 root 1.1 OUTPUT: RETVAL
546    
547 root 1.3 mone_on_fail
548     tdb_check (TDB_CONTEXT *tdb, SV *fn = &PL_sv_undef)
549     CODE:
550     RETVAL = tdb_check (tdb, SvOK (fn) ? check_cb : 0, fn);
551     OUTPUT: RETVAL
552    
553     mone_on_fail
554     tdb_rescue (TDB_CONTEXT *tdb, SV *fn)
555     CODE:
556     RETVAL = tdb_rescue (tdb, rescue_cb, fn);
557     OUTPUT: RETVAL
558