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