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