1/*
2 * Copyright 2010 Michael Lotz, mmlr@mlotz.ch
3 * Copyright 2011-2017 Haiku, Inc. All rights reserved.
4 * All rights reserved. Distributed under the terms of the MIT License.
5 *
6 * Authors:
7 * 		Michael Lotz <mmlr@mlotz.ch>
8 * 		Alexander von Gluck IV <kallisti5@unixzen.com>
9 */
10
11
12#include <errno.h>
13#include <getopt.h>
14#include <fcntl.h>
15#include <unistd.h>
16#include <stdint.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <sys/stat.h>
21
22#define EXIT_FAILURE 1
23
24
25//#define DEBUG_ANYBOOT
26#ifdef DEBUG_ANYBOOT
27#   define TRACE(x...) printf("anyboot: " x)
28#else
29#   define TRACE(x...) ;
30#endif
31
32
33static uint8_t *sCopyBuffer = NULL;
34static size_t sCopyBufferSize = 2 * 1024 * 1024;
35static const size_t kBlockSize = 512;
36
37
38// Haiku Anyboot Image:
39//   (MBR Table + Boot Sector)
40//   ISO (Small Haiku ISO9660)
41//   First Partition (Haiku OS Image, BFS)
42//   Second Partition (EFI Loader, FAT)
43//   Third Partition (EFI Mac, HFS) <not implemented>
44
45
46static void
47print_usage(bool error)
48{
49	printf("\n");
50	printf("usage: anyboot [-b BIOS-Loader] [-e EFI-Filesystem] <isoFile> <imageFile> <outputFile>\n");
51	printf("       -b, --bios-loader <file>     Legacy BIOS bootloader\n");
52	printf("       -e, --efi-filesystem <file>  EFI filesystem\n");
53	exit(error ? EXIT_FAILURE : 0);
54}
55
56
57static void
58checkError(bool failed, const char *message)
59{
60	if (!failed)
61		return;
62
63	printf("%s: %s\n", message, strerror(errno));
64	free(sCopyBuffer);
65	exit(1);
66}
67
68
69#if 0
70static void
71chsAddressFor(uint32_t offset, uint8_t *address, uint32_t sectorsPerTrack,
72	uint32_t headsPerCylinder)
73{
74	if (offset >= 1024 * sectorsPerTrack * headsPerCylinder) {
75		// not encodable, force LBA
76		address[0] = 0xff;
77		address[1] = 0xff;
78		address[2] = 0xff;
79		return;
80	}
81
82	uint32_t cylinders = 0;
83	uint32_t heads = 0;
84	uint32_t sectors = 0;
85
86	uint32_t temp = 0;
87	while (temp * sectorsPerTrack * headsPerCylinder <= offset)
88		cylinders = temp++;
89
90	offset -= (sectorsPerTrack * headsPerCylinder * cylinders);
91
92	temp = 0;
93	while (temp * sectorsPerTrack <= offset)
94		heads = temp++;
95
96	sectors = offset - (sectorsPerTrack * heads) + 1;
97
98	address[0] = heads;
99	address[1] = sectors;
100	address[1] |= (cylinders >> 2) & 0xc0;
101	address[2] = cylinders;
102}
103#endif
104
105
106static void
107createPartition(int handle, int index, bool active, uint8_t type,
108	uint32_t offset, uint32_t size)
109{
110	uint8_t bootable = active ? 0x80 : 0x0;
111	uint8_t partition[16] = {
112		bootable,				// bootable
113		0xff, 0xff, 0xff,		// CHS first block (default to LBA)
114		type,					// partition type
115		0xff, 0xff, 0xff,		// CHS last block (default to LBA)
116		0x00, 0x00, 0x00, 0x00,	// imageOffset in blocks (written below)
117		0x00, 0x00, 0x00, 0x00	// imageSize in blocks (written below)
118	};
119
120	// fill in LBA values
121	uint32_t partitionOffset = (uint32_t)(offset / kBlockSize);
122	((uint32_t *)partition)[2] = partitionOffset;
123	((uint32_t *)partition)[3] = (uint32_t)(size / kBlockSize);
124
125	TRACE("%s: #%d %c bytes: %u-%u, sectors: %u-%u\n", __func__, index,
126		active ? 'b' : '-', offset, offset + size, partitionOffset,
127		partitionOffset + uint32_t(size / kBlockSize));
128#if 0
129	// while this should basically work, it makes the boot code needlessly
130	// use chs which has a high potential of failure due to the geometry
131	// being unknown beforehand (a fixed geometry would be used here).
132
133	uint32_t sectorsPerTrack = 63;
134	uint32_t headsPerCylinder = 255;
135
136	// fill in CHS values
137	chsAddressFor(partitionOffset, &partition[1], sectorsPerTrack,
138		headsPerCylinder);
139	chsAddressFor(partitionOffset + (uint32_t)(size / kBlockSize),
140		&partition[5], sectorsPerTrack, headsPerCylinder);
141#endif
142
143	ssize_t written = pwrite(handle, partition, 16, 512 - 2 - 16 * (4 - index));
144	checkError(written != 16, "failed to write partition entry");
145
146	if (active) {
147		// make it bootable
148		written = pwrite(handle, &partitionOffset, 4, offset + 512 - 2 - 4);
149		checkError(written != 4, "failed to make image bootable");
150	}
151	return;
152}
153
154
155static int
156copyLoop(int from, int to, off_t position)
157{
158	while (true) {
159		ssize_t copyLength = read(from, sCopyBuffer, sCopyBufferSize);
160		if (copyLength <= 0)
161			return copyLength;
162
163		ssize_t written = pwrite(to, sCopyBuffer, copyLength, position);
164		if (written != copyLength) {
165			if (written < 0)
166				return written;
167			else
168				return -1;
169		}
170
171		position += copyLength;
172	}
173}
174
175
176int
177main(int argc, char *argv[])
178{
179	const char *biosFile = NULL;
180	const char *efiFile = NULL;
181	const char *isoFile = NULL;
182	const char *imageFile = NULL;
183	const char *outputFile = NULL;
184
185	while (1) {
186		int c;
187		static struct option long_options[] = {
188			{"bios-loader", required_argument, 0, 'b'},
189			{"efi-loader", required_argument, 0, 'e'},
190			{"help", no_argument, 0, 'h'},
191			{0, 0, 0, 0}
192		};
193
194		opterr = 1; /* don't print errors */
195		c = getopt_long(argc, argv, "+hb:e:", long_options, NULL);
196
197		if (c == -1)
198			break;
199
200		switch (c) {
201			case 'h':
202				print_usage(false);
203				break;
204			case 'b':
205				biosFile = optarg;
206				break;
207			case 'e':
208				efiFile = optarg;
209				break;
210			default:
211				print_usage(true);
212		}
213	}
214
215	if ((argc - optind) != 3)
216		print_usage(true);
217
218	for (int index = optind; index < argc; index++) {
219		if (isoFile == NULL)
220			isoFile = argv[index];
221		else if (imageFile == NULL)
222			imageFile = argv[index];
223		else if (outputFile == NULL)
224			outputFile = argv[index];
225	}
226
227	sCopyBuffer = (uint8_t *)malloc(sCopyBufferSize);
228	checkError(sCopyBuffer == NULL, "no memory for copy buffer");
229
230	int outputFileHandle = open(outputFile, O_WRONLY | O_TRUNC | O_CREAT,
231		S_IRUSR | S_IWUSR);
232	checkError(outputFileHandle < 0, "failed to open output file");
233
234	int isoFileHandle = open(isoFile, O_RDONLY);
235	checkError(isoFileHandle < 0, "failed to open ISO file");
236
237	struct stat stat;
238	int result = fstat(isoFileHandle, &stat);
239	checkError(result != 0, "failed to stat ISO file");
240	off_t isoSize = stat.st_size;
241
242	int biosFileHandle = -1;
243	if (biosFile != NULL) {
244		biosFileHandle = open(biosFile, O_RDONLY);
245		checkError(biosFileHandle < 0, "failed to open BIOS bootloader file");
246	}
247
248	int efiFileHandle = -1;
249	off_t efiSize = 0;
250	if (efiFile != NULL) {
251		efiFileHandle = open(efiFile, O_RDONLY);
252		checkError(efiFileHandle < 0, "failed to open EFI bootloader file");
253
254		result = fstat(efiFileHandle, &stat);
255		checkError(result != 0, "failed to stat EFI filesystem image");
256		efiSize = stat.st_size;
257	}
258
259	int imageFileHandle = open(imageFile, O_RDONLY);
260	checkError(imageFileHandle < 0, "failed to open image file");
261
262	result = fstat(imageFileHandle, &stat);
263	checkError(result != 0, "failed to stat image file");
264	off_t imageSize = stat.st_size;
265
266	result = copyLoop(isoFileHandle, outputFileHandle, 0);
267	checkError(result != 0, "failed to copy iso file to output");
268
269	// isoSize rounded to the next full megabyte
270	off_t alignment = 1 * 1024 * 1024 - 1;
271	off_t imageOffset = (isoSize + alignment) & ~alignment;
272
273	result = copyLoop(imageFileHandle, outputFileHandle, imageOffset);
274	checkError(result != 0, "failed to copy image file to output");
275
276	if (biosFileHandle >= 0) {
277		result = copyLoop(biosFileHandle, outputFileHandle, 0);
278		checkError(result != 0, "failed to copy BIOS bootloader to output");
279	}
280
281	// Haiku Image Partition
282	alignment = kBlockSize - 1;
283	imageSize = (imageSize + alignment) & ~alignment;
284	createPartition(outputFileHandle, 0, true, 0xeb, imageOffset, imageSize);
285
286	// Optional EFI Filesystem
287	if (efiFile != NULL) {
288		off_t efiOffset = (imageOffset + imageSize + alignment) & ~alignment;
289		efiSize = (efiSize + alignment) & ~alignment;
290		result = copyLoop(efiFileHandle, outputFileHandle, efiOffset);
291		checkError(result != 0, "failed to copy EFI filesystem image to output");
292		createPartition(outputFileHandle, 1, false, 0xef, efiOffset, efiSize);
293	}
294
295	// TODO: MacEFI (HFS) maybe someday
296
297	free(sCopyBuffer);
298	return 0;
299}
300