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