10b3848e57b1bf49b30ebcbff2bf16fec7948597
[tedtools.git] / flatdb.c
1 /*
2  * Copyright (c) 2004 Teodor Sigaev <teodor@sigaev.ru>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *        notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *        notice, this list of conditions and the following disclaimer in the
12  *        documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the author nor the names of any co-contributors
14  *        may be used to endorse or promote products derived from this software
15  *        without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <fcntl.h>
32 #include <unistd.h>
33 #include <string.h>
34 #include <errno.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <sys/file.h>
38
39 #include "tlog.h"
40 #include "tmalloc.h"
41
42 #include "flatdb.h"
43
44 static FDBFreeSpace*
45 findFreeSpace(FDB *db, size_t length) {
46         FDBFreeSpace *ptr = db->space;
47         
48         while(ptr && ptr - db->space < db->listcur) {
49                 if ( ptr->length >= length )
50                         return ptr; 
51                 ptr++;
52         }
53
54         return NULL; 
55 }
56
57 static void
58 addFreeSpace(FDB *db, off_t offset, size_t length) {
59         if ( db->listcur >= db->listlen ) {
60                 db->listlen *= 2;
61                 db->space = (FDBFreeSpace*) trealloc( db->space, db->listlen * sizeof(FDBFreeSpace) );
62         }
63
64         db->space[ db->listcur ].offset=offset;
65         db->space[ db->listcur ].length=length;
66
67         db->listcur++;
68 }
69
70 static int
71 cmpFS(const void* a, const void* b) {
72         if ( ((FDBFreeSpace*)a)->offset == ((FDBFreeSpace*)b)->offset )
73                 return 0;
74         return ( ((FDBFreeSpace*)a)->offset > ((FDBFreeSpace*)b)->offset ) ? 1 : -1;
75 }
76
77 void
78 FDBVacuumFreeSpace(FDB *db) {
79         FDBFreeSpace *ptr=db->space+1, *ok=db->space;
80
81         if ( db->listcur < 2 )
82                 return;
83
84         qsort( db->space, db->listcur, sizeof(FDBFreeSpace), cmpFS);
85
86         /* merge spaces */
87         while( ptr - db->space < db->listcur ) {
88                 if ( ok->offset + ok->length == ptr->offset || ptr->length==0 ) {
89                         ok->length += ptr->length;
90                         ptr->length=0;
91                 } else
92                         ok++;
93                 ptr++;
94         }
95
96         /* remove void spaces */
97         ptr = ok = db->space;
98         while( ptr - db->space < db->listcur ) {
99                 if ( ptr->length != 0 ) {
100                         if ( ok != ptr )
101                                 memcpy(ok,ptr,sizeof(FDBFreeSpace));
102                         ok++;
103                 }
104                 ptr++;
105         }
106         db->listcur = ok - db->space;   
107 }
108
109 int 
110 FDBOpen(FDB *db, char *file, int readonly) {
111         FDBHeader header;
112         int rc;
113
114         memset(db, 0, sizeof(FDB));
115
116         if ( readonly ) {
117                 db->readonly=1;
118                 db->fd = open(file, O_RDONLY);
119                 if ( db->fd < 0 ) {
120                         tlog(TL_CRIT,"FDBOpen: open failed: %s",strerror(errno));
121                         return FDB_ERROR;
122                 }
123                 if ( flock( db->fd, LOCK_SH ) < 0 ) {
124                         tlog(TL_CRIT,"FDBOpen: flock failed: %s",strerror(errno));
125                         close(db->fd);
126                         return FDB_ERROR;
127                 } 
128         } else {
129                 db->fd = open(file, O_CREAT | O_RDWR, 0666);
130                 if ( db->fd < 0 ) {
131                         tlog(TL_CRIT,"FDBOpen: open failed: %s",strerror(errno));
132                         return FDB_ERROR;
133                 }
134                 if ( flock( db->fd, LOCK_EX ) < 0 ) {
135                         tlog(TL_CRIT,"FDBOpen: flock failed: %s",strerror(errno));
136                         close(db->fd);
137                         return FDB_ERROR;
138                 } 
139         }
140
141         rc = read(db->fd, &header, sizeof(FDBHeader));
142
143         if ( rc<0 ) {
144                 flock( db->fd, LOCK_UN );
145                 close(db->fd);
146                 tlog(TL_CRIT,"FDBOpen: read failed: %s", strerror(errno));
147                 return FDB_ERROR;
148         } else if ( rc==0 ) {
149                 memset(&header, 0, sizeof(FDBHeader));
150         } else if ( rc != sizeof(FDBHeader) ) {
151                 flock( db->fd, LOCK_UN );
152                 close(db->fd);
153                 tlog(TL_CRIT,"FDBOpen: header fault: %d bytes only", rc);
154                 return FDB_ERROR;
155         } else if ( header.isopened ) {
156                 flock( db->fd, LOCK_UN );
157                 close(db->fd);
158                 tlog(TL_CRIT,"FDBOpen: file wasn't closed correctly");
159                 return FDB_ERROR;
160         }
161                 
162         
163         if ( !db->readonly ) {
164                 if ( header.freespace ) {
165                         db->listlen = db->listcur = (header.lenfreespace / sizeof(FDBFreeSpace));
166                         db->space = (FDBFreeSpace*)tmalloc( header.lenfreespace + sizeof(FDBFreeSpace) );
167                 
168                         if ( lseek(db->fd, header.freespace, SEEK_SET)!=header.freespace || 
169                                         read( db->fd, db->space,  header.lenfreespace ) !=  header.lenfreespace ) {
170                                 flock( db->fd, LOCK_UN );
171                                 close(db->fd);
172                                 tlog(TL_CRIT,"FDBOpen: free space read failed: %s", strerror(errno));
173                                 return FDB_ERROR;
174                         }
175                         FDBVacuumFreeSpace(db);
176                 } else {
177                         db->listlen = 8;
178                         db->space = (FDBFreeSpace*)tmalloc( db->listlen*sizeof(FDBFreeSpace) );
179                 }       
180
181                 header.freespace = 0;
182                 header.lenfreespace = 0;
183                 header.isopened = 1;
184
185                 if ( lseek(db->fd, 0, SEEK_SET)!=0 || 
186                                 write(db->fd, &header, sizeof(FDBHeader)) != sizeof(FDBHeader) || 
187                                 fsync(db->fd) ) {
188                         flock( db->fd, LOCK_UN );
189                         close(db->fd);
190                         if ( db->space ) tfree( db->space );
191                         tlog(TL_CRIT,"FDBOpen: can't modify header: %s", strerror(errno));
192                         return FDB_ERROR;
193                 }
194         }       
195
196         return FDB_OK;
197 }
198
199
200 int
201 FDBClose(FDB *db) {
202         if ( !db->readonly) {
203                 FDBHeader header;
204
205                 memset(&header,0,sizeof(FDBHeader));
206
207                 if ( db->listcur ) {
208                         FDBFreeSpace    *ptr;
209
210                         FDBVacuumFreeSpace(db);
211
212                         header.lenfreespace = sizeof(FDBFreeSpace)*db->listcur;
213
214                         ptr = findFreeSpace( db, header.lenfreespace );
215                 
216                         if ( ptr ) {
217                                 header.freespace = ptr->offset;
218                                 if ( lseek(db->fd, ptr->offset, SEEK_SET) != ptr->offset )
219                                         tlog(TL_CRIT|TL_EXIT,"FDBClose: lseek failed: %s", strerror(errno));
220                         } else {
221                                 if ( (header.freespace = lseek(db->fd, 0, SEEK_END)) < 0 ) 
222                                         tlog(TL_CRIT|TL_EXIT,"FDBClose: lseek failed: %s", strerror(errno));
223                                 header.lenfreespace += sizeof(FDBFreeSpace); 
224                                 addFreeSpace(db, header.freespace, header.lenfreespace); 
225                         }
226
227                         if ( write(db->fd, db->space, header.lenfreespace) != header.lenfreespace )
228                                 tlog(TL_CRIT|TL_EXIT,"FDBClose: write failed: %s", strerror(errno));
229                 }
230
231                 header.isopened=0;
232
233                 if ( lseek(db->fd,0,SEEK_SET)!=0 || 
234                                 write(db->fd, &header, sizeof(FDBHeader)) != sizeof(FDBHeader) || 
235                                 fsync(db->fd))
236                         tlog(TL_CRIT|TL_EXIT,"FDBClose: header write  failed: %s", strerror(errno)); 
237         }
238
239         flock( db->fd, LOCK_UN );
240         close(db->fd);
241
242         if ( db->space )
243                 tfree( db->space );
244
245         return FDB_OK;
246 }
247
248 static int
249 readLen(FDB *db, off_t offset, size_t *size) { 
250         if ( lseek(db->fd,offset,SEEK_SET)!=offset)
251                 return FDB_ERROR;
252         
253         if ( read(db->fd,size,sizeof(size_t)) != sizeof(size_t) )
254                 return FDB_ERROR;
255
256         return FDB_OK;
257 }
258
259
260 int
261 FDBDelete(FDB *db, off_t offset, size_t length) {
262         if ( db->readonly )
263                 return FDB_ERROR;
264
265         if ( length==0 ) 
266                 if ( readLen(db, offset, &length) != FDB_OK )
267                         return FDB_ERROR;
268
269         addFreeSpace(db, offset, PTRALIGN(length));
270
271         return FDB_OK;
272 }
273
274
275 int
276 FDBGet(FDB *db, off_t offset, size_t length, FDBRecord **record) {
277         size_t rc;
278
279         *record=NULL;
280
281         if ( offset < sizeof(FDBHeader) )       
282                 return FDB_ERROR;
283
284         if ( length==0 )
285                 if ( readLen(db, offset, &length) != FDB_OK )
286                         return FDB_ERROR;
287
288         *record = (FDBRecord*)tmalloc( length );
289
290         if ( lseek(db->fd,offset,SEEK_SET)!=offset)
291                 return FDB_ERROR;
292
293         if ( (rc=read(db->fd,*record,length)) != length ) {
294                 (*record)->length = rc;
295                 tlog(TL_CRIT,"FDBGet: read (%d bytes) less than needed (%d bytes): %s", rc, length, strerror(errno));
296                 return FDB_INCORRECT;
297         }
298
299         if ( (*record)->length != length ) {
300                 tlog(TL_ALARM, "FDBGet: wrong length in opts: %d bytes and %d bytes really", length, (*record)->length);
301                 if ( (*record)->length > length ) {
302                         rc = (*record)->length;
303                         tfree( *record );
304                         return FDBGet(db, offset, rc, record);
305                 }
306         } 
307
308         return FDB_OK;
309 }
310          
311                 
312 int 
313 FDBPut(FDB *db, FDBRecord *record, off_t *offset ) {
314         FDBFreeSpace *ptr;
315         size_t  aligned;
316
317         if ( db->readonly )
318                 return FDB_ERROR;
319
320         aligned = PTRALIGN(record->length);
321
322         ptr = findFreeSpace( db, aligned ); 
323         if ( ptr ) {
324                 *offset = ptr->offset;
325                 ptr->length -= record->length;
326                 if ( ptr->length == 0 ) {
327                         if ( (ptr - db->space) + 1 != db->listcur ) 
328                                 memmove(ptr, ptr+1, (db->listcur - (ptr - db->space) + 1) * sizeof(FDBFreeSpace));
329                         db->listcur--; 
330                 } else
331                         ptr->offset += record->length;
332                 if ( lseek(db->fd, *offset, SEEK_SET) != *offset ) 
333                         tlog(TL_CRIT|TL_EXIT,"FDBPut: lseek failed: %s", strerror(errno)); 
334         } else {
335                 if ( (*offset = lseek(db->fd, 0, SEEK_END)) < 0 ) 
336                         tlog(TL_CRIT|TL_EXIT,"FDBPut: lseek failed: %s", strerror(errno)); 
337         }
338
339         if ( write(db->fd, record, record->length) != record->length ) 
340                 tlog(TL_CRIT|TL_EXIT,"FDBPut: write failed: %s", strerror(errno));
341         if ( record->length != aligned ) {
342                 char    buf[] = {0, 0, 0, 0, 0, 0, 0, 0};
343                 if ( write(db->fd, buf, aligned - record->length) != (aligned - record->length) ) 
344                         tlog(TL_CRIT|TL_EXIT,"FDBPut: write failed: %s", strerror(errno));
345         }
346
347         return FDB_OK;
348 }
349
350