/*
 * Copyright (c) 1996, by Sun Microsystems, Inc.
 * All rights reserved.
 */

/*LINTLIBRARY*/
#ident	"@(#)listmgr.c	1.1	96/04/05 SMI"	/* SVr4.0 1.9.1.1	*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libintl.h>
#include "pkglib.h"

/*
 * This is the module responsible for allocating and maintaining lists that
 * require allocation of memory. For certain lists, large chunks are
 * allocated once to contain a large number of entries in each chunk (bl_*
 * for block list). The other approach involves the augmentation of linked
 * lists, each entry of which is alloc'd individually.
 */
#define	ERR_CS_ALLOC	"ERROR: Cannot allocate control structure for %s array."
#define	ERR_MEM_ALLOC	"ERROR: Cannot allocate memory for %s array."

#define	MAX_ARRAYS	50

#define	ARRAY_END(x)	(bl_cs_array[x]->cur_segment->avail_ptr)
#define	REC_SIZE(x)	(bl_cs_array[x]->struct_size)
#define	EOSEG(x)	(bl_cs_array[x]->cur_segment->eoseg_ptr)
#define	GET_AVAIL(x)	(ARRAY_END(x) + REC_SIZE(x))

struct alloc_seg {
	char *seg_ptr;		/* ptr to the allocated block */
	char *avail_ptr;	/* ptr to the next available list element */
	char *eoseg_ptr;	/* last byte in the segment */
	int full;		/* segment has no available space */
	struct alloc_seg *next;	/* next record */
};

struct blk_list_cs {
	int struct_size;		/* size of a single list element */
	int count_per_block;		/* number of list elements per block */
	int block_size;			/* just to save time - alloc size */
	int data_handle;		/* list_handle for pointer array */
	struct alloc_seg *alloc_segs;	/* memory pool */

	struct alloc_seg *cur_segment;	/* the current allocated segment */
	int total_elem;			/* total elements stored */
	int contiguous;			/* use realloc to grow */
	char *desc;			/* description of the list */
};

static struct blk_list_cs *bl_cs_array[MAX_ARRAYS];
static int next_array_elem;

/* Support functions */
static int
invalid_handle(int list_handle)
{
	if (list_handle < 0 || list_handle >= next_array_elem)
		return (1);

	return (0);
}

static int
invalid_record(int list_handle, int recno)
{
	if (invalid_handle(list_handle))
		return (1);

	if (recno < 0 || recno > bl_cs_array[list_handle]->total_elem)
		return (1);

	return (0);
}

static void
free_list(int list_handle)
{
	struct blk_list_cs *bl_ptr;
	struct alloc_seg *segstr_ptr, *nextstr_ptr;

	/* Make sure this wasn't free'd earlier */
	if (bl_cs_array[list_handle] == NULL)
		return;

	bl_ptr = bl_cs_array[list_handle];

	/* First free the alloc_seg list. */
	segstr_ptr = bl_ptr->alloc_segs;

	if (segstr_ptr) {
		do {
			nextstr_ptr = segstr_ptr->next;

			/* Free the memory block. */
			free((void *)segstr_ptr->seg_ptr);

			/* Free the control structure. */
			free((void *)segstr_ptr);
			segstr_ptr = nextstr_ptr;
		} while (segstr_ptr);
	}

	/* Free the block control structure. */
	free((void *)bl_ptr->desc);
	free((void *)bl_ptr);

	bl_cs_array[list_handle] = NULL;
}

