00001 /****************************************************************************** 00002 * 00003 * $Id:$ 00004 * 00005 * 00006 * Copyright (C) 1997-2008 by Dimitri van Heesch. 00007 * 00008 * Permission to use, copy, modify, and distribute this software and its 00009 * documentation under the terms of the GNU General Public License is hereby 00010 * granted. No representations are made about the suitability of this software 00011 * for any purpose. It is provided "as is" without express or implied warranty. 00012 * See the GNU General Public License for more details. 00013 * 00014 * Documents produced by Doxygen are derivative works derived from the 00015 * input used in their production; they are not affected by this license. 00016 * 00017 */ 00018 00019 #include "store.h" 00020 #include "portable.h" 00021 00022 00023 #include <stdio.h> 00024 #include <stdlib.h> 00025 #include <errno.h> 00026 #include <string.h> 00027 #include <assert.h> 00028 00029 #define BLOCK_SIZE 512 // should be >8 and a multiple of 8 00030 #define BLOCK_POINTER_SIZE sizeof(portable_off_t) 00031 00032 00033 #define ASSERTS_ENABLED 00034 00035 #ifdef ASSERTS_ENABLED 00036 #define STORE_ASSERT(x) assert(x) 00037 #else 00038 #define STORE_ASSERT(x) 00039 #endif 00040 00041 //------------------------------------------------------------------------------------ 00042 00043 Store::Store() 00044 { 00045 m_file = 0; 00046 m_front = 0; 00047 m_head = 0; 00048 m_state = Init; 00049 m_reads = 0; 00050 m_writes = 0; 00051 } 00052 00053 Store::~Store() 00054 { 00055 if (m_file) fclose(m_file); 00056 00057 // clean up free list 00058 while (m_head) 00059 { 00060 Node *node = m_head; 00061 m_head = node->next; 00062 delete node; 00063 } 00064 } 00065 00066 int Store::open(const char *name) 00067 { 00068 int i; 00069 STORE_ASSERT(m_state==Init); 00070 if (m_file) return 0; // already open 00071 m_file = fopen(name,"w+b"); 00072 if (m_file==0) return -1; 00073 00074 // first block serves as header, so offset=0 can be used as the end of the list. 00075 for (i=0;i<BLOCK_SIZE/8;i++) 00076 { 00077 fputc('D',m_file); 00078 fputc('O',m_file); 00079 fputc('X',m_file); 00080 fputc('Y',m_file); 00081 fputc('G',m_file); 00082 fputc('E',m_file); 00083 fputc('N',m_file); 00084 fputc(0,m_file); 00085 } 00086 m_front = BLOCK_SIZE; 00087 m_head = 0; 00088 m_state = Reading; 00089 return 0; 00090 } 00091 00092 void Store::close() 00093 { 00094 if (m_file) fclose(m_file); 00095 m_file=0; 00096 m_state = Init; 00097 } 00098 00099 portable_off_t Store::alloc() 00100 { 00101 STORE_ASSERT(m_state==Reading); 00102 m_state=Writing; 00103 portable_off_t pos; 00104 if (m_head==0) // allocate new block 00105 { 00106 //printf("alloc: new block\n"); 00107 if (portable_fseek(m_file,0,SEEK_END)==-1) // go to end of the file 00108 { 00109 fprintf(stderr,"Store::alloc: Error seeking to end of file: %s\n",strerror(errno)); 00110 exit(1); 00111 } 00112 pos = portable_ftell(m_file); 00113 STORE_ASSERT( (pos & (BLOCK_SIZE-1))==0 ); 00114 m_front = pos + BLOCK_SIZE; // move front to end of this block 00115 } 00116 else // reuse freed block 00117 { 00118 //printf("alloc: reuse block: m_head=%d\n",(int)m_head); 00119 Node *node = m_head; 00120 pos = node->pos; 00121 // point head to next free item 00122 m_head = node->next; 00123 delete node; 00124 // move to start of the block 00125 if (portable_fseek(m_file,pos,SEEK_SET)==-1) 00126 { 00127 fprintf(stderr,"Store::alloc: Error seeking to position %d: %s\n", 00128 (int)pos,strerror(errno)); 00129 exit(1); 00130 } 00131 STORE_ASSERT( (pos & (BLOCK_SIZE-1))==0 ); 00132 } 00133 //printf("%x: Store::alloc\n",(int)pos); 00134 return pos; 00135 } 00136 00137 int Store::write(const char *buf,uint size) 00138 { 00139 STORE_ASSERT(m_state==Writing); 00140 //printf("%x: Store::write\n",(int)portable_ftell(m_file)); 00141 do 00142 { 00143 portable_off_t curPos = portable_ftell(m_file); 00144 int bytesInBlock = BLOCK_SIZE - BLOCK_POINTER_SIZE - (curPos & (BLOCK_SIZE-1)); 00145 int bytesLeft = bytesInBlock<(int)size ? (int)size-bytesInBlock : 0; 00146 int numBytes = size - bytesLeft; 00147 STORE_ASSERT(bytesInBlock>=0); 00148 STORE_ASSERT(numBytes<=(int)(BLOCK_SIZE-BLOCK_POINTER_SIZE)); 00149 if (numBytes>0) 00150 { 00151 if ((int)fwrite(buf,1,numBytes,m_file)!=numBytes) 00152 { 00153 fprintf(stderr,"Error writing: %s\n",strerror(errno)); 00154 exit(1); 00155 } 00156 m_writes++; 00157 } 00158 if (bytesLeft>0) // still more bytes to write 00159 { 00160 STORE_ASSERT(((portable_ftell(m_file)+BLOCK_POINTER_SIZE)&(BLOCK_SIZE-1))==0); 00161 // allocate new block 00162 if (m_head==0) // no free blocks to reuse 00163 { 00164 //printf("%x: Store::write: new: pos=%x\n",(int)m_front,(int)portable_ftell(m_file)); 00165 // write pointer to next block 00166 if (fwrite(&m_front,BLOCK_POINTER_SIZE,1,m_file)!=1) 00167 { 00168 fprintf(stderr,"Error writing to store: %s\n",strerror(errno)); 00169 exit(1); 00170 } 00171 STORE_ASSERT(portable_ftell(m_file)==(curPos&~(BLOCK_SIZE-1))+BLOCK_SIZE); 00172 00173 // move to next block 00174 if (portable_fseek(m_file,0,SEEK_END)==-1) // go to end of the file 00175 { 00176 fprintf(stderr,"Store::alloc: Error seeking to end of file: %s\n",strerror(errno)); 00177 exit(1); 00178 } 00179 STORE_ASSERT(portable_ftell(m_file)==m_front); 00180 // move front to the next of the block 00181 m_front+=BLOCK_SIZE; 00182 } 00183 else // reuse block from the free list 00184 { 00185 // write pointer to next block 00186 if (fwrite(&m_head->pos,BLOCK_POINTER_SIZE,1,m_file)!=1) 00187 { 00188 fprintf(stderr,"Error writing to store: %s\n",strerror(errno)); 00189 exit(1); 00190 } 00191 Node *node = m_head; 00192 portable_off_t pos = node->pos; 00193 // point head to next free item 00194 m_head = node->next; 00195 delete node; 00196 // move to start of the block 00197 if (portable_fseek(m_file,pos,SEEK_SET)==-1) 00198 { 00199 fprintf(stderr,"Store::write: Error seeking to position %d: %s\n", 00200 (int)pos,strerror(errno)); 00201 exit(1); 00202 } 00203 //printf("%x: Store::write: reuse\n",(int)pos); 00204 } 00205 } 00206 size-=numBytes; 00207 buf+=numBytes; 00208 } 00209 while (size>0); 00210 return size; 00211 } 00212 00213 void Store::end() 00214 { 00215 STORE_ASSERT(m_state==Writing); 00216 portable_off_t curPos = portable_ftell(m_file); 00217 int bytesInBlock = BLOCK_SIZE - (curPos & (BLOCK_SIZE-1)); 00218 //printf("%x: Store::end erasing %x bytes\n",(int)curPos&~(BLOCK_SIZE-1),bytesInBlock); 00219 //printf("end: bytesInBlock=%x\n",bytesInBlock); 00220 // zero out rest of the block 00221 int i; 00222 for (i=0;i<bytesInBlock;i++) 00223 { 00224 fputc(0,m_file); 00225 } 00226 m_state=Reading; 00227 } 00228 00229 void Store::release(portable_off_t pos) 00230 { 00231 STORE_ASSERT(m_state==Reading); 00232 //printf("%x: Store::release\n",(int)pos); 00233 STORE_ASSERT(pos>0 && (pos & (BLOCK_SIZE-1))==0); 00234 // goto end of the block 00235 portable_off_t cur = pos, next; 00236 while (1) 00237 { 00238 // add new node to the free list 00239 Node *node = new Node; 00240 node->next = m_head; 00241 node->pos = cur; 00242 00243 m_head = node; 00244 // goto the end of cur block 00245 if (portable_fseek(m_file,cur+BLOCK_SIZE-BLOCK_POINTER_SIZE,SEEK_SET)==-1) 00246 { 00247 fprintf(stderr,"Store::release: Error seeking to position %d: %s\n", 00248 (int)(cur+BLOCK_SIZE-BLOCK_POINTER_SIZE),strerror(errno)); 00249 exit(1); 00250 } 00251 // read pointer to next block 00252 if (fread(&next,BLOCK_POINTER_SIZE,1,m_file)!=1) 00253 { 00254 fprintf(stderr,"Store::release: Error reading from store: %s\n",strerror(errno)); 00255 exit(1); 00256 } 00257 if (next==0) break; // found end of list -> cur is last element 00258 STORE_ASSERT((next & (BLOCK_SIZE-1))==0); 00259 cur = next; 00260 //printf("%x: Store::release\n",(int)cur); 00261 } 00262 } 00263 00264 void Store::seek(portable_off_t pos) 00265 { 00266 STORE_ASSERT(m_state==Reading); 00267 //printf("%x: Store::seek\n",(int)pos); 00268 if (portable_fseek(m_file,pos,SEEK_SET)==-1) 00269 { 00270 fprintf(stderr,"Store::seek: Error seeking to position %d: %s\n", 00271 (int)pos,strerror(errno)); 00272 exit(1); 00273 } 00274 STORE_ASSERT((pos&(BLOCK_SIZE-1))==0); 00275 } 00276 00277 int Store::read(char *buf,uint size) 00278 { 00279 STORE_ASSERT(m_state==Reading); 00280 //printf("%x: Store::read total=%d\n",(int)portable_ftell(m_file),size); 00281 do 00282 { 00283 portable_off_t curPos = portable_ftell(m_file); 00284 int bytesInBlock = BLOCK_SIZE - BLOCK_POINTER_SIZE - (curPos & (BLOCK_SIZE-1)); 00285 int bytesLeft = bytesInBlock<(int)size ? (int)size-bytesInBlock : 0; 00286 int numBytes = size - bytesLeft; 00287 //printf(" Store::read: pos=%x num=%d left=%d\n",(int)curPos,numBytes,bytesLeft); 00288 00289 if (numBytes>0) 00290 { 00291 //printf("%x: Store::read: %d out of %d bytes\n",(int)portable_ftell(m_file),numBytes,size); 00292 if ((int)fread(buf,1,numBytes,m_file)!=numBytes) 00293 { 00294 fprintf(stderr,"Error reading from store: %s\n",strerror(errno)); 00295 exit(1); 00296 } 00297 m_reads++; 00298 } 00299 if (bytesLeft>0) 00300 { 00301 portable_off_t newPos; 00302 // read offset of the next block 00303 STORE_ASSERT(((portable_ftell(m_file)+BLOCK_POINTER_SIZE)&(BLOCK_SIZE-1))==0); 00304 if (fread((char *)&newPos,BLOCK_POINTER_SIZE,1,m_file)!=1) 00305 { 00306 fprintf(stderr,"Error reading from store: %s\n",strerror(errno)); 00307 exit(1); 00308 } 00309 //printf("%x: Store::read: continue in next block, %d bytes to go\n",(int)newPos,bytesLeft); 00310 //printf(" Store::read: next block=%x\n",(int)newPos); 00311 STORE_ASSERT(newPos!=0); 00312 STORE_ASSERT((newPos&(BLOCK_SIZE-1))==0); 00313 curPos = newPos; 00314 // move to next block 00315 if (portable_fseek(m_file,curPos,SEEK_SET)==-1) 00316 { 00317 fprintf(stderr,"Store::read: Error seeking to position %d: %s\n", 00318 (int)curPos,strerror(errno)); 00319 exit(1); 00320 } 00321 } 00322 00323 size-=numBytes; 00324 buf+=numBytes; 00325 } 00326 while (size>0); 00327 return size; 00328 } 00329 00330 void Store::printFreeList() 00331 { 00332 printf("FreeList: "); 00333 portable_off_t pos = m_head->pos; 00334 while (pos) 00335 { 00336 printf("%x ",(int)pos); 00337 m_head = m_head->next; 00338 } 00339 printf("\n"); 00340 } 00341 00342 void Store::printStats() 00343 { 00344 printf("ObjStore: block size %d bytes, total size %ld blocks, wrote %d blocks, read %d blocks\n", 00345 BLOCK_SIZE,(long)(m_front/BLOCK_SIZE),m_reads,m_writes); 00346 } 00347 00348 #ifdef STORE_TEST 00349 00350 int main() 00351 { 00352 printf("sizeof(portable_off_t)=%d\n",(int)sizeof(portable_off_t)); 00353 Store s; 00354 if (s.open("test.db")==0) 00355 { 00356 const char *str1 = "This is a test message... "; 00357 const char *str2 = "Another message. "; 00358 00359 int i,j; 00360 for (j=0;j<5;j++) 00361 { 00362 char buf[100]; 00363 00364 portable_off_t handle = s.alloc(); 00365 for (i=0;i<1000000000;i++) 00366 { 00367 s.write(str1,strlen(str1)+1); 00368 } 00369 s.end(); 00370 portable_off_t handle2 = s.alloc(); 00371 for (i=0;i<10;i++) 00372 { 00373 s.write(str2,strlen(str2)+1); 00374 } 00375 s.end(); 00376 00377 s.seek(handle); 00378 for (i=0;i<3;i++) 00379 { 00380 s.read(buf,strlen(str1)+1); 00381 printf("i=%d Read: %s\n",i,buf); 00382 } 00383 00384 s.release(handle); 00385 00386 s.seek(handle2); 00387 for (i=0;i<3;i++) 00388 { 00389 s.read(buf,strlen(str2)+1); 00390 printf("i=%d Read: %s\n",i,buf); 00391 } 00392 00393 s.release(handle2); 00394 } 00395 00396 s.close(); 00397 } 00398 else 00399 { 00400 printf("Open failed! %s\n",strerror(errno)); 00401 } 00402 } 00403 00404 #endif 00405