// SPDX-License-Identifier: BSD-2-Clause-Patent
/*
 * mock-variables.c - a mock GetVariable/SetVariable/GNVN/etc
 *                    implementation for testing.
 * Copyright Peter Jones <pjones@redhat.com>
 */
#include "shim.h"
#include "mock-variables.h"

#include <dirent.h>
#include <efivar/efivar.h>
#include <err.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-function"

list_t mock_default_variable_limits;
list_t *mock_qvi_limits = &mock_default_variable_limits;
list_t *mock_sv_limits = &mock_default_variable_limits;

list_t mock_variables = LIST_HEAD_INIT(mock_variables);

mock_sort_policy_t mock_variable_sort_policy = MOCK_SORT_APPEND;
mock_sort_policy_t mock_config_table_sort_policy = MOCK_SORT_APPEND;

UINT32 mock_variable_delete_attr_policy;

mock_set_variable_pre_hook_t *mock_set_variable_pre_hook = NULL;
mock_set_variable_post_hook_t *mock_set_variable_post_hook = NULL;
mock_get_variable_pre_hook_t *mock_get_variable_pre_hook = NULL;
mock_get_variable_post_hook_t *mock_get_variable_post_hook = NULL;
mock_get_next_variable_name_pre_hook_t *mock_get_next_variable_name_pre_hook = NULL;
mock_get_next_variable_name_post_hook_t *mock_get_next_variable_name_post_hook = NULL;
mock_query_variable_info_pre_hook_t *mock_query_variable_info_pre_hook = NULL;
mock_query_variable_info_post_hook_t *mock_query_variable_info_post_hook = NULL;

static EFI_STATUS
mock_sv_pre_hook(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, UINTN size,
		 VOID *data)
{
	EFI_STATUS status = EFI_SUCCESS;
	if (mock_set_variable_pre_hook)
		status = mock_set_variable_pre_hook(name, guid,
						    attrs, size, data);
	return status;
}

static void
mock_sv_post_hook_(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, UINTN size,
		   VOID *data, EFI_STATUS *status, mock_variable_op_t op,
		   const char * const file, const int line,
		   const char * const func)
{
	if (mock_set_variable_post_hook)
		mock_set_variable_post_hook(name, guid, attrs, size,
		                            data, status, op, file, line, func);
}
#define mock_sv_post_hook(name, guid, attrs, size, data, status, op) \
	mock_sv_post_hook_(name, guid, attrs, size, data, status, op,\
			   __FILE__, __LINE__, __func__)

static EFI_STATUS
mock_gv_pre_hook(CHAR16 *name, EFI_GUID *guid, UINT32 *attrs, UINTN *size,
                 VOID *data)
{
	EFI_STATUS status = EFI_SUCCESS;
	if (mock_get_variable_pre_hook)
		status = mock_get_variable_pre_hook(name, guid,
						    attrs, size, data);
	return status;
}

static void
mock_gv_post_hook_(CHAR16 *name, EFI_GUID *guid, UINT32 *attrs, UINTN *size,
		   VOID *data, EFI_STATUS *status, const char * const file,
		   const int line, const char * const func)
{
	if (mock_get_variable_post_hook)
		mock_get_variable_post_hook(name, guid, attrs, size, data,
					    status, file, line, func);
}
#define mock_gv_post_hook(name, guid, attrs, size, data, status) \
	mock_gv_post_hook_(name, guid, attrs, size, data, status,\
			   __FILE__, __LINE__, __func__)

static EFI_STATUS
mock_gnvn_pre_hook(UINTN *size, CHAR16 *name, EFI_GUID *guid)
{
	EFI_STATUS status = EFI_SUCCESS;
	if (mock_get_next_variable_name_pre_hook)
		status = mock_get_next_variable_name_pre_hook(size, name, guid);
	return status;
}

static void
mock_gnvn_post_hook_(UINTN *size, CHAR16 *name, EFI_GUID *guid,
		     EFI_STATUS *status, const char * const file,
		     const int line, const char * const func)
{
	if (mock_get_next_variable_name_post_hook)
		mock_get_next_variable_name_post_hook(size, name, guid, status,
						      file, line, func);
}
#define mock_gnvn_post_hook(size, name, guid, status) \
	mock_gnvn_post_hook_(size, name, guid, status,\
			     __FILE__, __LINE__, __func__)

static EFI_STATUS
mock_qvi_pre_hook(UINT32 attrs, UINT64 *max_var_storage,
                  UINT64 *remaining_var_storage, UINT64 *max_var_size)
{
	EFI_STATUS status = EFI_SUCCESS;
	if (mock_query_variable_info_pre_hook)
		status = mock_query_variable_info_pre_hook(
					attrs, max_var_storage,
					remaining_var_storage, max_var_size);
	return status;
}

static void
mock_qvi_post_hook_(UINT32 attrs, UINT64 *max_var_storage,
		    UINT64 *remaining_var_storage, UINT64 *max_var_size,
		    EFI_STATUS *status, const char * const file,
		    const int line, const char * const func)
{
	if (mock_query_variable_info_post_hook)
		mock_query_variable_info_post_hook(attrs, max_var_storage,
		                                   remaining_var_storage,
		                                   max_var_size, status,
						   file, line, func);
}
#define mock_qvi_post_hook(attrs, max_var_storage, remaining_var_storage,\
			   max_var_size, status) \
	mock_qvi_post_hook_(attrs, max_var_storage, remaining_var_storage,\
			    max_var_size, status, \
			    __FILE__, __LINE__, __func__)

static const size_t guidstr_size = sizeof("8be4df61-93ca-11d2-aa0d-00e098032b8c");

static int
variable_limits_cmp(const struct mock_variable_limits * const v0,
		    const struct mock_variable_limits * const v1)
{
	UINT32 mask = EFI_VARIABLE_NON_VOLATILE |
		      EFI_VARIABLE_BOOTSERVICE_ACCESS |
		      EFI_VARIABLE_RUNTIME_ACCESS;

	return (v0->attrs & mask) - (v1->attrs & mask);
}

