diff --git a/lib/falloc.c b/lib/falloc.c new file mode 100644 index 000000000..3f6ac149f --- /dev/null +++ b/lib/falloc.c @@ -0,0 +1,202 @@ +#include +#include +#include + +#include "falloc.h" + +#define FA_MAGIC 0x02050920 + +typedef unsigned int u32; /* this could be wrong someday */ + +struct faFileHeader{ + u32 magic; + u32 firstFree; +}; + +/* the free list is kept *sorted* to keep fragment compaction fast */ + +struct faBlock { + u32 isFree; + u32 next; /* only meaningful if free */ + u32 prev; /* only meaningful if free */ + u32 size; +}; + +/* flags here is the same as for open(2) - NULL returned on error */ +faFile faOpen(char * path, int flags, int perms) { + struct faFileHeader newHdr; + struct faFile_s fas; + faFile fa; + off_t end; + + if (flags & O_WRONLY) { + return NULL; + } + if (flags & O_RDWR) { + fas.readOnly = 0; + } else { + fas.readOnly = 1; + } + + fas.fd = open(path, flags, perms); + if (fas.fd < 0) return NULL; + + /* is this file brand new? */ + end = lseek(fas.fd, 0, SEEK_END); + if (!end) { + newHdr.magic = FA_MAGIC; + newHdr.firstFree = 0; + if (write(fas.fd, &newHdr, sizeof(newHdr)) != sizeof(newHdr)) { + close(fas.fd); + return NULL; + } + fas.firstFree = 0; + } + else { + if (read(fas.fd, &newHdr, sizeof(newHdr)) != sizeof(newHdr)) { + close(fas.fd); + return NULL; + } + if (newHdr.magic != FA_MAGIC) { + close(fas.fd); + return NULL; + } + fas.firstFree = newHdr.firstFree; + } + + fa = malloc(sizeof(*fa)); + if (fa) *fa = fas; + + return fa; +} + +unsigned int faAlloc(faFile fa, unsigned int size) { /* returns 0 on failure */ + u32 nextFreeBlock; + u32 bestFreeBlock = 0; + u32 bestFreeSize = 0; + unsigned int newBlock; + struct faBlock block, prevBlock, nextBlock; + int failed = 0; + + /* Make sure they are allocing multiples of four bytes. It'll keep + things smoother that way */ + (size % 4) ? size += (4 - (size % 4)) : 0; + + /* first, look through the free list for the best fit */ + nextFreeBlock = fa->firstFree; + while (nextFreeBlock) { + if (lseek(fa->fd, nextFreeBlock, SEEK_SET) < 0) return 0; + if (read(fa->fd, &block, sizeof(block)) != sizeof(block)) return 0; + + if (block.size >= size) { + if (bestFreeBlock) { + if (block.size < bestFreeSize) { + bestFreeSize = block.size; + bestFreeBlock = nextFreeBlock; + } + } else { + bestFreeSize = block.size; + bestFreeBlock = nextFreeBlock; + } + } + + nextFreeBlock = block.next; + } + + if (bestFreeBlock) { + if (lseek(fa->fd, bestFreeBlock, SEEK_SET) < 0) return 0; + if (read(fa->fd, &block, sizeof(block)) != sizeof(block)) + return 0; + + /* update the free list chain */ + if (lseek(fa->fd, block.prev, SEEK_SET) < 0) return 0; + if (read(fa->fd, &prevBlock, sizeof(prevBlock)) != sizeof(prevBlock)) + return 0; + if (lseek(fa->fd, block.next, SEEK_SET) < 0) return 0; + if (read(fa->fd, &nextBlock, sizeof(nextBlock)) != sizeof(nextBlock)) + return 0; + + prevBlock.next = block.next; + nextBlock.prev = block.prev; + + if (lseek(fa->fd, block.prev, SEEK_SET) < 0) return 0; + if (write(fa->fd, &prevBlock, sizeof(prevBlock)) != sizeof(prevBlock)) + return 0; + + if (lseek(fa->fd, block.next, SEEK_SET) < 0) { + failed = 1; + } else { + if (write(fa->fd, &nextBlock, sizeof(nextBlock)) != + sizeof(nextBlock)) { + failed = 1; + } + } + + if (failed) { + /* try and restore the "prev" block, this won't be a complete + disaster */ + prevBlock.next = bestFreeBlock; + + lseek(fa->fd, block.prev, SEEK_SET); + write(fa->fd, &prevBlock, sizeof(prevBlock)); + + return 0; + } + + block.isFree = 0; /* mark it as used */ + block.prev = block.next = 0; + + /* At some point, we should split this block into two if it's + bigger then the amount that's being allocated. Any space left + at the end of this block is wasted right now ***/ + + if (lseek(fa->fd, bestFreeBlock, SEEK_SET) < 0) { + failed = 1; + } else { + if (write(fa->fd, &block, sizeof(block)) != sizeof(block)) { + failed = 1; + } + } + + if (failed) { + /* this space is gone :-( this really shouldn't ever happen. It + won't result in furthur date coruption though, so lets not + make it worse! */ + return 0; + } + + newBlock = bestFreeBlock; + } else { + char * space; + + /* make a new block */ + if (lseek(fa->fd, 0, SEEK_END) < 0) return 0; + newBlock = lseek(fa->fd, 0, SEEK_CUR); + + space = calloc(1, size); + if (!space) return 0; + + block.next = block.prev = 0; + block.size = size; + block.isFree = 0; + + if (write(fa->fd, &block, sizeof(block)) != sizeof(block)) { + free(space); + return 0; + } + if (write(fa->fd, space, size) != size) { + free(space); + return 0; + } + free(space); + } + + return newBlock + sizeof(block); +} + +void faFree(faFile fa, unsigned int offset); + +void faClose(faFile fa) { + close(fa->fd); + free(fa); +} diff --git a/lib/falloc.h b/lib/falloc.h new file mode 100644 index 000000000..c6339993a --- /dev/null +++ b/lib/falloc.h @@ -0,0 +1,20 @@ +#ifndef H_FALLOC +#define H_FALLOC + +/* File space allocation routines. Best fit allocation is used, free blocks + are compacted. Minimal fragmentation is more important then speed. This + uses 32 bit offsets on all platforms and should be byte order independent */ + +typedef struct faFile_s { + int fd; + int readOnly; + unsigned int firstFree; +} * faFile; + +/* flags here is the same as for open(2) - NULL returned on error */ +faFile faOpen(char * path, int flags, int perms); +unsigned int faAlloc(faFile fa, unsigned int size); /* returns 0 on failure */ +void faFree(faFile fa, unsigned int offset); +void faClose(faFile fa); + +#endif H_FALLOC