/* * Copyright (c) 2004 Teodor Sigaev * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include "tlog.h" #include "tmalloc.h" #include "flatdb.h" static FDBFreeSpace* findFreeSpace(FDB *db, size_t length) { FDBFreeSpace *ptr = db->space; while(ptr && ptr - db->space < db->listcur) { if ( ptr->length >= length ) return ptr; ptr++; } return NULL; } static void addFreeSpace(FDB *db, off_t offset, size_t length) { if ( db->listcur >= db->listlen ) { db->listlen *= 2; db->space = (FDBFreeSpace*) trealloc( db->space, db->listlen * sizeof(FDBFreeSpace) ); } db->space[ db->listcur ].offset=offset; db->space[ db->listcur ].length=length; db->listcur++; } static int cmpFS(const void* a, const void* b) { if ( ((FDBFreeSpace*)a)->offset == ((FDBFreeSpace*)b)->offset ) return 0; return ( ((FDBFreeSpace*)a)->offset > ((FDBFreeSpace*)b)->offset ) ? 1 : -1; } void FDBVacuumFreeSpace(FDB *db) { FDBFreeSpace *ptr=db->space+1, *ok=db->space; if ( db->listcur < 2 ) return; qsort( db->space, db->listcur, sizeof(FDBFreeSpace), cmpFS); /* merge spaces */ while( ptr - db->space < db->listcur ) { if ( ok->offset + ok->length == ptr->offset || ptr->length==0 ) { ok->length += ptr->length; ptr->length=0; } else ok++; ptr++; } /* remove void spaces */ ptr = ok = db->space; while( ptr - db->space < db->listcur ) { if ( ptr->length != 0 ) { if ( ok != ptr ) memcpy(ok,ptr,sizeof(FDBFreeSpace)); ok++; } ptr++; } db->listcur = ok - db->space; } int FDBOpen(FDB *db, char *file, int readonly) { FDBHeader header; int rc; memset(db, 0, sizeof(FDB)); if ( readonly ) { db->readonly=1; db->fd = open(file, O_RDONLY); if ( db->fd < 0 ) { tlog(TL_CRIT,"FDBOpen: open failed: %s",strerror(errno)); return FDB_ERROR; } if ( flock( db->fd, LOCK_SH ) < 0 ) { tlog(TL_CRIT,"FDBOpen: flock failed: %s",strerror(errno)); close(db->fd); return FDB_ERROR; } } else { db->fd = open(file, O_CREAT | O_RDWR, 0666); if ( db->fd < 0 ) { tlog(TL_CRIT,"FDBOpen: open failed: %s",strerror(errno)); return FDB_ERROR; } if ( flock( db->fd, LOCK_EX ) < 0 ) { tlog(TL_CRIT,"FDBOpen: flock failed: %s",strerror(errno)); close(db->fd); return FDB_ERROR; } } rc = read(db->fd, &header, sizeof(FDBHeader)); if ( rc<0 ) { flock( db->fd, LOCK_UN ); close(db->fd); tlog(TL_CRIT,"FDBOpen: read failed: %s", strerror(errno)); return FDB_ERROR; } else if ( rc==0 ) { memset(&header, 0, sizeof(FDBHeader)); } else if ( rc != sizeof(FDBHeader) ) { flock( db->fd, LOCK_UN ); close(db->fd); tlog(TL_CRIT,"FDBOpen: header fault: %d bytes only", rc); return FDB_ERROR; } else if ( header.isopened ) { flock( db->fd, LOCK_UN ); close(db->fd); tlog(TL_CRIT,"FDBOpen: file wasn't closed correctly"); return FDB_ERROR; } if ( !db->readonly ) { if ( header.freespace ) { db->listlen = db->listcur = (header.lenfreespace / sizeof(FDBFreeSpace)); db->space = (FDBFreeSpace*)tmalloc( header.lenfreespace + sizeof(FDBFreeSpace) ); if ( lseek(db->fd, header.freespace, SEEK_SET)!=header.freespace || read( db->fd, db->space, header.lenfreespace ) != header.lenfreespace ) { flock( db->fd, LOCK_UN ); close(db->fd); tlog(TL_CRIT,"FDBOpen: free space read failed: %s", strerror(errno)); return FDB_ERROR; } FDBVacuumFreeSpace(db); } else { db->listlen = 8; db->space = (FDBFreeSpace*)tmalloc( db->listlen*sizeof(FDBFreeSpace) ); } header.freespace = 0; header.lenfreespace = 0; header.isopened = 1; if ( lseek(db->fd, 0, SEEK_SET)!=0 || write(db->fd, &header, sizeof(FDBHeader)) != sizeof(FDBHeader) || fsync(db->fd) ) { flock( db->fd, LOCK_UN ); close(db->fd); if ( db->space ) tfree( db->space ); tlog(TL_CRIT,"FDBOpen: can't modify header: %s", strerror(errno)); return FDB_ERROR; } } return FDB_OK; } int FDBClose(FDB *db) { if ( !db->readonly) { FDBHeader header; memset(&header,0,sizeof(FDBHeader)); if ( db->listcur ) { FDBFreeSpace *ptr; FDBVacuumFreeSpace(db); header.lenfreespace = sizeof(FDBFreeSpace)*db->listcur; ptr = findFreeSpace( db, header.lenfreespace ); if ( ptr ) { header.freespace = ptr->offset; if ( lseek(db->fd, ptr->offset, SEEK_SET) != ptr->offset ) tlog(TL_CRIT|TL_EXIT,"FDBClose: lseek failed: %s", strerror(errno)); } else { if ( (header.freespace = lseek(db->fd, 0, SEEK_END)) < 0 ) tlog(TL_CRIT|TL_EXIT,"FDBClose: lseek failed: %s", strerror(errno)); header.lenfreespace += sizeof(FDBFreeSpace); addFreeSpace(db, header.freespace, header.lenfreespace); } if ( write(db->fd, db->space, header.lenfreespace) != header.lenfreespace ) tlog(TL_CRIT|TL_EXIT,"FDBClose: write failed: %s", strerror(errno)); } header.isopened=0; if ( lseek(db->fd,0,SEEK_SET)!=0 || write(db->fd, &header, sizeof(FDBHeader)) != sizeof(FDBHeader) || fsync(db->fd)) tlog(TL_CRIT|TL_EXIT,"FDBClose: header write failed: %s", strerror(errno)); } flock( db->fd, LOCK_UN ); close(db->fd); if ( db->space ) tfree( db->space ); return FDB_OK; } static int readLen(FDB *db, off_t offset, size_t *size) { if ( lseek(db->fd,offset,SEEK_SET)!=offset) return FDB_ERROR; if ( read(db->fd,size,sizeof(size_t)) != sizeof(size_t) ) return FDB_ERROR; return FDB_OK; } int FDBDelete(FDB *db, off_t offset, size_t length) { if ( db->readonly ) return FDB_ERROR; if ( length==0 ) if ( readLen(db, offset, &length) != FDB_OK ) return FDB_ERROR; addFreeSpace(db, offset, PTRALIGN(length)); return FDB_OK; } int FDBGet(FDB *db, off_t offset, size_t length, FDBRecord **record) { size_t rc; *record=NULL; if ( offset < sizeof(FDBHeader) ) return FDB_ERROR; if ( length==0 ) if ( readLen(db, offset, &length) != FDB_OK ) return FDB_ERROR; *record = (FDBRecord*)tmalloc( length ); if ( lseek(db->fd,offset,SEEK_SET)!=offset) return FDB_ERROR; if ( (rc=read(db->fd,*record,length)) != length ) { (*record)->length = rc; tlog(TL_CRIT,"FDBGet: read (%d bytes) less than needed (%d bytes): %s", rc, length, strerror(errno)); return FDB_INCORRECT; } if ( (*record)->length != length ) { tlog(TL_ALARM, "FDBGet: wrong length in opts: %d bytes and %d bytes really", length, (*record)->length); if ( (*record)->length > length ) { rc = (*record)->length; tfree( *record ); return FDBGet(db, offset, rc, record); } } return FDB_OK; } int FDBPut(FDB *db, FDBRecord *record, off_t *offset ) { FDBFreeSpace *ptr; size_t aligned; if ( db->readonly ) return FDB_ERROR; aligned = PTRALIGN(record->length); ptr = findFreeSpace( db, aligned ); if ( ptr ) { *offset = ptr->offset; ptr->length -= record->length; if ( ptr->length == 0 ) { if ( (ptr - db->space) + 1 != db->listcur ) memmove(ptr, ptr+1, (db->listcur - (ptr - db->space) + 1) * sizeof(FDBFreeSpace)); db->listcur--; } else ptr->offset += record->length; if ( lseek(db->fd, *offset, SEEK_SET) != *offset ) tlog(TL_CRIT|TL_EXIT,"FDBPut: lseek failed: %s", strerror(errno)); } else { if ( (*offset = lseek(db->fd, 0, SEEK_END)) < 0 ) tlog(TL_CRIT|TL_EXIT,"FDBPut: lseek failed: %s", strerror(errno)); } if ( write(db->fd, record, record->length) != record->length ) tlog(TL_CRIT|TL_EXIT,"FDBPut: write failed: %s", strerror(errno)); if ( record->length != aligned ) { char buf[] = {0, 0, 0, 0, 0, 0, 0, 0}; if ( write(db->fd, buf, aligned - record->length) != (aligned - record->length) ) tlog(TL_CRIT|TL_EXIT,"FDBPut: write failed: %s", strerror(errno)); } return FDB_OK; }