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