static INT64
variable_cmp(const struct mock_variable * const v0,
	     const struct mock_variable * const v1)
{
	INT64 ret;
	if (v0 == NULL || v1 == NULL)
		return (uintptr_t)v0 - (uintptr_t)v1;

	ret = CompareGuid(&v0->guid, &v1->guid);
	ret <<= 8ul;
#if (defined(SHIM_DEBUG) && SHIM_DEBUG > 3)
	printf("%s:%d:%s():  "GUID_FMT" %s "GUID_FMT" (0x%011"PRIx64" %"PRId64")\n",
	       __FILE__, __LINE__-1, __func__,
	       GUID_ARGS(v0->guid),
	       ret < 0 ? "<" : (ret > 0 ? ">" : "="),
	       GUID_ARGS(v1->guid),
	       (UINT64)ret & 0x1fffffffffful,
	       ret);
#endif
	if (ret != 0) {
		return ret;
	}

	ret = StrCmp(v0->name, v1->name);
#if (defined(SHIM_DEBUG) && SHIM_DEBUG > 3)
	printf("%s:%d:%s():   \"%s\" %s \"%s\" (0x%02hhx (%d)\n",
	       __FILE__, __LINE__-1, __func__,
	       Str2str(v0->name),
	       ret < 0 ? "<" : (ret > 0 ? ">" : "=="),
	       Str2str(v1->name),
	       ret, ret);
#endif
	return ret;
}

static char *
list2var(list_t *pos)
{
	static char buf0[1024];
	static char buf1[1024];
	char *out;
	static int n;
	struct mock_variable *var;

	out = n++ % 2 ? buf0 : buf1;
	if (n > 1)
		n -= 2;
	SetMem(out, 1024, 0);
	if (pos == &mock_variables) {
		strcpy(out, "list tail");
		return out;
	}
	var = list_entry(pos, struct mock_variable, list);
	snprintf(out, 1023, GUID_FMT"-%s",
		 GUID_ARGS(var->guid),
		 Str2str(var->name));
	return out;
}

EFI_STATUS EFIAPI
mock_get_variable(CHAR16 *name, EFI_GUID *guid, UINT32 *attrs, UINTN *size,
                  VOID *data)
{
	list_t *pos = NULL;
	struct mock_variable goal = {
		.name = name,
		.guid = *guid,
	};
	struct mock_variable *result = NULL;
	EFI_STATUS status;

	status = mock_gv_pre_hook(name, guid, attrs, size, data);
	if (EFI_ERROR(status))
		return status;

	if (name == NULL || guid == NULL || size == NULL) {
		status = EFI_INVALID_PARAMETER;
		mock_gv_post_hook(name, guid, attrs, size, data, &status);
		return status;
	}

	list_for_each(pos, &mock_variables) {
		struct mock_variable *var;

		var = list_entry(pos, struct mock_variable, list);
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
		printf("%s:%d:%s(): varcmp("GUID_FMT"-%s, "GUID_FMT"-%s)\n",
		       __FILE__, __LINE__-1, __func__,
		       GUID_ARGS(goal.guid), Str2str(goal.name),
		       GUID_ARGS(var->guid), Str2str(var->name));
#endif
		if (variable_cmp(&goal, var) == 0) {
			if (attrs != NULL)
				*attrs = var->attrs;
			if (var->size > *size) {
				*size = var->size;
				status = EFI_BUFFER_TOO_SMALL;
				mock_gv_post_hook(name, guid, attrs, size, data,
				                  &status);
				return status;
			}
			if (data == NULL) {
				status = EFI_INVALID_PARAMETER;
				mock_gv_post_hook(name, guid, attrs, size, data,
						  &status);
				return status;
			}
			*size = var->size;
			memcpy(data, var->data, var->size);
			status = EFI_SUCCESS;
			mock_gv_post_hook(name, guid, attrs, size, data,
			                  &status);
			return status;
		}
	}

	status = EFI_NOT_FOUND;
	mock_gv_post_hook(name, guid, attrs, size, data, &status);
	return status;
}

static EFI_STATUS
mock_gnvn_set_result(UINTN *size, CHAR16 *name, EFI_GUID *guid,
		     struct mock_variable *result)
{
	EFI_STATUS status;

	if (*size < StrSize(result->name)) {
		*size = StrSize(result->name);
		status = EFI_BUFFER_TOO_SMALL;
		mock_gnvn_post_hook(size, name, guid, &status);
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 3
		printf("%s:%d:%s():  returning %lx\n",
		       __FILE__, __LINE__-1, __func__, status);
#endif
		return status;
	}

	*size = StrLen(result->name) + 1;
	StrCpy(name, result->name);
	memcpy(guid, &result->guid, sizeof(EFI_GUID));

	status = EFI_SUCCESS;
	mock_gnvn_post_hook(size, name, guid, &status);
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 3
	printf("%s:%d:%s():  returning %lx\n",
	       __FILE__, __LINE__-1, __func__, status);
#endif
	return status;
}

