⛏️ index : haiku.git

/*
 * Copyright 2010 Michael Lotz, mmlr@mlotz.ch
 * Copyright 2011-2017 Haiku, Inc. All rights reserved.
 * All rights reserved. Distributed under the terms of the MIT License.
 *
 * Authors:
 * 		Michael Lotz <mmlr@mlotz.ch>
 * 		Alexander von Gluck IV <kallisti5@unixzen.com>
 */


#include <errno.h>
#include <getopt.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#define EXIT_FAILURE 1


//#define DEBUG_ANYBOOT
#ifdef DEBUG_ANYBOOT
#   define TRACE(x...) printf("anyboot: " x)
#else
#   define TRACE(x...) ;
#endif


static uint8_t *sCopyBuffer = NULL;
static size_t sCopyBufferSize = 2 * 1024 * 1024;
static const size_t kBlockSize = 512;


// Haiku Anyboot Image:
//   (MBR Table + Boot Sector)
//   ISO (Small Haiku ISO9660)
//   First Partition (Haiku OS Image, BFS)
//   Second Partition (EFI Loader, FAT)
//   Third Partition (EFI Mac, HFS) <not implemented>


static void
print_usage(bool error)
{
	printf("\n");
	printf("usage: anyboot [-b BIOS-Loader] [-e EFI-Filesystem] <isoFile> <imageFile> <outputFile>\n");
	printf("       -b, --bios-loader <file>     Legacy BIOS bootloader\n");
	printf("       -e, --efi-filesystem <file>  EFI filesystem\n");
	exit(error ? EXIT_FAILURE : 0);
}


static void
checkError(bool failed, const char *message)
{
	if (!failed)
		return;

	printf("%s: %s\n", message, strerror(errno));
	free(sCopyBuffer);
	exit(1);
}


#if 0
static void
chsAddressFor(uint32_t offset, uint8_t *address, uint32_t sectorsPerTrack,
	uint32_t headsPerCylinder)
{
	if (offset >= 1024 * sectorsPerTrack * headsPerCylinder) {
		// not encodable, force LBA
		address[0] = 0xff;
		address[1] = 0xff;
		address[2] = 0xff;
		return;
	}

	uint32_t cylinders = 0;
	uint32_t heads = 0;
	uint32_t sectors = 0;

	uint32_t temp = 0;
	while (temp * sectorsPerTrack * headsPerCylinder <= offset)
		cylinders = temp++;

	offset -= (sectorsPerTrack * headsPerCylinder * cylinders);

	temp = 0;
	while (temp * sectorsPerTrack <= offset)
		heads = temp++;

	sectors = offset - (sectorsPerTrack * heads) + 1;

	address[0] = heads;
	address[1] = sectors;
	address[1] |= (cylinders >> 2) & 0xc0;
	address[2] = cylinders;
}
#endif


static void
createPartition(int handle, int index, bool active, uint8_t type,
	uint32_t offset, uint32_t size)
{
	uint8_t bootable = active ? 0x80 : 0x0;
	uint8_t partition[16] = {
		bootable,				// bootable
		0xff, 0xff, 0xff,		// CHS first block (default to LBA)
		type,					// partition type
		0xff, 0xff, 0xff,		// CHS last block (default to LBA)
		0x00, 0x00, 0x00, 0x00,	// imageOffset in blocks (written below)
		0x00, 0x00, 0x00, 0x00	// imageSize in blocks (written below)
	};

	// fill in LBA values
	uint32_t partitionOffset = (uint32_t)(offset / kBlockSize);
	((uint32_t *)partition)[2] = partitionOffset;
	((uint32_t *)partition)[3] = (uint32_t)(size / kBlockSize);

	TRACE("%s: #%d %c bytes: %u-%u, sectors: %u-%u\n", __func__, index,
		active ? 'b' : '-', offset, offset + size, partitionOffset,
		partitionOffset + uint32_t(size / kBlockSize));
#if 0
	// while this should basically work, it makes the boot code needlessly
	// use chs which has a high potential of failure due to the geometry
	// being unknown beforehand (a fixed geometry would be used here).

	uint32_t sectorsPerTrack = 63;
	uint32_t headsPerCylinder = 255;

	// fill in CHS values
	chsAddressFor(partitionOffset, &partition[1], sectorsPerTrack,
		headsPerCylinder);
	chsAddressFor(partitionOffset + (uint32_t)(size / kBlockSize),
		&partition[5], sectorsPerTrack, headsPerCylinder);
#endif

	ssize_t written = pwrite(handle, partition, 16, 512 - 2 - 16 * (4 - index));
	checkError(written != 16, "failed to write partition entry");

	if (active) {
		// make it bootable
		written = pwrite(handle, &partitionOffset, 4, offset + 512 - 2 - 4);
		checkError(written != 4, "failed to make image bootable");
	}
	return;
}


