/* $NetBSD: setvar.c,v 1.4 2025/03/02 01:07:11 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: setvar.c,v 1.4 2025/03/02 01:07:11 riastradh Exp $");
#endif /* not lint */

#include <sys/efiio.h>

#include <assert.h>
#include <err.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uuid.h>

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

#define BOOTNEXT	"BootNext"
#define BOOTORDER	"BootOrder"
#define TIMEOUT		"Timeout"

static size_t
parse_csus(const char *csus, uint16_t **array, int base)
{
	uint16_t *data;
	const char *p;
	char *q;
	size_t n;

	n = 1;
	for (p = csus; *(p = strchrnul(p, ',')) != '\0'; p++)
		n++;

	data = emalloc(n * sizeof(*data));

	n = 0;
	p = csus;
	for (;;) {
		data[n++] = strtous(p, &q, base);
		if (*q != ',' && *q != '\0')
			errx(EXIT_FAILURE, "invalid CSUS string:"
			    " '%s' (at %s)\n",
			    csus, p);
		if (*q == '\0')
			break;
		p = q + 1;
	}

	*array = data;
	return n;
}

PUBLIC int
prefix_bootorder(int fd, const char *target, const char *csus,
    uint16_t bootnum)
{
	struct efi_var_ioc ev;
	char *targetorder;
	uint16_t *data;
	size_t datasize, n;
	int rv;

	easprintf(&targetorder, "%sOrder", target);
	ev = get_variable(fd, targetorder,
	    &EFI_GLOBAL_VARIABLE,
	    EFI_VARIABLE_NON_VOLATILE |
	    EFI_VARIABLE_BOOTSERVICE_ACCESS |
	    EFI_VARIABLE_RUNTIME_ACCESS);

	free(targetorder);

	if (csus != NULL)
		n = parse_csus(csus, &data, 16);
	else {
		data = emalloc(sizeof(*data));
		*data = bootnum;
		n = 1;
	}

	datasize = ev.datasize + n * sizeof(*data);
	data = erealloc(data, datasize);
	memcpy(data + n, ev.data, ev.datasize);

//	free(ev.data);	/* XXX: ??? */

	ev.data = data;
	ev.datasize = datasize;

	rv = set_variable(fd, &ev);
	free(ev.data);
	if (rv == -1)
		err(EXIT_FAILURE, "prefix_bootorder: %s %s", target, csus);
	return rv;
}

PUBLIC int
remove_bootorder(int fd, const char *target, const char *csus,
    uint16_t bootnum)
{
	struct efi_var_ioc ev;
	char *targetorder;
	uint16_t *data, *dp, *rmlist;
	size_t j, n, r;
	int rv;

	easprintf(&targetorder, "%sOrder", target);
	ev = get_variable(fd, targetorder, &EFI_GLOBAL_VARIABLE, 0);
	free(targetorder);

	if (ev.datasize == 0)	/* no such variable */
		return 0;

	if (csus != NULL)
		r = parse_csus(csus, &rmlist, 16);
	else {
		rmlist = emalloc(sizeof(*rmlist));
		*rmlist = bootnum;
		r = 1;
	}

	data = emalloc(ev.datasize);
	dp = ev.data;
	n = ev.datasize / sizeof(*data);
	j = 0;
	for (size_t i = 0; i < n; i++) {
		size_t k;
		for (k = 0; k < r; k++) {
			if (dp[i] == rmlist[k])
				break;
		}
		if (k == r)
			data[j++] = dp[i];
	}
	ev.data = data;
	ev.datasize = j * sizeof(*data);

	rv = set_variable(fd, &ev);
	free(ev.data);
	if (rv == -1)
		err(EXIT_FAILURE, "remove_bootorder: %s %u", target, bootnum);
	return rv;
}

PUBLIC int
set_bootorder(int fd, const char *target, const char *bootorder)
{
	struct efi_var_ioc ev;
	char *targetorder;
	uint16_t *data;
	size_t n;
	int rv;

	easprintf(&targetorder, "%sOrder", target);
	efi_var_init(&ev, targetorder,
	    &EFI_GLOBAL_VARIABLE,
	    EFI_VARIABLE_NON_VOLATILE |
	    EFI_VARIABLE_BOOTSERVICE_ACCESS |
	    EFI_VARIABLE_RUNTIME_ACCESS);
	free(targetorder);

	n = parse_csus(bootorder, &data, 16);
	ev.data = data;
	ev.datasize = n * sizeof(*data);

	rv = set_variable(fd, &ev);
	if (rv == -1)
		warn("set_variable");

	free(ev.name);
	return rv;
}

static int
delete_variable(int fd, const char *varname)
{
	struct efi_var_ioc ev;
	int rv;

	efi_var_init(&ev, varname,
	    &EFI_GLOBAL_VARIABLE,
	    EFI_VARIABLE_NON_VOLATILE |
	    EFI_VARIABLE_BOOTSERVICE_ACCESS |
	    EFI_VARIABLE_RUNTIME_ACCESS);

	rv = set_variable(fd, &ev);
	free(ev.name);
	return rv;
}