EFI_STATUS EFIAPI
mock_get_next_variable_name(UINTN *size, CHAR16 *name, EFI_GUID *guid)
{
	list_t *pos = NULL;
	struct mock_variable goal = {
		.name = name,
		.guid = *guid,
	};
	struct mock_variable *result = NULL;
	bool found = false;
	EFI_STATUS status;

	status = mock_gnvn_pre_hook(size, name, guid);
	if (EFI_ERROR(status))
		return status;

	if (size == NULL || name == NULL || guid == NULL) {
		status = EFI_INVALID_PARAMETER;
		mock_gnvn_post_hook(size, name, guid, &status);
		return status;
	}

	for (size_t i = 0; i < *size; i++) {
		if (name[i] == 0) {
			found = true;
			break;
		}
	}

	if (found == false) {
		status = EFI_INVALID_PARAMETER;
		mock_gnvn_post_hook(size, name, guid, &status);
		return status;
	}

	found = false;
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
	printf("%s:%d:%s():searching for "GUID_FMT"%s%s\n",
	       __FILE__, __LINE__-1, __func__,
	       GUID_ARGS(*guid),
	       name[0] == 0 ? "" : "-",
	       name[0] == 0 ? "" : Str2str(name));
#endif
	list_for_each(pos, &mock_variables) {
		struct mock_variable *var;

		var = list_entry(pos, struct mock_variable, list);
#if defined(SHIM_DEBUG)
# if SHIM_DEBUG > 1
		printf("%s:%d:%s():  candidate var:%p &var->guid:%p &var->list:%p\n",
			__FILE__, __LINE__-1, __func__, var, &var->guid, &var->list);
# elif SHIM_DEBUG > 0
		printf("%s:%d:%s():  candidate var:%p var->guid:" GUID_FMT"\n",
			__FILE__, __LINE__-1, __func__, var, GUID_ARGS(var->guid));
# endif
#endif
		if (name[0] == 0) {
			if (CompareGuid(&var->guid, guid) == 0) {
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
				printf("%s:%d:%s():  found guid in entry var:%p var->name:%p\n",
				       __FILE__, __LINE__-1, __func__, var, var->name);
#endif
				result = var;
				found = true;
				break;
			}
		} else {
			if (found) {
				if (CompareGuid(&var->guid, guid) == 0) {
					result = var;
					break;
				}
				continue;
			}

#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2
		printf("%s:%d:%s(): varcmp("GUID_FMT"-%s, "GUID_FMT"-%s)\n",
		       __FILE__, __LINE__-1, __func__,
		       GUID_ARGS(goal.guid), Str2str(goal.name),
		       GUID_ARGS(var->guid), Str2str(var->name));
#endif
			if (variable_cmp(&goal, var) == 0) {
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2
				printf("%s:%d:%s():  found\n",
				       __FILE__, __LINE__-1, __func__);
#endif
				found = true;
			}
		}
	}
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
	if (result) {
		printf("%s:%d:%s():  found:%d result:%p &result->guid:%p &result->list:%p\n",
		       __FILE__, __LINE__-1, __func__, found, result,
		       &result->guid, &result->list);
		printf("%s:%d:%s():  "GUID_FMT"-%s\n",
		       __FILE__, __LINE__-1, __func__, GUID_ARGS(result->guid),
		       Str2str(result->name));
	} else {
		printf("%s:%d:%s():  not found (found:%d status:0x%016x)\n",
		       __FILE__, __LINE__-1, __func__, found, status);
	}
#endif

	if (!found) {
		if (name[0] == 0)
			status = EFI_NOT_FOUND;
		else
			status = EFI_INVALID_PARAMETER;
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2
		printf("%s:%d:%s():  not found (found:%d status:0x%016x)\n",
		       __FILE__, __LINE__-1, __func__, found, status);
#endif
		mock_gnvn_post_hook(size, name, guid, &status);
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2
		printf("%s:%d:%s():  not found (found:%d status:0x%016x)\n",
		       __FILE__, __LINE__-1, __func__, found, status);
#endif
		return status;
	}

	if (!result) {
		status = EFI_NOT_FOUND;
		mock_gnvn_post_hook(size, name, guid, &status);
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2
		printf("%s:%d:%s():  found (found:%d status:0x%016x)\n",
		       __FILE__, __LINE__-1, __func__, found, status);
#endif
		return status;
	}

	return mock_gnvn_set_result(size, name, guid, result);
}

static void
free_var(struct mock_variable *var)
{
	if (!var)
		return;
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
	printf("%s:%d:%s(): var:%p &var->guid:%p ",
	       __FILE__, __LINE__-1, __func__,
	       var, var ? &var->guid : NULL);
	if (var)
		printf(GUID_FMT"-%s", GUID_ARGS(var->guid),
		       var->name ? Str2str(var->name) : "");
	printf("\n");
#endif
	list_del(&var->list);
	if (var->size && var->data)
		free(var->data);
	SetMem(var, sizeof(*var), 0);
	free(var);
}

static bool
mock_sv_attrs_match(UINT32 old, UINT32 new)
{
	UINT32 mask = ~((UINT32)EFI_VARIABLE_APPEND_WRITE);

	return (old & mask) == (new & mask);
}

static EFI_STATUS
mock_sv_adjust_usage_data(UINT32 attrs, size_t size, ssize_t change)
{
	const UINT32 bs = EFI_VARIABLE_BOOTSERVICE_ACCESS;
	const UINT32 bs_nv = bs | EFI_VARIABLE_NON_VOLATILE;
	const UINT32 bs_rt = bs | EFI_VARIABLE_RUNTIME_ACCESS;
	const UINT32 bs_rt_nv = bs_nv | bs_rt;
	struct mock_variable_limits goal = {
		.attrs = attrs & bs_rt_nv,
	};
	struct mock_variable_limits *qvi_limits = NULL;
	struct mock_variable_limits *sv_limits = NULL;
	list_t var, *pos = NULL;
	UINT64 remaining;

	list_for_each(pos, mock_qvi_limits) {
		struct mock_variable_limits *candidate;

		candidate = list_entry(pos, struct mock_variable_limits, list);
		if (variable_limits_cmp(&goal, candidate) == 0) {
			qvi_limits = candidate;
			break;
		}
	}

	list_for_each(pos, mock_sv_limits) {
		struct mock_variable_limits *candidate;

		candidate = list_entry(pos, struct mock_variable_limits, list);
		if (variable_limits_cmp(&goal, candidate) == 0) {
			sv_limits = candidate;
			break;
		}
	}
	if (!sv_limits) {
		return EFI_UNSUPPORTED;
	}

	if (sv_limits->status != EFI_SUCCESS)
		return sv_limits->status;

	if (*sv_limits->max_var_size < size) {
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
		printf("%s:%d:%s():*sv_limits->max_var_size:%zu size:%zu\n",
		       __FILE__, __LINE__, __func__,
		       *sv_limits->max_var_size, size);
#endif
		return EFI_OUT_OF_RESOURCES;
	}

	if (change > 0 && (UINT64)change > *sv_limits->remaining_var_storage) {
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
		printf("%s:%d:%s():*sv_limits->remaining_var_storage:%zu change:%zd\n",
		       __FILE__, __LINE__, __func__,
		       *sv_limits->remaining_var_storage, change);
#endif
		return EFI_OUT_OF_RESOURCES;
	}

	*sv_limits->remaining_var_storage += change;

	if (qvi_limits) {
		/*
		 * If the adjustment here is wrong, we don't want to not do
		 * the set variable, we also don't want to not account
		 * for it, and of course we can't have any integer UB.  So
		 * just limit it safely and move on, even though that may
		 * result in wrong checks against QueryVariableInfo() later.
		 *
		 * As if there are correct checks against QueryVariableInfo()...
		 */
		if (qvi_limits->remaining_var_storage == sv_limits->remaining_var_storage)
			;
		else if (change < 0 && (UINT64)-change > *qvi_limits->remaining_var_storage)
			*qvi_limits->remaining_var_storage = 0;
		else if (change > 0 && UINT64_MAX - *qvi_limits->remaining_var_storage < (UINT64)change)
			*qvi_limits->remaining_var_storage = UINT64_MAX;
		else
			*qvi_limits->remaining_var_storage += change;
	}
	return EFI_SUCCESS;
}

