/* $NetBSD: efiio.c,v 1.4 2025/03/30 14:36:48 riastradh Exp $ */

/*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: efiio.c,v 1.4 2025/03/30 14:36:48 riastradh Exp $");
#endif /* not lint */

#include <sys/efiio.h>
#include <sys/ioctl.h>

#include <assert.h>
#include <err.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <util.h>

#include "defs.h"
#include "efiio.h"
#include "utils.h"

//******************************************************
// Variable Attributes
//******************************************************
#if 0	/* see sys/efiio.h */
struct efi_var_ioc {
	uint16_t *	name;		/* vendor's variable name */
	size_t		namesize;	/* size in bytes of the name buffer */
	struct uuid	vendor;		/* unique identifier for vendor */
	uint32_t	attrib;		/* variable attribute bitmask */
	void *		data;		/* buffer containing variable data */
	size_t		datasize;	/* size in bytes of the data buffer */
};
#endif

static int
xioctl(int fd, unsigned long request, void *buf)
{
	int rv;

	rv = ioctl(fd, request, buf);
	if (rv == -1 && errno != ENOENT)
		err(EXIT_FAILURE, "%s: ioctl", __func__);
	return rv;
}

PUBLIC int
set_variable(int fd, struct efi_var_ioc *ev)
{

	return ioctl(fd, EFIIOC_VAR_SET, ev);
}

static inline int
get_variable_core(int fd, struct efi_var_ioc *ev)
{

	if (xioctl(fd, EFIIOC_VAR_GET, ev) == -1)
		return -1;

	ev->data = emalloc(ev->datasize);
	xioctl(fd, EFIIOC_VAR_GET, ev);
	return 0;
}

PUBLIC struct efi_var_ioc
get_variable(int fd, const char *name, struct uuid *vendor,
    uint32_t attrib)
{
	struct efi_var_ioc ev;

	efi_var_init(&ev, name, vendor, attrib);
	get_variable_core(fd, &ev);

	return ev;
}

PUBLIC struct efi_var_ioc *
get_next_variable(int fd, struct efi_var_ioc *ev)
{
	int rv;

	rv = ioctl(fd, EFIIOC_VAR_NEXT, ev);
	if (rv == -1 && errno != ENOENT)
		err(EXIT_FAILURE, "%s: ioctl", __func__);
	return ev;
}

PUBLIC void *
get_table(int fd, struct uuid *uuid, size_t *buflen)
{
	struct efi_get_table_ioc egt;

	memset(&egt, 0, sizeof(egt));
	memcpy(&egt.uuid, uuid, sizeof(egt.uuid));

	xioctl(fd, EFIIOC_GET_TABLE, &egt);
	egt.buf = ecalloc(egt.table_len, 1);
	egt.buf_len = egt.table_len;

	xioctl(fd, EFIIOC_GET_TABLE, &egt);
	*buflen = egt.table_len;
	return egt.buf;
}

PUBLIC size_t
get_variable_info(int fd, bool (*choose)(struct efi_var_ioc *, void *),
    int (*fn)(struct efi_var_ioc *ev, void *), void *arg)
{
	struct efi_var_ioc ev;
	size_t cnt;
	int rv;

	assert(fn != NULL);
	assert(arg != NULL);

	memset(&ev, 0, sizeof(ev));

	ev.name = ecalloc(EFI_VARNAME_MAXBYTES, 1);
	cnt = 0;
	for (;;) {
		ev.namesize = EFI_VARNAME_MAXBYTES;
		if (ioctl(fd, EFIIOC_VAR_NEXT, &ev) == -1) {
			char *buf;

			if (errno == ENOENT)
				break;

			/* XXX: ev is likely to be zero */
			buf = ucs2_to_utf8(ev.name, ev.namesize, NULL, NULL);
			err(EXIT_FAILURE, "%s: '%s'", __func__, buf);
		}

		if (choose != NULL && !choose(&ev, arg))
			continue;

		rv = get_variable_core(fd, &ev);
		assert(rv == 0);
		if (rv == -1)
			err(EXIT_FAILURE, "get_variable_core");

		cnt++;
		fn(&ev, arg);

		free(ev.data);
	}
	free(ev.name);
	return cnt;
}