static int
copyLoop(int from, int to, off_t position)
{
	while (true) {
		ssize_t copyLength = read(from, sCopyBuffer, sCopyBufferSize);
		if (copyLength <= 0)
			return copyLength;

		ssize_t written = pwrite(to, sCopyBuffer, copyLength, position);
		if (written != copyLength) {
			if (written < 0)
				return written;
			else
				return -1;
		}

		position += copyLength;
	}
}


int
main(int argc, char *argv[])
{
	const char *biosFile = NULL;
	const char *efiFile = NULL;
	const char *isoFile = NULL;
	const char *imageFile = NULL;
	const char *outputFile = NULL;

	while (1) {
		int c;
		static struct option long_options[] = {
			{"bios-loader", required_argument, 0, 'b'},
			{"efi-loader", required_argument, 0, 'e'},
			{"help", no_argument, 0, 'h'},
			{0, 0, 0, 0}
		};

		opterr = 1; /* don't print errors */
		c = getopt_long(argc, argv, "+hb:e:", long_options, NULL);

		if (c == -1)
			break;

		switch (c) {
			case 'h':
				print_usage(false);
				break;
			case 'b':
				biosFile = optarg;
				break;
			case 'e':
				efiFile = optarg;
				break;
			default:
				print_usage(true);
		}
	}

	if ((argc - optind) != 3)
		print_usage(true);

	for (int index = optind; index < argc; index++) {
		if (isoFile == NULL)
			isoFile = argv[index];
		else if (imageFile == NULL)
			imageFile = argv[index];
		else if (outputFile == NULL)
			outputFile = argv[index];
	}

	sCopyBuffer = (uint8_t *)malloc(sCopyBufferSize);
	checkError(sCopyBuffer == NULL, "no memory for copy buffer");

	int outputFileHandle = open(outputFile, O_WRONLY | O_TRUNC | O_CREAT,
		S_IRUSR | S_IWUSR);
	checkError(outputFileHandle < 0, "failed to open output file");

	int isoFileHandle = open(isoFile, O_RDONLY);
	checkError(isoFileHandle < 0, "failed to open ISO file");

	struct stat stat;
	int result = fstat(isoFileHandle, &stat);
	checkError(result != 0, "failed to stat ISO file");
	off_t isoSize = stat.st_size;

	int biosFileHandle = -1;
	if (biosFile != NULL) {
		biosFileHandle = open(biosFile, O_RDONLY);
		checkError(biosFileHandle < 0, "failed to open BIOS bootloader file");
	}

	int efiFileHandle = -1;
	off_t efiSize = 0;
	if (efiFile != NULL) {
		efiFileHandle = open(efiFile, O_RDONLY);
		checkError(efiFileHandle < 0, "failed to open EFI bootloader file");

		result = fstat(efiFileHandle, &stat);
		checkError(result != 0, "failed to stat EFI filesystem image");
		efiSize = stat.st_size;
	}

	int imageFileHandle = open(imageFile, O_RDONLY);
	checkError(imageFileHandle < 0, "failed to open image file");

	result = fstat(imageFileHandle, &stat);
	checkError(result != 0, "failed to stat image file");
	off_t imageSize = stat.st_size;

	result = copyLoop(isoFileHandle, outputFileHandle, 0);
	checkError(result != 0, "failed to copy iso file to output");

	// isoSize rounded to the next full megabyte
	off_t alignment = 1 * 1024 * 1024 - 1;
	off_t imageOffset = (isoSize + alignment) & ~alignment;

	result = copyLoop(imageFileHandle, outputFileHandle, imageOffset);
	checkError(result != 0, "failed to copy image file to output");

	if (biosFileHandle >= 0) {
		result = copyLoop(biosFileHandle, outputFileHandle, 0);
		checkError(result != 0, "failed to copy BIOS bootloader to output");
	}

	// Haiku Image Partition
	alignment = kBlockSize - 1;
	imageSize = (imageSize + alignment) & ~alignment;
	createPartition(outputFileHandle, 0, true, 0xeb, imageOffset, imageSize);

	// Optional EFI Filesystem
	if (efiFile != NULL) {
		off_t efiOffset = (imageOffset + imageSize + alignment) & ~alignment;
		efiSize = (efiSize + alignment) & ~alignment;
		result = copyLoop(efiFileHandle, outputFileHandle, efiOffset);
		checkError(result != 0, "failed to copy EFI filesystem image to output");
		createPartition(outputFileHandle, 1, false, 0xef, efiOffset, efiSize);
	}

	// TODO: MacEFI (HFS) maybe someday

	free(sCopyBuffer);
	return 0;
}