static EFI_STATUS
mock_delete_variable(struct mock_variable *var)
{
	EFI_STATUS status;

	status = mock_sv_adjust_usage_data(var->attrs, 0, - var->size);
	if (EFI_ERROR(status)) {
		printf("%s:%d:%s():  status:0x%lx\n",
		       __FILE__, __LINE__ - 1, __func__, status);
		mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size,
		                  var->data, &status, DELETE);
		return status;
	}

	status = EFI_SUCCESS;
	mock_sv_post_hook(var->name, &var->guid, var->attrs, 0, 0, &status,
	                  DELETE);
	free_var(var);
	return status;
}

static EFI_STATUS
mock_replace_variable(struct mock_variable *var, VOID *data, UINTN size)
{
	EFI_STATUS status;
	VOID *new;

	status = mock_sv_adjust_usage_data(var->attrs, size,
					   - var->size + size);
	if (EFI_ERROR(status)) {
		mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size,
		                  var->data, &status, REPLACE);
		return status;
	}

	new = calloc(1, size);
	if (!new) {
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
		printf("%s:%d:%s():calloc(1, %zu) failed\n",
		       __FILE__, __LINE__, __func__,
		       size);
#endif
		status = EFI_OUT_OF_RESOURCES;
		mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size,
		                  var->data, &status, REPLACE);
		return status;
	}
	memcpy(new, data, size);
	free(var->data);
	var->data = new;
	var->size = size;

	status = EFI_SUCCESS;
	mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size,
	                  var->data, &status, REPLACE);
	return status;
}

static EFI_STATUS
mock_sv_extend(struct mock_variable *var, VOID *data, UINTN size)
{
	EFI_STATUS status;
	uint8_t *new;

	if (size == 0) {
		status = EFI_SUCCESS;
		mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size,
		                  var->data, &status, APPEND);
		return status;
	}

	status = mock_sv_adjust_usage_data(var->attrs, var->size + size, size);
	if (EFI_ERROR(status)) {
		mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size,
		                  var->data, &status, APPEND);
		return status;
	}

	new = realloc(var->data, var->size + size);
	if (!new) {
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
		printf("%s:%d:%s():realloc(%zu) failed\n",
		       __FILE__, __LINE__, __func__,
		       var->size + size);
#endif
		status = EFI_OUT_OF_RESOURCES;
		mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size,
		                  var->data, &status, APPEND);
		return status;
	}

	memcpy(&new[var->size], data, size);
	var->data = (void *)new;
	var->size += size;

	status = EFI_SUCCESS;
	mock_sv_post_hook(var->name, &var->guid, var->attrs, var->size,
	                  var->data, &status, APPEND);
	return status;
}

void
mock_print_var_list(list_t *head)
{
	list_t *pos = NULL;
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
	printf("%s:%d:%s():variables so far:\n", __FILE__, __LINE__, __func__);
#endif
	list_for_each(pos, head) {
		struct mock_variable *var = NULL;

		var = list_entry(pos, struct mock_variable, list);
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
		printf("%s:%d:%s():  "GUID_FMT"-%s (%lu bytes)\n",
		       __FILE__, __LINE__ - 1, __func__,
		       GUID_ARGS(var->guid), Str2str(var->name), var->size);
#endif
	}
}

static EFI_STATUS
mock_new_variable(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, UINTN size,
		  VOID *data, struct mock_variable **out)
{
	EFI_STATUS status;
	struct mock_variable *var;
	uint8_t *buf;

	if (size == 0) {
		status = EFI_INVALID_PARAMETER;
		return status;
	}

	status = EFI_OUT_OF_RESOURCES;
	buf = calloc(1, sizeof(struct mock_variable) + StrSize(name));
	if (!buf) {
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
		printf("%s:%d:%s():  calloc(1, %zu) failed\n",
		       __FILE__, __LINE__, __func__,
		       sizeof(struct mock_variable) + StrSize(name));
#endif
		goto err;
	}
	var = (struct mock_variable *)buf;

#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2
	printf("%s:%d:%s():  var:%p &var->guid:%p &var->list:%p\n",
	       __FILE__, __LINE__-1, __func__, var, &var->guid, &var->list);
#endif

	var->data = malloc(size);
	if (!var->data)
		goto err_free;

	var->name = (CHAR16 *)&buf[sizeof(*var)];
	StrCpy(var->name, name);
	memcpy(&var->guid, guid, sizeof(EFI_GUID));
	memcpy(var->data, data, size);
	var->size = size;
	var->attrs = attrs;
	INIT_LIST_HEAD(&var->list);

#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2
	printf("%s:%d:%s():  var: "GUID_FMT"-%s\n",
	       __FILE__, __LINE__-1, __func__,
	       GUID_ARGS(var->guid), Str2str(var->name));
#endif

	*out = var;
	status = EFI_SUCCESS;
err_free:
	if (EFI_ERROR(status))
		free_var(var);
err:
	return status;
}

