/*
libundo, an easy to use undo/redo management library
Copyright 1999 Matt Kimball
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include "undoP.h"
#define BLOCK_FLAG_LARGE 0x01
/* Mac OS X patch */
#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
#define MAP_ANONYMOUS MAP_ANON
#endif
#define MEMORY_RAW_SIZE(mem) (*(size_t *)(mem))
#define MEMORY_USED(mem) (MEMORY_RAW_SIZE(mem) & 1)
#define MEMORY_SIZE(mem) (MEMORY_RAW_SIZE(mem) & ~1)
#define MEMORY_SET_SIZE_USED(mem, size, used) \
(MEMORY_RAW_SIZE(mem) = ((size) & ~1) | ((used) ? 1 : 0))
#define MEMORY_NEXT(mem) (MEMORY_OFFSET((mem), MEMORY_SIZE(mem)))
#define MEMORY_BODY(mem) (MEMORY_OFFSET((mem), UNDO_MEMORY_OVERHEAD))
#define FOREACH_SIZED_BLOCK(mem, size, block_ix, block) \
for((block_ix) = 0, (block) = &(mem)->size##_alloc_list[0]; \
(block_ix) < (mem)->size##_alloc_list_count; \
(block_ix)++, (block) = &(mem)->size##_alloc_list[(block_ix)])
#define FOREACH_SMALL_BLOCK(mem, block_ix, block) \
FOREACH_SIZED_BLOCK(mem, small, block_ix, block)
#define FOREACH_LARGE_BLOCK(mem, block_ix, block) \
FOREACH_SIZED_BLOCK(mem, large, block_ix, block)
#define MEMORY_BACKUP_OVERHEAD(mem) \
((mem) = MEMORY_OFFSET((mem), -UNDO_MEMORY_OVERHEAD))
#define BLOCK_END(block) MEMORY_OFFSET((block).mem, (block).size)
#define IN_BLOCK(block, m) ((m) >= (block).mem && (m) < BLOCK_END(block))
#define BLOCK_PAGES(mem, block) ((block).size / mem->pagesize)
#define FOREACH_IN_BLOCK(block, mem) \
for((mem) = (block).mem; \
(mem) != BLOCK_END(block); \
(mem) = MEMORY_NEXT(mem))
#define MAP_NEW_ANON_AT_FLAGS(pos, size, flags) \
(mmap((char *)(pos), (size), \
PROT_READ | PROT_WRITE | PROT_EXEC, \
MAP_PRIVATE | MAP_ANONYMOUS | (flags), 0, 0))
#define MAP_NEW_ANON(size) MAP_NEW_ANON_AT_FLAGS(0, (size), 0)
#define MAP_NEW_ANON_AT(pos, size) \
MAP_NEW_ANON_AT_FLAGS((pos), (size), MAP_FIXED)
#define PAGE_REMAINDER(mem, size) (mem->pagesize - (size))
#define IF_IN_BLOCK_VAR \
unsigned _if_in_block_ix; \
UNDO_BLOCK *_if_in_block
#define IF_IN_SMALL_BLOCK(memory, ptr) \
FOREACH_SMALL_BLOCK((memory), _if_in_block_ix, _if_in_block) \
if(IN_BLOCK(*_if_in_block, (ptr)))
#define IF_IN_LARGE_BLOCK(memory, ptr) \
FOREACH_LARGE_BLOCK((memory), _if_in_block_ix, _if_in_block) \
if((ptr) == _if_in_block->mem)
static void *undo_memory_alloc_small(UNDO_MEMORY *memory, size_t size);
static void *undo_memory_alloc_large(UNDO_MEMORY *memory, size_t size);
static void *undo_memory_alloc_small_block(UNDO_BLOCK *block, size_t size);
static void undo_memory_block_coalesce_free(UNDO_BLOCK *block);
static void *undo_memory_alloc_new_small_block(UNDO_MEMORY *mem, size_t size);
static void *undo_memory_new_block(UNDO_BLOCK **block_list, unsigned *length,
size_t size);
static int undo_memory_new_block_record(UNDO_BLOCK **block_list, unsigned *len,
void *mem, size_t size);
static int undo_memory_delete_block(UNDO_BLOCK **block_list, unsigned *length,
unsigned index);
static void undo_stream_destroy(UNDO_MEMORY_STREAM *stream);
static size_t undo_stream_read(UNDO_MEMORY_STREAM *stream, size_t offset,
void *mem, size_t size);
static size_t undo_stream_block_write(size_t offset, void *mem, size_t size,
size_t *pos,
UNDO_BLOCK *block, unsigned flags);
static void undo_memory_clear(UNDO_MEMORY *memory);
static int undo_memory_add_blocks_from_stream(UNDO_MEMORY *memory,
UNDO_MEMORY_STREAM *stream);
static void undo_memory_block_header_record(UNDO_MEMORY *memory,
UNDO_MEMORY_STREAM_BLOCK_HEADER *header);
UNDO_MEMORY *undo_memory_new(void) {
UNDO_MEMORY *mem;
mem = NEW(UNDO_MEMORY);
if (mem) {
mem->pagesize = getpagesize();
}
return mem;
}
int undo_memory_destroy(UNDO_MEMORY *memory) {
if(memory == NULL)
return UNDO_BADPARAM;
undo_memory_clear(memory);
free(memory);
UNDO_SUCCESS;
}
size_t undo_memory_used(UNDO_MEMORY *memory) {
size_t used;
unsigned block_ix;
UNDO_BLOCK *block;
used = 0;
FOREACH_SMALL_BLOCK(memory, block_ix, block) {
void *mem;
FOREACH_IN_BLOCK(*block, mem) {
if(MEMORY_USED(mem)) {
used += MEMORY_SIZE(mem);
}
}
}
FOREACH_LARGE_BLOCK(memory, block_ix, block) {
used += block->size;
}
return used;
}
void *undo_memory_alloc(UNDO_MEMORY *memory, size_t size) {
while(size & (sizeof(size_t) - 1))
size++;
if(size < memory->pagesize - UNDO_MEMORY_OVERHEAD * 2) {
return undo_memory_alloc_small(memory, size);
} else {
return undo_memory_alloc_large(memory, size);
}
}
int undo_memory_free(UNDO_MEMORY *memory, void *alloc) {
IF_IN_BLOCK_VAR;
IF_IN_SMALL_BLOCK(memory, alloc) {
MEMORY_BACKUP_OVERHEAD(alloc);
MEMORY_SET_SIZE_USED(alloc, MEMORY_SIZE(alloc), 0);
UNDO_SUCCESS;
}
IF_IN_LARGE_BLOCK(memory, alloc) {
return undo_memory_delete_block(&memory->large_alloc_list,
&memory->large_alloc_list_count,
_if_in_block_ix);
}
return UNDO_BADPARAM;
}
size_t undo_memory_size(UNDO_MEMORY *memory, void *alloc) {
IF_IN_BLOCK_VAR;
IF_IN_SMALL_BLOCK(memory, alloc) {
MEMORY_BACKUP_OVERHEAD(alloc);
return MEMORY_SIZE(alloc) - UNDO_MEMORY_OVERHEAD;
}
IF_IN_LARGE_BLOCK(memory, alloc) {
return _if_in_block->size;
}
return 0;
}
unsigned undo_memory_pages_used(UNDO_MEMORY *memory) {
unsigned block_ix;
UNDO_BLOCK *block;
unsigned pages;
pages = 0;
FOREACH_SMALL_BLOCK(memory, block_ix, block) {
pages += BLOCK_PAGES(memory, *block);
}
FOREACH_LARGE_BLOCK(memory, block_ix, block) {
pages += BLOCK_PAGES(memory, *block);
}
return pages;
}
UNDO_MEMORY_STREAM *undo_memory_stream(UNDO_MEMORY *memory) {
UNDO_MEMORY_STREAM *stream;
stream = NEW(UNDO_MEMORY_STREAM);
if(stream == NULL)
return NULL;
stream->implementation = memory;
stream->destroy = undo_stream_destroy;
stream->read = undo_stream_read;
return (UNDO_MEMORY_STREAM *)stream;
}
int undo_memory_set(UNDO_MEMORY *memory, UNDO_MEMORY_STREAM *stream) {
undo_memory_clear(memory);
return undo_memory_add_blocks_from_stream(memory, stream);
}
static void *undo_memory_alloc_small(UNDO_MEMORY *memory, size_t size) {
unsigned block_ix;
UNDO_BLOCK *block;
FOREACH_SMALL_BLOCK(memory, block_ix, block) {
void *mem;
mem = undo_memory_alloc_small_block(block, size);
if(mem != NULL) {
return mem;
}
}
return undo_memory_alloc_new_small_block(memory, size);
}
static void *undo_memory_alloc_large(UNDO_MEMORY *memory, size_t size) {
size_t page_fraction;
page_fraction = size & (memory->pagesize - 1);
if(page_fraction) {
size += PAGE_REMAINDER(memory, page_fraction);
}
return undo_memory_new_block(&memory->large_alloc_list,
&memory->large_alloc_list_count,
size);
}
static void *undo_memory_alloc_small_block(UNDO_BLOCK *block, size_t size) {
void *mem;
size_t new_size;
undo_memory_block_coalesce_free(block);
new_size = size + UNDO_MEMORY_OVERHEAD;
FOREACH_IN_BLOCK(*block, mem) {
if(!MEMORY_USED(mem) && MEMORY_SIZE(mem) >= new_size) {
if(MEMORY_SIZE(mem) == new_size) {
MEMORY_SET_SIZE_USED(mem, new_size, 1);
} else {
size_t old_size;
void *next;
old_size = MEMORY_SIZE(mem);
MEMORY_SET_SIZE_USED(mem, new_size, 1);
next = MEMORY_NEXT(mem);
MEMORY_SET_SIZE_USED(next, old_size - new_size, 0);
}
return MEMORY_BODY(mem);
}
}
return NULL;
}
static void undo_memory_block_coalesce_free(UNDO_BLOCK *block) {
void *last, *mem;
last = NULL;
FOREACH_IN_BLOCK(*block, mem) {
if(last == NULL) {
last = mem;
continue;
}
if(!MEMORY_USED(last) && !MEMORY_USED(mem)) {
MEMORY_SET_SIZE_USED(last,
MEMORY_SIZE(last) + MEMORY_SIZE(mem),
0);
continue;
}
last = mem;
}
}
static void *undo_memory_alloc_new_small_block(UNDO_MEMORY *mem, size_t size) {
void *page, *next;
page = undo_memory_new_block(&mem->small_alloc_list,
&mem->small_alloc_list_count,
mem->pagesize);
if(page == NULL) {
return NULL;
}
MEMORY_SET_SIZE_USED(page, size + UNDO_MEMORY_OVERHEAD, 1);
next = MEMORY_NEXT(page);
MEMORY_SET_SIZE_USED(next,
PAGE_REMAINDER(mem, size + UNDO_MEMORY_OVERHEAD), 0);
return MEMORY_BODY(page);
}
static void *undo_memory_new_block(UNDO_BLOCK **block_list, unsigned *len,
size_t size) {
void *mem;
mem = MAP_NEW_ANON(size);
if(mem == MAP_FAILED)
return NULL;
if(undo_memory_new_block_record(block_list, len, mem, size)) {
munmap((char *)mem, size);
return NULL;
}
return mem;
}
static int undo_memory_new_block_record(UNDO_BLOCK **block_list, unsigned *len,
void *mem, size_t size) {
UNDO_BLOCK *new_block_list;
new_block_list = (UNDO_BLOCK *)realloc(*block_list,
(*len + 1) * sizeof(UNDO_BLOCK));
if(new_block_list == NULL) {
return UNDO_NOMEM;
}
*block_list = new_block_list;
(*block_list)[*len].mem = mem;
(*block_list)[*len].size = size;
(*len)++;
UNDO_SUCCESS;
}
static int undo_memory_delete_block(UNDO_BLOCK **block_list, unsigned *length,
unsigned index) {
UNDO_BLOCK *block;
UNDO_BLOCK *new_block_list;
block = &(*block_list)[index];
munmap((char *)(block->mem), block->size);
if(*length == 1) {
free(*block_list);
new_block_list = NULL;
} else {
memcpy(&(*block_list)[index], &(*block_list)[index + 1],
sizeof(UNDO_BLOCK) * (*length - 1 - index));
new_block_list = (UNDO_BLOCK *) realloc(*block_list,
sizeof(UNDO_BLOCK) * (*length - 1));
if(new_block_list == NULL) {
(*length)--;
UNDO_SUCCESS;
}
}
*block_list = new_block_list;
(*length)--;
UNDO_SUCCESS;
}
static void undo_stream_destroy(UNDO_MEMORY_STREAM *stream) {
free(stream);
}
static size_t undo_stream_read(UNDO_MEMORY_STREAM *stream, size_t offset,
void *mem, size_t size) {
unsigned block_ix;
UNDO_BLOCK *block;
UNDO_MEMORY *memory;
size_t ret;
size_t pos;
unsigned flags;
memory = (UNDO_MEMORY *)stream->implementation;
ret = 0;
pos = 0;
flags = 0;
FOREACH_SMALL_BLOCK(memory, block_ix, block) {
ret += undo_stream_block_write(offset, mem, size,
&pos, block, flags);
}
flags = BLOCK_FLAG_LARGE;
FOREACH_LARGE_BLOCK(memory, block_ix, block) {
ret += undo_stream_block_write(offset, mem, size,
&pos, block, flags);
}
return ret;
}
static size_t undo_stream_block_write(size_t offset, void *mem, size_t size,
size_t *pos,
UNDO_BLOCK *block, unsigned flags) {
size_t ret;
ret = 0;
ret += undo_memory_stream_write(offset, mem, size,
pos, &block->mem, sizeof(void *));
ret += undo_memory_stream_write(offset, mem, size,
pos, &block->size, sizeof(size_t));
ret += undo_memory_stream_write(offset, mem, size,
pos, &flags, sizeof(unsigned));
ret += undo_memory_stream_write(offset, mem, size,
pos, block->mem, block->size);
return ret;
}
static void undo_memory_clear(UNDO_MEMORY *memory) {
unsigned block_ix;
UNDO_BLOCK *block;
FOREACH_SMALL_BLOCK(memory, block_ix, block) {
munmap((char *) block->mem, block->size);
}
memory->small_alloc_list = NULL;
memory->small_alloc_list_count = 0;
FOREACH_LARGE_BLOCK(memory, block_ix, block) {
munmap((char *) block->mem, block->size);
}
memory->large_alloc_list = NULL;
memory->large_alloc_list_count = 0;
}
static int undo_memory_add_blocks_from_stream(UNDO_MEMORY *memory,
UNDO_MEMORY_STREAM *stream) {
size_t pos, num_read;
UNDO_MEMORY_STREAM_BLOCK_HEADER header;
pos = 0;
do {
num_read = undo_memory_stream_read_header(stream, pos, &header);
if(num_read != STREAM_SERIALIZED_BLOCK_HEADER_SIZE) {
UNDO_SUCCESS;
}
pos += num_read;
if(MAP_NEW_ANON_AT(header.addr, header.size) == MAP_FAILED) {
return UNDO_NOMEM;
}
undo_memory_block_header_record(memory, &header);
num_read = stream->read(stream, pos, header.addr, header.size);
pos += num_read;
} while(1);
}
static void undo_memory_block_header_record(UNDO_MEMORY *memory,
UNDO_MEMORY_STREAM_BLOCK_HEADER *header) {
UNDO_BLOCK **undo_block_list;
unsigned *undo_block_list_count;
if(header->flags & BLOCK_FLAG_LARGE) {
undo_block_list = &memory->large_alloc_list;
undo_block_list_count = &memory->large_alloc_list_count;
} else {
undo_block_list = &memory->small_alloc_list;
undo_block_list_count = &memory->small_alloc_list_count;
}
undo_memory_new_block_record(undo_block_list, undo_block_list_count,
header->addr, header->size);
}
syntax highlighted by Code2HTML, v. 0.9.1