Loading...
// SPDX-License-Identifier: GPL-2.0+
/*
 * Handles a contiguous list of pointers which be allocated and freed
 *
 * Copyright 2023 Google LLC
 * Written by Simon Glass <sjg@chromium.org>
 */

#include <alist.h>
#include <display_options.h>
#include <log.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>

enum {
	ALIST_INITIAL_SIZE	= 4,	/* default size of unsized list */
};

bool alist_init(struct alist *lst, uint obj_size, uint start_size)
{
	/* Avoid realloc for the initial size to help malloc_simple */
	memset(lst, '\0', sizeof(struct alist));
	if (start_size) {
		lst->data = calloc(obj_size, start_size);
		if (!lst->data) {
			lst->flags = ALISTF_FAIL;
			return false;
		}
		lst->alloc = start_size;
	}
	lst->obj_size = obj_size;

	return true;
}

void alist_uninit(struct alist *lst)
{
	free(lst->data);

	/* Clear fields to avoid any confusion */
	memset(lst, '\0', sizeof(struct alist));
}

void alist_empty(struct alist *lst)
{
	lst->count = 0;
}

/**
 * alist_expand_to() - Expand a list to the given size
 *
 * @lst: List to modify
 * @inc_by: Amount to expand to
 * Return: true if OK, false if out of memory
 */
static bool alist_expand_to(struct alist *lst, uint new_alloc)
{
	void *new_data;

	if (lst->flags & ALISTF_FAIL)
		return false;

	/* avoid using realloc() since it increases code size */
	new_data = malloc(lst->obj_size * new_alloc);
	if (!new_data) {
		lst->flags |= ALISTF_FAIL;
		return false;
	}

	memcpy(new_data, lst->data, lst->obj_size * lst->alloc);
	free(lst->data);

	memset(new_data + lst->obj_size * lst->alloc, '\0',
	       lst->obj_size * (new_alloc - lst->alloc));
	lst->alloc = new_alloc;
	lst->data = new_data;

	return true;
}

bool alist_expand_by(struct alist *lst, uint inc_by)
{
	return alist_expand_to(lst, lst->alloc + inc_by);
}

/**
 * alist_expand_min() - Expand to at least the provided size
 *
 * Expands to the lowest power of two which can incorporate the new size
 *
 * @lst: alist to expand
 * @min_alloc: Minimum new allocated size; if 0 then ALIST_INITIAL_SIZE is used
 * Return: true if OK, false if out of memory
 */
static bool alist_expand_min(struct alist *lst, uint min_alloc)
{
	uint new_alloc;

	for (new_alloc = lst->alloc ?: ALIST_INITIAL_SIZE;
	     new_alloc < min_alloc;)
		new_alloc *= 2;

	return alist_expand_to(lst, new_alloc);
}

const void *alist_get_ptr(const struct alist *lst, uint index)
{
	if (index >= lst->count)
		return NULL;

	return lst->data + index * lst->obj_size;
}

int alist_calc_index(const struct alist *lst, const void *ptr)
{
	uint index;

	if (!lst->count || ptr < lst->data)
		return -1;

	index = (ptr - lst->data) / lst->obj_size;

	return index;
}

void alist_update_end(struct alist *lst, const void *ptr)
{
	int index;

	index = alist_calc_index(lst, ptr);
	lst->count = index == -1 ? 0 : index;
}

bool alist_chk_ptr(const struct alist *lst, const void *ptr)
{
	int index = alist_calc_index(lst, ptr);

	return index >= 0 && index < lst->count;
}

const void *alist_next_ptrd(const struct alist *lst, const void *ptr)
{
	int index = alist_calc_index(lst, ptr);

	assert(index != -1);

	return alist_get_ptr(lst, index + 1);
}

void *alist_ensure_ptr(struct alist *lst, uint index)
{
	uint minsize = index + 1;
	void *ptr;

	if (index >= lst->alloc && !alist_expand_min(lst, minsize))
		return NULL;

	ptr = lst->data + index * lst->obj_size;
	if (minsize >= lst->count)
		lst->count = minsize;

	return ptr;
}

void *alist_add_placeholder(struct alist *lst)
{
	return alist_ensure_ptr(lst, lst->count);
}

void *alist_add_ptr(struct alist *lst, void *obj)
{
	void *ptr;

	ptr = alist_add_placeholder(lst);
	if (!ptr)
		return NULL;
	memcpy(ptr, obj, lst->obj_size);

	return ptr;
}

void *alist_uninit_move_ptr(struct alist *alist, size_t *countp)
{
	void *ptr;

	if (countp)
		*countp = alist->count;
	if (!alist->count) {
		alist_uninit(alist);
		return NULL;
	}

	ptr = alist->data;

	/* Clear everything out so there is no record of the data */
	alist_init(alist, alist->obj_size, 0);

	return ptr;
}