EFI_STATUS EFIAPI
mock_set_variable(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, UINTN size,
                  VOID *data)
{
	list_t *pos = NULL, *tmp = NULL, *var_list = NULL;
	struct mock_variable goal = {
		.name = name,
		.guid = *guid,
	};
	struct mock_variable *var = NULL;
	bool found = false;
	bool add_tail = true;
	EFI_STATUS status;
	long cmp = -1;

	status = mock_sv_pre_hook(name, guid, attrs, size, data);
	if (EFI_ERROR(status))
		return status;

	if (!name || name[0] == 0 || !guid) {
		status = EFI_INVALID_PARAMETER;
		mock_sv_post_hook(name, guid, attrs, size, data, &status,
		                  CREATE);
		return status;
	}

	if ((attrs & EFI_VARIABLE_RUNTIME_ACCESS) &&
	    !(attrs & EFI_VARIABLE_BOOTSERVICE_ACCESS)) {
		status = EFI_INVALID_PARAMETER;
		mock_sv_post_hook(name, guid, attrs, size, data, &status,
		                  CREATE);
		return status;
	}

#if 0
	/*
	 * We don't ever operate after ExitBootServices(), so I'm not
	 * checking for the missing EFI_VARIABLE_RUNTIME_ACCESS case
	 */
	if (has_exited_boot_services() && !(attrs & EFI_VARIABLE_RUNTIME_ACCESS)) {
		status = EFI_INVALID_PARAMETER;
		mock_sv_post_hook(name, guid, attrs, size, data, &status,
				  CREATE);
		return status;
	}
#endif

#if 0
	/*
	 * For now, we're ignoring that we don't support these.
	 */
	if (attrs & (EFI_VARIABLE_HARDWARE_ERROR_RECORD |
		     EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS |
		     EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS |
		     EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS)) {
		status = EFI_UNSUPPORTED;
		mock_sv_post_hook(name, guid, attrs, size, data, &status,
				  CREATE);
		return status;
	}
#endif

#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2
	printf("%s:%d:%s():Setting "GUID_FMT"-%s size:0x%"PRIx64"\n",
	       __FILE__, __LINE__ - 1, __func__,
	       GUID_ARGS(*guid), Str2str(name), size);
#endif
	switch (mock_variable_sort_policy) {
	case MOCK_SORT_PREPEND:
		var_list = &mock_variables;
		add_tail = false;
		break;
	case MOCK_SORT_APPEND:
		var_list = &mock_variables;
		add_tail = true;
		break;
	case MOCK_SORT_DESCENDING:
		add_tail = true;
		break;
	case MOCK_SORT_ASCENDING:
		add_tail = true;
		break;
	default:
		break;
	}

	pos = &mock_variables;
	list_for_each_safe(pos, tmp, &mock_variables) {
		found = false;
		var = list_entry(pos, struct mock_variable, list);
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2
		printf("%s:%d:%s(): varcmp("GUID_FMT"-%s, "GUID_FMT"-%s)\n",
		       __FILE__, __LINE__-1, __func__,
		       GUID_ARGS(goal.guid), Str2str(goal.name),
		       GUID_ARGS(var->guid), Str2str(var->name));
#endif
		cmp = variable_cmp(&goal, var);
		cmp = cmp < 0 ? -1 : (cmp > 0 ? 1 : 0);

		switch (mock_variable_sort_policy) {
		case MOCK_SORT_DESCENDING:
			if (cmp >= 0) {
				var_list = pos;
				found = true;
			}
			break;
		case MOCK_SORT_ASCENDING:
			if (cmp <= 0) {
				var_list = pos;
				found = true;
			}
			break;
		default:
			if (cmp == 0) {
				var_list = pos;
				found = true;
			}
			break;
		}
		if (found)
			break;
	}
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2
	printf("%s:%d:%s():var_list:%p &mock_variables:%p cmp:%ld\n",
	       __FILE__, __LINE__ - 1, __func__,
	       var_list, &mock_variables, cmp);
#endif
	if (cmp != 0 || (cmp == 0 && var_list == &mock_variables)) {
		size_t totalsz = size + StrSize(name);
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2
		printf("%s:%d:%s():var:%p attrs:0x%lx\n",
		       __FILE__, __LINE__ - 1, __func__, var, attrs);
#endif
		status = mock_sv_adjust_usage_data(attrs, size, -totalsz);
		if (EFI_ERROR(status)) {
			mock_sv_post_hook(name, guid, attrs, size, data,
					  &status, CREATE);
			return status;
		}
		status = mock_new_variable(name, guid, attrs, size, data, &var);
		mock_sv_post_hook(name, guid, attrs, size, data,
				  &status, CREATE);
		if (EFI_ERROR(status)) {
			mock_sv_adjust_usage_data(attrs, 0, totalsz);
			return status;
		}

#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1
		printf("%s:%d:%s(): Adding "GUID_FMT"-%s %s %s\n",
		       __FILE__, __LINE__ - 1, __func__,
		       GUID_ARGS(var->guid), Str2str(var->name),
		       add_tail ? "after" : "before",
		       list2var(pos));
#endif
		if (add_tail)
			list_add_tail(&var->list, pos);
		else
			list_add(&var->list, pos);
		return status;
	}

	var = list_entry(var_list, struct mock_variable, list);
#if defined(SHIM_DEBUG) && SHIM_DEBUG != 0
	printf("%s:%d:%s():var:%p attrs:%s cmp:%ld size:%ld\n",
	       __FILE__, __LINE__ - 1, __func__,
	       var, format_var_attrs(var->attrs), cmp, size);
#endif
	if (!mock_sv_attrs_match(var->attrs, attrs)) {
		status = EFI_INVALID_PARAMETER;
		if (size == 0 && !(attrs & EFI_VARIABLE_APPEND_WRITE)) {
			if ((mock_variable_delete_attr_policy & MOCK_VAR_DELETE_ATTR_ALLOW_ZERO)
			    && attrs == 0) {
				status = EFI_SUCCESS;
			} else if (mock_variable_delete_attr_policy & MOCK_VAR_DELETE_ATTR_ALOW_MISMATCH) {
				status = EFI_SUCCESS;
			}
		}
		if (EFI_ERROR(status)) {
			printf("%s:%d:%s(): var->attrs:%s attrs:%s\n",
			       __FILE__, __LINE__ - 1, __func__,
			       format_var_attrs(var->attrs),
			       format_var_attrs(attrs));
			mock_sv_post_hook(name, guid, attrs, size, data,
					  &status, REPLACE);
			return status;
		}
	}

	if (attrs & EFI_VARIABLE_APPEND_WRITE)
		return mock_sv_extend(var, data, size);

	if (size == 0) {
		UINT32 mask = EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS
			      | EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS;
		/*
		 * We can't process deletes on these correctly unless we
		 * parse the header.
		 */
		if (attrs & mask) {
			return EFI_INVALID_PARAMETER;
		}

		return mock_delete_variable(var);
	}

	return mock_replace_variable(var, data, size);
}

