Fix incorrect masking in swp_getmapping()
[hed.git] / libhed / swap.c
blobfa9740a69081de0addb1d5fadb85a5be3f4ecd52
1 /*
2 * hed - Hexadecimal editor
3 * Copyright (C) 2011 Petr Tesarik <petr@tesarici.cz>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 /* Feature macros needed for:
20 * - pread, pwrite
22 #define _GNU_SOURCE
24 #include <config.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <stdlib.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <unistd.h>
33 #include <assert.h>
35 #include <util/lists.h>
36 #include "swap.h"
37 #include "access.h"
39 #ifdef HED_CONFIG_SWAP
41 #undef SWAP_DEBUG
42 #ifdef SWAP_DEBUG
43 # include <stdio.h>
44 # define SDEBUG(x...) fprintf(stderr, x)
45 #else
46 # define SDEBUG(x...)
47 #endif
49 /* A swap file structure:
51 * 0 File header
52 * SWAP_BLOCK_SIZE 1st mapping
53 * ... Allocated blocks
54 * SWAP_BLOCK_SIZE + SWP_MAPEXTENT 2nd mapping
55 * ...
58 struct swp_block {
59 struct list_head list;
60 hed_off_t pos;
61 void **paddr;
62 struct list_head alloclist;
63 struct list_head freelist;
66 struct swp_alloc {
67 struct list_head list;
68 unsigned off; /* Offset from block beginning */
69 unsigned size; /* Size is max SWAP_BLOCK_SIZE */
72 #define SWP_MAPSIZE ((SWAP_BLOCK_SIZE) / sizeof(void*))
73 #define SWP_MAPMASK ((signed long)SWP_MAPSIZE - 1)
74 #define SWP_MAPEXTENT ((SWAP_BLOCK_SIZE) * (SWP_MAPSIZE))
75 #define SWP_MAPEXTSHIFT (2*SWAP_BLOCK_SHIFT - bitsize(sizeof(void*)))
76 #define SWP_ALLOC_ALIGN (8)
78 static struct swp_block *swp_bappend(struct swp_file *swp);
80 #ifdef SWAP_DEBUG
82 static void
83 swp_dump(struct swp_file *swp)
85 struct swp_block *block;
86 struct swp_alloc *a;
88 fputs("-- swap file dump --\n", stderr);
90 fprintf(stderr, "size: %ld\n", (long) swp->size);
92 fputs("free blocks:\n", stderr);
93 list_for_each_entry(block, &swp->freeblocks, list) {
94 fprintf(stderr, "%p: [%p] @freed %ld\n",
95 block, block->paddr, (long) block->pos);
96 assert(list_empty(&block->alloclist));
97 assert(list_empty(&block->freelist));
100 fputs("used blocks:\n", stderr);
101 list_for_each_entry(block, &swp->usedblocks, list) {
102 fprintf(stderr, "%p: [%p] @%p %ld\n",
103 block, block->paddr, *block->paddr,
104 (long) block->pos);
106 if (!list_empty(&block->alloclist))
107 fputs(" allocations:\n", stderr);
108 list_for_each_entry(a, &block->alloclist, list)
109 fprintf(stderr, " %p: 0x%x + %u\n",
110 a, a->off, a->size);
112 if (!list_empty(&block->freelist))
113 fputs(" free list: \n", stderr);
114 list_for_each_entry (a, &block->freelist, list)
115 fprintf(stderr, " %p: 0x%x + %u\n",
116 a, a->off, a->size);
119 fputs("-- swap file dump end--\n", stderr);
122 #else /* SWAP_DEBUG */
124 #define swp_dump(s) do{} while(0)
126 #endif
128 #ifdef HED_CONFIG_SWAP_MMAP
130 #include <sys/mman.h>
132 #define SWP_PAGE_INVALID MAP_FAILED
134 static inline void
135 swp_init_flags(struct swp_file *swp, int flags)
137 swp->mmap_prot = (flags & O_ACCMODE) == O_RDWR
138 ? PROT_READ | PROT_WRITE
139 : PROT_READ;
140 swp->mmap_flags = swp->fd < 0
141 ? MAP_PRIVATE | MAP_ANONYMOUS
142 : MAP_SHARED;
145 /* Set the file size. */
146 static int
147 swp_setsize(struct swp_file *swp)
149 return swp->fd >= 0
150 ? ftruncate(swp->fd, swp->size + SWAP_BLOCK_SIZE)
151 : 0;
154 /* Allocate data for a file block */
155 static void *
156 swp_page_alloc(struct swp_file *swp, hed_off_t pos)
158 return mmap(NULL, SWAP_BLOCK_SIZE,
159 swp->mmap_prot, swp->mmap_flags,
160 swp->fd, pos);
163 /* De-allocate data for a file block */
164 static inline void
165 swp_page_free(struct swp_file *swp, void *ptr)
167 munmap(ptr, SWAP_BLOCK_SIZE);
170 static inline size_t
171 swp_page_read(struct swp_file *swp, void *ptr, hed_off_t pos)
173 return 0;
176 static inline size_t
177 swp_page_write(struct swp_file *swp, void *ptr, hed_off_t pos)
179 return msync(ptr, SWAP_BLOCK_SIZE, MS_ASYNC);
182 #else /* HED_CONFIG_SWAP_MMAP */
184 #define SWP_PAGE_INVALID NULL
186 static inline void
187 swp_init_flags(struct swp_file *swp, int flags)
191 /* Set the file size. */
192 static inline int
193 swp_setsize(struct swp_file *swp)
195 return 0;
198 /* Allocate data for a file block */
199 static inline void *
200 swp_page_alloc(struct swp_file *swp, hed_off_t pos)
202 return malloc(SWAP_BLOCK_SIZE);
205 /* De-allocate data for a file block */
206 static inline void
207 swp_page_free(struct swp_file *swp, void *ptr)
209 free(ptr);
212 static inline size_t
213 swp_page_read(struct swp_file *swp, void *ptr, hed_off_t pos)
215 return SWAP_BLOCK_SIZE - pread(swp->fd, ptr, SWAP_BLOCK_SIZE, pos);
218 static inline size_t
219 swp_page_write(struct swp_file *swp, void *ptr, hed_off_t pos)
221 return SWAP_BLOCK_SIZE - pwrite(swp->fd, ptr, SWAP_BLOCK_SIZE, pos);
224 #endif /* HED_CONFIG_SWAP_MMAP */
226 static inline int
227 swp_close(struct swp_file *swp)
229 int ret = swp->fd >= 0
230 ? close(swp->fd)
231 : 0;
232 swp->fd = -1;
233 return ret;
236 static struct swp_file *
237 swp_init(const char *name, int openflags)
239 struct swp_file *swp = calloc(1, sizeof(struct swp_file));
240 if (swp) {
241 INIT_LIST_HEAD(&swp->freeblocks);
242 INIT_LIST_HEAD(&swp->usedblocks);
243 swp->fd = open(name, openflags, 0664);
244 swp_init_flags(swp, openflags);
246 return swp;
249 struct swp_file *
250 swp_init_write(const char *name)
252 struct swp_file *swp;
253 struct swp_header *hdr;
255 if (! (swp = swp_init(name, O_RDWR | O_CREAT)) )
256 return NULL;
258 if (swp_setsize(swp))
259 goto fail;
260 hdr = swp_page_alloc(swp, 0);
261 if (hdr == SWP_PAGE_INVALID)
262 goto fail;
263 swp->hdr = hdr;
265 memcpy(hdr->signature, SWAP_SIGNATURE, SIG_LEN);
266 hdr->version = SWAP_VERSION;
267 hdr->offsize = sizeof(hed_off_t);
269 return swp;
271 fail:
272 swp_done(swp);
273 return NULL;
277 swp_done(struct swp_file *swp)
279 struct swp_alloc *cura, *nexta;
280 struct swp_block *curb, *nextb;
281 int ret;
283 /* Free all swap file pages */
284 list_for_each_entry_safe_reverse(curb, nextb, &swp->usedblocks, list) {
285 list_for_each_entry_safe(cura, nexta, &curb->alloclist, list)
286 free(cura);
287 list_for_each_entry_safe(cura, nexta, &curb->freelist, list)
288 free(cura);
289 swp_page_free(swp, *curb->paddr);
290 free(curb);
292 list_for_each_entry_safe(curb, nextb, &swp->freeblocks, list)
293 free(curb);
294 if (swp->maps)
295 free(swp->maps);
296 if (swp->hdr)
297 swp_page_free(swp, swp->hdr);
299 ret = swp_close(swp);
300 free(swp);
301 return ret;
304 /* Get an existing mapping */
305 static void **
306 swp_getmapping(struct swp_file *swp, hed_off_t pos)
308 int mapnum = pos >> SWP_MAPEXTSHIFT;
309 unsigned long mapoff = (pos >> SWAP_BLOCK_SHIFT) & SWP_MAPMASK;
310 assert(mapnum < swp->nmaps);
311 return swp->maps[mapnum] + mapoff;
314 /* Append a new mapping at EOF */
315 static void **
316 swp_appendmapping(struct swp_file *swp)
318 if (swp->size == (swp->nmaps << SWP_MAPEXTSHIFT)) {
319 void ***mapvec;
320 void **newmap;
321 struct swp_block *block;
323 mapvec = realloc(swp->maps,
324 (swp->nmaps + 1) * sizeof(void **));
325 if (!mapvec)
326 return NULL;
327 swp->maps = mapvec;
329 newmap = swp_page_alloc(swp, swp->size + SWAP_BLOCK_SIZE);
330 if (newmap == SWP_PAGE_INVALID)
331 return NULL;
332 swp->maps[swp->nmaps++] = newmap;
334 block = swp_bappend(swp);
335 if (!block) {
336 --swp->nmaps;
337 swp_page_free(swp, newmap);
338 return NULL;
340 *block->paddr = newmap;
342 return swp_getmapping(swp, swp->size);
345 /* Mark a mapping as free */
346 static void
347 swp_freemapping(struct swp_file *swp, void **paddr)
349 *paddr = SWP_PAGE_INVALID;
351 if (swp->size == ((swp->nmaps-1) << SWP_MAPEXTSHIFT) + SWAP_BLOCK_SIZE) {
352 struct swp_block *block;
354 assert(!list_empty(&swp->usedblocks));
355 block = list_entry(swp->usedblocks.prev,
356 struct swp_block, list);
358 assert(*block->paddr == swp->maps[swp->nmaps-1]);
359 assert(block->paddr == *block->paddr);
361 list_del(&block->list);
362 swp_page_free(swp, block->paddr);
363 swp->size -= SWAP_BLOCK_SIZE;
364 swp_setsize(swp);
365 --swp->nmaps;
369 /* Append a page at EOF */
370 static struct swp_block *
371 swp_bappend(struct swp_file *swp)
373 struct swp_block *ret;
374 if (! (ret = malloc(sizeof(struct swp_block))) )
375 goto err;
376 if (! (ret->paddr = swp_appendmapping(swp)) )
377 goto err_free;
379 INIT_LIST_HEAD(&ret->list);
380 ret->pos = swp->size;
381 INIT_LIST_HEAD(&ret->alloclist);
382 INIT_LIST_HEAD(&ret->freelist);
384 swp->size += SWAP_BLOCK_SIZE;
385 if (swp_setsize(swp))
386 goto err_free_map;
387 list_add_tail(&ret->list, &swp->usedblocks);
388 return ret;
390 err_free_map:
391 swp_freemapping(swp, ret->paddr);
392 err_free:
393 free(ret);
394 err:
395 return NULL;
398 static void
399 swp_bfree(struct swp_file *swp, struct swp_block *block)
401 struct swp_block *cur;
403 if (*block->paddr != SWP_PAGE_INVALID)
404 swp_page_free(swp, *block->paddr);
406 list_for_each_entry_reverse(cur, &swp->freeblocks, list) {
407 if (cur->pos < block->pos)
408 break;
410 list_move(&block->list, &cur->list);
412 while (swp->freeblocks.prev != &swp->freeblocks) {
413 struct swp_block *cur = list_entry(swp->freeblocks.prev,
414 struct swp_block, list);
415 if (cur->pos != swp->size - SWAP_BLOCK_SIZE)
416 break;
418 swp->size -= SWAP_BLOCK_SIZE;
419 swp_setsize(swp);
420 swp_freemapping(swp, cur->paddr);
421 list_del(&cur->list);
422 free(cur);
426 static struct swp_block *
427 swp_balloc(struct swp_file *swp)
429 struct swp_block *ret = list_entry(swp->freeblocks.next,
430 struct swp_block, list);
432 if (&ret->list != &swp->freeblocks) {
433 struct swp_block *cur;
434 list_for_each_entry_reverse(cur, &swp->usedblocks, list)
435 if (cur->pos < ret->pos)
436 break;
437 list_move(&ret->list, &cur->list);
438 } else if (! (ret = swp_bappend(swp)) )
439 return NULL;
441 *ret->paddr = swp_page_alloc(swp, ret->pos + SWAP_BLOCK_SIZE);
442 if (*ret->paddr == SWP_PAGE_INVALID) {
443 swp_bfree(swp, ret);
444 return NULL;
447 return ret;
450 static struct swp_alloc *
451 swp_malloc_new(struct swp_file *swp, struct swp_block **pblock)
453 struct swp_block *block;
454 struct swp_alloc *ret;
456 if (! (ret = malloc(sizeof(struct swp_alloc))) )
457 goto fail;
458 if (! (block = swp_balloc(swp)) )
459 goto fail_free;
461 ret->off = 0;
462 ret->size = SWAP_BLOCK_SIZE;
464 *pblock = block;
465 return ret;
467 fail_free:
468 free(ret);
469 fail:
470 return NULL;
473 static struct list_head *
474 freelist_pos(struct swp_block *block, unsigned off)
476 struct swp_alloc *a;
477 list_for_each_entry(a, &block->freelist, list)
478 if (a->off >= off)
479 break;
480 return &a->list;
483 static void
484 swp_split(struct swp_block *block, struct swp_alloc *a, unsigned splitpoint)
486 unsigned remain;
487 struct list_head *pos;
488 struct swp_alloc *next, *prev;
490 splitpoint += SWP_ALLOC_ALIGN - 1;
491 splitpoint -= splitpoint % SWP_ALLOC_ALIGN;
492 remain = a->size - splitpoint;
494 if (!remain)
495 return;
497 /* Find the neighbours */
498 pos = freelist_pos(block, a->off);
499 if (pos != &block->freelist) {
500 next = list_entry(pos, struct swp_alloc, list);
501 if (next->off != a->off + a->size)
502 next = NULL;
503 } else
504 next = NULL;
506 pos = pos->prev;
507 if (pos != &block->freelist) {
508 prev = list_entry(pos, struct swp_alloc, list);
509 if (prev->off + prev->size != a->off)
510 prev = NULL;
511 } else
512 prev = NULL;
514 if (prev && next) {
515 if (!splitpoint) {
516 prev->size += next->size;
517 list_del(&next->list);
518 free(next);
519 } else if (prev->size < next->size)
520 prev = NULL;
523 if (prev) {
524 prev->size += remain;
525 } else if (next) {
526 next->off -= remain;
527 next->size += remain;
528 } else if (splitpoint) {
529 if (! (next = malloc(sizeof(struct swp_alloc))) )
530 return;
531 next->off = a->off + splitpoint;
532 next->size = remain;
533 list_add(&next->list, pos);
534 } else {
535 list_move(&a->list, pos);
536 return;
539 a->size = splitpoint;
540 if (!splitpoint) {
541 list_del(&a->list);
542 free(a);
546 static struct swp_alloc *
547 find_free_alloc(struct swp_file *swp, size_t size, struct swp_block **pblock,
548 struct list_head *endnode)
550 struct list_head *blknode;
552 assert(size <= SWAP_BLOCK_SIZE);
554 blknode = swp->usedblocks.next;
555 while (blknode != endnode) {
556 struct swp_block *block =
557 list_entry(blknode, struct swp_block, list);
558 struct swp_alloc *a;
559 list_for_each_entry(a, &block->freelist, list) {
560 if (a->size >= size) {
561 list_del(&a->list);
562 *pblock = block;
563 return a;
566 blknode = blknode->next;
569 return NULL;
572 void *
573 swp_malloc(struct swp_file *swp, size_t size)
575 struct swp_block *block;
576 struct swp_alloc *a;
578 a = find_free_alloc(swp, size, &block, &swp->usedblocks);
579 if (!a && ! (a = swp_malloc_new(swp, &block)) )
580 return NULL;
582 list_add(&a->list, &block->alloclist);
584 swp_split(block, a, size);
586 SDEBUG("Allocated %ld bytes at %p\n",
587 (long)size, *block->paddr + a->off);
588 swp_dump(swp);
590 return *block->paddr + a->off;
593 void *
594 swp_zalloc(struct swp_file *swp, size_t size)
596 void *ret = swp_malloc(swp, size);
597 if (ret)
598 memset(ret, 0, size);
599 return ret;
602 static void
603 do_free(struct swp_file *swp, struct swp_block *block, struct swp_alloc *a)
605 swp_split(block, a, 0);
607 if (list_empty(&block->alloclist)) {
608 struct swp_alloc *cur, *next;
609 list_for_each_entry_safe(cur, next, &block->freelist, list)
610 free(cur);
611 INIT_LIST_HEAD(&block->freelist);
612 swp_bfree(swp, block);
616 static struct swp_alloc *
617 swp_find_alloc(struct swp_file *swp, void *ptr, struct swp_block **pblock)
619 struct swp_block *block;
620 struct swp_alloc *a;
621 unsigned off = 0;
623 list_for_each_entry(block, &swp->usedblocks, list) {
624 off = ptr - *block->paddr;
625 if (off < SWAP_BLOCK_SIZE)
626 break;
629 /* Fail if we cannot find the block */
630 assert(&block->list != &swp->usedblocks);
632 list_for_each_entry(a, &block->alloclist, list)
633 if (a->off == off)
634 break;
636 /* Fail if we get an unallocated @ptr */
637 assert(&a->list != &block->alloclist);
639 *pblock = block;
640 return a;
643 void *
644 swp_shrink(struct swp_file *swp, void *ptr, size_t newsize)
646 struct swp_block *block, *newblock;
647 struct swp_alloc *a, *newa;
649 SDEBUG("Shrink %p to %ld\n", ptr, (long)newsize);
651 a = swp_find_alloc(swp, ptr, &block);
653 newa = find_free_alloc(swp, newsize, &newblock, &block->list);
654 if (newa) {
655 list_add(&newa->list, &newblock->alloclist);
656 memcpy(*newblock->paddr + newa->off, ptr, newsize);
657 do_free(swp, block, a);
658 a = newa;
659 block = newblock;
661 swp_split(block, a, newsize);
663 swp_dump(swp);
664 return *block->paddr + a->off;
667 void
668 swp_free(struct swp_file *swp, void *ptr)
670 struct swp_alloc *a;
671 struct swp_block *block;
673 SDEBUG("Free %p\n", ptr);
675 a = swp_find_alloc(swp, ptr, &block);
676 do_free(swp, block, a);
678 swp_dump(swp);
682 swp_write(struct swp_file *swp)
684 struct swp_block *block;
685 int ret = 0;
687 SDEBUG("Write swap file\n");
688 swp_dump(swp);
690 if (swp_page_write(swp, swp->hdr, 0))
691 ret = -1;
693 list_for_each_entry(block, &swp->usedblocks, list)
694 if (swp_page_write(swp, *block->paddr,
695 block->pos + SWAP_BLOCK_SIZE))
696 ret = -1;
698 return ret;
701 static int
702 read_header(struct swp_file *swp)
704 struct swp_header *hdr;
705 int i;
706 hed_off_t size;
707 hed_off_t mapoff;
709 /* Read the header block */
710 hdr = swp_page_alloc(swp, 0);
711 if (hdr == SWP_PAGE_INVALID)
712 return -1;
713 swp->hdr = hdr;
715 if (swp_page_read(swp, hdr, 0))
716 return -1;
718 /* Sanity checks */
719 if (memcmp(hdr->signature, SWAP_SIGNATURE, SIG_LEN))
720 return -1;
721 if (hdr->version != SWAP_VERSION)
722 return -1;
723 if (hdr->offsize != sizeof(hed_off_t))
724 return -1;
726 /* Allocate the maps */
727 if ( (size = lseek(swp->fd, 0, SEEK_END)) < 0)
728 return -1;
729 swp->size = size - SWAP_BLOCK_SIZE;
730 swp->nmaps = ((swp->size - SWAP_BLOCK_SIZE) >> SWP_MAPEXTSHIFT) + 1;
731 swp->maps = calloc(swp->nmaps, sizeof(void **));
732 if (!swp->maps)
733 return -1;
735 /* Read in the maps */
736 mapoff = SWAP_BLOCK_SIZE;
737 for (i = 0; i < swp->nmaps; ++i, mapoff += SWP_MAPEXTENT) {
738 swp->maps[i] = swp_page_alloc(swp, mapoff);
739 if (swp->maps[i] == SWP_PAGE_INVALID)
740 return -1;
741 if (swp_page_read(swp, swp->maps[i], mapoff))
742 return -1;
745 return 0;
748 struct swp_file *
749 swp_init_read(const char *name)
751 struct swp_file *swp;
753 if (! (swp = swp_init(name, O_RDONLY)) )
754 return NULL;
756 if (read_header(swp)) {
757 swp_done(swp);
758 return NULL;
761 return swp;
764 static hed_off_t
765 addr2off(struct swp_file *swp, void *ptr)
767 int i, j;
768 for (i = 0; i < swp->nmaps; ++i)
769 for (j = 0; j < SWP_MAPSIZE; ++j) {
770 size_t off = ptr - swp->maps[i][j];
771 if (off < SWAP_BLOCK_SIZE)
772 return SWAP_BLOCK_SIZE + off +
773 (i << SWP_MAPEXTSHIFT) +
774 (j << SWAP_BLOCK_SHIFT);
776 return 0;
779 size_t
780 swp_cpin(struct swp_file *swp, void *buf, void *ptr, size_t len)
782 hed_off_t off = addr2off(swp, ptr);
783 if (!off)
784 return len;
785 return len - pread(swp->fd, buf, len, off);
788 #endif /* HED_CONFIG_SWAP */