mirror of
				git://git.code.sf.net/p/zsh/code
				synced 2025-10-31 18:10:56 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1496 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1496 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * mem.c - memory management
 | |
|  *
 | |
|  * This file is part of zsh, the Z shell.
 | |
|  *
 | |
|  * Copyright (c) 1992-1997 Paul Falstad
 | |
|  * All rights reserved.
 | |
|  *
 | |
|  * Permission is hereby granted, without written agreement and without
 | |
|  * license or royalty fees, to use, copy, modify, and distribute this
 | |
|  * software and to distribute modified versions of this software for any
 | |
|  * purpose, provided that the above copyright notice and the following
 | |
|  * two paragraphs appear in all copies of this software.
 | |
|  *
 | |
|  * In no event shall Paul Falstad or the Zsh Development Group be liable
 | |
|  * to any party for direct, indirect, special, incidental, or consequential
 | |
|  * damages arising out of the use of this software and its documentation,
 | |
|  * even if Paul Falstad and the Zsh Development Group have been advised of
 | |
|  * the possibility of such damage.
 | |
|  *
 | |
|  * Paul Falstad and the Zsh Development Group specifically disclaim any
 | |
|  * warranties, including, but not limited to, the implied warranties of
 | |
|  * merchantability and fitness for a particular purpose.  The software
 | |
|  * provided hereunder is on an "as is" basis, and Paul Falstad and the
 | |
|  * Zsh Development Group have no obligation to provide maintenance,
 | |
|  * support, updates, enhancements, or modifications.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #include "zsh.mdh"
 | |
| #include "mem.pro"
 | |
| 
 | |
| /*
 | |
| 	There are two ways to allocate memory in zsh.  The first way is
 | |
| 	to call zalloc/zshcalloc, which call malloc/calloc directly.  It
 | |
| 	is legal to call realloc() or free() on memory allocated this way.
 | |
| 	The second way is to call zhalloc/hcalloc, which allocates memory
 | |
| 	from one of the memory pools on the heap stack.  Such memory pools 
 | |
| 	will automatically created when the heap allocation routines are
 | |
| 	called.  To be sure that they are freed at appropriate times
 | |
| 	one should call pushheap() before one starts using heaps and
 | |
| 	popheap() after that (when the memory allocated on the heaps since
 | |
| 	the last pushheap() isn't needed anymore).
 | |
| 	pushheap() saves the states of all currently allocated heaps and
 | |
| 	popheap() resets them to the last state saved and destroys the
 | |
| 	information about that state.  If you called pushheap() and
 | |
| 	allocated some memory on the heaps and then come to a place where
 | |
| 	you don't need the allocated memory anymore but you still want
 | |
| 	to allocate memory on the heap, you should call freeheap().  This
 | |
| 	works like popheap(), only that it doesn't free the information
 | |
| 	about the heap states (i.e. the heaps are like after the call to
 | |
| 	pushheap() and you have to call popheap some time later).
 | |
| 
 | |
| 	Memory allocated in this way does not have to be freed explicitly;
 | |
| 	it will all be freed when the pool is destroyed.  In fact,
 | |
| 	attempting to free this memory may result in a core dump.
 | |
| 
 | |
| 	If possible, the heaps are allocated using mmap() so that the
 | |
| 	(*real*) heap isn't filled up with empty zsh heaps. If mmap()
 | |
| 	is not available and zsh's own allocator is used, we use a simple trick
 | |
| 	to avoid that: we allocate a large block of memory before allocating
 | |
| 	a heap pool, this memory is freed again immediately after the pool
 | |
| 	is allocated. If there are only small blocks on the free list this
 | |
| 	guarantees that the memory for the pool is at the end of the memory
 | |
| 	which means that we can give it back to the system when the pool is
 | |
| 	freed.
 | |
| 
 | |
| 	hrealloc(char *p, size_t old, size_t new) is an optimisation
 | |
| 	with a similar interface to realloc().  Typically the new size
 | |
| 	will be larger than the old one, since there is no gain in
 | |
| 	shrinking the allocation (indeed, that will confused hrealloc()
 | |
| 	since it will forget that the unused space once belonged to this
 | |
| 	pointer).  However, new == 0 is a special case; then if we
 | |
| 	had to allocate a special heap for this memory it is freed at
 | |
| 	that point.
 | |
| */
 | |
| 
 | |
| #if defined(HAVE_SYS_MMAN_H) && defined(HAVE_MMAP) && defined(HAVE_MUNMAP)
 | |
| 
 | |
| #include <sys/mman.h>
 | |
| 
 | |
| #if defined(MAP_ANONYMOUS) && defined(MAP_PRIVATE)
 | |
| 
 | |
| #define USE_MMAP 1
 | |
| #define MMAP_FLAGS (MAP_ANONYMOUS | MAP_PRIVATE)
 | |
| 
 | |
| #endif
 | |
| #endif
 | |
| 
 | |
| #ifdef ZSH_MEM_WARNING
 | |
| # ifndef DEBUG
 | |
| #  define DEBUG 1
 | |
| # endif
 | |
| #endif
 | |
| 
 | |
| #if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
 | |
| 
 | |
| static int h_m[1025], h_push, h_pop, h_free;
 | |
| 
 | |
| #endif
 | |
| 
 | |
| /* Make sure we align to the longest fundamental type. */
 | |
| union mem_align {
 | |
|     zlong l;
 | |
|     double d;
 | |
| };
 | |
| 
 | |
| #define H_ISIZE  sizeof(union mem_align)
 | |
| #define HEAPSIZE (16384 - H_ISIZE)
 | |
| /* Memory available for user data in default arena size */
 | |
| #define HEAP_ARENA_SIZE (HEAPSIZE - sizeof(struct heap))
 | |
| #define HEAPFREE (16384 - H_ISIZE)
 | |
| 
 | |
| /* Memory available for user data in heap h */
 | |
| #define ARENA_SIZEOF(h) ((h)->size - sizeof(struct heap))
 | |
| 
 | |
| /* list of zsh heaps */
 | |
| 
 | |
| static Heap heaps;
 | |
| 
 | |
| /* a heap with free space, not always correct (it will be the last heap
 | |
|  * if that was newly allocated but it may also be another one) */
 | |
| 
 | |
| static Heap fheap;
 | |
| 
 | |
| /* Use new heaps from now on. This returns the old heap-list. */
 | |
| 
 | |
| /**/
 | |
| mod_export Heap
 | |
| new_heaps(void)
 | |
| {
 | |
|     Heap h;
 | |
| 
 | |
|     queue_signals();
 | |
|     h = heaps;
 | |
| 
 | |
|     fheap = heaps = NULL;
 | |
|     unqueue_signals();
 | |
| 
 | |
|     return h;
 | |
| }
 | |
| 
 | |
| /* Re-install the old heaps again, freeing the new ones. */
 | |
| 
 | |
| /**/
 | |
| mod_export void
 | |
| old_heaps(Heap old)
 | |
| {
 | |
|     Heap h, n;
 | |
| 
 | |
|     queue_signals();
 | |
|     for (h = heaps; h; h = n) {
 | |
| 	n = h->next;
 | |
| 	DPUTS(h->sp, "BUG: old_heaps() with pushed heaps");
 | |
| #ifdef USE_MMAP
 | |
| 	munmap((void *) h, sizeof(*h));
 | |
| #else
 | |
| 	zfree(h, sizeof(*h));
 | |
| #endif
 | |
|     }
 | |
|     heaps = old;
 | |
|     fheap = NULL;
 | |
|     unqueue_signals();
 | |
| }
 | |
| 
 | |
| /* Temporarily switch to other heaps (or back again). */
 | |
| 
 | |
| /**/
 | |
| mod_export Heap
 | |
| switch_heaps(Heap new)
 | |
| {
 | |
|     Heap h;
 | |
| 
 | |
|     queue_signals();
 | |
|     h = heaps;
 | |
| 
 | |
|     heaps = new;
 | |
|     fheap = NULL;
 | |
|     unqueue_signals();
 | |
| 
 | |
|     return h;
 | |
| }
 | |
| 
 | |
| /* save states of zsh heaps */
 | |
| 
 | |
| /**/
 | |
| mod_export void
 | |
| pushheap(void)
 | |