EFI_STATUS EFIAPI
mock_query_variable_info(UINT32 attrs, UINT64 *max_var_storage,
                         UINT64 *remaining_var_storage, UINT64 *max_var_size)
{
	list_t mvl, *pos = NULL;
	struct mock_variable_limits goal = {
		.attrs = attrs,
	};
	struct mock_variable_limits *limits = NULL;
	EFI_STATUS status;

	status = mock_qvi_pre_hook(attrs, max_var_storage,
				   remaining_var_storage, max_var_size);
	if (EFI_ERROR(status))
		return status;

	if (max_var_storage == NULL ||
	    remaining_var_storage == NULL ||
	    max_var_size == NULL) {
		status = EFI_INVALID_PARAMETER;
		mock_qvi_post_hook(attrs, max_var_storage,
		                   remaining_var_storage, max_var_size,
		                   &status);
		return status;
	}

	list_for_each(pos, mock_qvi_limits) {
		limits = list_entry(pos, struct mock_variable_limits, list);
		if (variable_limits_cmp(&goal, limits) == 0) {
			*max_var_storage = *limits->max_var_storage;
			*remaining_var_storage = *limits->remaining_var_storage;
			*max_var_size = *limits->max_var_size;

			status = EFI_SUCCESS;
			mock_qvi_post_hook(attrs, max_var_storage,
			                   remaining_var_storage, max_var_size,
			                   &status);
			return status;
		}
	}

	status = EFI_UNSUPPORTED;
	mock_qvi_post_hook(attrs, max_var_storage, remaining_var_storage,
	                   max_var_size, &status);
	return status;
}

static UINT64 default_max_var_storage;
static UINT64 default_remaining_var_storage;
static UINT64 default_max_var_size;

static struct mock_variable_limits default_limits[] = {
	{.attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS,
	 .max_var_storage = &default_max_var_storage,
	 .remaining_var_storage = &default_remaining_var_storage,
	 .max_var_size = &default_max_var_size,
	 .status = EFI_SUCCESS,
	},
	{.attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS |
		  EFI_VARIABLE_RUNTIME_ACCESS,
	 .max_var_storage = &default_max_var_storage,
	 .remaining_var_storage = &default_remaining_var_storage,
	 .max_var_size = &default_max_var_size,
	 .status = EFI_SUCCESS,
	},
	{.attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS |
		  EFI_VARIABLE_NON_VOLATILE,
	 .max_var_storage = &default_max_var_storage,
	 .remaining_var_storage = &default_remaining_var_storage,
	 .max_var_size = &default_max_var_size,
	 .status = EFI_SUCCESS,
	},
	{.attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS |
		  EFI_VARIABLE_RUNTIME_ACCESS |
		  EFI_VARIABLE_NON_VOLATILE,
	 .max_var_storage = &default_max_var_storage,
	 .remaining_var_storage = &default_remaining_var_storage,
	 .max_var_size = &default_max_var_size,
	 .status = EFI_SUCCESS,
	},
	{.attrs = 0, }
};

void
mock_set_usage_limits(list_t *limit_list,
		      struct mock_variable_limits *limits)
{
	INIT_LIST_HEAD(limit_list);
	for (size_t i = 0; limits[i].attrs != 0; i++) {
		INIT_LIST_HEAD(&limits[i].list);
		list_add_tail(&limits[i].list, limit_list);
	}

	mock_qvi_limits = limit_list;
	mock_sv_limits = limit_list;
}

void
mock_set_default_usage_limits(void)
{
	default_max_var_storage = 65536;
	default_remaining_var_storage = 65536;
	default_max_var_size = 32768;

	mock_set_usage_limits(&mock_default_variable_limits, &default_limits[0]);
}

void
mock_load_one_variable(int dfd, const char * const dirname, char * const name)
{
	int fd;
	FILE *f;
	int rc;
	struct stat statbuf;
	size_t guidlen, namelen;
	efi_guid_t guid;
	size_t sz;
	ssize_t offset = 0;
	EFI_STATUS status;
	UINT32 attrs;

	rc = fstatat(dfd, name, &statbuf, 0);
	if (rc < 0)
		err(2, "Could not stat \"%s/%s\"", dirname, name);

	if (!(S_ISREG(statbuf.st_mode)))
		return;

	if (statbuf.st_size < 5)
		errx(2, "Test data variable \"%s/%s\" is too small (%ld bytes)",
		     dirname, name, statbuf.st_size);

#if 0
	mock_print_var_list(&mock_variables);
#endif

	uint8_t buf[statbuf.st_size];

	fd = openat(dfd, name, O_RDONLY);
	if (fd < 0)
		err(2, "Could not open \"%s/%s\"", dirname, name);

	f = fdopen(fd, "r");
	if (!f)
		err(2, "Could not open \"%s/%s\"", dirname, name);

	while (offset != statbuf.st_size) {
		sz = fread(buf + offset, 1, statbuf.st_size - offset, f);
		if (sz == 0) {
			if (ferror(f))
				err(2, "Could not read from \"%s/%s\"",
				    dirname, name);
			if (feof(f))
				errx(2, "Unexpected end of file reading \"%s/%s\"",
				     dirname, name);
		}

		offset += sz;
	}

	guidlen = strlen("8be4df61-93ca-11d2-aa0d-00e098032b8c");
	namelen = strlen(name) - guidlen;

	if (namelen < 2)
		errx(2, "namelen for \"%s\" is %zu!?!", name, namelen);

	CHAR16 namebuf[namelen];

	name[namelen-1] = 0;
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
	printf("%s:%d:%s(): loading %s-%s\n", __FILE__, __LINE__, __func__,
	       &name[namelen], name);
#endif
	for (size_t i = 0; i < namelen; i++)
		namebuf[i] = name[i];

	rc = efi_str_to_guid(&name[namelen], &guid);
	if (rc < 0)
		err(2, "Could not parse \"%s\" as EFI GUID", &name[namelen]);

	memcpy(&attrs, (UINT32 *)buf, sizeof(UINT32));

	status = RT->SetVariable(namebuf, (EFI_GUID *)&guid, attrs,
				 statbuf.st_size - sizeof(attrs),
				 &buf[sizeof(attrs)]);
	if (EFI_ERROR(status))
		errx(2, "%s:%d:%s(): Could not set variable: 0x%llx",
		     __FILE__, __LINE__ - 1, __func__,
		     (unsigned long long)status);

	fclose(f);
}