PUBLIC int
del_bootorder(int fd, const char *target __unused)
{
	int rv;

	rv = delete_variable(fd, BOOTORDER);
	if (rv == -1)
		warn("delete_variable");

	return rv;
}

PUBLIC int
del_bootorder_dups(int fd, const char *target)
{
	struct efi_var_ioc ev;
	char *targetorder;
	uint16_t *data, *dp;
	size_t i, j, k, n;
	int rv;

	easprintf(&targetorder, "%sOrder", target);
	ev = get_variable(fd, targetorder, &EFI_GLOBAL_VARIABLE, 0);
	free(targetorder);

	if (ev.datasize == 0)
		return 0;

	data = emalloc(ev.datasize);
	dp = ev.data;

	n = ev.datasize / sizeof(*data);
	j = 0;
	for (i = 0; i < n; i++) {
		for (k = 0; k < j; k++) {  /* XXX: O(n^2) */
			if (data[k] == dp[i])
				break;
		}
		if (k == j)
			data[j++] = dp[i];
	}

	ev.data = data;
	ev.datasize = j * sizeof(*data);

	rv = set_variable(fd, &ev);
	free(ev.data);
	if (rv == -1)
		err(EXIT_FAILURE, "del_bootorder_dups");
	return rv;
}

PUBLIC int
set_bootnext(int fd, uint16_t bootnum)
{
	struct efi_var_ioc ev;
	int rv;

	efi_var_init(&ev, BOOTNEXT,
	    &EFI_GLOBAL_VARIABLE,
	    EFI_VARIABLE_NON_VOLATILE |
	    EFI_VARIABLE_BOOTSERVICE_ACCESS |
	    EFI_VARIABLE_RUNTIME_ACCESS);

	ev.data = &bootnum;
	ev.datasize = sizeof(bootnum);

	printf("set BootNext = Boot%04X\n", bootnum);

	rv = set_variable(fd, &ev);
	if (rv == -1)
		warn("set_variable");

	free(ev.name);
	return rv;
}

PUBLIC int
del_bootnext(int fd)
{
	int rv;

	rv = delete_variable(fd, BOOTNEXT);
	if (rv == -1)
		warn("delete_variable");

	return rv;
}

PUBLIC int
set_timeout(int fd, uint16_t timeout)
{
	struct efi_var_ioc ev;
	int rv;

	efi_var_init(&ev, TIMEOUT,
	    &EFI_GLOBAL_VARIABLE,
	    EFI_VARIABLE_NON_VOLATILE |
	    EFI_VARIABLE_BOOTSERVICE_ACCESS |
	    EFI_VARIABLE_RUNTIME_ACCESS);

	ev.data = &timeout;
	ev.datasize = sizeof(timeout);

	printf("set Timeout = %u seconds\n", timeout);

	rv = set_variable(fd, &ev);
	if (rv == -1)
		warn("set_variable");

	free(ev.name);
	return rv;
}

PUBLIC int
del_timeout(int fd)
{
	int rv;

	rv = delete_variable(fd, TIMEOUT);
	if (rv == -1)
		warn("del_variable");

	return rv;
}

PUBLIC int
set_active(int efi_fd, const char *target, uint16_t bootnum, bool active)
{
	struct efi_var_ioc ev;
	boot_var_t *bb;
	char *name;
	int rv;

	easprintf(&name, "%s%04X", target, bootnum);
	ev = get_variable(efi_fd, name, &EFI_GLOBAL_VARIABLE, 0);
	free(name);

	bb = ev.data;
	if (active)
		bb->Attributes |= LOAD_OPTION_ACTIVE;
	else
		bb->Attributes &= (uint32_t)(~LOAD_OPTION_ACTIVE);

	rv = set_variable(efi_fd, &ev);
	if (rv == -1)
		err(EXIT_FAILURE, "set_variable");

	return rv;
}

#if 0
PUBLIC int
del_variable(int fd, const char *varname)
{
	struct efi_var_ioc ev;
	int rv;

	efi_var_init(&ev, varname,
	    &EFI_GLOBAL_VARIABLE,
	    EFI_VARIABLE_NON_VOLATILE |
	    EFI_VARIABLE_BOOTSERVICE_ACCESS |
	    EFI_VARIABLE_RUNTIME_ACCESS);

	rv = set_variable(fd, &ev);
	free(ev.name);
	return rv;
}
#endif

PUBLIC int
del_variable(int efi_fd, const char *target, uint16_t bootnum)
{
	char *name;
	int rv;

	easprintf(&name, "%s%04X", target, bootnum);
	printf("deleting '%s'\n", name);
	rv = delete_variable(efi_fd, name);
	free(name);

	if (rv == -1)
		err(EXIT_FAILURE, "del_variable");

	rv = remove_bootorder(efi_fd, target, NULL, bootnum);
	if (rv == -1)
		err(EXIT_FAILURE, "remove_bootorder");

	return rv;
}