]> git.ozlabs.org Git - ccan/blob - ccan/tdb/io.c
Improved stringmap to support strings with null characters
[ccan] / ccan / tdb / io.c
1  /* 
2    Unix SMB/CIFS implementation.
3
4    trivial database library
5
6    Copyright (C) Andrew Tridgell              1999-2005
7    Copyright (C) Paul `Rusty' Russell              2000
8    Copyright (C) Jeremy Allison                    2000-2003
9    
10      ** NOTE! The following LGPL license applies to the tdb
11      ** library. This does NOT imply that all of Samba is released
12      ** under the LGPL
13    
14    This library is free software; you can redistribute it and/or
15    modify it under the terms of the GNU Lesser General Public
16    License as published by the Free Software Foundation; either
17    version 3 of the License, or (at your option) any later version.
18
19    This library is distributed in the hope that it will be useful,
20    but WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22    Lesser General Public License for more details.
23
24    You should have received a copy of the GNU Lesser General Public
25    License along with this library; if not, see <http://www.gnu.org/licenses/>.
26 */
27
28 #include "tdb_private.h"
29 #ifndef MAX
30 #define MAX(a,b) ((a) > (b) ? (a) : (b))
31 #endif
32
33 /* check for an out of bounds access - if it is out of bounds then
34    see if the database has been expanded by someone else and expand
35    if necessary 
36    note that "len" is the minimum length needed for the db
37 */
38 static int tdb_oob(struct tdb_context *tdb, tdb_off_t len, int probe)
39 {
40         struct stat st;
41         if (len <= tdb->map_size)
42                 return 0;
43         if (tdb->flags & TDB_INTERNAL) {
44                 if (!probe) {
45                         /* Ensure ecode is set for log fn. */
46                         tdb->ecode = TDB_ERR_IO;
47                         TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_oob len %d beyond internal malloc size %d\n",
48                                  (int)len, (int)tdb->map_size));
49                 }
50                 return TDB_ERRCODE(TDB_ERR_IO, -1);
51         }
52
53         if (fstat(tdb->fd, &st) == -1) {
54                 return TDB_ERRCODE(TDB_ERR_IO, -1);
55         }
56
57         if (st.st_size < (size_t)len) {
58                 if (!probe) {
59                         /* Ensure ecode is set for log fn. */
60                         tdb->ecode = TDB_ERR_IO;
61                         TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_oob len %d beyond eof at %d\n",
62                                  (int)len, (int)st.st_size));
63                 }
64                 return TDB_ERRCODE(TDB_ERR_IO, -1);
65         }
66
67         /* Unmap, update size, remap */
68         if (tdb_munmap(tdb) == -1)
69                 return TDB_ERRCODE(TDB_ERR_IO, -1);
70         tdb->map_size = st.st_size;
71         tdb_mmap(tdb);
72         return 0;
73 }
74
75 /* write a lump of data at a specified offset */
76 static int tdb_write(struct tdb_context *tdb, tdb_off_t off, 
77                      const void *buf, tdb_len_t len)
78 {
79         if (len == 0) {
80                 return 0;
81         }
82
83         if (tdb->read_only || tdb->traverse_read) {
84                 tdb->ecode = TDB_ERR_RDONLY;
85                 return -1;
86         }
87
88         if (tdb->methods->tdb_oob(tdb, off + len, 0) != 0)
89                 return -1;
90
91         if (tdb->map_ptr) {
92                 memcpy(off + (char *)tdb->map_ptr, buf, len);
93         } else {
94                 ssize_t written = pwrite(tdb->fd, buf, len, off);
95                 if ((written != (ssize_t)len) && (written != -1)) {
96                         /* try once more */
97                         TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_write: wrote only "
98                                  "%d of %d bytes at %d, trying once more\n",
99                                  (int)written, len, off));
100                         errno = ENOSPC;
101                         written = pwrite(tdb->fd, (const void *)((const char *)buf+written),
102                                          len-written,
103                                          off+written);
104                 }
105                 if (written == -1) {
106                 /* Ensure ecode is set for log fn. */
107                 tdb->ecode = TDB_ERR_IO;
108                         TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_write failed at %d "
109                                  "len=%d (%s)\n", off, len, strerror(errno)));
110                         return TDB_ERRCODE(TDB_ERR_IO, -1);
111                 } else if (written != (ssize_t)len) {
112                         TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_write: failed to "
113                                  "write %d bytes at %d in two attempts\n",
114                                  len, off));
115                         errno = ENOSPC;
116                 return TDB_ERRCODE(TDB_ERR_IO, -1);
117         }
118         }
119         return 0;
120 }
121
122 /* Endian conversion: we only ever deal with 4 byte quantities */
123 void *tdb_convert(void *buf, uint32_t size)
124 {
125         uint32_t i, *p = (uint32_t *)buf;
126         for (i = 0; i < size / 4; i++)
127                 p[i] = TDB_BYTEREV(p[i]);
128         return buf;
129 }
130
131
132 /* read a lump of data at a specified offset, maybe convert */
133 static int tdb_read(struct tdb_context *tdb, tdb_off_t off, void *buf, 
134                     tdb_len_t len, int cv)
135 {
136         if (tdb->methods->tdb_oob(tdb, off + len, 0) != 0) {
137                 return -1;
138         }
139
140         if (tdb->map_ptr) {
141                 memcpy(buf, off + (char *)tdb->map_ptr, len);
142         } else {
143                 ssize_t ret = pread(tdb->fd, buf, len, off);
144                 if (ret != (ssize_t)len) {
145                         /* Ensure ecode is set for log fn. */
146                         tdb->ecode = TDB_ERR_IO;
147                         TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_read failed at %d "
148                                  "len=%d ret=%d (%s) map_size=%d\n",
149                                  (int)off, (int)len, (int)ret, strerror(errno),
150                                  (int)tdb->map_size));
151                         return TDB_ERRCODE(TDB_ERR_IO, -1);
152                 }
153         }
154         if (cv) {
155                 tdb_convert(buf, len);
156         }
157         return 0;
158 }
159
160
161
162 /*
163   do an unlocked scan of the hash table heads to find the next non-zero head. The value
164   will then be confirmed with the lock held
165 */              
166 static void tdb_next_hash_chain(struct tdb_context *tdb, uint32_t *chain)
167 {
168         uint32_t h = *chain;
169         if (tdb->map_ptr) {
170                 for (;h < tdb->header.hash_size;h++) {
171                         if (0 != *(uint32_t *)(TDB_HASH_TOP(h) + (unsigned char *)tdb->map_ptr)) {
172                                 break;
173                         }
174                 }
175         } else {
176                 uint32_t off=0;
177                 for (;h < tdb->header.hash_size;h++) {
178                         if (tdb_ofs_read(tdb, TDB_HASH_TOP(h), &off) != 0 || off != 0) {
179                                 break;
180                         }
181                 }
182         }
183         (*chain) = h;
184 }
185
186
187 int tdb_munmap(struct tdb_context *tdb)
188 {
189         if (tdb->flags & TDB_INTERNAL)
190                 return 0;
191
192 #if HAVE_MMAP
193         if (tdb->map_ptr) {
194                 int ret = munmap(tdb->map_ptr, tdb->map_size);
195                 if (ret != 0)
196                         return ret;
197         }
198 #endif
199         tdb->map_ptr = NULL;
200         return 0;
201 }
202
203 void tdb_mmap(struct tdb_context *tdb)
204 {
205         if (tdb->flags & TDB_INTERNAL)
206                 return;
207
208 #if HAVE_MMAP
209         if (!(tdb->flags & TDB_NOMMAP)) {
210                 tdb->map_ptr = mmap(NULL, tdb->map_size, 
211                                     PROT_READ|(tdb->read_only? 0:PROT_WRITE), 
212                                     MAP_SHARED, tdb->fd, 0);
213
214                 /*
215                  * NB. When mmap fails it returns MAP_FAILED *NOT* NULL !!!!
216                  */
217
218                 if (tdb->map_ptr == MAP_FAILED) {
219                         tdb->map_ptr = NULL;
220                         TDB_LOG((tdb, TDB_DEBUG_WARNING, "tdb_mmap failed for size %d (%s)\n", 
221                                  tdb->map_size, strerror(errno)));
222                 }
223         } else {
224                 tdb->map_ptr = NULL;
225         }
226 #else
227         tdb->map_ptr = NULL;
228 #endif
229 }
230
231 /* expand a file.  we prefer to use ftruncate, as that is what posix
232   says to use for mmap expansion */
233 static int tdb_expand_file(struct tdb_context *tdb, tdb_off_t size, tdb_off_t addition)
234 {
235         char buf[8192];
236
237         if (tdb->read_only || tdb->traverse_read) {
238                 tdb->ecode = TDB_ERR_RDONLY;
239                 return -1;
240         }
241
242         if (ftruncate(tdb->fd, size+addition) == -1) {
243                 char b = 0;
244                 ssize_t written = pwrite(tdb->fd,  &b, 1, (size+addition) - 1);
245                 if (written == 0) {
246                         /* try once more, potentially revealing errno */
247                         written = pwrite(tdb->fd,  &b, 1, (size+addition) - 1);
248                 }
249                 if (written == 0) {
250                         /* again - give up, guessing errno */
251                         errno = ENOSPC;
252                 }
253                 if (written != 1) {
254                         TDB_LOG((tdb, TDB_DEBUG_FATAL, "expand_file to %d failed (%s)\n", 
255                                  size+addition, strerror(errno)));
256                         return -1;
257                 }
258         }
259
260         /* now fill the file with something. This ensures that the
261            file isn't sparse, which would be very bad if we ran out of
262            disk. This must be done with write, not via mmap */
263         memset(buf, TDB_PAD_BYTE, sizeof(buf));
264         while (addition) {
265                 size_t n = addition>sizeof(buf)?sizeof(buf):addition;
266                 ssize_t written = pwrite(tdb->fd, buf, n, size);
267                 if (written == 0) {
268                         /* prevent infinite loops: try _once_ more */
269                         written = pwrite(tdb->fd, buf, n, size);
270                 }
271                 if (written == 0) {
272                         /* give up, trying to provide a useful errno */
273                         TDB_LOG((tdb, TDB_DEBUG_FATAL, "expand_file write "
274                                 "returned 0 twice: giving up!\n"));
275                         errno = ENOSPC;
276                         return -1;
277                 } else if (written == -1) {
278                         TDB_LOG((tdb, TDB_DEBUG_FATAL, "expand_file write of "
279                                  "%d bytes failed (%s)\n", (int)n,
280                                  strerror(errno)));
281                         return -1;
282                 } else if (written != n) {
283                         TDB_LOG((tdb, TDB_DEBUG_WARNING, "expand_file: wrote "
284                                  "only %d of %d bytes - retrying\n", (int)written,
285                                  (int)n));
286                 }
287                 addition -= written;
288                 size += written;
289         }
290         return 0;
291 }
292
293
294 /* expand the database at least size bytes by expanding the underlying
295    file and doing the mmap again if necessary */
296 int tdb_expand(struct tdb_context *tdb, tdb_off_t size)
297 {
298         struct list_struct rec;
299         tdb_off_t offset, new_size;     
300
301         if (tdb_lock(tdb, -1, F_WRLCK) == -1) {
302                 TDB_LOG((tdb, TDB_DEBUG_ERROR, "lock failed in tdb_expand\n"));
303                 return -1;
304         }
305
306         /* must know about any previous expansions by another process */
307         tdb->methods->tdb_oob(tdb, tdb->map_size + 1, 1);
308
309         /* always make room for at least 100 more records, and at
310            least 25% more space. Round the database up to a multiple
311            of the page size */
312         new_size = MAX(tdb->map_size + size*100, tdb->map_size * 1.25);
313         size = TDB_ALIGN(new_size, tdb->page_size) - tdb->map_size;
314
315         if (!(tdb->flags & TDB_INTERNAL))
316                 tdb_munmap(tdb);
317
318         /*
319          * We must ensure the file is unmapped before doing this
320          * to ensure consistency with systems like OpenBSD where
321          * writes and mmaps are not consistent.
322          */
323
324         /* expand the file itself */
325         if (!(tdb->flags & TDB_INTERNAL)) {
326                 if (tdb->methods->tdb_expand_file(tdb, tdb->map_size, size) != 0)
327                         goto fail;
328         }
329
330         tdb->map_size += size;
331
332         if (tdb->flags & TDB_INTERNAL) {
333                 char *new_map_ptr = (char *)realloc(tdb->map_ptr,
334                                                     tdb->map_size);
335                 if (!new_map_ptr) {
336                         tdb->map_size -= size;
337                         goto fail;
338                 }
339                 tdb->map_ptr = new_map_ptr;
340         } else {
341                 /*
342                  * We must ensure the file is remapped before adding the space
343                  * to ensure consistency with systems like OpenBSD where
344                  * writes and mmaps are not consistent.
345                  */
346
347                 /* We're ok if the mmap fails as we'll fallback to read/write */
348                 tdb_mmap(tdb);
349         }
350
351         /* form a new freelist record */
352         memset(&rec,'\0',sizeof(rec));
353         rec.rec_len = size - sizeof(rec);
354
355         /* link it into the free list */
356         offset = tdb->map_size - size;
357         if (tdb_free(tdb, offset, &rec) == -1)
358                 goto fail;
359
360         tdb_unlock(tdb, -1, F_WRLCK);
361         return 0;
362  fail:
363         tdb_unlock(tdb, -1, F_WRLCK);
364         return -1;
365 }
366
367 /* read/write a tdb_off_t */
368 int tdb_ofs_read(struct tdb_context *tdb, tdb_off_t offset, tdb_off_t *d)
369 {
370         return tdb->methods->tdb_read(tdb, offset, (char*)d, sizeof(*d), DOCONV());
371 }
372
373 int tdb_ofs_write(struct tdb_context *tdb, tdb_off_t offset, tdb_off_t *d)
374 {
375         tdb_off_t off = *d;
376         return tdb->methods->tdb_write(tdb, offset, CONVERT(off), sizeof(*d));
377 }
378
379
380 /* read a lump of data, allocating the space for it */
381 unsigned char *tdb_alloc_read(struct tdb_context *tdb, tdb_off_t offset, tdb_len_t len)
382 {
383         unsigned char *buf;
384
385         /* some systems don't like zero length malloc */
386         if (len == 0) {
387                 len = 1;
388         }
389
390         if (!(buf = (unsigned char *)malloc(len))) {
391                 /* Ensure ecode is set for log fn. */
392                 tdb->ecode = TDB_ERR_OOM;
393                 TDB_LOG((tdb, TDB_DEBUG_ERROR,"tdb_alloc_read malloc failed len=%d (%s)\n",
394                            len, strerror(errno)));
395                 return TDB_ERRCODE(TDB_ERR_OOM, buf);
396         }
397         if (tdb->methods->tdb_read(tdb, offset, buf, len, 0) == -1) {
398                 SAFE_FREE(buf);
399                 return NULL;
400         }
401         return buf;
402 }
403
404 /* Give a piece of tdb data to a parser */
405
406 int tdb_parse_data(struct tdb_context *tdb, TDB_DATA key,
407                    tdb_off_t offset, tdb_len_t len,
408                    int (*parser)(TDB_DATA key, TDB_DATA data,
409                                  void *private_data),
410                    void *private_data)
411 {
412         TDB_DATA data;
413         int result;
414
415         data.dsize = len;
416
417         if ((tdb->transaction == NULL) && (tdb->map_ptr != NULL)) {
418                 /*
419                  * Optimize by avoiding the malloc/memcpy/free, point the
420                  * parser directly at the mmap area.
421                  */
422                 if (tdb->methods->tdb_oob(tdb, offset+len, 0) != 0) {
423                         return -1;
424                 }
425                 data.dptr = offset + (unsigned char *)tdb->map_ptr;
426                 return parser(key, data, private_data);
427         }
428
429         if (!(data.dptr = tdb_alloc_read(tdb, offset, len))) {
430                 return -1;
431         }
432
433         result = parser(key, data, private_data);
434         free(data.dptr);
435         return result;
436 }
437
438 /* read/write a record */
439 int tdb_rec_read(struct tdb_context *tdb, tdb_off_t offset, struct list_struct *rec)
440 {
441         if (tdb->methods->tdb_read(tdb, offset, rec, sizeof(*rec),DOCONV()) == -1)
442                 return -1;
443         if (TDB_BAD_MAGIC(rec)) {
444                 /* Ensure ecode is set for log fn. */
445                 tdb->ecode = TDB_ERR_CORRUPT;
446                 TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_rec_read bad magic 0x%x at offset=%d\n", rec->magic, offset));
447                 return TDB_ERRCODE(TDB_ERR_CORRUPT, -1);
448         }
449         return tdb->methods->tdb_oob(tdb, rec->next+sizeof(*rec), 0);
450 }
451
452 int tdb_rec_write(struct tdb_context *tdb, tdb_off_t offset, struct list_struct *rec)
453 {
454         struct list_struct r = *rec;
455         return tdb->methods->tdb_write(tdb, offset, CONVERT(r), sizeof(r));
456 }
457
458 static const struct tdb_methods io_methods = {
459         tdb_read,
460         tdb_write,
461         tdb_next_hash_chain,
462         tdb_oob,
463         tdb_expand_file,
464         tdb_brlock
465 };
466
467 /*
468   initialise the default methods table
469 */
470 void tdb_io_init(struct tdb_context *tdb)
471 {
472         tdb->methods = &io_methods;
473 }