/* Allocate another alloc_seg structure. */
static int
alloc_next_seg(struct blk_list_cs *bl_ptr)
{
	struct alloc_seg *new_alloc_cs;

	if (bl_ptr->contiguous) {
		int offset_to_avail, seg_size, new_size;
		struct alloc_seg *alloc_segment;

		if (bl_ptr->alloc_segs) {
			alloc_segment = bl_ptr->alloc_segs;

			offset_to_avail = (alloc_segment->avail_ptr -
			    alloc_segment->seg_ptr);
			seg_size = (alloc_segment->eoseg_ptr -
			    alloc_segment->seg_ptr);
			new_size = (seg_size + bl_ptr->block_size);
		} else {
			if ((bl_ptr->alloc_segs =
			    (struct alloc_seg *)calloc(1,
			    sizeof (struct alloc_seg))) == NULL) {
				logerr(gettext(ERR_CS_ALLOC), (bl_ptr->desc ?
				    bl_ptr->desc : "an unknown"));
				return (0);
			}

			alloc_segment = bl_ptr->alloc_segs;

			offset_to_avail = 0;
			seg_size = 0;
			new_size = bl_ptr->block_size;
		}

		bl_ptr->cur_segment = alloc_segment;

		if ((alloc_segment->seg_ptr =
		    (char *)realloc((void *)alloc_segment->seg_ptr,
		    (unsigned)new_size)) == NULL) {
			logerr(gettext(ERR_MEM_ALLOC), (bl_ptr->desc ?
			    bl_ptr->desc : "an unknown"));
			return (0);
		}

		alloc_segment->next = NULL;

		/* reset the status */
		alloc_segment->full = 0;

		/* readjust the original pointers */
		alloc_segment->avail_ptr = alloc_segment->seg_ptr +
		    offset_to_avail;
		alloc_segment->eoseg_ptr = alloc_segment->seg_ptr + new_size;

		(void) memset(alloc_segment->avail_ptr, '\000',
		    bl_ptr->block_size);
	} else {
		/* Allocate the control structure and link it into the list. */
		if ((new_alloc_cs = (struct alloc_seg *)malloc(
		    sizeof (struct alloc_seg))) == NULL) {
			logerr(gettext(ERR_CS_ALLOC), (bl_ptr->desc ?
			    bl_ptr->desc : "an unknown"));
			return (0);
		}

		if (bl_ptr->alloc_segs == NULL) {
			/*
			 * If this is the first allocation, then initialize
			 * the head pointer and set cur_segment to this first
			 * block of memory.
			 */
			bl_ptr->alloc_segs = new_alloc_cs;
		} else {
			/*
			 * Otherwise, point the current cur_segment to the
			 * next one and then point to the new one.
			 */
			bl_ptr->cur_segment->next = new_alloc_cs;
		}

		new_alloc_cs->next = NULL;
		bl_ptr->cur_segment = new_alloc_cs;

		new_alloc_cs->full = 0;

		/* Now allocate the block of memory that this controls. */
		if ((new_alloc_cs->seg_ptr = calloc(bl_ptr->count_per_block,
		    bl_ptr->struct_size)) == NULL) {
			logerr(gettext(ERR_MEM_ALLOC), (bl_ptr->desc ?
			    bl_ptr->desc : "an unknown"));
			return (0);
		}

		new_alloc_cs->avail_ptr = new_alloc_cs->seg_ptr;
		new_alloc_cs->eoseg_ptr = (new_alloc_cs->seg_ptr +
		    bl_ptr->block_size);
	}

	return (1);
}

/*
 * These first functions (beginning with bl_*) manage simple block lists. The
 * pointers returned, may get lost if they aren't assigned to an array or
 * something. While individual records can be obtained by record number, the
 * process isn't very efficient. Look to the array management section
 * (ar_*)for an easily administrable list.
 */

/*
 * Create a block list. Allocate memory for a block list structure and
 * initialize that structure. This doesn't actually allocate memory for the
 * list yet, just the controlling data structure. Returns -1 on failure and a
 * valid block list handle otherwise.
 *
 * NOTE: At the time of writing, it was not seen as important to recover block
 * pointers made available with a bl_free() (two of these at most in
 * pkginstall). If this became important later, we could trade efficiency for
 * speed by ignoring next_array_elem and actually scanning through the array
 * for a NULL pointer and then return that.
 */
int
bl_create(int count_per_block, int struct_size, char *desc)
{
	struct blk_list_cs *bl_ptr;
	int retval;

	if ((bl_cs_array[next_array_elem] =
	    (struct blk_list_cs *)calloc(1, sizeof (struct blk_list_cs))) ==
	    NULL) {
		logerr(gettext(ERR_CS_ALLOC), (desc ? desc : "an unknown"));
		return (-1);
	}

	bl_ptr = bl_cs_array[next_array_elem];
	retval = next_array_elem++;

	bl_ptr->data_handle = -1;
	bl_ptr->struct_size = struct_size;
	bl_ptr->count_per_block = count_per_block;
	bl_ptr->block_size = (count_per_block * struct_size);
	bl_ptr->desc = strdup((desc ? desc : "unknown"));

	return (retval);
}

