Add flat file manager (need testing)
[tedtools.git] / flatdb.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <fcntl.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <errno.h>
7
8 #include "tlog.h"
9 #include "tmalloc.h"
10
11 #include "flatdb.h"
12
13 static FDBFreeSpace*
14 findFreeSpace(FDB *db, size_t length) {
15         FDBFreeSpace *ptr = db->space;
16         
17         while(ptr && ptr - db->space < db->listcur) {
18                 if ( ptr->length >= length )
19                         return ptr; 
20                 ptr++;
21         }
22
23         return NULL; 
24 }
25
26 static int
27 cmpFS(const void* a, const void* b) {
28         if ( ((FDBFreeSpace*)a)->offset == ((FDBFreeSpace*)b)->offset )
29                 return 0;
30         return ( ((FDBFreeSpace*)a)->offset > ((FDBFreeSpace*)b)->offset ) ? 1 : -1;
31 }
32
33 void
34 FDBVacuumFreeSpace(FDB *db) {
35         FDBFreeSpace *ptr=db->space+1, *ok=db->space;
36         if ( db->listcur < 2 )
37                 return;
38
39         qsort( db->space, db->listcur, sizeof(FDBFreeSpace), cmpFS);
40
41         while( ptr - db->space < db->listcur ) {
42                 tassert( ok->offset + ok->length <= ptr->offset );
43                 if ( ok->offset + ok->length == ptr->offset || ptr->length==0 ) {
44                         ok->length += ptr->length;
45                 } else {
46                         ok++;
47                         if ( ok != ptr-1 )
48                                 memcpy(ok, ptr, sizeof(FDBFreeSpace));   
49                 }
50                 ptr++;
51         }
52
53         db->listcur = ok - db->space + 1;       
54 }
55
56 int 
57 FDBOpen(FDB *db, char *file, int readonly) {
58         FDBHeader header;
59         int rc;
60
61         memset(db, 0, sizeof(FDB));
62
63         if ( readonly ) {
64                 db->readonly=1;
65                 db->fd = open(file, O_RDONLY | O_SHLOCK);
66         } else {
67                 db->fd = open(file, O_CREAT | O_RDWR | O_EXLOCK, 0666);
68         }
69
70         if ( db->fd < 0 ) {
71                 memset(db, 0, sizeof(FDB));
72                 tlog(TL_CRIT,"FDBOpen: open failed: %s", strerror(errno));
73                 return FDB_ERROR;
74         }
75
76         rc = read(db->fd, &header, sizeof(FDBHeader));
77
78         if ( rc<0 ) {
79                 close(db->fd);
80                 tlog(TL_CRIT,"FDBOpen: read failed: %s", strerror(errno));
81                 return FDB_ERROR;
82         } else if ( rc==0 ) {
83                 memset(&header, 0, sizeof(FDBHeader));
84         } else if ( rc != sizeof(FDBHeader) ) {
85                 close(db->fd);
86                 tlog(TL_CRIT,"FDBOpen: header fault: %d bytes only", rc);
87                 return FDB_ERROR;
88         } else if ( header.isopened ) {
89                 close(db->fd);
90                 tlog(TL_CRIT,"FDBOpen: file wasn't closed correctly");
91                 return FDB_ERROR;
92         }
93                 
94         
95         if ( !db->readonly ) {
96                 if ( header.freespace ) {
97                         db->listlen = db->listcur = (header.lenfreespace / sizeof(FDBFreeSpace));
98                         db->space = (FDBFreeSpace*)tmalloc( header.lenfreespace + sizeof(FDBFreeSpace) );
99                 
100                         if ( lseek(db->fd, header.freespace, SEEK_SET)!=header.freespace || 
101                                         read( db->fd, db->space,  header.lenfreespace ) !=  header.lenfreespace ) {
102                                 close(db->fd);
103                                 tlog(TL_CRIT,"FDBOpen: free space read failed: %s", strerror(errno));
104                                 return FDB_ERROR;
105                         }
106                 } else {
107                         db->listlen = 8;
108                         db->space = (FDBFreeSpace*)tmalloc( db->listlen*sizeof(FDBFreeSpace) );
109                 }       
110
111                 header.freespace = 0;
112                 header.lenfreespace = 0;
113                 header.isopened = 1;
114
115                 if ( lseek(db->fd, 0, SEEK_SET)!=0 || write(db->fd, &header, sizeof(FDBHeader)) != sizeof(FDBHeader) ) {
116                         close(db->fd);
117                         if ( db->space ) tfree( db->space );
118                         tlog(TL_CRIT,"FDBOpen: can't modify header: %s", strerror(errno));
119                         return FDB_ERROR;
120                 }
121         }       
122
123         return FDB_OK;
124 }
125
126
127 int
128 FDBClose(FDB *db) {
129         if ( !db->readonly) {
130                 FDBHeader header;
131
132                 memset(&header,0,sizeof(FDBHeader));
133
134                 if ( db->listcur ) {
135                         FDBFreeSpace    *ptr;
136
137                         vacuumFreeSpace(db);
138                         header.lenfreespace = sizeof(FDBFreeSpace)*db->listcur;
139                         ptr = findFreeSpace( db, header.lenfreespace );
140                 
141                         if ( ptr ) {
142                                 header.freespace = ptr->offset;
143                                 if ( lseek(db->fd, ptr->offset, SEEK_SET) != ptr->offset )
144                                         tlog(TL_CRIT|TL_EXIT,"FDBClose: lseek failed: %s", strerror(errno));
145                         } else {
146                                 if ( (header.freespace = lseek(db->fd, 0, SEEK_END)) < 0 ) 
147                                         tlog(TL_CRIT|TL_EXIT,"FDBClose: lseek failed: %s", strerror(errno)); 
148                         }
149
150                         if ( write(db->fd, db->space, header.lenfreespace) != header.lenfreespace )
151                                 tlog(TL_CRIT|TL_EXIT,"FDBClose: write failed: %s", strerror(errno));
152                 }
153
154                 header.isopened=0;
155
156                 if ( lseek(db->fd,0,SEEK_SET)!=0 || write(db->fd, &header, sizeof(FDBHeader)) != sizeof(FDBHeader) )
157                         tlog(TL_CRIT|TL_EXIT,"FDBClose: header write  failed: %s", strerror(errno)); 
158         }
159
160         close(db->fd);
161
162         if ( db->space )
163                 tfree( db->space );
164
165         return FDB_OK;
166 }
167
168 static int
169 readLen(FDB *db, off_t offset, size_t *size) { 
170         if ( lseek(db->fd,offset,SEEK_SET)!=offset)
171                 return FDB_ERROR;
172         
173         if ( read(db->fd,size,sizeof(size_t)) != sizeof(size_t) )
174                 return FDB_ERROR;
175
176         return FDB_OK;
177 }
178
179 int
180 FDBDelete(FDB *db, off_t offset, size_t length) {
181         if ( db->readonly )
182                 return FDB_ERROR;
183
184         if ( length==0 ) 
185                 if ( readLen(db, offset, &length) != FDB_OK )
186                         return FDB_ERROR;
187
188         if ( db->listcur >= db->listlen ) {
189                 db->listlen *= 2;
190                 db->space = (FDBFreeSpace*) trealloc( db->space, db->listlen * sizeof(FDBFreeSpace) );
191         }
192
193         db->space[ db->listcur ].offset=offset;
194         db->space[ db->listcur ].length=length;
195
196         db->listcur++;
197
198         return FDB_OK;
199 }
200
201
202 int
203 FDBGet(FDB *db, off_t offset, size_t length, FDBRecord **record) {
204         size_t rc;
205
206         *record=NULL;
207
208         if ( db->readonly )
209                 return FDB_ERROR;
210
211         if ( length==0 )
212                 if ( readLen(db, offset, &length) != FDB_OK )
213                         return FDB_ERROR;
214
215         *record = (FDBRecord*)tmalloc( length );
216
217         if ( lseek(db->fd,offset,SEEK_SET)!=offset)
218                 return FDB_ERROR;
219
220         if ( (rc=read(db->fd,*record,length)) != length ) {
221                 (*record)->length = rc;
222                 tlog(TL_CRIT,"FDBGet: read (%d bytes) less than needed (%d bytes): %s", rc, length, strerror(errno));
223                 return FDB_INCORRECT;
224         }
225
226         if ( (*record)->length != length ) {
227                 tlog(TL_ALARM, "FDBGet: wrong length in opts: %d bytes and %d bytes really", length, (*record)->length);
228                 if ( (*record)->length > length ) {
229                         rc = (*record)->length;
230                         tfree( *record );
231                         return FDBGet(db, offset, rc, record);
232                 }
233         } 
234
235         return FDB_OK;
236 }
237          
238                 
239 int 
240 FDBPut(FDB *db, FDBRecord *record, off_t *offset ) {
241         FDBFreeSpace *ptr;
242
243         if ( db->readonly )
244                 return FDB_ERROR;
245
246         ptr = findFreeSpace( db, record->length ); 
247         if ( ptr ) {
248                 *offset = ptr->offset;
249                 ptr->length -= record->length;
250                 if ( ptr->length == 0 ) 
251                         vacuumFreeSpace(db);
252                 if ( lseek(db->fd, *offset, SEEK_SET) != *offset ) 
253                         tlog(TL_CRIT|TL_EXIT,"FDBPut: lseek failed: %s", strerror(errno)); 
254         } else {
255                 if ( (*offset = lseek(db->fd, 0, SEEK_END)) < 0 ) 
256                         tlog(TL_CRIT|TL_EXIT,"FDBPut: lseek failed: %s", strerror(errno)); 
257         }
258
259         if ( write(db->fd, record, record->length) != record->length ) 
260                 tlog(TL_CRIT|TL_EXIT,"FDBPut: write lseek failed: %s", strerror(errno));
261
262         return FDB_OK;
263
264 }
265
266