Add linux compatibility
[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
38 #include "tlog.h"
39 #include "tmalloc.h"
40
41 #include "flatdb.h"
42
43 static FDBFreeSpace*
44 findFreeSpace(FDB *db, size_t length) {
45         FDBFreeSpace *ptr = db->space;
46         
47         while(ptr && ptr - db->space < db->listcur) {
48                 if ( ptr->length >= length )
49                         return ptr; 
50                 ptr++;
51         }
52
53         return NULL; 
54 }
55
56 static void
57 addFreeSpace(FDB *db, off_t offset, size_t length) {
58         if ( db->listcur >= db->listlen ) {
59                 db->listlen *= 2;
60                 db->space = (FDBFreeSpace*) trealloc( db->space, db->listlen * sizeof(FDBFreeSpace) );
61         }
62
63         db->space[ db->listcur ].offset=offset;
64         db->space[ db->listcur ].length=length;
65
66         db->listcur++;
67 }
68
69 static int
70 cmpFS(const void* a, const void* b) {
71         if ( ((FDBFreeSpace*)a)->offset == ((FDBFreeSpace*)b)->offset )
72                 return 0;
73         return ( ((FDBFreeSpace*)a)->offset > ((FDBFreeSpace*)b)->offset ) ? 1 : -1;
74 }
75
76 void
77 FDBVacuumFreeSpace(FDB *db) {
78         FDBFreeSpace *ptr=db->space+1, *ok=db->space;
79
80         if ( db->listcur < 2 )
81                 return;
82
83         qsort( db->space, db->listcur, sizeof(FDBFreeSpace), cmpFS);
84
85         /* merge spaces */
86         while( ptr - db->space < db->listcur ) {
87                 if ( ok->offset + ok->length == ptr->offset || ptr->length==0 ) {
88                         ok->length += ptr->length;
89                         ptr->length=0;
90                 } else
91                         ok++;
92                 ptr++;
93         }
94
95         /* remove void spaces */
96         ptr = ok = db->space;
97         while( ptr - db->space < db->listcur ) {
98                 if ( ptr->length != 0 ) {
99                         if ( ok != ptr )
100                                 memcpy(ok,ptr,sizeof(FDBFreeSpace));
101                         ok++;
102                 }
103                 ptr++;
104         }
105         db->listcur = ok - db->space;   
106 }
107
108 int 
109 FDBOpen(FDB *db, char *file, int readonly) {
110         FDBHeader header;
111         int rc;
112
113         memset(db, 0, sizeof(FDB));
114
115         if ( readonly ) {
116                 db->readonly=1;
117                 db->fd = open(file, O_RDONLY | O_SHLOCK);
118         } else {
119                 db->fd = open(file, O_CREAT | O_RDWR | O_EXLOCK, 0666);
120         }
121
122         if ( db->fd < 0 ) {
123                 memset(db, 0, sizeof(FDB));
124                 tlog(TL_CRIT,"FDBOpen: open failed: %s", strerror(errno));
125                 return FDB_ERROR;
126         }
127
128         rc = read(db->fd, &header, sizeof(FDBHeader));
129
130         if ( rc<0 ) {
131                 close(db->fd);
132                 tlog(TL_CRIT,"FDBOpen: read failed: %s", strerror(errno));
133                 return FDB_ERROR;
134         } else if ( rc==0 ) {
135                 memset(&header, 0, sizeof(FDBHeader));
136         } else if ( rc != sizeof(FDBHeader) ) {
137                 close(db->fd);
138                 tlog(TL_CRIT,"FDBOpen: header fault: %d bytes only", rc);
139                 return FDB_ERROR;
140         } else if ( header.isopened ) {
141                 close(db->fd);
142                 tlog(TL_CRIT,"FDBOpen: file wasn't closed correctly");
143                 return FDB_ERROR;
144         }
145                 
146         
147         if ( !db->readonly ) {
148                 if ( header.freespace ) {
149                         db->listlen = db->listcur = (header.lenfreespace / sizeof(FDBFreeSpace));
150                         db->space = (FDBFreeSpace*)tmalloc( header.lenfreespace + sizeof(FDBFreeSpace) );
151                 
152                         if ( lseek(db->fd, header.freespace, SEEK_SET)!=header.freespace || 
153                                         read( db->fd, db->space,  header.lenfreespace ) !=  header.lenfreespace ) {
154                                 close(db->fd);
155                                 tlog(TL_CRIT,"FDBOpen: free space read failed: %s", strerror(errno));
156                                 return FDB_ERROR;
157                         }
158                         FDBVacuumFreeSpace(db);
159                 } else {
160                         db->listlen = 8;
161                         db->space = (FDBFreeSpace*)tmalloc( db->listlen*sizeof(FDBFreeSpace) );
162                 }       
163
164                 header.freespace = 0;
165                 header.lenfreespace = 0;
166                 header.isopened = 1;
167
168                 if ( lseek(db->fd, 0, SEEK_SET)!=0 || 
169                                 write(db->fd, &header, sizeof(FDBHeader)) != sizeof(FDBHeader) || 
170                                 fsync(db->fd) ) {
171                         close(db->fd);
172                         if ( db->space ) tfree( db->space );
173                         tlog(TL_CRIT,"FDBOpen: can't modify header: %s", strerror(errno));
174                         return FDB_ERROR;
175                 }
176         }       
177
178         return FDB_OK;
179 }
180
181
182 int
183 FDBClose(FDB *db) {
184         if ( !db->readonly) {
185                 FDBHeader header;
186
187                 memset(&header,0,sizeof(FDBHeader));
188
189                 if ( db->listcur ) {
190                         FDBFreeSpace    *ptr;
191
192                         FDBVacuumFreeSpace(db);
193
194                         header.lenfreespace = sizeof(FDBFreeSpace)*db->listcur;
195                         ptr = findFreeSpace( db, header.lenfreespace );
196                 
197                         if ( ptr ) {
198                                 header.freespace = ptr->offset;
199                                 if ( lseek(db->fd, ptr->offset, SEEK_SET) != ptr->offset )
200                                         tlog(TL_CRIT|TL_EXIT,"FDBClose: lseek failed: %s", strerror(errno));
201                         } else {
202                                 if ( (header.freespace = lseek(db->fd, 0, SEEK_END)) < 0 ) 
203                                         tlog(TL_CRIT|TL_EXIT,"FDBClose: lseek failed: %s", strerror(errno));
204                                 header.lenfreespace += sizeof(FDBFreeSpace); 
205                                 addFreeSpace(db, header.freespace, header.lenfreespace); 
206                         }
207
208                         if ( write(db->fd, db->space, header.lenfreespace) != header.lenfreespace )
209                                 tlog(TL_CRIT|TL_EXIT,"FDBClose: write failed: %s", strerror(errno));
210                 }
211
212                 header.isopened=0;
213
214                 if ( lseek(db->fd,0,SEEK_SET)!=0 || 
215                                 write(db->fd, &header, sizeof(FDBHeader)) != sizeof(FDBHeader) || 
216                                 fsync(db->fd))
217                         tlog(TL_CRIT|TL_EXIT,"FDBClose: header write  failed: %s", strerror(errno)); 
218         }
219
220         close(db->fd);
221
222         if ( db->space )
223                 tfree( db->space );
224
225         return FDB_OK;
226 }
227
228 static int
229 readLen(FDB *db, off_t offset, size_t *size) { 
230         if ( lseek(db->fd,offset,SEEK_SET)!=offset)
231                 return FDB_ERROR;
232         
233         if ( read(db->fd,size,sizeof(size_t)) != sizeof(size_t) )
234                 return FDB_ERROR;
235
236         return FDB_OK;
237 }
238
239
240 int
241 FDBDelete(FDB *db, off_t offset, size_t length) {
242         if ( db->readonly )
243                 return FDB_ERROR;
244
245         if ( length==0 ) 
246                 if ( readLen(db, offset, &length) != FDB_OK )
247                         return FDB_ERROR;
248
249         addFreeSpace(db, offset, length);
250
251         return FDB_OK;
252 }
253
254
255 int
256 FDBGet(FDB *db, off_t offset, size_t length, FDBRecord **record) {
257         size_t rc;
258
259         *record=NULL;
260
261         if ( offset < sizeof(FDBHeader) )       
262                 return FDB_ERROR;
263
264         if ( length==0 )
265                 if ( readLen(db, offset, &length) != FDB_OK )
266                         return FDB_ERROR;
267
268         *record = (FDBRecord*)tmalloc( length );
269
270         if ( lseek(db->fd,offset,SEEK_SET)!=offset)
271                 return FDB_ERROR;
272
273         if ( (rc=read(db->fd,*record,length)) != length ) {
274                 (*record)->length = rc;
275                 tlog(TL_CRIT,"FDBGet: read (%d bytes) less than needed (%d bytes): %s", rc, length, strerror(errno));
276                 return FDB_INCORRECT;
277         }
278
279         if ( (*record)->length != length ) {
280                 tlog(TL_ALARM, "FDBGet: wrong length in opts: %d bytes and %d bytes really", length, (*record)->length);
281                 if ( (*record)->length > length ) {
282                         rc = (*record)->length;
283                         tfree( *record );
284                         return FDBGet(db, offset, rc, record);
285                 }
286         } 
287
288         return FDB_OK;
289 }
290          
291                 
292 int 
293 FDBPut(FDB *db, FDBRecord *record, off_t *offset ) {
294         FDBFreeSpace *ptr;
295
296         if ( db->readonly )
297                 return FDB_ERROR;
298
299         ptr = findFreeSpace( db, record->length ); 
300         if ( ptr ) {
301                 *offset = ptr->offset;
302                 ptr->length -= record->length;
303                 if ( ptr->length == 0 ) {
304                         if ( (ptr - db->space) + 1 != db->listcur ) 
305                                 memmove(ptr, ptr+1, (db->listcur - (ptr - db->space) + 1) * sizeof(FDBFreeSpace));
306                         db->listcur--; 
307                 } else
308                         ptr->offset += record->length;
309                 if ( lseek(db->fd, *offset, SEEK_SET) != *offset ) 
310                         tlog(TL_CRIT|TL_EXIT,"FDBPut: lseek failed: %s", strerror(errno)); 
311         } else {
312                 if ( (*offset = lseek(db->fd, 0, SEEK_END)) < 0 ) 
313                         tlog(TL_CRIT|TL_EXIT,"FDBPut: lseek failed: %s", strerror(errno)); 
314         }
315
316         if ( write(db->fd, record, record->length) != record->length ) 
317                 tlog(TL_CRIT|TL_EXIT,"FDBPut: write failed: %s", strerror(errno));
318
319         return FDB_OK;
320
321 }
322
323