void
mock_load_variables(const char *const dirname, const char *filters[],
		    bool filter_out)
{
	int dfd;
	DIR *d;
	struct dirent *entry;

#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1
	printf("Started loading variablles from \"%s\"\n", dirname);
#endif
	d = opendir(dirname);
	if (!d)
		err(1, "Could not open directory \"%s\"", dirname);

	dfd = dirfd(d);
	if (dfd < 0)
		err(1, "Could not get directory file descriptor for \"%s\"",
		    dirname);

	while ((entry = readdir(d)) != NULL) {
		size_t len = strlen(entry->d_name);
		bool found = false;
		if (entry->d_type != DT_REG)
			continue;
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1
		printf("%s:%d:%s(): maybe adding entry \"%s\"\n", __FILE__, __LINE__, __func__, entry->d_name);
#endif
		if (filters && len > guidstr_size + 1) {
			len -= guidstr_size;
			for (size_t i = 0; filters[i]; i++) {
				if (strlen(filters[i]) > len)
					continue;
				if (!strncmp(entry->d_name, filters[i], len)) {
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2
					printf("%s:%d:%s(): filter matched for \"%s\" && \"%s\"\n", __FILE__, __LINE__, __func__, entry->d_name, filters[i]);
#endif
					found = true;
					break;
				}
			}
		}
		if ((found == false && filter_out == true) ||
		    (found == true && filter_out == false)) {
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1
			printf("%s:%d:%s(): Adding \"%s\" because filter %s\n",
			       __FILE__, __LINE__-1, __func__, entry->d_name,
			       found ? "matched" : "did not match");
#endif
			mock_load_one_variable(dfd, dirname, entry->d_name);
		} else {
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1
			printf("%s:%d:%s(): Skipping \"%s\" because filter %s\n",
			       __FILE__, __LINE__-1, __func__, entry->d_name,
			       found ? "matched" : "did not match");
#endif
		}
	}
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1
	printf("Done loading variablles from \"%s\"\n", dirname);
#endif

	closedir(d);
#if 0
	mock_print_var_list(&mock_variables);
#endif
}

static bool qvi_installed = false;

void
mock_install_query_variable_info(void)
{
	qvi_installed = true;
	RT->Hdr.Revision = 2ul << 16ul;
	RT->QueryVariableInfo = mock_query_variable_info;
}

void
mock_uninstall_query_variable_info(void)
{
	qvi_installed = false;
	RT->Hdr.Revision = EFI_1_10_SYSTEM_TABLE_REVISION;
	RT->QueryVariableInfo = mock_efi_unsupported;
}

EFI_CONFIGURATION_TABLE mock_config_table[MOCK_CONFIG_TABLE_ENTRIES] = {
	{
		.VendorGuid = { 0, },
		.VendorTable = NULL
	},
};

int
mock_config_table_cmp(const void *p0, const void *p1)
{
	EFI_CONFIGURATION_TABLE *entry0, *entry1;
	long cmp;

	if (!p0 || !p1) {
		cmp = (int)((intptr_t)p0 - (intptr_t)p1);
	} else {
		entry0 = (EFI_CONFIGURATION_TABLE *)p0;
		entry1 = (EFI_CONFIGURATION_TABLE *)p1;
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
		printf("comparing %p to %p\n", p0, p1);
#endif
		cmp = CompareGuid(&entry0->VendorGuid, &entry1->VendorGuid);
	}

	if (mock_config_table_sort_policy == MOCK_SORT_DESCENDING) {
		cmp = -cmp;
	}

	return cmp;
}

