2 Trivial Database 2: fetch, store and misc routines.
3 Copyright (C) Rusty Russell 2010
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 3 of the License, or (at your option) any later version.
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 #ifndef HAVE_LIBREPLACE
20 #include <ccan/asprintf/asprintf.h>
24 static enum NTDB_ERROR
update_rec_hdr(struct ntdb_context
*ntdb
,
28 struct ntdb_used_record
*rec
,
31 uint64_t dataroom
= rec_data_length(rec
) + rec_extra_padding(rec
);
32 enum NTDB_ERROR ecode
;
34 ecode
= set_header(ntdb
, rec
, NTDB_USED_MAGIC
, keylen
, datalen
,
35 keylen
+ dataroom
, h
);
36 if (ecode
== NTDB_SUCCESS
) {
37 ecode
= ntdb_write_convert(ntdb
, off
, rec
, sizeof(*rec
));
42 static enum NTDB_ERROR
replace_data(struct ntdb_context
*ntdb
,
44 NTDB_DATA key
, NTDB_DATA dbuf
,
45 ntdb_off_t old_off
, ntdb_len_t old_room
,
49 enum NTDB_ERROR ecode
;
51 /* Allocate a new record. */
52 new_off
= alloc(ntdb
, key
.dsize
, dbuf
.dsize
, h
->h
, NTDB_USED_MAGIC
,
54 if (NTDB_OFF_IS_ERR(new_off
)) {
55 return NTDB_OFF_TO_ERR(new_off
);
58 /* We didn't like the existing one: remove it. */
61 ecode
= add_free_record(ntdb
, old_off
,
62 sizeof(struct ntdb_used_record
)
63 + key
.dsize
+ old_room
,
64 NTDB_LOCK_WAIT
, true);
65 if (ecode
== NTDB_SUCCESS
)
66 ecode
= replace_in_hash(ntdb
, h
, new_off
);
68 ecode
= add_to_hash(ntdb
, h
, new_off
);
70 if (ecode
!= NTDB_SUCCESS
) {
74 new_off
+= sizeof(struct ntdb_used_record
);
75 ecode
= ntdb
->io
->twrite(ntdb
, new_off
, key
.dptr
, key
.dsize
);
76 if (ecode
!= NTDB_SUCCESS
) {
81 ecode
= ntdb
->io
->twrite(ntdb
, new_off
, dbuf
.dptr
, dbuf
.dsize
);
82 if (ecode
!= NTDB_SUCCESS
) {
86 if (ntdb
->flags
& NTDB_SEQNUM
)
87 ntdb_inc_seqnum(ntdb
);
92 static enum NTDB_ERROR
update_data(struct ntdb_context
*ntdb
,
97 enum NTDB_ERROR ecode
;
99 ecode
= ntdb
->io
->twrite(ntdb
, off
, dbuf
.dptr
, dbuf
.dsize
);
100 if (ecode
== NTDB_SUCCESS
&& extra
) {
101 /* Put a zero in; future versions may append other data. */
102 ecode
= ntdb
->io
->twrite(ntdb
, off
+ dbuf
.dsize
, "", 1);
104 if (ntdb
->flags
& NTDB_SEQNUM
)
105 ntdb_inc_seqnum(ntdb
);
110 _PUBLIC_
enum NTDB_ERROR
ntdb_store(struct ntdb_context
*ntdb
,
111 NTDB_DATA key
, NTDB_DATA dbuf
, int flag
)
115 ntdb_len_t old_room
= 0;
116 struct ntdb_used_record rec
;
117 enum NTDB_ERROR ecode
;
119 off
= find_and_lock(ntdb
, key
, F_WRLCK
, &h
, &rec
, NULL
);
120 if (NTDB_OFF_IS_ERR(off
)) {
121 return NTDB_OFF_TO_ERR(off
);
124 /* Now we have lock on this hash bucket. */
125 if (flag
== NTDB_INSERT
) {
127 ecode
= NTDB_ERR_EXISTS
;
132 old_room
= rec_data_length(&rec
)
133 + rec_extra_padding(&rec
);
134 if (old_room
>= dbuf
.dsize
) {
135 /* Can modify in-place. Easy! */
136 ecode
= update_rec_hdr(ntdb
, off
,
137 key
.dsize
, dbuf
.dsize
,
139 if (ecode
!= NTDB_SUCCESS
) {
142 ecode
= update_data(ntdb
,
145 old_room
- dbuf
.dsize
);
146 if (ecode
!= NTDB_SUCCESS
) {
149 ntdb_unlock_hashes(ntdb
, h
.hlock_start
,
150 h
.hlock_range
, F_WRLCK
);
154 if (flag
== NTDB_MODIFY
) {
155 /* if the record doesn't exist and we
156 are in NTDB_MODIFY mode then we should fail
158 ecode
= NTDB_ERR_NOEXIST
;
164 /* If we didn't use the old record, this implies we're growing. */
165 ecode
= replace_data(ntdb
, &h
, key
, dbuf
, off
, old_room
, off
);
167 ntdb_unlock_hashes(ntdb
, h
.hlock_start
, h
.hlock_range
, F_WRLCK
);
171 _PUBLIC_
enum NTDB_ERROR
ntdb_append(struct ntdb_context
*ntdb
,
172 NTDB_DATA key
, NTDB_DATA dbuf
)
176 struct ntdb_used_record rec
;
177 ntdb_len_t old_room
= 0, old_dlen
;
178 unsigned char *newdata
;
180 enum NTDB_ERROR ecode
;
182 off
= find_and_lock(ntdb
, key
, F_WRLCK
, &h
, &rec
, NULL
);
183 if (NTDB_OFF_IS_ERR(off
)) {
184 return NTDB_OFF_TO_ERR(off
);
188 old_dlen
= rec_data_length(&rec
);
189 old_room
= old_dlen
+ rec_extra_padding(&rec
);
191 /* Fast path: can append in place. */
192 if (rec_extra_padding(&rec
) >= dbuf
.dsize
) {
193 ecode
= update_rec_hdr(ntdb
, off
, key
.dsize
,
194 old_dlen
+ dbuf
.dsize
, &rec
,
196 if (ecode
!= NTDB_SUCCESS
) {
200 off
+= sizeof(rec
) + key
.dsize
+ old_dlen
;
201 ecode
= update_data(ntdb
, off
, dbuf
,
202 rec_extra_padding(&rec
));
207 newdata
= ntdb
->alloc_fn(ntdb
, key
.dsize
+ old_dlen
+ dbuf
.dsize
,
210 ecode
= ntdb_logerr(ntdb
, NTDB_ERR_OOM
, NTDB_LOG_ERROR
,
212 " failed to allocate %zu bytes",
213 (size_t)(key
.dsize
+ old_dlen
217 ecode
= ntdb
->io
->tread(ntdb
, off
+ sizeof(rec
) + key
.dsize
,
219 if (ecode
!= NTDB_SUCCESS
) {
220 goto out_free_newdata
;
222 memcpy(newdata
+ old_dlen
, dbuf
.dptr
, dbuf
.dsize
);
223 new_dbuf
.dptr
= newdata
;
224 new_dbuf
.dsize
= old_dlen
+ dbuf
.dsize
;
230 /* If they're using ntdb_append(), it implies they're growing record. */
231 ecode
= replace_data(ntdb
, &h
, key
, new_dbuf
, off
, old_room
, true);
234 ntdb
->free_fn(newdata
, ntdb
->alloc_data
);
236 ntdb_unlock_hashes(ntdb
, h
.hlock_start
, h
.hlock_range
, F_WRLCK
);
240 _PUBLIC_
enum NTDB_ERROR
ntdb_fetch(struct ntdb_context
*ntdb
, NTDB_DATA key
,
244 struct ntdb_used_record rec
;
246 enum NTDB_ERROR ecode
;
248 off
= find_and_lock(ntdb
, key
, F_RDLCK
, &h
, &rec
, NULL
);
249 if (NTDB_OFF_IS_ERR(off
)) {
250 return NTDB_OFF_TO_ERR(off
);
254 ecode
= NTDB_ERR_NOEXIST
;
256 data
->dsize
= rec_data_length(&rec
);
257 data
->dptr
= ntdb_alloc_read(ntdb
, off
+ sizeof(rec
) + key
.dsize
,
259 if (NTDB_PTR_IS_ERR(data
->dptr
)) {
260 ecode
= NTDB_PTR_ERR(data
->dptr
);
262 ecode
= NTDB_SUCCESS
;
265 ntdb_unlock_hashes(ntdb
, h
.hlock_start
, h
.hlock_range
, F_RDLCK
);
269 _PUBLIC_
bool ntdb_exists(struct ntdb_context
*ntdb
, NTDB_DATA key
)
272 struct ntdb_used_record rec
;
275 off
= find_and_lock(ntdb
, key
, F_RDLCK
, &h
, &rec
, NULL
);
276 if (NTDB_OFF_IS_ERR(off
)) {
279 ntdb_unlock_hashes(ntdb
, h
.hlock_start
, h
.hlock_range
, F_RDLCK
);
281 return off
? true : false;
284 _PUBLIC_
enum NTDB_ERROR
ntdb_delete(struct ntdb_context
*ntdb
, NTDB_DATA key
)
287 struct ntdb_used_record rec
;
289 enum NTDB_ERROR ecode
;
291 off
= find_and_lock(ntdb
, key
, F_WRLCK
, &h
, &rec
, NULL
);
292 if (NTDB_OFF_IS_ERR(off
)) {
293 return NTDB_OFF_TO_ERR(off
);
297 ecode
= NTDB_ERR_NOEXIST
;
301 ecode
= delete_from_hash(ntdb
, &h
);
302 if (ecode
!= NTDB_SUCCESS
) {
306 /* Free the deleted entry. */
308 ecode
= add_free_record(ntdb
, off
,
309 sizeof(struct ntdb_used_record
)
310 + rec_key_length(&rec
)
311 + rec_data_length(&rec
)
312 + rec_extra_padding(&rec
),
313 NTDB_LOCK_WAIT
, true);
315 if (ntdb
->flags
& NTDB_SEQNUM
)
316 ntdb_inc_seqnum(ntdb
);
319 ntdb_unlock_hashes(ntdb
, h
.hlock_start
, h
.hlock_range
, F_WRLCK
);
323 _PUBLIC_
unsigned int ntdb_get_flags(struct ntdb_context
*ntdb
)
328 static bool inside_transaction(const struct ntdb_context
*ntdb
)
330 return ntdb
->transaction
!= NULL
;
333 static bool readonly_changable(struct ntdb_context
*ntdb
, const char *caller
)
335 if (inside_transaction(ntdb
)) {
336 ntdb_logerr(ntdb
, NTDB_ERR_EINVAL
, NTDB_LOG_USE_ERROR
,
338 " NTDB_RDONLY inside transaction",
345 _PUBLIC_
void ntdb_add_flag(struct ntdb_context
*ntdb
, unsigned flag
)
347 if (ntdb
->flags
& NTDB_INTERNAL
) {
348 ntdb_logerr(ntdb
, NTDB_ERR_EINVAL
, NTDB_LOG_USE_ERROR
,
349 "ntdb_add_flag: internal db");
354 ntdb
->flags
|= NTDB_NOLOCK
;
357 ntdb
->flags
|= NTDB_NOMMAP
;
358 #ifndef HAVE_INCOHERENT_MMAP
359 ntdb_munmap(ntdb
->file
);
363 ntdb
->flags
|= NTDB_NOSYNC
;
366 ntdb
->flags
|= NTDB_SEQNUM
;
368 case NTDB_ALLOW_NESTING
:
369 ntdb
->flags
|= NTDB_ALLOW_NESTING
;
372 if (readonly_changable(ntdb
, "ntdb_add_flag"))
373 ntdb
->flags
|= NTDB_RDONLY
;
376 ntdb_logerr(ntdb
, NTDB_ERR_EINVAL
, NTDB_LOG_USE_ERROR
,
377 "ntdb_add_flag: Unknown flag %u", flag
);
381 _PUBLIC_
void ntdb_remove_flag(struct ntdb_context
*ntdb
, unsigned flag
)
383 if (ntdb
->flags
& NTDB_INTERNAL
) {
384 ntdb_logerr(ntdb
, NTDB_ERR_EINVAL
, NTDB_LOG_USE_ERROR
,
385 "ntdb_remove_flag: internal db");
390 ntdb
->flags
&= ~NTDB_NOLOCK
;
393 ntdb
->flags
&= ~NTDB_NOMMAP
;
394 #ifndef HAVE_INCOHERENT_MMAP
395 /* If mmap incoherent, we were mmaping anyway. */
400 ntdb
->flags
&= ~NTDB_NOSYNC
;
403 ntdb
->flags
&= ~NTDB_SEQNUM
;
405 case NTDB_ALLOW_NESTING
:
406 ntdb
->flags
&= ~NTDB_ALLOW_NESTING
;
409 if ((ntdb
->open_flags
& O_ACCMODE
) == O_RDONLY
) {
410 ntdb_logerr(ntdb
, NTDB_ERR_EINVAL
, NTDB_LOG_USE_ERROR
,
411 "ntdb_remove_flag: can't"
412 " remove NTDB_RDONLY on ntdb"
413 " opened with O_RDONLY");
416 if (readonly_changable(ntdb
, "ntdb_remove_flag"))
417 ntdb
->flags
&= ~NTDB_RDONLY
;
420 ntdb_logerr(ntdb
, NTDB_ERR_EINVAL
, NTDB_LOG_USE_ERROR
,
421 "ntdb_remove_flag: Unknown flag %u",
426 _PUBLIC_
const char *ntdb_errorstr(enum NTDB_ERROR ecode
)
428 /* Gcc warns if you miss a case in the switch, so use that. */
429 switch (NTDB_ERR_TO_OFF(ecode
)) {
430 case NTDB_ERR_TO_OFF(NTDB_SUCCESS
): return "Success";
431 case NTDB_ERR_TO_OFF(NTDB_ERR_CORRUPT
): return "Corrupt database";
432 case NTDB_ERR_TO_OFF(NTDB_ERR_IO
): return "IO Error";
433 case NTDB_ERR_TO_OFF(NTDB_ERR_LOCK
): return "Locking error";
434 case NTDB_ERR_TO_OFF(NTDB_ERR_OOM
): return "Out of memory";
435 case NTDB_ERR_TO_OFF(NTDB_ERR_EXISTS
): return "Record exists";
436 case NTDB_ERR_TO_OFF(NTDB_ERR_EINVAL
): return "Invalid parameter";
437 case NTDB_ERR_TO_OFF(NTDB_ERR_NOEXIST
): return "Record does not exist";
438 case NTDB_ERR_TO_OFF(NTDB_ERR_RDONLY
): return "write not permitted";
440 return "Invalid error code";
443 enum NTDB_ERROR COLD
ntdb_logerr(struct ntdb_context
*ntdb
,
444 enum NTDB_ERROR ecode
,
445 enum ntdb_log_level level
,
446 const char *fmt
, ...)
451 /* ntdb_open paths care about errno, so save it. */
452 int saved_errno
= errno
;
458 len
= vsnprintf(NULL
, 0, fmt
, ap
);
461 message
= ntdb
->alloc_fn(ntdb
, len
+ 1, ntdb
->alloc_data
);
463 ntdb
->log_fn(ntdb
, NTDB_LOG_ERROR
, NTDB_ERR_OOM
,
464 "out of memory formatting message:", ntdb
->log_data
);
465 ntdb
->log_fn(ntdb
, level
, ecode
, fmt
, ntdb
->log_data
);
468 vsnprintf(message
, len
+1, fmt
, ap
);
470 ntdb
->log_fn(ntdb
, level
, ecode
, message
, ntdb
->log_data
);
471 ntdb
->free_fn(message
, ntdb
->alloc_data
);
477 _PUBLIC_
enum NTDB_ERROR
ntdb_parse_record_(struct ntdb_context
*ntdb
,
479 enum NTDB_ERROR (*parse
)(NTDB_DATA k
,
485 struct ntdb_used_record rec
;
487 enum NTDB_ERROR ecode
;
489 off
= find_and_lock(ntdb
, key
, F_RDLCK
, &h
, &rec
, NULL
);
490 if (NTDB_OFF_IS_ERR(off
)) {
491 return NTDB_OFF_TO_ERR(off
);
495 ecode
= NTDB_ERR_NOEXIST
;
498 dptr
= ntdb_access_read(ntdb
, off
+ sizeof(rec
) + key
.dsize
,
499 rec_data_length(&rec
), false);
500 if (NTDB_PTR_IS_ERR(dptr
)) {
501 ecode
= NTDB_PTR_ERR(dptr
);
503 NTDB_DATA d
= ntdb_mkdata(dptr
, rec_data_length(&rec
));
505 ecode
= parse(key
, d
, data
);
506 ntdb_access_release(ntdb
, dptr
);
510 ntdb_unlock_hashes(ntdb
, h
.hlock_start
, h
.hlock_range
, F_RDLCK
);
514 _PUBLIC_
const char *ntdb_name(const struct ntdb_context
*ntdb
)
519 _PUBLIC_
int64_t ntdb_get_seqnum(struct ntdb_context
*ntdb
)
521 return ntdb_read_off(ntdb
, offsetof(struct ntdb_header
, seqnum
));
525 _PUBLIC_
int ntdb_fd(const struct ntdb_context
*ntdb
)
527 return ntdb
->file
->fd
;
530 struct traverse_state
{
531 enum NTDB_ERROR error
;
532 struct ntdb_context
*dest_db
;
536 traverse function for repacking
538 static int repack_traverse(struct ntdb_context
*ntdb
, NTDB_DATA key
, NTDB_DATA data
,
539 struct traverse_state
*state
)
541 state
->error
= ntdb_store(state
->dest_db
, key
, data
, NTDB_INSERT
);
542 if (state
->error
!= NTDB_SUCCESS
) {
548 _PUBLIC_
enum NTDB_ERROR
ntdb_repack(struct ntdb_context
*ntdb
)
550 struct ntdb_context
*tmp_db
;
551 struct traverse_state state
;
553 state
.error
= ntdb_transaction_start(ntdb
);
554 if (state
.error
!= NTDB_SUCCESS
) {
558 tmp_db
= ntdb_open("tmpdb", NTDB_INTERNAL
, O_RDWR
|O_CREAT
, 0, NULL
);
559 if (tmp_db
== NULL
) {
560 state
.error
= ntdb_logerr(ntdb
, NTDB_ERR_OOM
, NTDB_LOG_ERROR
,
562 " Failed to create tmp_db");
563 ntdb_transaction_cancel(ntdb
);
567 state
.dest_db
= tmp_db
;
568 if (ntdb_traverse(ntdb
, repack_traverse
, &state
) < 0) {
572 state
.error
= ntdb_wipe_all(ntdb
);
573 if (state
.error
!= NTDB_SUCCESS
) {
577 state
.dest_db
= ntdb
;
578 if (ntdb_traverse(tmp_db
, repack_traverse
, &state
) < 0) {
583 return ntdb_transaction_commit(ntdb
);
586 ntdb_transaction_cancel(ntdb
);