97da809bc612f10722b0af23c0eb697b5004e624
[ira/wip.git] / source / lib / tdb / common / 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 2 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, write to the Free Software
26    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27 */
28
29
30 #include "tdb_private.h"
31
32 #ifndef HAVE_PREAD
33  static ssize_t pread(int fd, void *buf, size_t count, off_t offset)
34 {
35         if (lseek(fd, offset, SEEK_SET) != offset) {
36                 errno = EIO;
37                 return -1;
38         }
39         return read(fd, buf, count);
40 }
41 #endif
42
43 #ifndef HAVE_PWRITE
44  static ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset)
45 {
46         if (lseek(fd, offset, SEEK_SET) != offset) {
47                 errno = EIO;
48                 return -1;
49         }
50         return write(fd, buf, count);
51 }
52 #endif
53
54 /* check for an out of bounds access - if it is out of bounds then
55    see if the database has been expanded by someone else and expand
56    if necessary 
57    note that "len" is the minimum length needed for the db
58 */
59 int tdb_oob(struct tdb_context *tdb, tdb_off_t len, int probe)
60 {
61         struct stat st;
62         if (len <= tdb->map_size)
63                 return 0;
64         if (tdb->flags & TDB_INTERNAL) {
65                 if (!probe) {
66                         /* Ensure ecode is set for log fn. */
67                         tdb->ecode = TDB_ERR_IO;
68                         TDB_LOG((tdb, 0,"tdb_oob len %d beyond internal malloc size %d\n",
69                                  (int)len, (int)tdb->map_size));
70                 }
71                 return TDB_ERRCODE(TDB_ERR_IO, -1);
72         }
73
74         if (fstat(tdb->fd, &st) == -1) {
75                 return TDB_ERRCODE(TDB_ERR_IO, -1);
76         }
77
78         if (st.st_size < (size_t)len) {
79                 if (!probe) {
80                         /* Ensure ecode is set for log fn. */
81                         tdb->ecode = TDB_ERR_IO;
82                         TDB_LOG((tdb, 0,"tdb_oob len %d beyond eof at %d\n",
83                                  (int)len, (int)st.st_size));
84                 }
85                 return TDB_ERRCODE(TDB_ERR_IO, -1);
86         }
87
88         /* Unmap, update size, remap */
89         if (tdb_munmap(tdb) == -1)
90                 return TDB_ERRCODE(TDB_ERR_IO, -1);
91         tdb->map_size = st.st_size;
92         tdb_mmap(tdb);
93         return 0;
94 }
95
96 /* write a lump of data at a specified offset */
97 int tdb_write(struct tdb_context *tdb, tdb_off_t off, void *buf, tdb_len_t len)
98 {
99         if (tdb_oob(tdb, off + len, 0) != 0)
100                 return -1;
101
102         if (tdb->map_ptr) {
103                 memcpy(off + (char *)tdb->map_ptr, buf, len);
104         } else if (pwrite(tdb->fd, buf, len, off) != (ssize_t)len) {
105                 /* Ensure ecode is set for log fn. */
106                 tdb->ecode = TDB_ERR_IO;
107                 TDB_LOG((tdb, 0,"tdb_write failed at %d len=%d (%s)\n",
108                            off, len, strerror(errno)));
109                 return TDB_ERRCODE(TDB_ERR_IO, -1);
110         }
111         return 0;
112 }
113
114 /* Endian conversion: we only ever deal with 4 byte quantities */
115 void *tdb_convert(void *buf, u32 size)
116 {
117         u32 i, *p = buf;
118         for (i = 0; i < size / 4; i++)
119                 p[i] = TDB_BYTEREV(p[i]);
120         return buf;
121 }
122
123
124 /* read a lump of data at a specified offset, maybe convert */
125 int tdb_read(struct tdb_context *tdb, tdb_off_t off, void *buf, tdb_len_t len, int cv)
126 {
127         if (tdb_oob(tdb, off + len, 0) != 0) {
128                 return -1;
129         }
130
131         if (tdb->map_ptr) {
132                 memcpy(buf, off + (char *)tdb->map_ptr, len);
133         } else {
134                 ssize_t ret = pread(tdb->fd, buf, len, off);
135                 if (ret != (ssize_t)len) {
136                         /* Ensure ecode is set for log fn. */
137                         tdb->ecode = TDB_ERR_IO;
138                         TDB_LOG((tdb, 0,"tdb_read failed at %d len=%d ret=%d (%s) map_size=%d\n",
139                                  off, len, ret, strerror(errno), tdb->map_size));
140                         return TDB_ERRCODE(TDB_ERR_IO, -1);
141                 }
142         }
143         if (cv)
144                 tdb_convert(buf, len);
145         return 0;
146 }
147
148
149
150 /*
151   do an unlocked scan of the hash table heads to find the next non-zero head. The value
152   will then be confirmed with the lock held
153 */              
154 void tdb_next_hash_chain(struct tdb_context *tdb, u32 *chain)
155 {
156         u32 h = *chain;
157         if (tdb->map_ptr) {
158                 for (;h < tdb->header.hash_size;h++) {
159                         if (0 != *(u32 *)(TDB_HASH_TOP(h) + (unsigned char *)tdb->map_ptr)) {
160                                 break;
161                         }
162                 }
163         } else {
164                 u32 off;
165                 for (;h < tdb->header.hash_size;h++) {
166                         if (tdb_ofs_read(tdb, TDB_HASH_TOP(h), &off) != 0 || off != 0) {
167                                 break;
168                         }
169                 }
170         }
171         (*chain) = h;
172 }
173
174
175 int tdb_munmap(struct tdb_context *tdb)
176 {
177         if (tdb->flags & TDB_INTERNAL)
178                 return 0;
179
180 #ifdef HAVE_MMAP
181         if (tdb->map_ptr) {
182                 int ret = munmap(tdb->map_ptr, tdb->map_size);
183                 if (ret != 0)
184                         return ret;
185         }
186 #endif
187         tdb->map_ptr = NULL;
188         return 0;
189 }
190
191 void tdb_mmap(struct tdb_context *tdb)
192 {
193         if (tdb->flags & TDB_INTERNAL)
194                 return;
195
196 #ifdef HAVE_MMAP
197         if (!(tdb->flags & TDB_NOMMAP)) {
198                 tdb->map_ptr = mmap(NULL, tdb->map_size, 
199                                     PROT_READ|(tdb->read_only? 0:PROT_WRITE), 
200                                     MAP_SHARED|MAP_FILE, tdb->fd, 0);
201
202                 /*
203                  * NB. When mmap fails it returns MAP_FAILED *NOT* NULL !!!!
204                  */
205
206                 if (tdb->map_ptr == MAP_FAILED) {
207                         tdb->map_ptr = NULL;
208                         TDB_LOG((tdb, 2, "tdb_mmap failed for size %d (%s)\n", 
209                                  tdb->map_size, strerror(errno)));
210                 }
211         } else {
212                 tdb->map_ptr = NULL;
213         }
214 #else
215         tdb->map_ptr = NULL;
216 #endif
217 }
218
219 /* expand a file.  we prefer to use ftruncate, as that is what posix
220   says to use for mmap expansion */
221 static int tdb_expand_file(struct tdb_context *tdb, tdb_off_t size, tdb_off_t addition)
222 {
223         char buf[1024];
224         if (ftruncate(tdb->fd, size+addition) == -1) {
225                 char b = 0;
226                 if (pwrite(tdb->fd,  &b, 1, (size+addition) - 1) != 1) {
227                         TDB_LOG((tdb, 0, "expand_file to %d failed (%s)\n", 
228                                  size+addition, strerror(errno)));
229                         return -1;
230                 }
231         }
232
233         /* now fill the file with something. This ensures that the file isn't sparse, which would be
234            very bad if we ran out of disk. This must be done with write, not via mmap */
235         memset(buf, 0x42, sizeof(buf));
236         while (addition) {
237                 int n = addition>sizeof(buf)?sizeof(buf):addition;
238                 int ret = pwrite(tdb->fd, buf, n, size);
239                 if (ret != n) {
240                         TDB_LOG((tdb, 0, "expand_file write of %d failed (%s)\n", 
241                                    n, strerror(errno)));
242                         return -1;
243                 }
244                 addition -= n;
245                 size += n;
246         }
247         return 0;
248 }
249
250
251 /* expand the database at least size bytes by expanding the underlying
252    file and doing the mmap again if necessary */
253 int tdb_expand(struct tdb_context *tdb, tdb_off_t size)
254 {
255         struct list_struct rec;
256         tdb_off_t offset;
257
258         if (tdb_lock(tdb, -1, F_WRLCK) == -1) {
259                 TDB_LOG((tdb, 0, "lock failed in tdb_expand\n"));
260                 return -1;
261         }
262
263         /* must know about any previous expansions by another process */
264         tdb_oob(tdb, tdb->map_size + 1, 1);
265
266         /* always make room for at least 10 more records, and round
267            the database up to a multiple of TDB_PAGE_SIZE */
268         size = TDB_ALIGN(tdb->map_size + size*10, TDB_PAGE_SIZE) - tdb->map_size;
269
270         if (!(tdb->flags & TDB_INTERNAL))
271                 tdb_munmap(tdb);
272
273         /*
274          * We must ensure the file is unmapped before doing this
275          * to ensure consistency with systems like OpenBSD where
276          * writes and mmaps are not consistent.
277          */
278
279         /* expand the file itself */
280         if (!(tdb->flags & TDB_INTERNAL)) {
281                 if (tdb_expand_file(tdb, tdb->map_size, size) != 0)
282                         goto fail;
283         }
284
285         tdb->map_size += size;
286
287         if (tdb->flags & TDB_INTERNAL) {
288                 char *new_map_ptr = realloc(tdb->map_ptr, tdb->map_size);
289                 if (!new_map_ptr) {
290                         tdb->map_size -= size;
291                         goto fail;
292                 }
293                 tdb->map_ptr = new_map_ptr;
294         } else {
295                 /*
296                  * We must ensure the file is remapped before adding the space
297                  * to ensure consistency with systems like OpenBSD where
298                  * writes and mmaps are not consistent.
299                  */
300
301                 /* We're ok if the mmap fails as we'll fallback to read/write */
302                 tdb_mmap(tdb);
303         }
304
305         /* form a new freelist record */
306         memset(&rec,'\0',sizeof(rec));
307         rec.rec_len = size - sizeof(rec);
308
309         /* link it into the free list */
310         offset = tdb->map_size - size;
311         if (tdb_free(tdb, offset, &rec) == -1)
312                 goto fail;
313
314         tdb_unlock(tdb, -1, F_WRLCK);
315         return 0;
316  fail:
317         tdb_unlock(tdb, -1, F_WRLCK);
318         return -1;
319 }
320
321 /* read/write a tdb_off_t */
322 int tdb_ofs_read(struct tdb_context *tdb, tdb_off_t offset, tdb_off_t *d)
323 {
324         return tdb_read(tdb, offset, (char*)d, sizeof(*d), DOCONV());
325 }
326
327 int tdb_ofs_write(struct tdb_context *tdb, tdb_off_t offset, tdb_off_t *d)
328 {
329         tdb_off_t off = *d;
330         return tdb_write(tdb, offset, CONVERT(off), sizeof(*d));
331 }
332
333
334 /* read a lump of data, allocating the space for it */
335 unsigned char *tdb_alloc_read(struct tdb_context *tdb, tdb_off_t offset, tdb_len_t len)
336 {
337         unsigned char *buf;
338
339         if (!(buf = malloc(len))) {
340                 /* Ensure ecode is set for log fn. */
341                 tdb->ecode = TDB_ERR_OOM;
342                 TDB_LOG((tdb, 0,"tdb_alloc_read malloc failed len=%d (%s)\n",
343                            len, strerror(errno)));
344                 return TDB_ERRCODE(TDB_ERR_OOM, buf);
345         }
346         if (tdb_read(tdb, offset, buf, len, 0) == -1) {
347                 SAFE_FREE(buf);
348                 return NULL;
349         }
350         return buf;
351 }
352
353 /* read/write a record */
354 int tdb_rec_read(struct tdb_context *tdb, tdb_off_t offset, struct list_struct *rec)
355 {
356         if (tdb_read(tdb, offset, rec, sizeof(*rec),DOCONV()) == -1)
357                 return -1;
358         if (TDB_BAD_MAGIC(rec)) {
359                 /* Ensure ecode is set for log fn. */
360                 tdb->ecode = TDB_ERR_CORRUPT;
361                 TDB_LOG((tdb, 0,"tdb_rec_read bad magic 0x%x at offset=%d\n", rec->magic, offset));
362                 return TDB_ERRCODE(TDB_ERR_CORRUPT, -1);
363         }
364         return tdb_oob(tdb, rec->next+sizeof(*rec), 0);
365 }
366
367 int tdb_rec_write(struct tdb_context *tdb, tdb_off_t offset, struct list_struct *rec)
368 {
369         struct list_struct r = *rec;
370         return tdb_write(tdb, offset, CONVERT(r), sizeof(r));
371 }
372