mirror of
https://github.com/lcn2/calc.git
synced 2025-08-16 01:03:29 +03:00
Converted all ASCII tabs to ASCII spaces using a 8 character tab stop, for all files, except for all Makefiles (plus rpm.mk). The `git diff -w` reports no changes.
753 lines
19 KiB
C
753 lines
19 KiB
C
/*
|
|
* block - fixed, dynamic, fifo and circular memory blocks
|
|
*
|
|
* Copyright (C) 1999-2007,2021-2023 Landon Curt Noll and Ernest Bowen
|
|
*
|
|
* Primary author: Landon Curt Noll
|
|
*
|
|
* Calc is open software; you can redistribute it and/or modify it under
|
|
* the terms of the version 2.1 of the GNU Lesser General Public License
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* Calc 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 Lesser General
|
|
* Public License for more details.
|
|
*
|
|
* A copy of version 2.1 of the GNU Lesser General Public License is
|
|
* distributed with calc under the filename COPYING-LGPL. You should have
|
|
* received a copy with calc; if not, write to Free Software Foundation, Inc.
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Under source code control: 1997/02/27 00:29:40
|
|
* File existed as early as: 1997
|
|
*
|
|
* chongo <was here> /\oo/\ http://www.isthe.com/chongo/
|
|
* Share and enjoy! :-) http://www.isthe.com/chongo/tech/comp/calc/
|
|
*/
|
|
|
|
|
|
#include <stdio.h>
|
|
#include "have_string.h"
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
#include "alloc.h"
|
|
#include "value.h"
|
|
#include "zmath.h"
|
|
#include "config.h"
|
|
#include "block.h"
|
|
#include "nametype.h"
|
|
#include "str.h"
|
|
|
|
|
|
#include "errtbl.h"
|
|
#include "banned.h" /* include after system header <> includes */
|
|
|
|
|
|
#define NBLOCKCHUNK 16
|
|
|
|
STATIC long nblockcount = 0;
|
|
STATIC long maxnblockcount = 0;
|
|
STATIC STRINGHEAD nblocknames;
|
|
STATIC NBLOCK **nblocks;
|
|
|
|
|
|
/* forward declarations */
|
|
S_FUNC void blkchk(BLOCK*);
|
|
|
|
|
|
/*
|
|
* blkalloc - allocate a block
|
|
*
|
|
* given:
|
|
* len - initial memory length of the block
|
|
* type - BLK_TYPE_XYZ
|
|
* chunk - allocation chunk size
|
|
*
|
|
* returns:
|
|
* pointer to a newly allocated BLOCK
|
|
*/
|
|
BLOCK *
|
|
blkalloc(int len, int chunk)
|
|
{
|
|
BLOCK *nblk; /* new block allocated */
|
|
|
|
/*
|
|
* firewall
|
|
*/
|
|
if (len < 0)
|
|
len = 0;
|
|
if (chunk <= 0)
|
|
chunk = BLK_CHUNKSIZE;
|
|
|
|
/*
|
|
* allocate BLOCK
|
|
*/
|
|
nblk = (BLOCK *)malloc(sizeof(BLOCK));
|
|
if (nblk == NULL) {
|
|
math_error("cannot allocate block");
|
|
not_reached();
|
|
}
|
|
|
|
/*
|
|
* initialize BLOCK
|
|
*/
|
|
nblk->blkchunk = chunk;
|
|
nblk->maxsize = ((len+chunk)/chunk)*chunk;
|
|
nblk->data = (USB8*)malloc(nblk->maxsize);
|
|
if (nblk->data == NULL) {
|
|
math_error("cannot allocate block data storage");
|
|
not_reached();
|
|
}
|
|
memset(nblk->data, 0, nblk->maxsize);
|
|
nblk->datalen = len;
|
|
|
|
/*
|
|
* return BLOCK
|
|
*/
|
|
if (conf->calc_debug & CALCDBG_BLOCK) {
|
|
blkchk(nblk);
|
|
}
|
|
return nblk;
|
|
}
|
|
|
|
|
|
/*
|
|
* blk_free - free a block
|
|
*
|
|
* NOTE: THIS IS NOT THE CALC blktrunc() BUILTIN FUNCTION!! This
|
|
* is what is called to free block storage.
|
|
*
|
|
* given:
|
|
* blk - the block to free
|
|
*/
|
|
void
|
|
blk_free(BLOCK *blk)
|
|
{
|
|
/* free if non-NULL */
|
|
if (blk != NULL) {
|
|
|
|
/* free data storage */
|
|
if (blk->data != NULL) {
|
|
free(blk->data);
|
|
}
|
|
|
|
/* free the block */
|
|
free(blk);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* blkchk - check the sanity of a block
|
|
*
|
|
* These checks should never fail if calc is working correctly. During
|
|
* debug time, we plan to call this function often. Once we are satisfied,
|
|
* we will normally call this code only in a few places.
|
|
*
|
|
* If "calc_debug" has the bit corresponding to CALCDBG_BLOCK set, this
|
|
* function is called during execution of the following builtins:
|
|
*
|
|
* alloc(), realloc(), free()
|
|
*
|
|
* given:
|
|
* blk - the BLOCK to check
|
|
*
|
|
* returns:
|
|
* if all is OK, otherwise math_error() is called and this
|
|
* function does not return
|
|
*/
|
|
S_FUNC void
|
|
blkchk(BLOCK *blk)
|
|
{
|
|
|
|
/*
|
|
* firewall - general sanity check
|
|
*/
|
|
if ((conf->calc_debug & CALCDBG_BLOCK) == 0) {
|
|
/* do nothing when debugging is disabled */
|
|
return;
|
|
}
|
|
if (blk == NULL) {
|
|
math_error("internal: blk ptr is NULL");
|
|
not_reached();
|
|
}
|
|
|
|
/*
|
|
* pointers must not be NULL
|
|
*/
|
|
if (blk->data == NULL) {
|
|
math_error("internal: blk->data ptr is NULL");
|
|
not_reached();
|
|
}
|
|
|
|
/*
|
|
* check data lengths
|
|
*/
|
|
if (blk->datalen < 0) {
|
|
math_error("internal: blk->datalen < 0");
|
|
not_reached();
|
|
}
|
|
|
|
/*
|
|
* check the datalen and datalen2 values
|
|
*/
|
|
if (blk->datalen < 0) {
|
|
math_error("internal: blk->datalen < 0");
|
|
not_reached();
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* blkrealloc - reallocate a block
|
|
*
|
|
* Reallocation of a block can change several aspects of a block.
|
|
*
|
|
* It can change the much data it holds or can hold.
|
|
*
|
|
* It can change the memory footprint (in terms of
|
|
* how much storage is malloced for current or future use).
|
|
*
|
|
* It can change the chunk size used to grow malloced size
|
|
* as the data size grows.
|
|
*
|
|
* Each of the len and chunksize may be kept the same.
|
|
*
|
|
* given:
|
|
* blk - old BLOCK to reallocate
|
|
* newlen - how much data the block holds
|
|
* newchunk - allocation chunk size (<0 ==> no change, 0 == default)
|
|
*/
|
|
BLOCK *
|
|
blkrealloc(BLOCK *blk, int newlen, int newchunk)
|
|
{
|
|
USB8 *nblk; /* realloced storage */
|
|
int newmax; /* new maximum stoage size */
|
|
|
|
/*
|
|
* firewall
|
|
*/
|
|
if (conf->calc_debug & CALCDBG_BLOCK) {
|
|
blkchk(blk);
|
|
}
|
|
|
|
/*
|
|
* process args
|
|
*/
|
|
/* newlen < 0 means do not change the length */
|
|
if (newlen < 0) {
|
|
newlen = blk->datalen;
|
|
}
|
|
/* newchunk <= 0 means do not change the chunk size */
|
|
if (newchunk < 0) {
|
|
newchunk = blk->blkchunk;
|
|
} else if (newchunk == 0) {
|
|
newchunk = BLK_CHUNKSIZE;
|
|
}
|
|
|
|
/*
|
|
* reallocate storage if we have a different allocation size
|
|
*/
|
|
newmax = ((newlen+newchunk)/newchunk)*newchunk;
|
|
if (newmax != blk->maxsize) {
|
|
|
|
/* reallocate new storage */
|
|
nblk = (USB8*)realloc(blk->data, newmax);
|
|
if (nblk == NULL) {
|
|
math_error("cannot reallocate block storage");
|
|
not_reached();
|
|
}
|
|
|
|
/* clear any new storage */
|
|
if (newmax > blk->maxsize) {
|
|
memset(nblk+blk->maxsize, 0, (newmax-blk->maxsize));
|
|
}
|
|
blk->maxsize = newmax;
|
|
|
|
/* restore the data pointers */
|
|
blk->data = nblk;
|
|
}
|
|
|
|
/*
|
|
* deal the case of a newlen == 0 early and return
|
|
*/
|
|
if (newlen == 0) {
|
|
|
|
/*
|
|
* setup the empty buffer
|
|
*
|
|
* We know that newtype is not circular since we force
|
|
* newlen to be at least 1 (because circular blocks
|
|
* always have at least one unused octet).
|
|
*/
|
|
if (blk->datalen < blk->maxsize) {
|
|
memset(blk->data, 0, blk->datalen);
|
|
} else {
|
|
memset(blk->data, 0, blk->maxsize);
|
|
}
|
|
blk->datalen = 0;
|
|
if (conf->calc_debug & CALCDBG_BLOCK) {
|
|
blkchk(blk);
|
|
}
|
|
return blk;
|
|
}
|
|
|
|
/*
|
|
* Set the data length
|
|
*
|
|
* We also know that the new block is not empty since we have
|
|
* already dealt with that case above.
|
|
*
|
|
* After this section of code, limit and datalen will be
|
|
* correct in terms of the new type.
|
|
*/
|
|
if (newlen > blk->datalen) {
|
|
|
|
/* there is new storage, clear it */
|
|
memset(blk->data + blk->datalen, 0, newlen-blk->datalen);
|
|
/* growing storage for blocks grows the data */
|
|
blk->datalen = newlen;
|
|
|
|
} else if (newlen <= blk->datalen) {
|
|
|
|
/* the block will be full */
|
|
blk->datalen = newlen;
|
|
}
|
|
|
|
/*
|
|
* return realloced type
|
|
*/
|
|
if (conf->calc_debug & CALCDBG_BLOCK) {
|
|
blkchk(blk);
|
|
}
|
|
return blk;
|
|
}
|
|
|
|
|
|
/*
|
|
* blktrunc - truncate a BLOCK down to a minimal fixed block
|
|
*
|
|
* NOTE: THIS IS NOT THE INTERNAL CALC FREE FUNCTION!! This
|
|
* is what blktrunc() builtin calls to reduce storage of a block
|
|
* down to an absolute minimum.
|
|
*
|
|
* This actually forms a zero length fixed block with a chunk of 1.
|
|
*
|
|
* given:
|
|
* blk - the BLOCK to shrink
|
|
*
|
|
* returns:
|
|
* pointer to a newly allocated BLOCK
|
|
*/
|
|
void
|
|
blktrunc(BLOCK *blk)
|
|
{
|
|
/*
|
|
* firewall
|
|
*/
|
|
if (conf->calc_debug & CALCDBG_BLOCK) {
|
|
blkchk(blk);
|
|
}
|
|
|
|
/*
|
|
* free the old storage
|
|
*/
|
|
free(blk->data);
|
|
|
|
/*
|
|
* setup as a zero length fixed block
|
|
*/
|
|
blk->blkchunk = 1;
|
|
blk->maxsize = 1;
|
|
blk->datalen = 0;
|
|
blk->data = (USB8*)malloc(1);
|
|
if (blk->data == NULL) {
|
|
math_error("cannot allocate truncated block storage");
|
|
not_reached();
|
|
}
|
|
blk->data[0] = (USB8)0;
|
|
if (conf->calc_debug & CALCDBG_BLOCK) {
|
|
blkchk(blk);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* blk_copy - copy a block
|
|
*
|
|
* given:
|
|
* blk - the block to copy
|
|
*
|
|
* returns:
|
|
* pointer to copy of blk
|
|
*/
|
|
BLOCK *
|
|
blk_copy(BLOCK *blk)
|
|
{
|
|
BLOCK *nblk; /* copy of blk */
|
|
|
|
/*
|
|
* malloc new block
|
|
*/
|
|
nblk = (BLOCK *)malloc(sizeof(BLOCK));
|
|
if (nblk == NULL) {
|
|
math_error("blk_copy: cannot malloc BLOCK");
|
|
not_reached();
|
|
}
|
|
|
|
/*
|
|
* duplicate most of the block
|
|
*/
|
|
*nblk = *blk;
|
|
|
|
/*
|
|
* duplicate block data
|
|
*/
|
|
nblk->data = (USB8 *)malloc(blk->maxsize);
|
|
if (nblk->data == NULL) {
|
|
math_error("blk_copy: cannot duplicate block data");
|
|
not_reached();
|
|
}
|
|
memcpy(nblk->data, blk->data, blk->maxsize);
|
|
return nblk;
|
|
}
|
|
|
|
|
|
/*
|
|
* blk_cmp - compare blocks
|
|
*
|
|
* given:
|
|
* a first BLOCK
|
|
* b second BLOCK
|
|
*
|
|
* returns:
|
|
* true => BLOCKs are different
|
|
* false => BLOCKs are the same
|
|
*/
|
|
int
|
|
blk_cmp(BLOCK *a, BLOCK *b)
|
|
{
|
|
/*
|
|
* firewall and quick check
|
|
*/
|
|
if (a == b) {
|
|
/* pointers to the same object */
|
|
return false;
|
|
}
|
|
if (a == NULL || b == NULL) {
|
|
/* one pointer is NULL, so they differ */
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* compare lengths
|
|
*/
|
|
if (a->datalen != b->datalen) {
|
|
/* different lengths are different */
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* compare the section
|
|
*
|
|
* We have the same lengths and types, so compare the data sections.
|
|
*/
|
|
if (memcmp(a->data, b->data, a->datalen) != 0) {
|
|
/* different sections are different */
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* the blocks are the same
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Print chunksize, maxsize, datalen on line line and if datalen > 0,
|
|
* up to * 30 octets on the following line, with ... if datalen exceeds 30.
|
|
*/
|
|
/*ARGSUSED*/
|
|
void
|
|
blk_print(BLOCK *blk)
|
|
{
|
|
long i;
|
|
bool havetail;
|
|
USB8 *ptr;
|
|
|
|
/* XXX - should use the config parameters for better print control */
|
|
|
|
printf("chunksize = %d, maxsize = %d, datalen = %d\n\t",
|
|
(int)blk->blkchunk, (int)blk->maxsize, (int)blk->datalen);
|
|
i = blk->datalen;
|
|
havetail = (i > 30);
|
|
if (havetail)
|
|
i = 30;
|
|
ptr = blk->data;
|
|
while (i-- > 0)
|
|
printf("%02x", *ptr++);
|
|
if (havetail)
|
|
printf("...");
|
|
}
|
|
|
|
|
|
/*
|
|
* Routine to print id and name of a named block and details of its
|
|
* block component.
|
|
*/
|
|
void
|
|
nblock_print(NBLOCK *nblk)
|
|
{
|
|
BLOCK *blk;
|
|
|
|
/* XXX - use the config parameters for better print control */
|
|
|
|
blk = nblk->blk;
|
|
printf("block %d: %s\n\t", nblk->id, nblk->name);
|
|
if (blk->data == NULL) {
|
|
printf("chunksize = %d, maxsize = %d, datalen = %d\n\t",
|
|
(int)blk->blkchunk, (int)blk->maxsize, (int)blk->datalen);
|
|
printf("NULL");
|
|
} else {
|
|
blk_print(blk);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* realloc a named block specified by its id. The new datalen and
|
|
* chunksize are specified by len >= 0 and chunk > 0. If len < 0
|
|
* or chunk <= 0, these values used are the current datalen and
|
|
* chunksize, so there is no point in calling this unless len >= 0
|
|
* and/or chunk > 0.
|
|
* No reallocation occurs if the new maxsize is equal to the old maxsize.
|
|
*/
|
|
NBLOCK *
|
|
reallocnblock(int id, int len, int chunk)
|
|
{
|
|
BLOCK *blk;
|
|
int newsize;
|
|
int oldsize;
|
|
USB8* newdata;
|
|
|
|
/* Fire wall */
|
|
if (id < 0 || id >= nblockcount) {
|
|
math_error("Bad id in call to reallocnblock");
|
|
not_reached();
|
|
}
|
|
|
|
blk = nblocks[id]->blk;
|
|
if (len < 0)
|
|
len = blk->datalen;
|
|
if (chunk < 0)
|
|
chunk = blk->blkchunk;
|
|
else if (chunk == 0)
|
|
chunk = BLK_CHUNKSIZE;
|
|
newsize = (1 + len/chunk) * chunk;
|
|
oldsize = blk->maxsize;
|
|
newdata = blk->data;
|
|
if (newdata == NULL) {
|
|
newdata = malloc(newsize);
|
|
if (newdata == NULL) {
|
|
math_error("Allocation failed");
|
|
not_reached();
|
|
}
|
|
} else if (newsize != oldsize) {
|
|
newdata = realloc(blk->data, newsize);
|
|
if (newdata == NULL) {
|
|
math_error("Reallocation failed");
|
|
not_reached();
|
|
}
|
|
}
|
|
memset(newdata + len, 0, newsize - len);
|
|
|
|
blk->maxsize = newsize;
|
|
blk->datalen = len;
|
|
blk->blkchunk = chunk;
|
|
blk->data = newdata;
|
|
return nblocks[id];
|
|
}
|
|
|
|
|
|
/*
|
|
* Create and return a new namedblock with specified name, len and
|
|
* chunksize.
|
|
*/
|
|
NBLOCK *
|
|
createnblock(char *name, int len, int chunk)
|
|
{
|
|
NBLOCK *res;
|
|
char *newname;
|
|
|
|
if (nblockcount >= maxnblockcount) {
|
|
if (maxnblockcount <= 0) {
|
|
maxnblockcount = NBLOCKCHUNK;
|
|
nblocks = (NBLOCK **)malloc(NBLOCKCHUNK *
|
|
sizeof(NBLOCK *));
|
|
if (nblocks == NULL) {
|
|
maxnblockcount = 0;
|
|
math_error("unable to malloc new named blocks");
|
|
not_reached();
|
|
}
|
|
} else {
|
|
maxnblockcount += NBLOCKCHUNK;
|
|
nblocks = (NBLOCK **)realloc(nblocks, maxnblockcount *
|
|
sizeof(NBLOCK *));
|
|
if (nblocks == NULL) {
|
|
maxnblockcount = 0;
|
|
math_error("cannot malloc more named blocks");
|
|
not_reached();
|
|
}
|
|
}
|
|
}
|
|
if (nblockcount == 0)
|
|
initstr(&nblocknames);
|
|
if (findstr(&nblocknames, name) >= 0) {
|
|
math_error("Named block already exists!!!");
|
|
not_reached();
|
|
}
|
|
newname = addstr(&nblocknames, name);
|
|
if (newname == NULL) {
|
|
math_error("Block name allocation failed");
|
|
not_reached();
|
|
}
|
|
|
|
res = (NBLOCK *) malloc(sizeof(NBLOCK));
|
|
if (res == NULL) {
|
|
math_error("Named block allocation failed");
|
|
not_reached();
|
|
}
|
|
|
|
nblocks[nblockcount] = res;
|
|
res->name = newname;
|
|
res->subtype = V_NOSUBTYPE;
|
|
res->id = nblockcount++;
|
|
res->blk = blkalloc(len, chunk);
|
|
return res;
|
|
}
|
|
|
|
|
|
/*
|
|
* find a named block
|
|
*/
|
|
int
|
|
findnblockid(char * name)
|
|
{
|
|
return findstr(&nblocknames, name);
|
|
}
|
|
|
|
|
|
/*
|
|
* free data block for named block with specified id
|
|
*/
|
|
int
|
|
removenblock(int id)
|
|
{
|
|
NBLOCK *nblk;
|
|
|
|
if (id < 0 || id >= nblockcount)
|
|
return E_BLKFREE_3;
|
|
nblk = nblocks[id];
|
|
if (nblk->blk->data == NULL)
|
|
return 0;
|
|
if (nblk->subtype & V_NOREALLOC)
|
|
return E_BLKFREE_5;
|
|
free(nblk->blk->data);
|
|
nblk->blk->data = NULL;
|
|
nblk->blk->maxsize = 0;
|
|
nblk->blk->datalen = 0;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* count number of current unfreed named blocks
|
|
*/
|
|
int
|
|
countnblocks(void)
|
|
{
|
|
int n;
|
|
int id;
|
|
|
|
for (n = 0, id = 0; id < nblockcount; id++) {
|
|
if (nblocks[id]->blk->data != NULL)
|
|
n++;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
|
|
/*
|
|
* display id and name for each unfreed named block
|
|
*/
|
|
void
|
|
shownblocks(void)
|
|
{
|
|
int id;
|
|
|
|
if (countnblocks() == 0) {
|
|
printf("No unfreed named blocks\n\n");
|
|
return;
|
|
}
|
|
printf(" id name\n");
|
|
printf("---- -----\n");
|
|
for (id = 0; id < nblockcount; id++) {
|
|
if (nblocks[id]->blk->data != NULL)
|
|
printf("%3d %s\n", id, nblocks[id]->name);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
|
|
/*
|
|
* Return pointer to nblock with specified id, NULL if never created.
|
|
* The memory for the nblock found may have been freed.
|
|
*/
|
|
NBLOCK *
|
|
findnblock(int id)
|
|
{
|
|
if (id < 0 || id >= nblockcount)
|
|
return NULL;
|
|
return nblocks[id];
|
|
}
|
|
|
|
|
|
/*
|
|
* Create a new block with specified newlen and new chunksize and copy
|
|
* min(newlen, oldlen) octets to the new block. The old block is
|
|
* not changed.
|
|
*/
|
|
BLOCK *
|
|
copyrealloc(BLOCK *blk, int newlen, int newchunk)
|
|
{
|
|
BLOCK * newblk;
|
|
int oldlen;
|
|
|
|
oldlen = blk->datalen;
|
|
|
|
if (newlen < 0) /* retain length */
|
|
newlen = oldlen;
|
|
|
|
if (newchunk < 0) /* retain chunksize */
|
|
newchunk = blk->blkchunk;
|
|
else if (newchunk == 0) /* use default chunksize */
|
|
newchunk = BLK_CHUNKSIZE;
|
|
|
|
|
|
newblk = blkalloc(newlen, newchunk);
|
|
|
|
if (newlen < oldlen)
|
|
oldlen = newlen;
|
|
|
|
if (newlen > 0)
|
|
memcpy(newblk->data, blk->data, oldlen);
|
|
|
|
return newblk;
|
|
}
|