/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 *
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

#include <afsconfig.h>
#include <afs/param.h>
#include <afs/stds.h>

#include <roken.h>

#include <ubik.h>
#include <afs/bubasics.h>

#include "budb_errs.h"
#include "database.h"
#include "budb_internal.h"

/* block and structure allocation routines */

static int nEntries[NBLOCKTYPES];
static int sizeEntries[NBLOCKTYPES];

afs_int32
InitDBalloc(void)
{
    nEntries[0] = 0;
    sizeEntries[0] = 0;

    nEntries[volFragment_BLOCK] = NvolFragmentS;
    nEntries[volInfo_BLOCK] = NvolInfoS;
    nEntries[tape_BLOCK] = NtapeS;
    nEntries[dump_BLOCK] = NdumpS;

    sizeEntries[volFragment_BLOCK] = sizeof(struct vfBlock_frag);
    sizeEntries[volInfo_BLOCK] = sizeof(struct viBlock_info);
    sizeEntries[tape_BLOCK] = sizeof(struct tBlock_tape);
    sizeEntries[dump_BLOCK] = sizeof(struct dBlock_dump);

    return 0;
}

/* AllocBlock
 *	allocate a (basic) database block. Extends the database if
 *	no free blocks are available.
 * exit:
 *	0 - aP points to a cleared block
 *	n - error
 */

afs_int32
AllocBlock(struct ubik_trans *ut,
	   struct block *block, /* copy of data */
	   dbadr *aP)		/* db addr of block */
{
    dbadr a;

    if (db.h.freePtrs[0] == 0) {
	/* if there are no free blocks, extend the database */
	LogDebug(2, "AllocBlock: extending db\n");

	a = ntohl(db.h.eofPtr);
	if (set_header_word(ut, eofPtr, htonl(a + BLOCKSIZE)))
	    return BUDB_IO;
    } else {
	a = ntohl(db.h.freePtrs[0]);
	if (dbread(ut, a, (char *)block, sizeof(block->h))	/* read hdr */
	    ||set_header_word(ut, freePtrs[0], block->h.next)	/* set next */
	    )
	    return BUDB_IO;
    }

    /* clear and return the block */
    memset(block, 0, sizeof(*block));
    *aP = a;
    return 0;
}

/* FreeBlock
 *	Free a basic block
 * entry:
 *	bh - block header ptr. Memory copy of the block header.
 *	a - disk address of the block
 */

afs_int32
FreeBlock(struct ubik_trans *ut,
	  struct blockHeader *bh,	/* copy of data */
	  dbadr a)			/* db address of block */
{
    if (a != BlockBase(a))
	db_panic("Block addr no good");
    memset(bh, 0, sizeof(*bh));
    bh->next = db.h.freePtrs[0];
    if (set_header_word(ut, freePtrs[0], htonl(a))
	|| dbwrite(ut, a, (char *)bh, sizeof(*bh)))
	return BUDB_IO;
    return 0;
}


/* AllocStructure
 * entry:
 *	type - type of structure to allocate
 *	related - address of related block
 *	saP - db addr of structure
 *	s -  structure data
 */

afs_int32
AllocStructure(struct ubik_trans *ut, char type, dbadr related, dbadr *saP, void *s)
{
    dbadr a;			/* block addr */
    struct block b;		/* copy of data */
    int i;			/* block structure array index */
    afs_int32 *bs;		/* ptr to first word of structure */
    int nFree;

    if ((type == 0)
	|| (type > MAX_STRUCTURE_BLOCK_TYPE)
	) {
	db_panic("bad structure type");
    }
    bs = (afs_int32 *) b.a;	/* ptr to first structure of block */

    if (db.h.freePtrs[(int) type] == 0) {
	/* no free items of specified type */

	if (AllocBlock(ut, &b, &a)
	    || set_header_word(ut, freePtrs[(int) type], htonl(a))
	    ) {
	    return BUDB_IO;
	}

	b.h.next = 0;
	b.h.type = type;
	b.h.flags = 0;
	b.h.nFree = ntohs(nEntries[(int) type] - 1);
	*bs = 1;		/* not free anymore */

	if (dbwrite(ut, a, (char *)&b, sizeof(b)))
	    return BUDB_IO;
	LogDebug(2, "AllocStructure: allocated new block\n");
    } else {
	int count = 10;

	/* Only do 10 (or so) at a time, to avoid transactions which modify
	 * many buffer.
	 */

	while (1) {
	    a = ntohl(db.h.freePtrs[(int) type]);
	    if (dbread(ut, a, (char *)&b, sizeof(b)))
		return BUDB_IO;

	    nFree = ntohs(b.h.nFree);
	    if (nFree == 0)
		db_panic("nFree is zero");

	    /* Completely empty blocks go to generic free list if there are
	     * more blocks on this free list
	     */
	    if (b.h.next && (nFree == nEntries[(int) type]) && (count-- > 0)) {
		if (set_header_word(ut, freePtrs[(int) type], b.h.next)
		    || FreeBlock(ut, &b.h, a)
		    ) {
		    return BUDB_IO;
		}
		LogDebug(2, "AllocStrucure: add to free block list\n");
	    } else {
		/* we found a free structure */
		if (nFree == 1) {
		    /* if last free one: unthread block */
		    if (set_header_word(ut, freePtrs[(int) type], b.h.next))
			return BUDB_IO;
		}
		break;
	    }
	}

	/* find the free structure - arbitrarily uses first word as
	 * allocated/free status. PA.
	 */
	i = 0;
	while (*bs) {
	    i++;
	    bs = (afs_int32 *) ((char *)bs + sizeEntries[(int) type]);
	}

	if (i >= nEntries[(int) type])
	    db_panic("free count inconsistent with block");

	b.h.nFree = htons(nFree - 1);
	if (dbwrite(ut, a, (char *)&b, sizeof(b.h)))
	    return BUDB_IO;
    }
    *(afs_int32 *) s = 1;	/* make sure structure is not free */
    *saP = a + ((char *)bs - (char *)&b);

    LogDebug(3, "allocated at %d, block at %d, offset %ld\n", *saP, a,
	     (long int)((char *)bs - (char *)&b));
    /* caller must write back at least first word of structure */
    return 0;
}



afs_int32
FreeStructure(struct ubik_trans *ut,
	      char type,		/* type of structure to allocate */
	      dbadr sa)			/* db addr of structure */
{
    struct blockHeader bh;	/* header of containing block */
    dbadr a;			/* db address of block */
    int nFree;			/* new free structures count */
    afs_int32 freeWord;

    if ((type == 0) || (type > MAX_STRUCTURE_BLOCK_TYPE))
	db_panic("bad structure type");

    a = BlockBase(sa);
    if (dbread(ut, a, (char *)&bh, sizeof(bh)))
	return BUDB_IO;
    if (type != bh.type)
	db_panic("block and structure of different types");

    bh.nFree = htons(nFree = ntohs(bh.nFree) + 1);
    if (nFree > nEntries[(int) type])
	db_panic("free count too large");
    if (nFree == 1) {		/* add to free list for type */
	bh.next = db.h.freePtrs[(int) type];
	if (set_header_word(ut, freePtrs[(int) type], htonl(a)))
	    return BUDB_IO;
    }

    /* mark the structure as free, and write out block header */
    if (set_word_offset(ut, sa, &freeWord, 0, 0)
	|| dbwrite(ut, a, (char *)&bh, sizeof(bh)))
	return BUDB_IO;
    return 0;
}