| {
 | |
|     Heap h;
 | |
|     Heapstack hs;
 | |
| 
 | |
|     queue_signals();
 | |
| 
 | |
| #if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
 | |
|     h_push++;
 | |
| #endif
 | |
| 
 | |
|     for (h = heaps; h; h = h->next) {
 | |
| 	DPUTS(!h->used, "BUG: empty heap");
 | |
| 	hs = (Heapstack) zalloc(sizeof(*hs));
 | |
| 	hs->next = h->sp;
 | |
| 	h->sp = hs;
 | |
| 	hs->used = h->used;
 | |
|     }
 | |
|     unqueue_signals();
 | |
| }
 | |
| 
 | |
| /* reset heaps to previous state */
 | |
| 
 | |
| /**/
 | |
| mod_export void
 | |
| freeheap(void)
 | |
| {
 | |
|     Heap h, hn, hl = NULL;
 | |
| 
 | |
|     queue_signals();
 | |
| 
 | |
| #if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
 | |
|     h_free++;
 | |
| #endif
 | |
| 
 | |
|     fheap = NULL;
 | |
|     for (h = heaps; h; h = hn) {
 | |
| 	hn = h->next;
 | |
| 	if (h->sp) {
 | |
| #ifdef ZSH_MEM_DEBUG
 | |
| 	    memset(arena(h) + h->sp->used, 0xff, h->used - h->sp->used);
 | |
| #endif
 | |
| 	    h->used = h->sp->used;
 | |
| 	    if (!fheap && h->used < ARENA_SIZEOF(h))
 | |
| 		fheap = h;
 | |
| 	    hl = h;
 | |
| 	} else {
 | |
| #ifdef USE_MMAP
 | |
| 	    munmap((void *) h, h->size);
 | |
| #else
 | |
| 	    zfree(h, HEAPSIZE);
 | |
| #endif
 | |
| 	}
 | |
|     }
 | |
|     if (hl)
 | |
| 	hl->next = NULL;
 | |
|     else
 | |
| 	heaps = NULL;
 | |
| 
 | |
|     unqueue_signals();
 | |
| }
 | |
| 
 | |
| /* reset heap to previous state and destroy state information */
 | |
| 
 | |
| /**/
 | |
| mod_export void
 | |
| popheap(void)
 | |
| {
 | |
|     Heap h, hn, hl = NULL;
 | |
|     Heapstack hs;
 | |
| 
 | |
|     queue_signals();
 | |
| 
 | |
| #if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
 | |
|     h_pop++;
 | |
| #endif
 | |
| 
 | |
|     fheap = NULL;
 | |
|     for (h = heaps; h; h = hn) {
 | |
| 	hn = h->next;
 | |
| 	if ((hs = h->sp)) {
 | |
| 	    h->sp = hs->next;
 | |
| #ifdef ZSH_MEM_DEBUG
 | |
| 	    memset(arena(h) + hs->used, 0xff, h->used - hs->used);
 | |
| #endif
 | |
| 	    h->used = hs->used;
 | |
| 	    if (!fheap && h->used < ARENA_SIZEOF(h))
 | |
| 		fheap = h;
 | |
| 	    zfree(hs, sizeof(*hs));
 | |
| 
 | |
| 	    hl = h;
 | |
| 	} else {
 | |
| #ifdef USE_MMAP
 | |
| 	    munmap((void *) h, h->size);
 | |
| #else
 | |
| 	    zfree(h, HEAPSIZE);
 | |
| #endif
 | |
| 	}
 | |
|     }
 | |
|     if (hl)
 | |
| 	hl->next = NULL;
 | |
|     else
 | |
| 	heaps = NULL;
 | |
| 
 | |
|     unqueue_signals();
 | |
| }
 | |
| 
 | |
| #ifdef USE_MMAP
 | |
| /*
 | |
|  * Utility function to allocate a heap area of at least *n bytes.
 | |
|  * *n will be rounded up to the next page boundary.
 | |
|  */
 | |
| static Heap
 | |
| mmap_heap_alloc(size_t *n)
 | |