EFI_STATUS EFIAPI
mock_install_configuration_table(EFI_GUID *guid, VOID *table)
{
	bool found = false;
	EFI_CONFIGURATION_TABLE *entry;
	int idx = 0;
	size_t sz;

	if (!guid)
		return EFI_INVALID_PARAMETER;

	for (UINTN i = 0; i < ST->NumberOfTableEntries; i++) {
		EFI_CONFIGURATION_TABLE *entry = &ST->ConfigurationTable[i];

		if (CompareGuid(guid, &entry->VendorGuid) == 0) {
			found = true;
			if (table) {
				// replace it
				entry->VendorTable = table;
			} else {
				// delete it
				ST->NumberOfTableEntries -= 1;
				sz = ST->NumberOfTableEntries - i;
				sz *= sizeof(*entry);
				memmove(&entry[0], &entry[1], sz);
			}
			return EFI_SUCCESS;
		}
	}
	if (!found && table == NULL)
		return EFI_NOT_FOUND;
	if (ST->NumberOfTableEntries == MOCK_CONFIG_TABLE_ENTRIES - 1) {
		/*
		 * If necessary, we could allocate another table and copy
		 * the data, but I'm lazy and we probably don't need to.
		 */
		return EFI_OUT_OF_RESOURCES;
	}

	switch (mock_config_table_sort_policy) {
	case MOCK_SORT_DESCENDING:
	case MOCK_SORT_ASCENDING:
	case MOCK_SORT_APPEND:
		idx = ST->NumberOfTableEntries;
		break;
	case MOCK_SORT_PREPEND:
		sz = ST->NumberOfTableEntries ? ST->NumberOfTableEntries : 0;
		sz *= sizeof(ST->ConfigurationTable[0]);
		memmove(&ST->ConfigurationTable[1], &ST->ConfigurationTable[0], sz);
		idx = 0;
		break;
	default:
		break;
	}

	entry = &ST->ConfigurationTable[idx];
	memcpy(&entry->VendorGuid, guid, sizeof(EFI_GUID));
	entry->VendorTable = table;
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
	printf("%s:%d:%s(): installing entry %p={%p,%p} as entry %d\n",
	       __FILE__, __LINE__, __func__,
	       entry, &entry->VendorGuid, entry->VendorTable, idx);
#endif
	ST->NumberOfTableEntries += 1;

#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
	printf("%s:%d:%s():ST->ConfigurationTable:%p\n"
	       "\t[%d]:%p\n"
	       "\t[%d]:%p\n",
	       __FILE__, __LINE__, __func__, ST->ConfigurationTable,
	       0, &ST->ConfigurationTable[0],
	       1, &ST->ConfigurationTable[1]);
#endif
	switch (mock_config_table_sort_policy) {
	case MOCK_SORT_DESCENDING:
	case MOCK_SORT_ASCENDING:
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
		printf("%s:%d:%s(): entries before sorting:\n", __FILE__, __LINE__, __func__);
		for (UINTN i = 0; i < ST->NumberOfTableEntries; i++) {
			printf("\t[%d] = %p = {", i, &ST->ConfigurationTable[i]);
			printf(".VendorGuid=" GUID_FMT, GUID_ARGS(ST->ConfigurationTable[i].VendorGuid));
			printf(".VendorTable=%p}\n", ST->ConfigurationTable[i].VendorTable);
		}
#endif
		qsort(&ST->ConfigurationTable[0], ST->NumberOfTableEntries,
		      sizeof(ST->ConfigurationTable[0]),
		      mock_config_table_cmp);
		break;
	default:
		break;
	}
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
	printf("%s:%d:%s(): entries:\n", __FILE__, __LINE__, __func__);
	for (UINTN i = 0; i < ST->NumberOfTableEntries; i++) {
		printf("\t[%d] = %p = {", i, &ST->ConfigurationTable[i]);
		printf(".VendorGuid=" GUID_FMT, GUID_ARGS(ST->ConfigurationTable[i].VendorGuid));
		printf(".VendorTable=%p}\n", ST->ConfigurationTable[i].VendorTable);
	}
#endif

	return EFI_SUCCESS;
}

void CONSTRUCTOR
mock_reset_variables(void)
{
	list_t *pos = NULL, *tmp = NULL;
	static bool once = true;

	init_efi_system_table();

	mock_set_variable_pre_hook = NULL;
	mock_set_variable_post_hook = NULL;
	mock_get_variable_pre_hook = NULL;
	mock_get_variable_post_hook = NULL;
	mock_get_next_variable_name_pre_hook = NULL;
	mock_get_next_variable_name_post_hook = NULL;
	mock_query_variable_info_pre_hook = NULL;
	mock_query_variable_info_post_hook = NULL;

	if (once) {
		INIT_LIST_HEAD(&mock_variables);
		once = false;
#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
		printf("%s:%d:%s():mock_variables = {%p,%p};\n",
		       __FILE__, __LINE__-1, __func__,
		       mock_variables.next,
		       mock_variables.prev);
		printf("%s:%d:%s():list_empty(&mock_variables):%d\n",
		       __FILE__, __LINE__-1, __func__, list_empty(&mock_variables));
		printf("%s:%d:%s():list_size(&mock_variables):%d\n",
		       __FILE__, __LINE__-1, __func__, list_size(&mock_variables));
#endif
	}

	list_for_each_safe(pos, tmp, &mock_variables) {
		struct mock_variable *var = NULL;
		var = list_entry(pos, struct mock_variable, list);

#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0)
		printf("%s:%d:%s():var:"GUID_FMT"-%s\n",
		       __FILE__, __LINE__-1, __func__,
		       GUID_ARGS(var->guid), Str2str(var->name));
#endif
		mock_delete_variable(var);
	}
	INIT_LIST_HEAD(&mock_variables);
	mock_set_default_usage_limits();

	mock_variable_delete_attr_policy = MOCK_VAR_DELETE_ATTR_ALLOW_ZERO;

	RT->GetVariable = mock_get_variable;
	RT->GetNextVariableName = mock_get_next_variable_name;
	RT->SetVariable = mock_set_variable;
	if (qvi_installed)
		mock_install_query_variable_info();
	else
		mock_uninstall_query_variable_info();
}

void CONSTRUCTOR
mock_reset_config_table(void)
{
	init_efi_system_table();

	/*
	 * Note that BS->InstallConfigurationTable() is *not* defined as
	 * freeing these.  If a test case installs non-malloc()ed tables,
	 * it needs to call BS->InstallConfigurationTable(guid, NULL) to
	 * clear them.
	 */
	for (UINTN i = 0; i < ST->NumberOfTableEntries; i++) {
		EFI_CONFIGURATION_TABLE *entry = &ST->ConfigurationTable[i];

		if (entry->VendorTable)
			free(entry->VendorTable);
	}

	SetMem(ST->ConfigurationTable,
	       ST->NumberOfTableEntries * sizeof(EFI_CONFIGURATION_TABLE),
	       0);

	ST->NumberOfTableEntries = 0;

	if (ST->ConfigurationTable != mock_config_table) {
		free(ST->ConfigurationTable);
		ST->ConfigurationTable = mock_config_table;
		SetMem(mock_config_table, sizeof(mock_config_table), 0);
	}

	BS->InstallConfigurationTable = mock_install_configuration_table;
}

void DESTRUCTOR
mock_finalize_vars_and_configs(void)
{
	mock_reset_variables();
	mock_reset_config_table();
}

// vim:fenc=utf-8:tw=75:noet
