|
1 /****************************************************************************** |
|
2 * |
|
3 * |
|
4 * |
|
5 * |
|
6 * Copyright (C) 1997-2008 by Dimitri van Heesch. |
|
7 * |
|
8 * Permission to use, copy, modify, and distribute this software and its |
|
9 * documentation under the terms of the GNU General Public License is hereby |
|
10 * granted. No representations are made about the suitability of this software |
|
11 * for any purpose. It is provided "as is" without express or implied warranty. |
|
12 * See the GNU General Public License for more details. |
|
13 * |
|
14 * Documents produced by Doxygen are derivative works derived from the |
|
15 * input used in their production; they are not affected by this license. |
|
16 * |
|
17 */ |
|
18 |
|
19 #include "store.h" |
|
20 #include "portable.h" |
|
21 |
|
22 |
|
23 #include <stdio.h> |
|
24 #include <stdlib.h> |
|
25 #include <errno.h> |
|
26 #include <string.h> |
|
27 #include <assert.h> |
|
28 |
|
29 #define BLOCK_SIZE 512 // should be >8 and a multiple of 8 |
|
30 #define BLOCK_POINTER_SIZE sizeof(portable_off_t) |
|
31 |
|
32 |
|
33 #define ASSERTS_ENABLED |
|
34 |
|
35 #ifdef ASSERTS_ENABLED |
|
36 #define STORE_ASSERT(x) assert(x) |
|
37 #else |
|
38 #define STORE_ASSERT(x) |
|
39 #endif |
|
40 |
|
41 //------------------------------------------------------------------------------------ |
|
42 |
|
43 Store::Store() |
|
44 { |
|
45 m_file = 0; |
|
46 m_front = 0; |
|
47 m_head = 0; |
|
48 m_state = Init; |
|
49 m_reads = 0; |
|
50 m_writes = 0; |
|
51 } |
|
52 |
|
53 Store::~Store() |
|
54 { |
|
55 if (m_file) fclose(m_file); |
|
56 |
|
57 // clean up free list |
|
58 while (m_head) |
|
59 { |
|
60 Node *node = m_head; |
|
61 m_head = node->next; |
|
62 delete node; |
|
63 } |
|
64 } |
|
65 |
|
66 int Store::open(const char *name) |
|
67 { |
|
68 int i; |
|
69 STORE_ASSERT(m_state==Init); |
|
70 if (m_file) return 0; // already open |
|
71 m_file = fopen(name,"w+b"); |
|
72 if (m_file==0) return -1; |
|
73 |
|
74 // first block serves as header, so offset=0 can be used as the end of the list. |
|
75 for (i=0;i<BLOCK_SIZE/8;i++) |
|
76 { |
|
77 fputc('D',m_file); |
|
78 fputc('O',m_file); |
|
79 fputc('X',m_file); |
|
80 fputc('Y',m_file); |
|
81 fputc('G',m_file); |
|
82 fputc('E',m_file); |
|
83 fputc('N',m_file); |
|
84 fputc(0,m_file); |
|
85 } |
|
86 m_front = BLOCK_SIZE; |
|
87 m_head = 0; |
|
88 m_state = Reading; |
|
89 return 0; |
|
90 } |
|
91 |
|
92 void Store::close() |
|
93 { |
|
94 if (m_file) fclose(m_file); |
|
95 m_file=0; |
|
96 m_state = Init; |
|
97 } |
|
98 |
|
99 portable_off_t Store::alloc() |
|
100 { |
|
101 STORE_ASSERT(m_state==Reading); |
|
102 m_state=Writing; |
|
103 portable_off_t pos; |
|
104 if (m_head==0) // allocate new block |
|
105 { |
|
106 //printf("alloc: new block\n"); |
|
107 if (portable_fseek(m_file,0,SEEK_END)==-1) // go to end of the file |
|
108 { |
|
109 fprintf(stderr,"Store::alloc: Error seeking to end of file: %s\n",strerror(errno)); |
|
110 exit(1); |
|
111 } |
|
112 pos = portable_ftell(m_file); |
|
113 STORE_ASSERT( (pos & (BLOCK_SIZE-1))==0 ); |
|
114 m_front = pos + BLOCK_SIZE; // move front to end of this block |
|
115 } |
|
116 else // reuse freed block |
|
117 { |
|
118 //printf("alloc: reuse block: m_head=%d\n",(int)m_head); |
|
119 Node *node = m_head; |
|
120 pos = node->pos; |
|
121 // point head to next free item |
|
122 m_head = node->next; |
|
123 delete node; |
|
124 // move to start of the block |
|
125 if (portable_fseek(m_file,pos,SEEK_SET)==-1) |
|
126 { |
|
127 fprintf(stderr,"Store::alloc: Error seeking to position %d: %s\n", |
|
128 (int)pos,strerror(errno)); |
|
129 exit(1); |
|
130 } |
|
131 STORE_ASSERT( (pos & (BLOCK_SIZE-1))==0 ); |
|
132 } |
|
133 //printf("%x: Store::alloc\n",(int)pos); |
|
134 return pos; |
|
135 } |
|
136 |
|
137 int Store::write(const char *buf,uint size) |
|
138 { |
|
139 STORE_ASSERT(m_state==Writing); |
|
140 //printf("%x: Store::write\n",(int)portable_ftell(m_file)); |
|
141 do |
|
142 { |
|
143 portable_off_t curPos = portable_ftell(m_file); |
|
144 int bytesInBlock = BLOCK_SIZE - BLOCK_POINTER_SIZE - (curPos & (BLOCK_SIZE-1)); |
|
145 int bytesLeft = bytesInBlock<(int)size ? (int)size-bytesInBlock : 0; |
|
146 int numBytes = size - bytesLeft; |
|
147 STORE_ASSERT(bytesInBlock>=0); |
|
148 STORE_ASSERT(numBytes<=(int)(BLOCK_SIZE-BLOCK_POINTER_SIZE)); |
|
149 if (numBytes>0) |
|
150 { |
|
151 if ((int)fwrite(buf,1,numBytes,m_file)!=numBytes) |
|
152 { |
|
153 fprintf(stderr,"Error writing: %s\n",strerror(errno)); |
|
154 exit(1); |
|
155 } |
|
156 m_writes++; |
|
157 } |
|
158 if (bytesLeft>0) // still more bytes to write |
|
159 { |
|
160 STORE_ASSERT(((portable_ftell(m_file)+BLOCK_POINTER_SIZE)&(BLOCK_SIZE-1))==0); |
|
161 // allocate new block |
|
162 if (m_head==0) // no free blocks to reuse |
|
163 { |
|
164 //printf("%x: Store::write: new: pos=%x\n",(int)m_front,(int)portable_ftell(m_file)); |
|
165 // write pointer to next block |
|
166 if (fwrite(&m_front,BLOCK_POINTER_SIZE,1,m_file)!=1) |
|
167 { |
|
168 fprintf(stderr,"Error writing to store: %s\n",strerror(errno)); |
|
169 exit(1); |
|
170 } |
|
171 STORE_ASSERT(portable_ftell(m_file)==(curPos&~(BLOCK_SIZE-1))+BLOCK_SIZE); |
|
172 |
|
173 // move to next block |
|
174 if (portable_fseek(m_file,0,SEEK_END)==-1) // go to end of the file |
|
175 { |
|
176 fprintf(stderr,"Store::alloc: Error seeking to end of file: %s\n",strerror(errno)); |
|
177 exit(1); |
|
178 } |
|
179 STORE_ASSERT(portable_ftell(m_file)==m_front); |
|
180 // move front to the next of the block |
|
181 m_front+=BLOCK_SIZE; |
|
182 } |
|
183 else // reuse block from the free list |
|
184 { |
|
185 // write pointer to next block |
|
186 if (fwrite(&m_head->pos,BLOCK_POINTER_SIZE,1,m_file)!=1) |
|
187 { |
|
188 fprintf(stderr,"Error writing to store: %s\n",strerror(errno)); |
|
189 exit(1); |
|
190 } |
|
191 Node *node = m_head; |
|
192 portable_off_t pos = node->pos; |
|
193 // point head to next free item |
|
194 m_head = node->next; |
|
195 delete node; |
|
196 // move to start of the block |
|
197 if (portable_fseek(m_file,pos,SEEK_SET)==-1) |
|
198 { |
|
199 fprintf(stderr,"Store::write: Error seeking to position %d: %s\n", |
|
200 (int)pos,strerror(errno)); |
|
201 exit(1); |
|
202 } |
|
203 //printf("%x: Store::write: reuse\n",(int)pos); |
|
204 } |
|
205 } |
|
206 size-=numBytes; |
|
207 buf+=numBytes; |
|
208 } |
|
209 while (size>0); |
|
210 return size; |
|
211 } |
|
212 |
|
213 void Store::end() |
|
214 { |
|
215 STORE_ASSERT(m_state==Writing); |
|
216 portable_off_t curPos = portable_ftell(m_file); |
|
217 int bytesInBlock = BLOCK_SIZE - (curPos & (BLOCK_SIZE-1)); |
|
218 //printf("%x: Store::end erasing %x bytes\n",(int)curPos&~(BLOCK_SIZE-1),bytesInBlock); |
|
219 //printf("end: bytesInBlock=%x\n",bytesInBlock); |
|
220 // zero out rest of the block |
|
221 int i; |
|
222 for (i=0;i<bytesInBlock;i++) |
|
223 { |
|
224 fputc(0,m_file); |
|
225 } |
|
226 m_state=Reading; |
|
227 } |
|
228 |
|
229 void Store::release(portable_off_t pos) |
|
230 { |
|
231 STORE_ASSERT(m_state==Reading); |
|
232 //printf("%x: Store::release\n",(int)pos); |
|
233 STORE_ASSERT(pos>0 && (pos & (BLOCK_SIZE-1))==0); |
|
234 // goto end of the block |
|
235 portable_off_t cur = pos, next; |
|
236 while (1) |
|
237 { |
|
238 // add new node to the free list |
|
239 Node *node = new Node; |
|
240 node->next = m_head; |
|
241 node->pos = cur; |
|
242 |
|
243 m_head = node; |
|
244 // goto the end of cur block |
|
245 if (portable_fseek(m_file,cur+BLOCK_SIZE-BLOCK_POINTER_SIZE,SEEK_SET)==-1) |
|
246 { |
|
247 fprintf(stderr,"Store::release: Error seeking to position %d: %s\n", |
|
248 (int)(cur+BLOCK_SIZE-BLOCK_POINTER_SIZE),strerror(errno)); |
|
249 exit(1); |
|
250 } |
|
251 // read pointer to next block |
|
252 if (fread(&next,BLOCK_POINTER_SIZE,1,m_file)!=1) |
|
253 { |
|
254 fprintf(stderr,"Store::release: Error reading from store: %s\n",strerror(errno)); |
|
255 exit(1); |
|
256 } |
|
257 if (next==0) break; // found end of list -> cur is last element |
|
258 STORE_ASSERT((next & (BLOCK_SIZE-1))==0); |
|
259 cur = next; |
|
260 //printf("%x: Store::release\n",(int)cur); |
|
261 } |
|
262 } |
|
263 |
|
264 void Store::seek(portable_off_t pos) |
|
265 { |
|
266 STORE_ASSERT(m_state==Reading); |
|
267 //printf("%x: Store::seek\n",(int)pos); |
|
268 if (portable_fseek(m_file,pos,SEEK_SET)==-1) |
|
269 { |
|
270 fprintf(stderr,"Store::seek: Error seeking to position %d: %s\n", |
|
271 (int)pos,strerror(errno)); |
|
272 exit(1); |
|
273 } |
|
274 STORE_ASSERT((pos&(BLOCK_SIZE-1))==0); |
|
275 } |
|
276 |
|
277 int Store::read(char *buf,uint size) |
|
278 { |
|
279 STORE_ASSERT(m_state==Reading); |
|
280 //printf("%x: Store::read total=%d\n",(int)portable_ftell(m_file),size); |
|
281 do |
|
282 { |
|
283 portable_off_t curPos = portable_ftell(m_file); |
|
284 int bytesInBlock = BLOCK_SIZE - BLOCK_POINTER_SIZE - (curPos & (BLOCK_SIZE-1)); |
|
285 int bytesLeft = bytesInBlock<(int)size ? (int)size-bytesInBlock : 0; |
|
286 int numBytes = size - bytesLeft; |
|
287 //printf(" Store::read: pos=%x num=%d left=%d\n",(int)curPos,numBytes,bytesLeft); |
|
288 |
|
289 if (numBytes>0) |
|
290 { |
|
291 //printf("%x: Store::read: %d out of %d bytes\n",(int)portable_ftell(m_file),numBytes,size); |
|
292 if ((int)fread(buf,1,numBytes,m_file)!=numBytes) |
|
293 { |
|
294 fprintf(stderr,"Error reading from store: %s\n",strerror(errno)); |
|
295 exit(1); |
|
296 } |
|
297 m_reads++; |
|
298 } |
|
299 if (bytesLeft>0) |
|
300 { |
|
301 portable_off_t newPos; |
|
302 // read offset of the next block |
|
303 STORE_ASSERT(((portable_ftell(m_file)+BLOCK_POINTER_SIZE)&(BLOCK_SIZE-1))==0); |
|
304 if (fread((char *)&newPos,BLOCK_POINTER_SIZE,1,m_file)!=1) |
|
305 { |
|
306 fprintf(stderr,"Error reading from store: %s\n",strerror(errno)); |
|
307 exit(1); |
|
308 } |
|
309 //printf("%x: Store::read: continue in next block, %d bytes to go\n",(int)newPos,bytesLeft); |
|
310 //printf(" Store::read: next block=%x\n",(int)newPos); |
|
311 STORE_ASSERT(newPos!=0); |
|
312 STORE_ASSERT((newPos&(BLOCK_SIZE-1))==0); |
|
313 curPos = newPos; |
|
314 // move to next block |
|
315 if (portable_fseek(m_file,curPos,SEEK_SET)==-1) |
|
316 { |
|
317 fprintf(stderr,"Store::read: Error seeking to position %d: %s\n", |
|
318 (int)curPos,strerror(errno)); |
|
319 exit(1); |
|
320 } |
|
321 } |
|
322 |
|
323 size-=numBytes; |
|
324 buf+=numBytes; |
|
325 } |
|
326 while (size>0); |
|
327 return size; |
|
328 } |
|
329 |
|
330 void Store::printFreeList() |
|
331 { |
|
332 printf("FreeList: "); |
|
333 portable_off_t pos = m_head->pos; |
|
334 while (pos) |
|
335 { |
|
336 printf("%x ",(int)pos); |
|
337 m_head = m_head->next; |
|
338 } |
|
339 printf("\n"); |
|
340 } |
|
341 |
|
342 void Store::printStats() |
|
343 { |
|
344 printf("ObjStore: block size %d bytes, total size %ld blocks, wrote %d blocks, read %d blocks\n", |
|
345 BLOCK_SIZE,(long)(m_front/BLOCK_SIZE),m_reads,m_writes); |
|
346 } |
|
347 |
|
348 #ifdef STORE_TEST |
|
349 |
|
350 int main() |
|
351 { |
|
352 printf("sizeof(portable_off_t)=%d\n",(int)sizeof(portable_off_t)); |
|
353 Store s; |
|
354 if (s.open("test.db")==0) |
|
355 { |
|
356 const char *str1 = "This is a test message... "; |
|
357 const char *str2 = "Another message. "; |
|
358 |
|
359 int i,j; |
|
360 for (j=0;j<5;j++) |
|
361 { |
|
362 char buf[100]; |
|
363 |
|
364 portable_off_t handle = s.alloc(); |
|
365 for (i=0;i<1000000000;i++) |
|
366 { |
|
367 s.write(str1,strlen(str1)+1); |
|
368 } |
|
369 s.end(); |
|
370 portable_off_t handle2 = s.alloc(); |
|
371 for (i=0;i<10;i++) |
|
372 { |
|
373 s.write(str2,strlen(str2)+1); |
|
374 } |
|
375 s.end(); |
|
376 |
|
377 s.seek(handle); |
|
378 for (i=0;i<3;i++) |
|
379 { |
|
380 s.read(buf,strlen(str1)+1); |
|
381 printf("i=%d Read: %s\n",i,buf); |
|
382 } |
|
383 |
|
384 s.release(handle); |
|
385 |
|
386 s.seek(handle2); |
|
387 for (i=0;i<3;i++) |
|
388 { |
|
389 s.read(buf,strlen(str2)+1); |
|
390 printf("i=%d Read: %s\n",i,buf); |
|
391 } |
|
392 |
|
393 s.release(handle2); |
|
394 } |
|
395 |
|
396 s.close(); |
|
397 } |
|
398 else |
|
399 { |
|
400 printf("Open failed! %s\n",strerror(errno)); |
|
401 } |
|
402 } |
|
403 |
|
404 #endif |
|
405 |