| {
 | |
|     Heap h;
 | |
|     static size_t pgsz = 0;
 | |
| 
 | |
|     if (!pgsz) {
 | |
| 
 | |
| #ifdef _SC_PAGESIZE
 | |
| 	pgsz = sysconf(_SC_PAGESIZE);     /* SVR4 */
 | |
| #else
 | |
| # ifdef _SC_PAGE_SIZE
 | |
| 	pgsz = sysconf(_SC_PAGE_SIZE);    /* HPUX */
 | |
| # else
 | |
| 	pgsz = getpagesize();
 | |
| # endif
 | |
| #endif
 | |
| 
 | |
| 	pgsz--;
 | |
|     }
 | |
|     *n = (*n + pgsz) & ~pgsz;
 | |
|     h = (Heap) mmap(NULL, *n, PROT_READ | PROT_WRITE,
 | |
| 		    MMAP_FLAGS, -1, 0);
 | |
|     if (h == ((Heap) -1)) {
 | |
| 	zerr("fatal error: out of heap memory", NULL, 0);
 | |
| 	exit(1);
 | |
|     }
 | |
| 
 | |
|     return h;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /* check whether a pointer is within a memory pool */
 | |
| 
 | |
| /**/
 | |
| mod_export void *
 | |
| zheapptr(void *p)
 | |
| {
 | |
|     Heap h;
 | |
|     queue_signals();
 | |
|     for (h = heaps; h; h = h->next)
 | |
| 	if ((char *)p >= arena(h) &&
 | |
| 	    (char *)p + H_ISIZE < arena(h) + ARENA_SIZEOF(h))
 | |
| 	    break;
 | |
|     unqueue_signals();
 | |
|     return (h ? p : 0);
 | |
| }
 | |
| 
 | |
| /* allocate memory from the current memory pool */
 | |
| 
 | |
| /**/
 | |
| mod_export void *
 | |
| zhalloc(size_t size)
 | |
| {
 | |
|     Heap h;
 | |
|     size_t n;
 | |
| 
 | |
|     size = (size + H_ISIZE - 1) & ~(H_ISIZE - 1);
 | |
| 
 | |
|     queue_signals();
 | |
| 
 | |
| #if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
 | |
|     h_m[size < (1024 * H_ISIZE) ? (size / H_ISIZE) : 1024]++;
 | |
| #endif
 | |
| 
 | |
|     /* find a heap with enough free space */
 | |
| 
 | |
|     for (h = ((fheap && ARENA_SIZEOF(fheap) >= (size + fheap->used))
 | |
| 	      ? fheap : heaps);
 | |
| 	 h; h = h->next) {
 | |
| 	if (ARENA_SIZEOF(h) >= (n = size + h->used)) {
 | |
| 	    void *ret;
 | |
| 
 | |
| 	    h->used = n;
 | |
| 	    ret = arena(h) + n - size;
 | |
| 	    unqueue_signals();
 | |
| 	    return ret;
 | |
| 	}
 | |
|     }
 | |
|     {
 | |
| 	Heap hp;
 | |
|         /* not found, allocate new heap */
 | |
| #if defined(ZSH_MEM) && !defined(USE_MMAP)
 | |
| 	static int called = 0;
 | |
| 	void *foo = called ? (void *)malloc(HEAPFREE) : NULL;
 | |
|             /* tricky, see above */
 | |
| #endif
 | |
| 
 | |
| 	n = HEAP_ARENA_SIZE > size ? HEAPSIZE : size + sizeof(*h);
 | |
| 	for (hp = NULL, h = heaps; h; hp = h, h = h->next);
 | |
| 
 | |
| #ifdef USE_MMAP
 | |
| 	h = mmap_heap_alloc(&n);
 | |
| #else
 | |
| 	h = (Heap) zalloc(n);
 | |
| #endif
 | |
| 
 | |
| #if defined(ZSH_MEM) && !defined(USE_MMAP)
 | |
| 	if (called)
 | |
| 	    zfree(foo, HEAPFREE);
 | |
| 	called = 1;
 | |
| #endif
 | |
| 
 | |
| 	h->size = n;
 | |
| 	h->used = size;
 | |
| 	h->next = NULL;
 | |
| 	h->sp = NULL;
 | |
| 
 | |
| 	if (hp)
 | |
| 	    hp->next = h;
 | |
| 	else
 | |
| 	    heaps = h;
 | |
| 	fheap = h;
 | |
| 
 | |
| 	unqueue_signals();
 | |
| 	return arena(h);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**/
 | |
| mod_export void *
 | |
| hrealloc(char *p, size_t old, size_t new)
 | |
| {
 | |
|     Heap h, ph;
 | |
| 
 | |
|     old = (old + H_ISIZE - 1) & ~(H_ISIZE - 1);
 | |
|     new = (new + H_ISIZE - 1) & ~(H_ISIZE - 1);
 | |
| 
 | |
|     if (old == new)
 | |
| 	return p;
 | |
|     if (!old && !p)
 | |
| 	return zhalloc(new);
 | |
| 
 | |
|     /* find the heap with p */
 | |
| 
 | |
|     queue_signals();
 | |
|     for (h = heaps, ph = NULL; h; ph = h, h = h->next)
 | |
| 	if (p >= arena(h) && p < arena(h) + ARENA_SIZEOF(h))
 | |
| 	    break;
 | |
| 
 | |
|     DPUTS(!h, "BUG: hrealloc() called for non-heap memory.");
 | |
|     DPUTS(h->sp && arena(h) + h->sp->used > p,
 | |
| 	  "BUG: hrealloc() wants to realloc pushed memory");
 | |
| 
 | |
|     /*
 | |
|      * If the end of the old chunk is before the used pointer,
 | |
|      * more memory has been zhalloc'ed afterwards.
 | |
|      * We can't tell if that's still in use, obviously, since
 | |
|      * that's the whole point of heap memory.
 | |
|      * We have no choice other than to grab some more memory
 | |
|      * somewhere else and copy in the old stuff.
 | |
|      */
 | |
|     if (p + old < arena(h) + h->used) {
 | |
| 	if (new > old) {
 | |
| 	    char *ptr = (char *) zhalloc(new);
 | |
| 	    memcpy(ptr, p, old);
 | |
| #ifdef ZSH_MEM_DEBUG
 | |
| 	    memset(p, 0xff, old);
 | |
| #endif
 | |
| 	    unqueue_signals();
 | |
| 	    return ptr;
 | |
| 	} else {
 | |
| 	    unqueue_signals();
 | |
| 	    return new ? p : NULL;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|     DPUTS(p + old != arena(h) + h->used, "BUG: hrealloc more than allocated");
 | |
| 
 | |
|     /*
 | |
|      * We now know there's nothing afterwards in the heap, now see if
 | |
|      * there's nothing before.  Then we can reallocate the whole thing.
 | |
|      * Otherwise, we need to keep the stuff at the start of the heap,
 | |
|      * then allocate a new one too; this is handled below.  (This will
 | |
|      * guarantee we occupy a full heap next time round, provided we
 | |
|      * don't use the heap for anything else.)
 | |
|      */
 | |
|     if (p == arena(h)) {
 | |
| 	/*
 | |
| 	 * Zero new seems to be a special case saying we've finished
 | |
| 	 * with the specially reallocated memory, see scanner() in glob.c.
 | |
| 	 */
 | |
| 	if (!new) {
 | |
| 	    if (ph)
 | |
| 		ph->next = h->next;
 | |
| 	    else
 | |
| 		heaps = h->next;
 | |
| 	    fheap = NULL;
 | |
| #ifdef USE_MMAP
 | |
| 	    munmap((void *) h, h->size);
 | |
| #else
 | |
| 	    zfree(h, HEAPSIZE);
 | |
| #endif
 | |
| 	    unqueue_signals();
 | |
| 	    return NULL;
 | |
| 	}
 | |
| 	if (new > ARENA_SIZEOF(h)) {
 | |
| 	    /*
 | |
| 	     * Not enough memory in this heap.  Allocate a new
 | |
| 	     * one of sufficient size.
 | |
| 	     *
 | |
| 	     * To avoid this happening too often, allocate
 | |
| 	     * chunks in multiples of HEAPSIZE.
 | |
| 	     * (Historical note:  there didn't used to be any
 | |
| 	     * point in this since we didn't consistently record
 | |
| 	     * the allocated size of the heap, but now we do.)
 | |
| 	     */
 | |
| 	    size_t n = (new + sizeof(*h) + HEAPSIZE);
 | |
| 	    n -= n % HEAPSIZE;
 | |
| 	    fheap = NULL;
 | |
| 
 | |
| #ifdef USE_MMAP
 | |
| 	    {
 | |
| 		/*
 | |
| 		 * I don't know any easy portable way of requesting
 | |
| 		 * a mmap'd segment be extended, so simply allocate
 | |
| 		 * a new one and copy.
 | |
| 		 */
 | |
| 		Heap hnew;
 | |
| 
 | |
| 		hnew = mmap_heap_alloc(&n);
 | |
| 		/* Copy the entire heap, header (with next pointer) included */
 | |
| 		memcpy(hnew, h, h->size);
 | |
| 		munmap((void *)h, h->size);
 | |
| 		h = hnew;
 | |
| 	    }
 | |
| #else
 | |
| 	    h = (Heap) realloc(h, n);
 | |
| #endif
 | |
| 
 | |
| 	    h->size = n;
 | |
| 	    if (ph)
 | |
| 		ph->next = h;
 | |
| 	    else
 | |
| 		heaps = h;
 | |
| 	}
 | |
| 	h->used = new;
 | |
| 	unqueue_signals();
 | |
| 	return arena(h);
 | |
|     }
 | |
| #ifndef USE_MMAP
 | |
|     DPUTS(h->used > ARENA_SIZEOF(h), "BUG: hrealloc at invalid address");
 | |
| #endif
 | |
|     if (h->used + (new - old) <= ARENA_SIZEOF(h)) {
 | |
| 	h->used += new - old;
 | |
| 	unqueue_signals();
 | |
| 	return p;
 | |
|     } else {
 | |
| 	char *t = zhalloc(new);
 | |
| 	memcpy(t, p, old > new ? new : old);
 | |
| 	h->used -= old;
 | |
| #ifdef ZSH_MEM_DEBUG
 | |
| 	memset(p, 0xff, old);
 | |
| #endif
 | |
| 	unqueue_signals();
 | |
| 	return t;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* allocate memory from the current memory pool and clear it */
 | |
| 
 | |
| /**/
 | |
| mod_export void *
 | |
| hcalloc(size_t size)
 | |
| {
 | |
|     void *ptr;
 | |
| 
 | |
|     ptr = zhalloc(size);
 | |
|     memset(ptr, 0, size);
 | |
|     return ptr;
 | |
| }
 | |
| 
 | |
| /* allocate permanent memory */
 | |
| 
 | |
| /**/
 | |
| mod_export void *
 | |
| zalloc(size_t size)
 | |
| {
 | |
|     void *ptr;
 | |
| 
 | |
|     if (!size)
 | |
| 	size = 1;
 | |
|     queue_signals();
 | |
|     if (!(ptr = (void *) malloc(size))) {
 | |
| 	zerr("fatal error: out of memory", NULL, 0);
 | |
| 	exit(1);
 | |
|     }
 | |
|     unqueue_signals();
 | |
| 
 | |
|     return ptr;
 | |
| }
 | |
| 
 | |
| /**/
 | |
| mod_export void *
 | |
| zshcalloc(size_t size)
 | |
| {
 | |
|     void *ptr;
 | |
| 
 | |
|     if (!size)
 | |
| 	size = 1;
 | |
|     queue_signals();
 | |
|     if (!(ptr = (void *) malloc(size))) {
 | |
| 	zerr("fatal error: out of memory", NULL, 0);
 | |
| 	exit(1);
 | |
|     }
 | |
|     unqueue_signals();
 | |
|     memset(ptr, 0, size);
 | |
| 
 | |
|     return ptr;
 | |
| }
 | |
| 
 | |
| /* This front-end to realloc is used to make sure we have a realloc *
 | |
|  * that conforms to POSIX realloc.  Older realloc's can fail if     *
 | |
|  * passed a NULL pointer, but POSIX realloc should handle this.  A  *
 | |
|  * better solution would be for configure to check if realloc is    *
 | |
|  * POSIX compliant, but I'm not sure how to do that.                */
 | |
| 
 | |
| /**/
 | |
| mod_export void *
 | |
| zrealloc(void *ptr, size_t size)
 | |
| {
 | |
|     queue_signals();
 | |
|     if (ptr) {
 | |
| 	if (size) {
 | |
| 	    /* Do normal realloc */
 | |
| 	    if (!(ptr = (void *) realloc(ptr, size))) {
 | |
| 		zerr("fatal error: out of memory", NULL, 0);
 | |
| 		exit(1);
 | |
| 	    }
 | |
| 	    unqueue_signals();
 | |
| 	    return ptr;
 | |
| 	}
 | |
| 	else
 | |
| 	    /* If ptr is not NULL, but size is zero, *
 | |
| 	     * then object pointed to is freed.      */
 | |
| 	    free(ptr);
 | |
| 
 | |
| 	ptr = NULL;
 | |
|     } else {
 | |
| 	/* If ptr is NULL, then behave like malloc */
 | |
| 	ptr = malloc(size);
 | |
|     }
 | |
|     unqueue_signals();
 | |
| 
 | |
|     return ptr;
 | |
| }
 | |
| 
 | |
| /**/
 | |
| #ifdef ZSH_MEM
 | |
| 
 | |
| /*
 | |
|    Below is a simple segment oriented memory allocator for systems on
 | |
|    which it is better than the system's one. Memory is given in blocks
 | |
|    aligned to an integer multiple of sizeof(union mem_align), which will
 | |
|    probably be 64-bit as it is the longer of zlong or double. Each block is
 | |
|    preceded by a header which contains the length of the data part (in
 | |
|    bytes). In allocated blocks only this field of the structure m_hdr is
 | |
|    senseful. In free blocks the second field (next) is a pointer to the next
 | |
|    free segment on the free list.
 | |
| 
 | |
|    On top of this simple allocator there is a second allocator for small
 | |
|    chunks of data. It should be both faster and less space-consuming than
 | |
|    using the normal segment mechanism for such blocks.
 | |
|    For the first M_NSMALL-1 possible sizes memory is allocated in arrays
 | |
|    that can hold M_SNUM blocks. Each array is stored in one segment of the
 | |
|    main allocator. In these segments the third field of the header structure
 | |
|    (free) contains a pointer to the first free block in the array. The
 | |
|    last field (used) gives the number of already used blocks in the array.
 | |
| 
 | |
|    If the macro name ZSH_MEM_DEBUG is defined, some information about the memory
 | |
|    usage is stored. This information can than be viewed by calling the
 | |
|    builtin `mem' (which is only available if ZSH_MEM_DEBUG is set).
 | |
| 
 | |
|    If ZSH_MEM_WARNING is defined, error messages are printed in case of errors.
 | |
| 
 | |
|    If ZSH_SECURE_FREE is defined, free() checks if the given address is really
 | |
|    one that was returned by malloc(), it ignores it if it wasn't (printing
 | |
|    an error message if ZSH_MEM_WARNING is also defined).
 | |
| */
 | |
| #if !defined(__hpux) && !defined(DGUX) && !defined(__osf__)
 | |
| # if defined(_BSD)
 | |
| #  ifndef HAVE_BRK_PROTO
 | |
|    extern int brk _((caddr_t));
 | |
| #  endif
 | |
| #  ifndef HAVE_SBRK_PROTO
 | |
|    extern caddr_t sbrk _((int));
 | |
| #  endif
 | |
| # else
 | |
| #  ifndef HAVE_BRK_PROTO
 | |
|    extern int brk _((void *));
 | |
| #  endif
 | |
| #  ifndef HAVE_SBRK_PROTO
 | |
|    extern void *sbrk _((int));
 | |
| #  endif
 | |
| # endif
 | |
| #endif
 | |
| 
 | |
| #if defined(_BSD) && !defined(STDC_HEADERS)
 | |
| # define FREE_RET_T   int
 | |
| # define FREE_ARG_T   char *
 | |
| # define FREE_DO_RET
 | |
| # define MALLOC_RET_T char *
 | |
| # define MALLOC_ARG_T size_t
 | |
| #else
 | |
| # define FREE_RET_T   void
 | |
| # define FREE_ARG_T   void *
 | |
| # define MALLOC_RET_T void *
 | |
| # define MALLOC_ARG_T size_t
 | |
| #endif
 | |
| 
 | |
| /* structure for building free list in blocks holding small blocks */
 | |
| 
 | |
| struct m_shdr {
 | |
|     struct m_shdr *next;	/* next one on free list */
 | |
| #ifdef PAD_64_BIT
 | |
|     /* dummy to make this 64-bit aligned */
 | |
|     struct m_shdr *dummy;
 | |
| #endif
 | |
| };
 | |
| 
 | |
| struct m_hdr {
 | |
|     zlong len;			/* length of memory block */
 | |
| #if defined(PAD_64_BIT) && !defined(ZSH_64_BIT_TYPE)
 | |
|     /* either 1 or 2 zlong's, whichever makes up 64 bits. */
 | |
|     zlong dummy1;
 | |
| #endif
 | |
|     struct m_hdr *next;		/* if free: next on free list
 | |
| 				   if block of small blocks: next one with
 | |
| 				                 small blocks of same size*/
 | |
|     struct m_shdr *free;	/* if block of small blocks: free list */
 | |
|     zlong used;			/* if block of small blocks: number of used
 | |
| 				                                     blocks */
 | |
| #if defined(PAD_64_BIT) && !defined(ZSH_64_BIT_TYPE)
 | |
|     zlong dummy2;
 | |
| #endif
 | |
| };
 | |
| 
 | |
| 
 | |
| /* alignment for memory blocks */
 | |
| 
 | |
| #define M_ALIGN (sizeof(union mem_align))
 | |
| 
 | |
| /* length of memory header, length of first field of memory header and
 | |
|    minimal size of a block left free (if we allocate memory and take a
 | |
|    block from the free list that is larger than needed, it must have at
 | |
|    least M_MIN extra bytes to be splitted; if it has, the rest is put on
 | |
|    the free list) */
 | |
| 
 | |
| #define M_HSIZE (sizeof(struct m_hdr))
 | |
| #if defined(PAD_64_BIT) && !defined(ZSH_64_BIT_TYPE)
 | |
| # define M_ISIZE (2*sizeof(zlong))
 | |
| #else
 | |
| # define M_ISIZE (sizeof(zlong))
 | |
| #endif
 | |
| #define M_MIN   (2 * M_ISIZE)
 | |
| 
 | |
| /* M_FREE  is the number of bytes that have to be free before memory is
 | |
|  *         given back to the system
 | |
|  * M_KEEP  is the number of bytes that will be kept when memory is given
 | |
|  *         back; note that this has to be less than M_FREE
 | |
|  * M_ALLOC is the number of extra bytes to request from the system */
 | |
| 
 | |
| #define M_FREE  32768
 | |
| #define M_KEEP  16384
 | |
| #define M_ALLOC M_KEEP
 | |
| 
 | |
| /* a pointer to the last free block, a pointer to the free list (the blocks
 | |
|    on this list are kept in order - lowest address first) */
 | |
| 
 | |
| static struct m_hdr *m_lfree, *m_free;
 | |
| 
 | |
| /* system's pagesize */
 | |
| 
 | |
| static long m_pgsz = 0;
 | |
| 
 | |
| /* the highest and the lowest valid memory addresses, kept for fast validity
 | |
|    checks in free() and to find out if and when we can give memory back to
 | |
|    the system */
 | |
| 
 | |
| static char *m_high, *m_low;
 | |
| 
 | |
| /* Management of blocks for small blocks:
 | |
|    Such blocks are kept in lists (one list for each of the sizes that are
 | |
|    allocated in such blocks).  The lists are stored in the m_small array.
 | |
|    M_SIDX() calculates the index into this array for a given size.  M_SNUM
 | |
|    is the size (in small blocks) of such blocks.  M_SLEN() calculates the
 | |
|    size of the small blocks held in a memory block, given a pointer to the
 | |
|    header of it.  M_SBLEN() gives the size of a memory block that can hold
 | |
|    an array of small blocks, given the size of these small blocks.  M_BSLEN()
 | |
|    calculates the size of the small blocks held in a memory block, given the
 | |
|    length of that block (including the header of the memory block.  M_NSMALL
 | |
|    is the number of possible block sizes that small blocks should be used
 | |
|    for. */
 | |
| 
 | |
| 
 | |
| #define M_SIDX(S)  ((S) / M_ISIZE)
 | |
| #define M_SNUM     128
 | |
| #define M_SLEN(M)  ((M)->len / M_SNUM)
 | |
| #if defined(PAD_64_BIT) && !defined(ZSH_64_BIT_TYPE)
 | |
| /* Include the dummy in the alignment */
 | |
| #define M_SBLEN(S) ((S) * M_SNUM + sizeof(struct m_shdr *) +  \
 | |
| 		    2*sizeof(zlong) + sizeof(struct m_hdr *))
 | |
| #define M_BSLEN(S) (((S) - sizeof(struct m_shdr *) -  \
 | |
| 		     2*sizeof(zlong) - sizeof(struct m_hdr *)) / M_SNUM)
 | |
| #else
 | |
| #define M_SBLEN(S) ((S) * M_SNUM + sizeof(struct m_shdr *) +  \
 | |
| 		    sizeof(zlong) + sizeof(struct m_hdr *))
 | |
| #define M_BSLEN(S) (((S) - sizeof(struct m_shdr *) -  \
 | |
| 		     sizeof(zlong) - sizeof(struct m_hdr *)) / M_SNUM)
 | |
| #endif
 | |
| #define M_NSMALL    8
 | |
| 
 | |
| static struct m_hdr *m_small[M_NSMALL];
 | |
| 
 | |
| #ifdef ZSH_MEM_DEBUG
 | |
| 
 | |
| static int m_s = 0, m_b = 0;
 | |
| static int m_m[1025], m_f[1025];
 | |
| 
 | |
| static struct m_hdr *m_l;
 | |
| 
 | |
| #endif /* ZSH_MEM_DEBUG */
 | |
| 
 | |
| MALLOC_RET_T
 | |
| malloc(MALLOC_ARG_T size)
 | |
| {
 | |
|     struct m_hdr *m, *mp, *mt;
 | |
|     long n, s, os = 0;
 | |
| #ifndef USE_MMAP
 | |
|     struct heap *h, *hp, *hf = NULL, *hfp = NULL;
 | |
| #endif
 | |
| 
 | |
|     /* some systems want malloc to return the highest valid address plus one
 | |
|        if it is called with an argument of zero */
 | |
| 
 | |
|     if (!size)
 | |
| 	return (MALLOC_RET_T) m_high;
 | |
| 
 | |
|     queue_signals();  /* just queue signals rather than handling them */
 | |
| 
 | |
|     /* first call, get page size */
 | |
| 
 | |
|     if (!m_pgsz) {
 | |
| 
 | |
| #ifdef _SC_PAGESIZE
 | |
| 	m_pgsz = sysconf(_SC_PAGESIZE);     /* SVR4 */
 | |
| #else
 | |
| # ifdef _SC_PAGE_SIZE
 | |
| 	m_pgsz = sysconf(_SC_PAGE_SIZE);    /* HPUX */
 | |
| # else
 | |
| 	m_pgsz = getpagesize();
 | |
| # endif
 | |
| #endif
 | |
| 
 | |
| 	m_free = m_lfree = NULL;
 | |
|     }
 | |
|     size = (size + M_ALIGN - 1) & ~(M_ALIGN - 1);
 | |
| 
 | |
|     /* Do we need a small block? */
 | |
| 
 | |
|     if ((s = M_SIDX(size)) && s < M_NSMALL) {
 | |
| 	/* yep, find a memory block with free small blocks of the
 | |
| 	   appropriate size (if we find it in this list, this means that
 | |
| 	   it has room for at least one more small block) */
 | |
| 	for (mp = NULL, m = m_small[s]; m && !m->free; mp = m, m = m->next);
 | |
| 
 | |
| 	if (m) {
 | |
| 	    /* we found one */
 | |
| 	    struct m_shdr *sh = m->free;
 | |
| 
 | |
| 	    m->free = sh->next;
 | |
| 	    m->used++;
 | |
| 
 | |
| 	    /* if all small blocks in this block are allocated, the block is 
 | |
| 	       put at the end of the list blocks with small blocks of this
 | |
| 	       size (i.e., we try to keep blocks with free blocks at the
 | |
| 	       beginning of the list, to make the search faster) */
 | |
| 
 | |
| 	    if (m->used == M_SNUM && m->next) {
 | |
| 		for (mt = m; mt->next; mt = mt->next);
 | |
| 
 | |
| 		mt->next = m;
 | |
| 		if (mp)
 | |
| 		    mp->next = m->next;
 | |
| 		else
 | |
| 		    m_small[s] = m->next;
 | |
| 		m->next = NULL;
 | |
| 	    }
 | |
| #ifdef ZSH_MEM_DEBUG
 | |
| 	    m_m[size / M_ISIZE]++;
 | |
| #endif
 | |
| 
 | |
| 	    unqueue_signals();
 | |
| 	    return (MALLOC_RET_T) sh;
 | |
| 	}
 | |
| 	/* we still want a small block but there were no block with a free
 | |
| 	   small block of the requested size; so we use the real allocation
 | |
| 	   routine to allocate a block for small blocks of this size */
 | |
| 	os = size;
 | |
| 	size = M_SBLEN(size);
 | |
|     } else
 | |
| 	s = 0;
 | |
| 
 | |
|     /* search the free list for an block of at least the requested size */
 | |
|     for (mp = NULL, m = m_free; m && m->len < size; mp = m, m = m->next);
 | |
| 
 | |
| #ifndef USE_MMAP
 | |
| 
 | |
|     /* if there is an empty zsh heap at a lower address we steal it and take
 | |
|        the memory from it, putting the rest on the free list (remember
 | |
|        that the blocks on the free list are ordered) */
 | |
| 
 | |
|     for (hp = NULL, h = heaps; h; hp = h, h = h->next)
 | |
| 	if (!h->used &&
 | |
| 	    (!hf || h < hf) &&
 | |
| 	    (!m || ((char *)m) > ((char *)h)))
 | |
| 	    hf = h, hfp = hp;
 | |
| 
 | |
|     if (hf) {
 | |
| 	/* we found such a heap */
 | |
| 	Heapstack hso, hsn;
 | |
| 
 | |
| 	/* delete structures on the list holding the heap states */
 | |
| 	for (hso = hf->sp; hso; hso = hsn) {
 | |
| 	    hsn = hso->next;
 | |
| 	    zfree(hso, sizeof(*hso));
 | |
| 	}
 | |
| 	/* take it from the list of heaps */
 | |
| 	if (hfp)
 | |
| 	    hfp->next = hf->next;
 | |
| 	else
 | |
| 	    heaps = hf->next;
 | |
| 	/* now we simply free it and than search the free list again */
 | |
| 	zfree(hf, HEAPSIZE);
 | |
| 
 | |
| 	for (mp = NULL, m = m_free; m && m->len < size; mp = m, m = m->next);
 | |
|     }
 | |
| #endif
 | |
|     if (!m) {
 | |
| 	long nal;
 | |
| 	/* no matching free block was found, we have to request new
 | |
| 	   memory from the system */
 | |
| 	n = (size + M_HSIZE + M_ALLOC + m_pgsz - 1) & ~(m_pgsz - 1);
 | |
| 
 | |
| 	if (((char *)(m = (struct m_hdr *)sbrk(n))) == ((char *)-1)) {
 | |
| 	    DPUTS(1, "MEM: allocation error at sbrk.");
 | |
| 	    unqueue_signals();
 | |
| 	    return NULL;
 | |
| 	}
 | |
| 	if ((nal = ((long)(char *)m) & (M_ALIGN-1))) {
 | |
| 	    if ((char *)sbrk(M_ALIGN - nal) == (char *)-1) {
 | |
| 		DPUTS(1, "MEM: allocation error at sbrk.");
 | |
| 		unqueue_signals();
 | |
| 		return NULL;
 | |
| 	    }
 | |
| 	    m = (struct m_hdr *) ((char *)m + (M_ALIGN - nal));
 | |
| 	}
 | |
| 	/* set m_low, for the check in free() */
 | |
| 	if (!m_low)
 | |
| 	    m_low = (char *)m;
 | |
| 
 | |
| #ifdef ZSH_MEM_DEBUG
 | |
| 	m_s += n;
 | |
| 
 | |
| 	if (!m_l)
 | |
| 	    m_l = m;
 | |
| #endif
 | |
| 
 | |
| 	/* save new highest address */
 | |
| 	m_high = ((char *)m) + n;
 | |
| 
 | |
| 	/* initialize header */
 | |
| 	m->len = n - M_ISIZE;
 | |
| 	m->next = NULL;
 | |
| 
 | |
| 	/* put it on the free list and set m_lfree pointing to it */
 | |
| 	if ((mp = m_lfree))
 | |
| 	    m_lfree->next = m;
 | |
| 	m_lfree = m;
 | |
|     }
 | |
|     if ((n = m->len - size) > M_MIN) {
 | |
| 	/* the block we want to use has more than M_MIN bytes plus the
 | |
| 	   number of bytes that were requested; we split it in two and
 | |
| 	   leave the rest on the free list */
 | |
| 	struct m_hdr *mtt = (struct m_hdr *)(((char *)m) + M_ISIZE + size);
 | |
| 
 | |
| 	mtt->len = n - M_ISIZE;
 | |
| 	mtt->next = m->next;
 | |
| 
 | |
| 	m->len = size;
 | |
| 
 | |
| 	/* put the rest on the list */
 | |
| 	if (m_lfree == m)
 | |
| 	    m_lfree = mtt;
 | |
| 
 | |
| 	if (mp)
 | |
| 	    mp->next = mtt;
 | |
| 	else
 | |
| 	    m_free = mtt;
 | |
|     } else if (mp) {
 | |
| 	/* the block we found wasn't the first one on the free list */
 | |
| 	if (m == m_lfree)
 | |
| 	    m_lfree = mp;
 | |
| 	mp->next = m->next;
 | |
|     } else {
 | |
| 	/* it was the first one */
 | |
| 	m_free = m->next;
 | |
| 	if (m == m_lfree)
 | |
| 	    m_lfree = m_free;
 | |
|     }
 | |
| 
 | |
|     if (s) {
 | |
| 	/* we are allocating a block that should hold small blocks */
 | |
| 	struct m_shdr *sh, *shn;
 | |
| 
 | |
| 	/* build the free list in this block and set `used' filed */
 | |
| 	m->free = sh = (struct m_shdr *)(((char *)m) +
 | |
| 					 sizeof(struct m_hdr) + os);
 | |
| 
 | |
| 	for (n = M_SNUM - 2; n--; sh = shn)
 | |
| 	    shn = sh->next = sh + s;
 | |
| 	sh->next = NULL;
 | |
| 
 | |
| 	m->used = 1;
 | |
| 
 | |
| 	/* put the block on the list of blocks holding small blocks if
 | |
| 	   this size */
 | |
| 	m->next = m_small[s];
 | |
| 	m_small[s] = m;
 | |
| 
 | |
| #ifdef ZSH_MEM_DEBUG
 | |
| 	m_m[os / M_ISIZE]++;
 | |
| #endif
 | |
| 
 | |
| 	unqueue_signals();
 | |
| 	return (MALLOC_RET_T) (((char *)m) + sizeof(struct m_hdr));
 | |
|     }
 | |
| #ifdef ZSH_MEM_DEBUG
 | |
|     m_m[m->len < (1024 * M_ISIZE) ? (m->len / M_ISIZE) : 1024]++;
 | |
| #endif
 | |
| 
 | |
|     unqueue_signals();
 | |
|     return (MALLOC_RET_T) & m->next;
 | |
| }
 | |
| 
 | |
| /* this is an internal free(); the second argument may, but need not hold
 | |
|    the size of the block the first argument is pointing to; if it is the
 | |
|    right size of this block, freeing it will be faster, though; the value
 | |
|    0 for this parameter means: `don't know' */
 | |
| 
 | |
| /**/
 | |
| mod_export void
 | |
| zfree(void *p, int sz)
 | |
| {
 | |
|     struct m_hdr *m = (struct m_hdr *)(((char *)p) - M_ISIZE), *mp, *mt = NULL;
 | |
|     int i;
 | |
| # ifdef DEBUG
 | |
|     int osz = sz;
 | |
| # endif
 | |
| 
 | |
| #ifdef ZSH_SECURE_FREE
 | |
|     sz = 0;
 | |
| #else
 | |
|     sz = (sz + M_ALIGN - 1) & ~(M_ALIGN - 1);
 | |
| #endif
 | |
| 
 | |
|     if (!p)
 | |
| 	return;
 | |
| 
 | |
|     /* first a simple check if the given address is valid */
 | |
|     if (((char *)p) < m_low || ((char *)p) > m_high ||
 | |
| 	((long)p) & (M_ALIGN - 1)) {
 | |
| 	DPUTS(1, "BUG: attempt to free storage at invalid address");
 | |
| 	return;
 | |
|     }
 | |
| 
 | |
|     queue_signals();
 | |
| 
 | |
|   fr_rec:
 | |
| 
 | |
|     if ((i = sz / M_ISIZE) < M_NSMALL || !sz)
 | |
| 	/* if the given sizes says that it is a small block, find the
 | |
| 	   memory block holding it; we search all blocks with blocks
 | |
| 	   of at least the given size; if the size parameter is zero,
 | |
| 	   this means, that all blocks are searched */
 | |
| 	for (; i < M_NSMALL; i++) {
 | |
| 	    for (mp = NULL, mt = m_small[i];
 | |
| 		 mt && (((char *)mt) > ((char *)p) ||
 | |
| 			(((char *)mt) + mt->len) < ((char *)p));
 | |
| 		 mp = mt, mt = mt->next);
 | |
| 
 | |
| 	    if (mt) {
 | |
| 		/* we found the block holding the small block */
 | |
| 		struct m_shdr *sh = (struct m_shdr *)p;
 | |
| 
 | |
| #ifdef ZSH_SECURE_FREE
 | |
| 		struct m_shdr *sh2;
 | |
| 
 | |
| 		/* check if the given address is equal to the address of
 | |
| 		   the first small block plus an integer multiple of the
 | |
| 		   block size */
 | |
| 		if ((((char *)p) - (((char *)mt) + sizeof(struct m_hdr))) %
 | |
| 		    M_BSLEN(mt->len)) {
 | |
| 
 | |
| 		    DPUTS(1, "BUG: attempt to free storage at invalid address");
 | |
| 		    unqueue_signals();
 | |
| 		    return;
 | |
| 		}
 | |
| 		/* check, if the address is on the (block-intern) free list */
 | |
| 		for (sh2 = mt->free; sh2; sh2 = sh2->next)
 | |
| 		    if (((char *)p) == ((char *)sh2)) {
 | |
| 
 | |
| 			DPUTS(1, "BUG: attempt to free already free storage");
 | |
| 			unqueue_signals();
 | |
| 			return;
 | |
| 		    }
 | |
| #endif
 | |
| 		DPUTS(M_BSLEN(mt->len) < osz,
 | |
| 		      "BUG: attempt to free more than allocated.");
 | |
| 
 | |
| #ifdef ZSH_MEM_DEBUG
 | |
| 		m_f[M_BSLEN(mt->len) / M_ISIZE]++;
 | |
| 		memset(sh, 0xff, M_BSLEN(mt->len));
 | |
| #endif
 | |
| 
 | |
| 		/* put the block onto the free list */
 | |
| 		sh->next = mt->free;
 | |
| 		mt->free = sh;
 | |
| 
 | |
| 		if (--mt->used) {
 | |
| 		    /* if there are still used blocks in this block, we
 | |
| 		       put it at the beginning of the list with blocks
 | |
| 		       holding small blocks of the same size (since we
 | |
| 		       know that there is at least one free block in it,
 | |
| 		       this will make allocation of small blocks faster;
 | |
| 		       it also guarantees that long living memory blocks
 | |
| 		       are preferred over younger ones */
 | |
| 		    if (mp) {
 | |
| 			mp->next = mt->next;
 | |
| 			mt->next = m_small[i];
 | |
| 			m_small[i] = mt;
 | |
| 		    }
 | |
| 		    unqueue_signals();
 | |
| 		    return;
 | |
| 		}
 | |
| 		/* if there are no more used small blocks in this
 | |
| 		   block, we free the whole block */
 | |
| 		if (mp)
 | |
| 		    mp->next = mt->next;
 | |
| 		else
 | |
| 		    m_small[i] = mt->next;
 | |
| 
 | |
| 		m = mt;
 | |
| 		p = (void *) & m->next;
 | |
| 
 | |
| 		break;
 | |
| 	    } else if (sz) {
 | |
| 		/* if we didn't find a block and a size was given, try it
 | |
| 		   again as if no size were given */
 | |
| 		sz = 0;
 | |
| 		goto fr_rec;
 | |
| 	    }
 | |
| 	}
 | |
| #ifdef ZSH_MEM_DEBUG
 | |
|     if (!mt)
 | |
| 	m_f[m->len < (1024 * M_ISIZE) ? (m->len / M_ISIZE) : 1024]++;
 | |
| #endif
 | |
| 
 | |
| #ifdef ZSH_SECURE_FREE
 | |
|     /* search all memory blocks, if one of them is at the given address */
 | |
|     for (mt = (struct m_hdr *)m_low;
 | |
| 	 ((char *)mt) < m_high;
 | |
| 	 mt = (struct m_hdr *)(((char *)mt) + M_ISIZE + mt->len))
 | |
| 	if (((char *)p) == ((char *)&mt->next))
 | |
| 	    break;
 | |
| 
 | |
|     /* no block was found at the given address */
 | |
|     if (((char *)mt) >= m_high) {
 | |
| 	DPUTS(1, "BUG: attempt to free storage at invalid address");
 | |
| 	unqueue_signals();
 | |
| 	return;
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     /* see if the block is on the free list */
 | |
|     for (mp = NULL, mt = m_free; mt && mt < m; mp = mt, mt = mt->next);
 | |
| 
 | |
|     if (m == mt) {
 | |
| 	/* it is, ouch! */
 | |
| 	DPUTS(1, "BUG: attempt to free already free storage");
 | |
| 	unqueue_signals();
 | |
| 	return;
 | |
|     }
 | |
|     DPUTS(m->len < osz, "BUG: attempt to free more than allocated");
 | |
| #ifdef ZSH_MEM_DEBUG
 | |
|     memset(p, 0xff, m->len);
 | |
| #endif
 | |
|     if (mt && ((char *)mt) == (((char *)m) + M_ISIZE + m->len)) {
 | |
| 	/* the block after the one we are freeing is free, we put them
 | |
| 	   together */
 | |
| 	m->len += mt->len + M_ISIZE;
 | |
| 	m->next = mt->next;
 | |
| 
 | |
| 	if (mt == m_lfree)
 | |
| 	    m_lfree = m;
 | |
|     } else
 | |
| 	m->next = mt;
 | |
| 
 | |
|     if (mp && ((char *)m) == (((char *)mp) + M_ISIZE + mp->len)) {
 | |
| 	/* the block before the one we are freeing is free, we put them
 | |
| 	   together */
 | |
| 	mp->len += m->len + M_ISIZE;
 | |
| 	mp->next = m->next;
 | |
| 
 | |
| 	if (m == m_lfree)
 | |
| 	    m_lfree = mp;
 | |
|     } else if (mp)
 | |
| 	/* otherwise, we just put it on the free list */
 | |
| 	mp->next = m;
 | |
|     else {
 | |
| 	m_free = m;
 | |
| 	if (!m_lfree)
 | |
| 	    m_lfree = m_free;
 | |
|     }
 | |
| 
 | |
|     /* if the block we have just freed was at the end of the process heap
 | |
|        and now there is more than one page size of memory, we can give
 | |
|        it back to the system (and we do it ;-) */
 | |
|     if ((((char *)m_lfree) + M_ISIZE + m_lfree->len) == m_high &&
 | |
| 	m_lfree->len >= m_pgsz + M_MIN + M_FREE) {
 | |
| 	long n = (m_lfree->len - M_MIN - M_KEEP) & ~(m_pgsz - 1);
 | |
| 
 | |
| 	m_lfree->len -= n;
 | |
| #ifdef HAVE_BRK
 | |
| 	if (brk(m_high -= n) == -1) {
 | |
| #else
 | |
| 	m_high -= n;
 | |
| 	if (sbrk(-n) == (void *)-1) {
 | |
| #endif /* HAVE_BRK */
 | |
| 	    DPUTS(1, "MEM: allocation error at brk.");
 | |
| 	}
 | |
| 
 | |
| #ifdef ZSH_MEM_DEBUG
 | |
| 	m_b += n;
 | |
| #endif
 | |
|     }
 | |
|     unqueue_signals();
 | |
| }
 | |
| 
 | |
| FREE_RET_T
 | |
| free(FREE_ARG_T p)
 | |
| {
 | |
|     zfree(p, 0);		/* 0 means: size is unknown */
 | |
| 
 | |
| #ifdef FREE_DO_RET
 | |
|     return 0;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /* this one is for strings (and only strings, real strings, real C strings,
 | |
|    those that have a zero byte at the end) */
 | |
| 
 | |
| /**/
 | |
| mod_export void
 | |
| zsfree(char *p)
 | |
| {
 | |
|     if (p)
 | |
| 	zfree(p, strlen(p) + 1);
 | |
| }
 | |
| 
 | |
| MALLOC_RET_T
 | |
| realloc(MALLOC_RET_T p, MALLOC_ARG_T size)
 | |
| {
 | |
|     struct m_hdr *m = (struct m_hdr *)(((char *)p) - M_ISIZE), *mp, *mt;
 | |
|     char *r;
 | |
|     int i, l = 0;
 | |
| 
 | |
|     /* some system..., see above */
 | |
|     if (!p && size)
 | |
| 	return (MALLOC_RET_T) malloc(size);
 | |
|     /* and some systems even do this... */
 | |
|     if (!p || !size)
 | |
| 	return (MALLOC_RET_T) p;
 | |
| 
 | |
|     queue_signals();  /* just queue signals caught rather than handling them */
 | |
| 
 | |
|     /* check if we are reallocating a small block, if we do, we have
 | |
|        to compute the size of the block from the sort of block it is in */
 | |
|     for (i = 0; i < M_NSMALL; i++) {
 | |
| 	for (mp = NULL, mt = m_small[i];
 | |
| 	     mt && (((char *)mt) > ((char *)p) ||
 | |
| 		    (((char *)mt) + mt->len) < ((char *)p));
 | |
| 	     mp = mt, mt = mt->next);
 | |
| 
 | |
| 	if (mt) {
 | |
| 	    l = M_BSLEN(mt->len);
 | |
| 	    break;
 | |
| 	}
 | |
|     }
 | |
|     if (!l)
 | |
| 	/* otherwise the size of the block is in the memory just before
 | |
| 	   the given address */
 | |
| 	l = m->len;
 | |
| 
 | |
|     /* now allocate the new block, copy the old contents, and free the
 | |
|        old block */
 | |
|     r = malloc(size);
 | |
|     memcpy(r, (char *)p, (size > l) ? l : size);
 | |
|     free(p);
 | |
| 
 | |
|     unqueue_signals();
 | |
|     return (MALLOC_RET_T) r;
 | |
| }
 | |
| 
 | |
| MALLOC_RET_T
 | |
| calloc(MALLOC_ARG_T n, MALLOC_ARG_T size)
 | |
| {
 | |
|     long l;
 | |
|     char *r;
 | |
| 
 | |
|     if (!(l = n * size))
 | |
| 	return (MALLOC_RET_T) m_high;
 | |
| 
 | |
|     r = malloc(l);
 | |
| 
 | |
|     memset(r, 0, l);
 | |
| 
 | |
|     return (MALLOC_RET_T) r;
 | |
| }
 | |
| 
 | |
| #ifdef ZSH_MEM_DEBUG
 | |
| 
 | |
| /**/
 | |
| int
 | |
| bin_mem(char *name, char **argv, Options ops, int func)
 | |
| {
 | |
|     int i, ii, fi, ui, j;
 | |
|     struct m_hdr *m, *mf, *ms;
 | |
|     char *b, *c, buf[40];
 | |
|     long u = 0, f = 0, to, cu;
 | |
| 
 | |
|     queue_signals();
 | |
|     if (OPT_ISSET(ops,'v')) {
 | |
| 	printf("The lower and the upper addresses of the heap. Diff gives\n");
 | |
| 	printf("the difference between them, i.e. the size of the heap.\n\n");
 | |
|     }
 | |
|     printf("low mem %ld\t high mem %ld\t diff %ld\n",
 | |
| 	   (long)m_l, (long)m_high, (long)(m_high - ((char *)m_l)));
 | |
| 
 | |
|     if (OPT_ISSET(ops,'v')) {
 | |
| 	printf("\nThe number of bytes that were allocated using sbrk() and\n");
 | |
| 	printf("the number of bytes that were given back to the system\n");
 | |
| 	printf("via brk().\n");
 | |
|     }
 | |
|     printf("\nsbrk %d\tbrk %d\n", m_s, m_b);
 | |
| 
 | |
|     if (OPT_ISSET(ops,'v')) {
 | |
| 	printf("\nInformation about the sizes that were allocated or freed.\n");
 | |
| 	printf("For each size that were used the number of mallocs and\n");
 | |
| 	printf("frees is shown. Diff gives the difference between these\n");
 | |
| 	printf("values, i.e. the number of blocks of that size that is\n");
 | |
| 	printf("currently allocated. Total is the product of size and diff,\n");
 | |
| 	printf("i.e. the number of bytes that are allocated for blocks of\n");
 | |
| 	printf("this size. The last field gives the accumulated number of\n");
 | |
| 	printf("bytes for all sizes.\n");
 | |
|     }
 | |
|     printf("\nsize\tmalloc\tfree\tdiff\ttotal\tcum\n");
 | |
|     for (i = 0, cu = 0; i < 1024; i++)
 | |
| 	if (m_m[i] || m_f[i]) {
 | |
| 	    to = (long) i * M_ISIZE * (m_m[i] - m_f[i]);
 | |
| 	    printf("%ld\t%d\t%d\t%d\t%ld\t%ld\n",
 | |
| 		   (long)i * M_ISIZE, m_m[i], m_f[i], m_m[i] - m_f[i],
 | |
| 		   to, (cu += to));
 | |
| 	}
 | |
| 
 | |
|     if (m_m[i] || m_f[i])
 | |
| 	printf("big\t%d\t%d\t%d\n", m_m[i], m_f[i], m_m[i] - m_f[i]);
 | |
| 
 | |
|     if (OPT_ISSET(ops,'v')) {
 | |
| 	printf("\nThe list of memory blocks. For each block the following\n");
 | |
| 	printf("information is shown:\n\n");
 | |
| 	printf("num\tthe number of this block\n");
 | |
| 	printf("tnum\tlike num but counted separately for used and free\n");
 | |
| 	printf("\tblocks\n");
 | |
| 	printf("addr\tthe address of this block\n");
 | |
| 	printf("len\tthe length of the block\n");
 | |
| 	printf("state\tthe state of this block, this can be:\n");
 | |
| 	printf("\t  used\tthis block is used for one big block\n");
 | |
| 	printf("\t  free\tthis block is free\n");
 | |
| 	printf("\t  small\tthis block is used for an array of small blocks\n");
 | |
| 	printf("cum\tthe accumulated sizes of the blocks, counted\n");
 | |
| 	printf("\tseparately for used and free blocks\n");
 | |
| 	printf("\nFor blocks holding small blocks the number of free\n");
 | |
| 	printf("blocks, the number of used blocks and the size of the\n");
 | |
| 	printf("blocks is shown. For otherwise used blocks the first few\n");
 | |
| 	printf("bytes are shown as an ASCII dump.\n");
 | |
|     }
 | |
|     printf("\nblock list:\nnum\ttnum\taddr\t\tlen\tstate\tcum\n");
 | |
|     for (m = m_l, mf = m_free, ii = fi = ui = 1; ((char *)m) < m_high;
 | |
| 	 m = (struct m_hdr *)(((char *)m) + M_ISIZE + m->len), ii++) {
 | |
| 	for (j = 0, ms = NULL; j < M_NSMALL && !ms; j++)
 | |
| 	    for (ms = m_small[j]; ms; ms = ms->next)
 | |
| 		if (ms == m)
 | |
| 		    break;
 | |
| 
 | |
| 	if (m == mf)
 | |
| 	    buf[0] = '\0';
 | |
| 	else if (m == ms)
 | |
| 	    sprintf(buf, "%ld %ld %ld", (long)(M_SNUM - ms->used),
 | |
| 		    (long)ms->used,
 | |
| 		    (long)(m->len - sizeof(struct m_hdr)) / M_SNUM + 1);
 | |
| 
 | |
| 	else {
 | |
| 	    for (i = 0, b = buf, c = (char *)&m->next; i < 20 && i < m->len;
 | |
| 		 i++, c++)
 | |
| 		*b++ = (*c >= ' ' && *c < 127) ? *c : '.';
 | |
| 	    *b = '\0';
 | |
| 	}
 | |
| 
 | |
| 	printf("%d\t%d\t%ld\t%ld\t%s\t%ld\t%s\n", ii,
 | |
| 	       (m == mf) ? fi++ : ui++,
 | |
| 	       (long)m, (long)m->len,
 | |
| 	       (m == mf) ? "free" : ((m == ms) ? "small" : "used"),
 | |
| 	       (m == mf) ? (f += m->len) : (u += m->len),
 | |
| 	       buf);
 | |
| 
 | |
| 	if (m == mf)
 | |
| 	    mf = mf->next;
 | |
|     }
 | |
| 
 | |
|     if (OPT_ISSET(ops,'v')) {
 | |
| 	printf("\nHere is some information about the small blocks used.\n");
 | |
| 	printf("For each size the arrays with the number of free and the\n");
 | |
| 	printf("number of used blocks are shown.\n");
 | |
|     }
 | |
|     printf("\nsmall blocks:\nsize\tblocks (free/used)\n");
 | |
| 
 | |
|     for (i = 0; i < M_NSMALL; i++)
 | |
| 	if (m_small[i]) {
 | |
| 	    printf("%ld\t", (long)i * M_ISIZE);
 | |
| 
 | |
| 	    for (ii = 0, m = m_small[i]; m; m = m->next) {
 | |
| 		printf("(%ld/%ld) ", (long)(M_SNUM - m->used),
 | |
| 		       (long)m->used);
 | |
| 		if (!((++ii) & 7))
 | |
| 		    printf("\n\t");
 | |
| 	    }
 | |
| 	    putchar('\n');
 | |
| 	}
 | |
|     if (OPT_ISSET(ops,'v')) {
 | |
| 	printf("\n\nBelow is some information about the allocation\n");
 | |
| 	printf("behaviour of the zsh heaps. First the number of times\n");
 | |
| 	printf("pushheap(), popheap(), and freeheap() were called.\n");
 | |
|     }
 | |
|     printf("\nzsh heaps:\n\n");
 | |
| 
 | |
|     printf("push %d\tpop %d\tfree %d\n\n", h_push, h_pop, h_free);
 | |
| 
 | |
|     if (OPT_ISSET(ops,'v')) {
 | |
| 	printf("\nThe next list shows for several sizes the number of times\n");
 | |
| 	printf("memory of this size were taken from heaps.\n\n");
 | |
|     }
 | |
|     printf("size\tmalloc\ttotal\n");
 | |
|     for (i = 0; i < 1024; i++)
 | |
| 	if (h_m[i])
 | |
| 	    printf("%ld\t%d\t%ld\n", (long)i * H_ISIZE, h_m[i],
 | |
| 		   (long)i * H_ISIZE * h_m[i]);
 | |
|     if (h_m[1024])
 | |
| 	printf("big\t%d\n", h_m[1024]);
 | |
| 
 | |
|     unqueue_signals();
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| /**/
 | |
| #else				/* not ZSH_MEM */
 | |
| 
 | |
| /**/
 | |
| mod_export void
 | |
| zfree(void *p, UNUSED(int sz))
 | |
| {
 | |
|     if (p)
 | |
| 	free(p);
 | |
| }
 | |
| 
 | |
| /**/
 | |
| mod_export void
 | |
| zsfree(char *p)
 | |
| {
 | |
|     if (p)
 | |
| 	free(p);
 | |
| }
 | |
| 
 | |
| /**/
 | |
| #endif
 |