#define _GNU_SOURCE #include #include #include #include #include #include #include #include "mstorage.h" #ifndef PAGE_SIZE #define PAGE_SIZE 4096 #endif #define PAGEMASK ((PAGE_SIZE)-1) unsigned long mstorage_increment=4*PAGE_SIZE; /* Sadly, mremap is only available on Linux */ /* Please petition your congressman^Woperating system vendor to include it! */ ssize_t mstorage_reserve(mstorage_t* p,size_t n) { if (p->mapped-p->usedroot) { /* nothing allocated. mmap /dev/zero */ char* tmp; need=(n|PAGEMASK)+1; if (need==0) // integer overflow return -1; // #intof1 if ((ssize_t)need < 0) return -1; // #inttr1 #ifdef MREMAP_MAYMOVE #ifdef MAP_ANONYMOUS if ((tmp=mmap(0,need,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0))==MAP_FAILED) return -1; // #mmap1 #else int fd=open("/dev/zero",O_RDWR); if (fd<0) return -1; tmp=mmap(0,need,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0); close(fd); if (tmp==MAP_FAILED) return -1; #endif #else if (!(tmp=malloc(need))) return -1; #endif p->root=tmp; p->mapped=need; p->used=0; } else { need = p->used+n; if (need < n) // integer overflow return -1; // #intof2 need=(need|PAGEMASK)+1+mstorage_increment; if (need <= mstorage_increment) // integer overflow return -1; // #intof3 if ((ssize_t)need < 0) return -1; // #inttr2 char* tmp; #ifdef MREMAP_MAYMOVE tmp=mremap(p->root,p->mapped,need,MREMAP_MAYMOVE); if (tmp==MAP_FAILED) return -1; // #mmap2 #else if (p->fd==-1) { tmp=realloc(p->root,need); if (!tmp) return -1; } else { munmap(p->root,p->used); p->root=0; tmp=mmap(0,need,PROT_READ|PROT_WRITE,MAP_SHARED,p->fd,0); if (tmp==MAP_FAILED) { tmp=mmap(0,p->used,PROT_READ|PROT_WRITE,MAP_SHARED,p->fd,0); /* this should never fail, because we mmap exactly as much as we * had mmapped previously. We need to munmap before doing the * new mmap, though, because we may run into the address space * limit too early on 32-bit systems with lots of RAM */ if (tmp!=MAP_FAILED) p->root=tmp; // fail but leave p in usable state return -1; } } #endif p->root=tmp; } if (p->fd!=-1) { /* slight complication if the storage is file based: we need to * make sure the file size is extended, or the byte_copy will * yield a bus error. */ if (ftruncate(p->fd,need)==-1) return -1; // #trunc } p->mapped=need; } ssize_t l=p->used; p->used+=n; return l; } ssize_t mstorage_add(mstorage_t* p,const char* s,size_t n) { ssize_t l = mstorage_reserve(p,n); if (l>=0) memcpy(p->root+l,s,n); return l; } #ifdef UNITTEST #undef UNITTEST #include #include #include #include #include "mstorage_init.c" #include "mstorage_unmap.c" int main() { mstorage_t m; mstorage_init(&m); // test munmap fail case in mstorage_unmap m.root=(void*)1; m.mapped=4096; assert(mstorage_unmap(&m) == -1); mstorage_init(&m); // regular success cases assert(mstorage_add(&m,"foo",3) == 0); assert(mstorage_add(&m,"bar",3) == 3); assert(mstorage_add(&m,"foo",3) == 6); assert(m.mapped >= 9); assert(!memcmp(m.root,"foobarfoo",9)); char* tmp=calloc(PAGE_SIZE,2); assert(mstorage_add(&m,tmp,PAGE_SIZE*2) == 9); // now the failure cases assert(mstorage_unmap(&m) == 0); assert(mstorage_add(&m,"foobarbaz",9) == 0); assert(mstorage_add(&m, "foo", (size_t)-1) == -1); // #intof2 assert(mstorage_add(&m, "foo", (size_t)-1-4096) == -1); // #intof3 assert(mstorage_add(&m, "foo", (size_t)-1-mstorage_increment*2) == -1); // #inttr2 assert(mstorage_add(&m, "foo", ((size_t)-1)/2-mstorage_increment*2) == -1); // #mmap2 assert(mstorage_unmap(&m) == 0); assert(mstorage_add(&m, "foo", (size_t)-1) == -1); // #intof1 assert(mstorage_add(&m, "foo", (size_t)-1-4096) == -1); // #inttr1 assert(mstorage_add(&m, "foo", ((size_t)-1)/2-4096) == -1); // #mmap1 assert(mstorage_unmap(&m) == 0); assert((m.fd=open("Makefile",O_RDONLY)) != -1); assert(mstorage_add(&m, tmp, PAGE_SIZE) == -1); // #trunc assert(mstorage_unmap(&m) == -1); // drive-by 100% coverage :) free(tmp); return 0; } #endif