/*
 * Get the next available entry in the list. This will allocate memory as
 * required based on the initialization values in bl_create(). Returns a
 * pointer to the allocated memory segment or NULL if operation was not
 * possible.
 */
char *
bl_next_avail(int list_handle)
{
	struct blk_list_cs *bl_ptr;
	char *retval;

	if (invalid_handle(list_handle))
		return (NULL);

	bl_ptr = bl_cs_array[list_handle];

	/*
	 * Allocate more memory if none is allocated yet or our last access
	 * filled the allotted segment.
	 */
	if (bl_ptr->cur_segment == NULL || bl_ptr->cur_segment->full)
		if (!alloc_next_seg(bl_ptr))
			return (NULL);

	/* Get the correct pointer. */
	retval = bl_ptr->cur_segment->avail_ptr;

	/* Advance it and mark if full. */
	bl_ptr->cur_segment->avail_ptr += bl_ptr->struct_size;
	bl_ptr->total_elem++;

	if (bl_ptr->cur_segment->avail_ptr >= bl_ptr->cur_segment->eoseg_ptr)
		bl_ptr->cur_segment->full = 1;

	return (retval);
}

char *
bl_get_record(int list_handle, int recno)
{
	struct blk_list_cs *bl_ptr;
	struct alloc_seg *cur_as_ptr;
	int cur_rec = 0;

	if (invalid_record(list_handle, recno))
		return (NULL);

	bl_ptr = bl_cs_array[list_handle];

	cur_as_ptr = bl_ptr->alloc_segs;

	while (recno > (cur_rec + bl_ptr->count_per_block)) {
		cur_as_ptr = cur_as_ptr->next;

		if (cur_as_ptr == NULL)
			return (NULL);

		cur_rec += bl_ptr->count_per_block;
	}

	/*
	 * Now cur_as_ptr points to the allocated segment bearing the
	 * intended record and all we do now is move down that by the
	 * remaining record lengths.
	 */

	return ((char *)cur_as_ptr + ((recno - cur_rec) * bl_ptr->struct_size));
}

void
bl_free(int list_handle)
{
	int cur_handle;

	if (list_handle == -1) {
		for (cur_handle = 0; cur_handle < next_array_elem;
		    cur_handle++) {
			free_list(cur_handle);
		}
	} else {
		if (invalid_handle(list_handle))
			return;

		free_list(list_handle);
	}
}

/*
 * These are the array management functions. They insert into (and can return
 * a pointer to) a contiguous list of pointers to stuff. This keeps
 * everything together in a very handy package and is very similar in
 * appearance to the arrays created by the old AT&T code. The method for
 * presenting the interface is entirely different, however.
 */

/*
 * This constructs, maintains and returns pointers into a growable array of
 * pointers to structures of the form
 *	struct something *array[n]
 * The last element in the array is always NULL.
 */
int
ar_create(int count_per_block, int struct_size, char *desc)
{
	int data_handle, retval;
	char ar_desc[60];
	struct blk_list_cs *array_ptr;

	if ((data_handle = bl_create(count_per_block, struct_size, desc)) == -1)
		return (-1);

	sprintf(ar_desc, "%s pointer", desc);
	if ((retval = bl_create(count_per_block, sizeof (char *),
	    ar_desc)) == -1)
		return (-1);

	array_ptr = bl_cs_array[retval];

	array_ptr->contiguous = 1;
	array_ptr->data_handle = data_handle;

	return (retval);
}

/* Return a pointer to the first element in the array. */
char **
ar_get_head(int list_handle)
{
	if (invalid_handle(list_handle) ||
	    bl_cs_array[list_handle]->alloc_segs == NULL)
		return (NULL);

	return ((char **)bl_cs_array[list_handle]->alloc_segs->seg_ptr);
}

/*
 * Free up the entry in the array indicated by index, but hold onto it for
 * future use.
 */
