/*	$NetBSD: devopen.c,v 1.14 2023/06/20 07:46:03 rin Exp $	 */

/*-
 * Copyright (c) 2005 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Bang Jun-Young.
 *
 * 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``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 FOUNDATION 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.
 */

/*
 * Copyright (c) 1996, 1997
 *	Matthias Drochner.  All rights reserved.
 *
 * 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 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 "efiboot.h"

#include <lib/libsa/dev_net.h>
#include <lib/libsa/net.h>

#include <biosdisk.h>
#include "devopen.h"
#include <bootinfo.h>
#include "efidisk.h"

static int
dev2bios(char *devname, int unit, int *biosdev)
{

	if (strcmp(devname, "hd") == 0)
		*biosdev = 0x80 + unit;
	else if (strcmp(devname, "cd") == 0)
		*biosdev = 0x80 + get_harddrives() + unit;
	else
		return ENXIO;

	return 0;
}

void
bios2dev(int biosdev, daddr_t sector, char **devname, int *unit,
	 int *partition, const char **part_name)
{
	static char savedevname[MAXDEVNAME+1];

	*unit = biosdev & 0x7f;

	if (efi_bootdp_type == BOOT_DEVICE_TYPE_NET) {
		*devname = "net";
		*unit = efi_net_get_booted_interface_unit();
		if (*unit < 0)
			*unit = 0;
		*partition = 0;
		return;
	} else if (biosdev >= 0x80 + get_harddrives()) {
		*devname = "cd";
		*unit -= get_harddrives();
	} else
		*devname = "hd";

	(void)biosdisk_findpartition(biosdev, sector, partition, part_name);
	if (part_name != NULL && *part_name != NULL) {
		snprintf(savedevname, sizeof(savedevname),
		    "NAME=%s", *part_name);
		*devname = savedevname;
	}
}

#if defined(SUPPORT_NFS) || defined(SUPPORT_TFTP)
const struct netboot_fstab *
netboot_fstab_find(const char *name)
{
	int i;

	if (strcmp(name, "net") == 0)
		return &netboot_fstab[0];

	for (i = 0; i < nnetboot_fstab; i++) {
		if (strcmp(name, netboot_fstab[i].name) == 0)
			return &netboot_fstab[i];
	}

	return NULL;
}

static const struct netboot_fstab *
netboot_fstab_findn(const char *name, size_t len)
{
	int i;

	if (strncmp(name, "net", len) == 0)
		return &netboot_fstab[0];

	for (i = 0; i < nnetboot_fstab; i++) {
		if (strncmp(name, netboot_fstab[i].name, len) == 0)
			return &netboot_fstab[i];
	}

	return NULL;
}
#endif

struct btinfo_bootpath bibp;
extern bool kernel_loaded;

/*
 * Open the EFI disk device
 */
int
devopen(struct open_file *f, const char *fname, char **file)
{
	char *fsname, *devname;
	const char *xname = NULL;
	int unit, partition;
	int biosdev;
	int error;
#if defined(SUPPORT_NFS) || defined(SUPPORT_TFTP)
	struct devdesc desc;
	const struct netboot_fstab *nf;
	char *filename;
	size_t fsnamelen;
	int i, n;
#endif

	error = parsebootfile(fname, &fsname, &devname, &unit, &partition,
	    (const char **) file);
	if (error)
		return error;

	memcpy(file_system, file_system_disk,
	    sizeof(struct fs_ops) * nfsys_disk);
	nfsys = nfsys_disk;

	/* Search by GPT label or raidframe name */
	if (strstr(devname, "NAME=") == devname)
		xname = devname;
	if (strstr(devname, "raid") == devname)
		xname = fname;

	if (xname != NULL) {
		f->f_dev = &devsw[0];		/* must be biosdisk */

		if (!kernel_loaded) {
			strncpy(bibp.bootpath, *file, sizeof(bibp.bootpath));
			BI_ADD(&bibp, BTINFO_BOOTPATH, sizeof(bibp));
		}

		error = biosdisk_open_name(f, xname);
		return error;
	}

	/*
	 * Network
	 */
#if defined(SUPPORT_NFS) || defined(SUPPORT_TFTP)
	nf = netboot_fstab_find(devname);
	if (nf != NULL) {
		n = 0;
		if (strcmp(devname, "net") == 0) {
			for (i = 0; i < nnetboot_fstab; i++) {
				memcpy(&file_system[n++], netboot_fstab[i].ops,
				    sizeof(struct fs_ops));
			}
		} else {
			memcpy(&file_system[n++], nf->ops,
			    sizeof(struct fs_ops));
		}
		nfsys = n;

#ifdef SUPPORT_BOOTP
		try_bootp = 1;
#endif

		/* If we got passed a filename, pass it to the BOOTP server. */
		if (fname) {
			filename = strchr(fname, ':');
			if (filename != NULL)
				filename++;
			else
				filename = (char *)fname;
			strlcpy(bootfile, filename, sizeof(bootfile));
		}

		memset(&desc, 0, sizeof(desc));
		strlcpy(desc.d_name, "net", sizeof(desc.d_name));
		desc.d_unit = unit;

		f->f_dev = &devsw[1];		/* must be net */
		if (!kernel_loaded) {
			strncpy(bibp.bootpath, *file, sizeof(bibp.bootpath));
			BI_ADD(&bibp, BTINFO_BOOTPATH, sizeof(bibp));
		}
		error = DEV_OPEN(f->f_dev)(f, &desc);
		if (error)
			return error;

		/*
		 * If the DHCP server provided a file name:
		 * - If it contains a ":", assume it points to a NetBSD kernel.
		 * - If not, assume that the DHCP server was not able to pass
		 *   a separate filename for the kernel. (The name probably was
		 *   the same as used to load "efiboot".) Ignore it and use
		 *   the default in this case.
		 * So we cater to simple DHCP servers while being able to use
		 * the power of conditional behaviour in modern ones.
		 */
		filename = strchr(bootfile, ':');
		if (filename != NULL) {
			fname = bootfile;

			fsnamelen = filename - fname;
			nf = netboot_fstab_findn(fname, fsnamelen);
			if (nf == NULL ||
			    strncmp(fname, "net", fsnamelen) == 0) {
				printf("Invalid file system type specified in "
				    "%s\n", fname);
				error = EINVAL;
				goto neterr;
			}

			memcpy(file_system, nf->ops, sizeof(struct fs_ops));
			nfsys = 1;
		}

		filename = fname ? strchr(fname, ':') : NULL;
		if (filename != NULL) {
			filename++;
			if (*filename == '\0') {
				printf("No file specified in %s\n", fname);
				error = EINVAL;
				goto neterr;
			}
		} else
			filename = (char *)fname;

		*file = filename;
		return 0;

neterr:
		DEV_CLOSE(f->f_dev)(f);
		f->f_dev = NULL;
		return error;
	}
#endif

	/*
	 * biosdisk
	 */
	if (strcmp(devname, "esp") == 0) {
		bios2dev(boot_biosdev, boot_biossector, &devname, &unit,
		    &partition, NULL);
		if (efidisk_get_efi_system_partition(boot_biosdev, &partition))
			return ENXIO;
	}

	error = dev2bios(devname, unit, &biosdev);
	if (error)
		return error;

	f->f_dev = &devsw[0];		/* must be biosdisk */

	if (!kernel_loaded) {
		strncpy(bibp.bootpath, *file, sizeof(bibp.bootpath));
		BI_ADD(&bibp, BTINFO_BOOTPATH, sizeof(bibp));
	}

	return biosdisk_open(f, biosdev, partition);
}
