]> git.ozlabs.org Git - ccan/blob - ccan/tdb2/tdb.c
tdb2: remove tdb_null
[ccan] / ccan / tdb2 / tdb.c
1 #include "private.h"
2 #include <ccan/asprintf/asprintf.h>
3 #include <stdarg.h>
4
5 static enum TDB_ERROR update_rec_hdr(struct tdb_context *tdb,
6                                      tdb_off_t off,
7                                      tdb_len_t keylen,
8                                      tdb_len_t datalen,
9                                      struct tdb_used_record *rec,
10                                      uint64_t h)
11 {
12         uint64_t dataroom = rec_data_length(rec) + rec_extra_padding(rec);
13         enum TDB_ERROR ecode;
14
15         ecode = set_header(tdb, rec, TDB_USED_MAGIC, keylen, datalen,
16                            keylen + dataroom, h);
17         if (ecode == TDB_SUCCESS) {
18                 ecode = tdb_write_convert(tdb, off, rec, sizeof(*rec));
19         }
20         return ecode;
21 }
22
23 static enum TDB_ERROR replace_data(struct tdb_context *tdb,
24                                    struct hash_info *h,
25                                    struct tdb_data key, struct tdb_data dbuf,
26                                    tdb_off_t old_off, tdb_len_t old_room,
27                                    bool growing)
28 {
29         tdb_off_t new_off;
30         enum TDB_ERROR ecode;
31
32         /* Allocate a new record. */
33         new_off = alloc(tdb, key.dsize, dbuf.dsize, h->h, TDB_USED_MAGIC,
34                         growing);
35         if (TDB_OFF_IS_ERR(new_off)) {
36                 return new_off;
37         }
38
39         /* We didn't like the existing one: remove it. */
40         if (old_off) {
41                 add_stat(tdb, frees, 1);
42                 ecode = add_free_record(tdb, old_off,
43                                         sizeof(struct tdb_used_record)
44                                         + key.dsize + old_room);
45                 if (ecode == TDB_SUCCESS)
46                         ecode = replace_in_hash(tdb, h, new_off);
47         } else {
48                 ecode = add_to_hash(tdb, h, new_off);
49         }
50         if (ecode != TDB_SUCCESS) {
51                 return ecode;
52         }
53
54         new_off += sizeof(struct tdb_used_record);
55         ecode = tdb->methods->twrite(tdb, new_off, key.dptr, key.dsize);
56         if (ecode != TDB_SUCCESS) {
57                 return ecode;
58         }
59
60         new_off += key.dsize;
61         ecode = tdb->methods->twrite(tdb, new_off, dbuf.dptr, dbuf.dsize);
62         if (ecode != TDB_SUCCESS) {
63                 return ecode;
64         }
65
66         /* FIXME: tdb_increment_seqnum(tdb); */
67         return TDB_SUCCESS;
68 }
69
70 static enum TDB_ERROR update_data(struct tdb_context *tdb,
71                                   tdb_off_t off,
72                                   struct tdb_data dbuf,
73                                   tdb_len_t extra)
74 {
75         enum TDB_ERROR ecode;
76
77         ecode = tdb->methods->twrite(tdb, off, dbuf.dptr, dbuf.dsize);
78         if (ecode == TDB_SUCCESS && extra) {
79                 /* Put a zero in; future versions may append other data. */
80                 ecode = tdb->methods->twrite(tdb, off + dbuf.dsize, "", 1);
81         }
82         return ecode;
83 }
84
85 enum TDB_ERROR tdb_store(struct tdb_context *tdb,
86                          struct tdb_data key, struct tdb_data dbuf, int flag)
87 {
88         struct hash_info h;
89         tdb_off_t off;
90         tdb_len_t old_room = 0;
91         struct tdb_used_record rec;
92         enum TDB_ERROR ecode;
93
94         off = find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL);
95         if (TDB_OFF_IS_ERR(off)) {
96                 return off;
97         }
98
99         /* Now we have lock on this hash bucket. */
100         if (flag == TDB_INSERT) {
101                 if (off) {
102                         ecode = TDB_ERR_EXISTS;
103                         goto out;
104                 }
105         } else {
106                 if (off) {
107                         old_room = rec_data_length(&rec)
108                                 + rec_extra_padding(&rec);
109                         if (old_room >= dbuf.dsize) {
110                                 /* Can modify in-place.  Easy! */
111                                 ecode = update_rec_hdr(tdb, off,
112                                                        key.dsize, dbuf.dsize,
113                                                        &rec, h.h);
114                                 if (ecode != TDB_SUCCESS) {
115                                         goto out;
116                                 }
117                                 ecode = update_data(tdb,
118                                                     off + sizeof(rec)
119                                                     + key.dsize, dbuf,
120                                                     old_room - dbuf.dsize);
121                                 if (ecode != TDB_SUCCESS) {
122                                         goto out;
123                                 }
124                                 tdb_unlock_hashes(tdb, h.hlock_start,
125                                                   h.hlock_range, F_WRLCK);
126                                 return TDB_SUCCESS;
127                         }
128                 } else {
129                         if (flag == TDB_MODIFY) {
130                                 /* if the record doesn't exist and we
131                                    are in TDB_MODIFY mode then we should fail
132                                    the store */
133                                 ecode = TDB_ERR_NOEXIST;
134                                 goto out;
135                         }
136                 }
137         }
138
139         /* If we didn't use the old record, this implies we're growing. */
140         ecode = replace_data(tdb, &h, key, dbuf, off, old_room, off);
141 out:
142         tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_WRLCK);
143         return ecode;
144 }
145
146 enum TDB_ERROR tdb_append(struct tdb_context *tdb,
147                           struct tdb_data key, struct tdb_data dbuf)
148 {
149         struct hash_info h;
150         tdb_off_t off;
151         struct tdb_used_record rec;
152         tdb_len_t old_room = 0, old_dlen;
153         unsigned char *newdata;
154         struct tdb_data new_dbuf;
155         enum TDB_ERROR ecode;
156
157         off = find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL);
158         if (TDB_OFF_IS_ERR(off)) {
159                 return off;
160         }
161
162         if (off) {
163                 old_dlen = rec_data_length(&rec);
164                 old_room = old_dlen + rec_extra_padding(&rec);
165
166                 /* Fast path: can append in place. */
167                 if (rec_extra_padding(&rec) >= dbuf.dsize) {
168                         ecode = update_rec_hdr(tdb, off, key.dsize,
169                                                old_dlen + dbuf.dsize, &rec,
170                                                h.h);
171                         if (ecode != TDB_SUCCESS) {
172                                 goto out;
173                         }
174
175                         off += sizeof(rec) + key.dsize + old_dlen;
176                         ecode = update_data(tdb, off, dbuf,
177                                             rec_extra_padding(&rec));
178                         goto out;
179                 }
180
181                 /* Slow path. */
182                 newdata = malloc(key.dsize + old_dlen + dbuf.dsize);
183                 if (!newdata) {
184                         ecode = tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR,
185                                            "tdb_append:"
186                                            " failed to allocate %zu bytes",
187                                            (size_t)(key.dsize + old_dlen
188                                                     + dbuf.dsize));
189                         goto out;
190                 }
191                 ecode = tdb->methods->tread(tdb, off + sizeof(rec) + key.dsize,
192                                             newdata, old_dlen);
193                 if (ecode != TDB_SUCCESS) {
194                         goto out_free_newdata;
195                 }
196                 memcpy(newdata + old_dlen, dbuf.dptr, dbuf.dsize);
197                 new_dbuf.dptr = newdata;
198                 new_dbuf.dsize = old_dlen + dbuf.dsize;
199         } else {
200                 newdata = NULL;
201                 new_dbuf = dbuf;
202         }
203
204         /* If they're using tdb_append(), it implies they're growing record. */
205         ecode = replace_data(tdb, &h, key, new_dbuf, off, old_room, true);
206
207 out_free_newdata:
208         free(newdata);
209 out:
210         tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_WRLCK);
211         return ecode;
212 }
213
214 enum TDB_ERROR tdb_fetch(struct tdb_context *tdb, struct tdb_data key,
215                          struct tdb_data *data)
216 {
217         tdb_off_t off;
218         struct tdb_used_record rec;
219         struct hash_info h;
220         enum TDB_ERROR ecode;
221
222         off = find_and_lock(tdb, key, F_RDLCK, &h, &rec, NULL);
223         if (TDB_OFF_IS_ERR(off)) {
224                 return off;
225         }
226
227         if (!off) {
228                 ecode = TDB_ERR_NOEXIST;
229         } else {
230                 data->dsize = rec_data_length(&rec);
231                 data->dptr = tdb_alloc_read(tdb, off + sizeof(rec) + key.dsize,
232                                             data->dsize);
233                 if (TDB_PTR_IS_ERR(data->dptr)) {
234                         ecode = TDB_PTR_ERR(data->dptr);
235                 } else
236                         ecode = TDB_SUCCESS;
237         }
238
239         tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_RDLCK);
240         return ecode;
241 }
242
243 bool tdb_exists(struct tdb_context *tdb, TDB_DATA key)
244 {
245         tdb_off_t off;
246         struct tdb_used_record rec;
247         struct hash_info h;
248
249         off = find_and_lock(tdb, key, F_RDLCK, &h, &rec, NULL);
250         if (TDB_OFF_IS_ERR(off)) {
251                 return false;
252         }
253         tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_RDLCK);
254
255         return off ? true : false;
256 }
257
258 enum TDB_ERROR tdb_delete(struct tdb_context *tdb, struct tdb_data key)
259 {
260         tdb_off_t off;
261         struct tdb_used_record rec;
262         struct hash_info h;
263         enum TDB_ERROR ecode;
264
265         off = find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL);
266         if (TDB_OFF_IS_ERR(off)) {
267                 return off;
268         }
269
270         if (!off) {
271                 ecode = TDB_ERR_NOEXIST;
272                 goto unlock;
273         }
274
275         ecode = delete_from_hash(tdb, &h);
276         if (ecode != TDB_SUCCESS) {
277                 goto unlock;
278         }
279
280         /* Free the deleted entry. */
281         add_stat(tdb, frees, 1);
282         ecode = add_free_record(tdb, off,
283                                 sizeof(struct tdb_used_record)
284                                 + rec_key_length(&rec)
285                                 + rec_data_length(&rec)
286                                 + rec_extra_padding(&rec));
287
288 unlock:
289         tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_WRLCK);
290         return ecode;
291 }
292
293 unsigned int tdb_get_flags(struct tdb_context *tdb)
294 {
295         return tdb->flags;
296 }
297
298 void tdb_add_flag(struct tdb_context *tdb, unsigned flag)
299 {
300         if (tdb->flags & TDB_INTERNAL) {
301                 tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR,
302                            "tdb_add_flag: internal db");
303                 return;
304         }
305         switch (flag) {
306         case TDB_NOLOCK:
307                 tdb->flags |= TDB_NOLOCK;
308                 break;
309         case TDB_NOMMAP:
310                 tdb->flags |= TDB_NOMMAP;
311                 tdb_munmap(tdb->file);
312                 break;
313         case TDB_NOSYNC:
314                 tdb->flags |= TDB_NOSYNC;
315                 break;
316         default:
317                 tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR,
318                            "tdb_add_flag: Unknown flag %u", flag);
319         }
320 }
321
322 void tdb_remove_flag(struct tdb_context *tdb, unsigned flag)
323 {
324         if (tdb->flags & TDB_INTERNAL) {
325                 tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR,
326                            "tdb_remove_flag: internal db");
327                 return;
328         }
329         switch (flag) {
330         case TDB_NOLOCK:
331                 tdb->flags &= ~TDB_NOLOCK;
332                 break;
333         case TDB_NOMMAP:
334                 tdb->flags &= ~TDB_NOMMAP;
335                 tdb_mmap(tdb);
336                 break;
337         case TDB_NOSYNC:
338                 tdb->flags &= ~TDB_NOSYNC;
339                 break;
340         default:
341                 tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR,
342                            "tdb_remove_flag: Unknown flag %u", flag);
343         }
344 }
345
346 const char *tdb_errorstr(enum TDB_ERROR ecode)
347 {
348         /* Gcc warns if you miss a case in the switch, so use that. */
349         switch (ecode) {
350         case TDB_SUCCESS: return "Success";
351         case TDB_ERR_CORRUPT: return "Corrupt database";
352         case TDB_ERR_IO: return "IO Error";
353         case TDB_ERR_LOCK: return "Locking error";
354         case TDB_ERR_OOM: return "Out of memory";
355         case TDB_ERR_EXISTS: return "Record exists";
356         case TDB_ERR_EINVAL: return "Invalid parameter";
357         case TDB_ERR_NOEXIST: return "Record does not exist";
358         case TDB_ERR_RDONLY: return "write not permitted";
359         }
360         return "Invalid error code";
361 }
362
363 enum TDB_ERROR COLD tdb_logerr(struct tdb_context *tdb,
364                                enum TDB_ERROR ecode,
365                                enum tdb_log_level level,
366                                const char *fmt, ...)
367 {
368         char *message;
369         va_list ap;
370         size_t len;
371         /* tdb_open paths care about errno, so save it. */
372         int saved_errno = errno;
373
374         if (!tdb->logfn)
375                 return ecode;
376
377         va_start(ap, fmt);
378         len = vasprintf(&message, fmt, ap);
379         va_end(ap);
380
381         if (len < 0) {
382                 tdb->logfn(tdb, TDB_LOG_ERROR, tdb->log_private,
383                            "out of memory formatting message:");
384                 tdb->logfn(tdb, level, tdb->log_private, fmt);
385         } else {
386                 tdb->logfn(tdb, level, tdb->log_private, message);
387                 free(message);
388         }
389         errno = saved_errno;
390         return ecode;
391 }
392
393 enum TDB_ERROR tdb_parse_record_(struct tdb_context *tdb,
394                                  TDB_DATA key,
395                                  enum TDB_ERROR (*parse)(TDB_DATA key,
396                                                          TDB_DATA data,
397                                                          void *p),
398                                  void *p)
399 {
400         tdb_off_t off;
401         struct tdb_used_record rec;
402         struct hash_info h;
403         TDB_DATA data;
404         enum TDB_ERROR ecode;
405
406         off = find_and_lock(tdb, key, F_RDLCK, &h, &rec, NULL);
407         if (TDB_OFF_IS_ERR(off)) {
408                 return off;
409         }
410
411         if (!off) {
412                 ecode = TDB_ERR_NOEXIST;
413         } else {
414                 data.dsize = rec_data_length(&rec);
415                 data.dptr = (void *)tdb_access_read(tdb,
416                                                     off + sizeof(rec)
417                                                     + key.dsize,
418                                                     data.dsize, false);
419                 if (TDB_PTR_IS_ERR(data.dptr)) {
420                         ecode = TDB_PTR_ERR(data.dptr);
421                 } else {
422                         ecode = parse(key, data, p);
423                         tdb_access_release(tdb, data.dptr);
424                 }
425         }
426
427         tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_RDLCK);
428         return ecode;
429 }
430