int
ar_delete(int list_handle, int index)
{
	char **array;
	char *deleted_rec;
	int i;
	struct blk_list_cs *list_ptr, *data_ptr;

	if ((array = ar_get_head(list_handle)) == NULL)
		return (0);

	if (invalid_record(list_handle, index))
		return (0);

	/* Get the pointer to the array control structure. */
	list_ptr = bl_cs_array[list_handle];

	if (!(list_ptr->contiguous))
		return (0);	/* This isn't an array. */

	data_ptr = bl_cs_array[list_ptr->data_handle];

	/*
	 * Since this looks just like an array. Record the pointer being
	 * deleted for insertion into the avail list at the end and move all
	 * elements below it up one.
	 */
	deleted_rec = array[index];

	for (i = index; array[i] != NULL; i++)
		array[i] = array[i+1];

	/*
	 * Now insert the deleted entry into the avails list after the NULL
	 * and adjust the avail_ptr to point to the NULL again.
	 */
	array[i] = deleted_rec;
	list_ptr->alloc_segs->avail_ptr -= list_ptr->struct_size;

	/* Adjust other entries in the control structure. */
	list_ptr->alloc_segs->full = 0;
	list_ptr->total_elem -= 1;

	/* Clear the deleted data area. */
	(void) memset(deleted_rec, '\000', data_ptr->struct_size);

	return (1);
}

/*
 * Return a new pointer to a structure pointer. Find an available element in
 * the array and point it at an available element in the data pool
 * constructed of block lists. Allocate new memory as necessary.
 */
char **
ar_next_avail(int list_handle)
{
	struct blk_list_cs *array_ptr;
	char *data_area, **pointer_area;

	if (invalid_handle(list_handle) ||
	    !(bl_cs_array[list_handle]->contiguous) ||
	    invalid_handle(bl_cs_array[list_handle]->data_handle))
		return (NULL);

	array_ptr = bl_cs_array[list_handle];

	/*
	 * First see if an avail has already been allocated (it will be right
	 * after the NULL termination of the array if it exists). Return
	 * that, if found.
	 */
	if ((bl_cs_array[list_handle]->cur_segment != NULL) &&
	    (ARRAY_END(list_handle) + REC_SIZE(list_handle) <
	    EOSEG(list_handle)) &&
	    (*(pointer_area = (char **) GET_AVAIL(list_handle)) != NULL)) {
		/* We can reclaim a previous deletion. */
		data_area = *pointer_area;

		*(char **)(ARRAY_END(list_handle)) = data_area;	/* reactivate */
		*pointer_area-- = NULL;	/* new end */

		array_ptr->cur_segment->avail_ptr += array_ptr->struct_size;
		array_ptr->total_elem++;
	} else {
		/*
		 * Get the data area first. This is the record we're pointing
		 * to from the array.
		 */
		data_area = bl_next_avail(array_ptr->data_handle);

		/* Now get the next pointer from the pointer array. */
		pointer_area = (char **) bl_next_avail(list_handle);

		*pointer_area = data_area;

		/*
		 * The array must be NULL terminated. So, if the block list
		 * structure is full, we have to grow it without resetting
		 * the avail pointer. NOTE: This will only work for a
		 * contiguous list!
		 */
		if (bl_cs_array[list_handle]->alloc_segs->full) {
			char **old_list_pointer, **new_list_pointer;

			/*
			 * First grab the old numbers in case realloc() moves
			 * everything.
			 */
			old_list_pointer = ar_get_head(list_handle);

			/*
			 * Now allocate additional contiguous memory, moving
			 * the original block if necessary.
			 */
			if (!alloc_next_seg(array_ptr))
				return (NULL);

			/*
			 * Now determine if everything moved and readjust the
			 * pointer_area if required.
			 */
			new_list_pointer = ar_get_head(list_handle);

			if (old_list_pointer != new_list_pointer) {
				pointer_area += (new_list_pointer -
				    old_list_pointer);
			}
		}
	}

	return (pointer_area);
}

/*
 * Relinquish the array back to the memory pool. Note that there is no method
 * provided to free *all* arrays.
 */
void
ar_free(int list_handle)
{
	if (invalid_handle(list_handle))
		return;

	bl_free(bl_cs_array[list_handle]->data_handle);
	bl_free(list_handle);
}
