makebootable.cpp revision 6fd31acc
1/*
2 * Copyright 2005-2008, Ingo Weinhold, bonefish@users.sf.net.
3 * Distributed under the terms of the MIT License.
4 */
5
6#include <errno.h>
7#include <fcntl.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <unistd.h>
12#include <sys/stat.h>
13
14#include <ByteOrder.h>
15#include <Drivers.h>
16#include <Entry.h>
17#include <File.h>
18#include <fs_info.h>
19#include <Resources.h>
20#include <TypeConstants.h>
21
22// Linux and FreeBSD support
23#ifdef HAIKU_HOST_PLATFORM_LINUX
24#	include <ctype.h>
25#	include <linux/hdreg.h>
26#	include <sys/ioctl.h>
27
28#	include "PartitionMap.h"
29#	include "PartitionMapParser.h"
30#elif HAIKU_HOST_PLATFORM_FREEBSD
31#	include <ctype.h>
32#	include <sys/disklabel.h>
33#	include <sys/disk.h>
34#	include <sys/ioctl.h>
35
36#	include "PartitionMap.h"
37#	include "PartitionMapParser.h"
38#endif
39
40
41static const char *kCommandName = "makebootable";
42
43static const int kBootCodeSize				= 1024;
44static const int kFirstBootCodePartSize		= 512;
45static const int kSecondBootcodePartOffset	= 676;
46static const int kSecondBootcodePartSize	= kBootCodeSize
47												- kSecondBootcodePartOffset;
48static const int kPartitionOffsetOffset		= 506;
49
50static int kArgc;
51static const char *const *kArgv;
52
53// usage
54const char *kUsage =
55"Usage: %s [ options ] <file> ...\n"
56"\n"
57"Makes the specified BFS partitions/devices bootable by writing boot code\n"
58"into the first two sectors. It doesn't mark the partition(s) active.\n"
59"\n"
60"If a given <file> refers to a directory, the partition/device on which the\n"
61"directory resides will be made bootable. If it refers to a regular file,\n"
62"the file is considered a disk image and the boot code will be written to\n"
63"it.\n"
64"\n"
65"Options:\n"
66"  -h, --help    - Print this help text and exit.\n"
67"  --dry-run     - Do everything but actually writing the boot block to disk.\n"
68"\n"
69"[compatibility]\n"
70"  -alert        - Compatibility option. Ignored.\n"
71"  -full         - Compatibility option. Ignored.\n"
72"  -safe         - Compatibility option. Fail when specified.\n"
73;
74
75
76// print_usage
77static void
78print_usage(bool error)
79{
80	// get command name
81	const char *commandName = NULL;
82	if (kArgc > 0) {
83		if (const char *lastSlash = strchr(kArgv[0], '/'))
84			commandName = lastSlash + 1;
85		else
86			commandName = kArgv[0];
87	}
88
89	if (!commandName || strlen(commandName) == 0)
90		commandName = kCommandName;
91
92	// print usage
93	fprintf((error ? stderr : stdout), kUsage, commandName, commandName,
94		commandName);
95}
96
97
98// print_usage_and_exit
99static void
100print_usage_and_exit(bool error)
101{
102	print_usage(error);
103	exit(error ? 1 : 0);
104}
105
106
107// read_boot_code_data
108static uint8 *
109read_boot_code_data(const char* programPath)
110{
111	// open our executable
112	BFile executableFile;
113	status_t error = executableFile.SetTo(programPath, B_READ_ONLY);
114	if (error != B_OK) {
115		fprintf(stderr, "Error: Failed to open my executable file (\"%s\": "
116			"%s\n", programPath, strerror(error));
117		exit(1);
118	}
119
120	uint8 *bootCodeData = new uint8[kBootCodeSize];
121
122	// open our resources
123	BResources resources;
124	error = resources.SetTo(&executableFile);
125	const void *resourceData = NULL;
126	if (error == B_OK) {
127		// read the boot block from the resources
128		size_t resourceSize;
129		resourceData = resources.LoadResource(B_RAW_TYPE, 666, &resourceSize);
130
131		if (resourceData && resourceSize != (size_t)kBootCodeSize) {
132			resourceData = NULL;
133			printf("Warning: Something is fishy with my resources! The boot "
134				"code doesn't have the correct size. Trying the attribute "
135				"instead ...\n");
136		}
137	}
138
139	if (resourceData) {
140		// found boot data in the resources
141		memcpy(bootCodeData, resourceData, kBootCodeSize);
142	} else {
143		// no boot data in the resources; try the attribute
144		ssize_t bytesRead = executableFile.ReadAttr("BootCode", B_RAW_TYPE,
145			0, bootCodeData, kBootCodeSize);
146		if (bytesRead < 0) {
147			fprintf(stderr, "Error: Failed to read boot code from resources "
148				"or attribute.");
149			exit(1);
150		}
151		if (bytesRead != kBootCodeSize) {
152			fprintf(stderr, "Error: Failed to read boot code from resources, "
153				"and the boot code in the attribute has the wrong size!");
154			exit(1);
155		}
156	}
157
158	return bootCodeData;
159}
160
161
162// write_boot_code_part
163static void
164write_boot_code_part(const char *fileName, int fd, off_t imageOffset,
165	const uint8 *bootCodeData, int offset, int size, bool dryRun)
166{
167	if (!dryRun) {
168		ssize_t bytesWritten = write_pos(fd, imageOffset + offset,
169			bootCodeData + offset, size);
170		if (bytesWritten != size) {
171			fprintf(stderr, "Error: Failed to write to \"%s\": %s\n", fileName,
172				strerror(bytesWritten < 0 ? errno : B_ERROR));
173		}
174	}
175}
176
177
178// main
179int
180main(int argc, const char *const *argv)
181{
182	kArgc = argc;
183	kArgv = argv;
184
185	if (argc < 2)
186		print_usage_and_exit(true);
187
188	// parameters
189	const char **files = new const char*[argc];
190	int fileCount = 0;
191	bool dryRun = false;
192	off_t startOffset = 0;
193
194	// parse arguments
195	for (int argi = 1; argi < argc;) {
196		const char *arg = argv[argi++];
197
198		if (arg[0] == '-') {
199			if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
200				print_usage_and_exit(false);
201			} else if (strcmp(arg, "--dry-run") == 0) {
202				dryRun = true;
203			} else if (strcmp(arg, "-alert") == 0) {
204				// ignore
205			} else if (strcmp(arg, "-full") == 0) {
206				// ignore
207			} else if (strcmp(arg, "--start-offset") == 0) {
208				if (argi >= argc)
209					print_usage_and_exit(true);
210				startOffset = strtoll(argv[argi++], NULL, 0);
211			} else if (strcmp(arg, "-safe") == 0) {
212				fprintf(stderr, "Error: Sorry, BeOS R3 isn't supported!\n");
213				exit(1);
214			} else {
215				print_usage_and_exit(true);
216			}
217
218		} else {
219			files[fileCount++] = arg;
220		}
221	}
222
223	// we need at least one file
224	if (fileCount == 0)
225		print_usage_and_exit(true);
226
227	// read the boot code
228	uint8 *bootCodeData = read_boot_code_data(argv[0]);
229	if (!bootCodeData) {
230		fprintf(stderr, "Error: Failed to read ");
231		exit(1);
232	}
233
234	// iterate through the files and make them bootable
235	status_t error;
236	for (int i = 0; i < fileCount; i++) {
237		const char *fileName = files[i];
238		BEntry entry;
239		error = entry.SetTo(fileName, true);
240		if (error != B_OK) {
241			fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
242				fileName, strerror(error));
243			exit(1);
244		}
245
246		// get stat to check the type of the file
247		struct stat st;
248		error = entry.GetStat(&st);
249		if (error != B_OK) {
250			fprintf(stderr, "Error: Failed to stat \"%s\": %s\n",
251				fileName, strerror(error));
252			exit(1);
253		}
254
255		bool noPartition = false;
256		int64 partitionOffset = 0;
257		fs_info info;	// needs to be here (we use the device name later)
258		if (S_ISDIR(st.st_mode)) {
259			#ifdef __BEOS__
260
261				// a directory: get the device
262				error = fs_stat_dev(st.st_dev, &info);
263				if (error != B_OK) {
264					fprintf(stderr, "Error: Failed to determine device for "
265						"\"%s\": %s\n", fileName, strerror(error));
266					exit(1);
267				}
268
269				fileName = info.device_name;
270
271			#else
272
273				(void)info;
274				fprintf(stderr, "Error: Specifying directories not supported "
275					"on this platform!\n");
276				exit(1);
277
278			#endif
279
280		} else if (S_ISREG(st.st_mode)) {
281			// a regular file: fine
282			noPartition = true;
283		} else if (S_ISCHR(st.st_mode)) {
284			// character special: a device or partition under BeOS
285			// or under FreeBSD
286			#if !defined(__BEOS__) && !defined(HAIKU_HOST_PLATFORM_FREEBSD)
287
288				fprintf(stderr, "Error: Character special devices not "
289					"supported on this platform.\n");
290				exit(1);
291
292			#endif
293
294			#ifdef HAIKU_HOST_PLATFORM_FREEBSD
295
296				// chop off the trailing number
297				int fileNameLen = strlen(fileName);
298				int baseNameLen = -1;
299				for (int k = fileNameLen - 1; k >= 0; k--) {
300					if (!isdigit(fileName[k])) {
301						baseNameLen = k + 1;
302						break;
303					}
304				}
305
306				// Remove de 's' from 'ad2s2' slice device (partition for DOS
307				// users) to get 'ad2' base device
308				baseNameLen--;
309
310				if (baseNameLen < 0) {
311					// only digits?
312					fprintf(stderr, "Error: Failed to get base device name.\n");
313					exit(1);
314				}
315
316				if (baseNameLen < fileNameLen) {
317					// get base device name and partition index
318					char baseDeviceName[B_PATH_NAME_LENGTH];
319					int partitionIndex = atoi(fileName + baseNameLen + 1);
320						// Don't forget the 's' of slice :)
321					memcpy(baseDeviceName, fileName, baseNameLen);
322					baseDeviceName[baseNameLen] = '\0';
323
324					// open base device
325					int baseFD = open(baseDeviceName, O_RDONLY);
326					if (baseFD < 0) {
327						fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
328							baseDeviceName, strerror(errno));
329						exit(1);
330					}
331
332					// get device size
333					int64 deviceSize;
334					if (ioctl(baseFD, DIOCGMEDIASIZE, &deviceSize) == -1) {
335						fprintf(stderr, "Error: Failed to get device geometry "
336							"for \"%s\": %s\n", baseDeviceName,
337							strerror(errno));
338						exit(1);
339					}
340
341					// parse the partition map
342					PartitionMapParser parser(baseFD, 0, deviceSize);
343					PartitionMap map;
344					error = parser.Parse(NULL, &map);
345					if (error != B_OK) {
346						fprintf(stderr, "Error: Parsing partition table on "
347							"device \"%s\" failed: %s\n", baseDeviceName,
348							strerror(error));
349						exit(1);
350					}
351
352					close(baseFD);
353
354					// check the partition we are supposed to write at
355					Partition *partition = map.PartitionAt(partitionIndex - 1);
356					if (!partition || partition->IsEmpty()) {
357						fprintf(stderr, "Error: Invalid partition index %d.\n",
358							partitionIndex);
359						exit(1);
360					}
361
362					if (partition->IsExtended()) {
363						fprintf(stderr, "Error: Partition %d is an extended "
364							"partition.\n", partitionIndex);
365						exit(1);
366					}
367
368					partitionOffset = partition->Offset();
369
370				} else {
371					// The given device is the base device. We'll write at
372					// offset 0.
373				}
374
375			#endif // HAIKU_HOST_PLATFORM_FREEBSD
376
377		} else if (S_ISBLK(st.st_mode)) {
378			// block device: a device or partition under Linux
379			#ifdef HAIKU_HOST_PLATFORM_LINUX
380
381				// chop off the trailing number
382				int fileNameLen = strlen(fileName);
383				int baseNameLen = -1;
384				for (int k = fileNameLen - 1; k >= 0; k--) {
385					if (!isdigit(fileName[k])) {
386						baseNameLen = k + 1;
387						break;
388					}
389				}
390
391				if (baseNameLen < 0) {
392					// only digits?
393					fprintf(stderr, "Error: Failed to get base device name.\n");
394					exit(1);
395				}
396
397				if (baseNameLen < fileNameLen) {
398					// get base device name and partition index
399					char baseDeviceName[B_PATH_NAME_LENGTH];
400					int partitionIndex = atoi(fileName + baseNameLen);
401					memcpy(baseDeviceName, fileName, baseNameLen);
402					baseDeviceName[baseNameLen] = '\0';
403
404					// open base device
405					int baseFD = open(baseDeviceName, O_RDONLY);
406					if (baseFD < 0) {
407						fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
408							baseDeviceName, strerror(errno));
409						exit(1);
410					}
411
412					// get device geometry
413					hd_geometry geometry;
414					if (ioctl(baseFD, HDIO_GETGEO, &geometry) < 0) {
415						fprintf(stderr, "Error: Failed to get device geometry "
416							"for \"%s\": %s\n", baseDeviceName,
417							strerror(errno));
418						exit(1);
419					}
420					int64 deviceSize = (int64)geometry.heads * geometry.sectors
421						* geometry.cylinders * 512;
422
423					// parse the partition map
424					PartitionMapParser parser(baseFD, 0, deviceSize);
425					PartitionMap map;
426					error = parser.Parse(NULL, &map);
427					if (error != B_OK) {
428						fprintf(stderr, "Error: Parsing partition table on "
429							"device \"%s\" failed: %s\n", baseDeviceName,
430							strerror(error));
431						exit(1);
432					}
433
434					close(baseFD);
435
436					// check the partition we are supposed to write at
437					Partition *partition = map.PartitionAt(partitionIndex - 1);
438					if (!partition || partition->IsEmpty()) {
439						fprintf(stderr, "Error: Invalid partition index %d.\n",
440							partitionIndex);
441						exit(1);
442					}
443
444					if (partition->IsExtended()) {
445						fprintf(stderr, "Error: Partition %d is an extended "
446							"partition.\n", partitionIndex);
447						exit(1);
448					}
449
450					partitionOffset = partition->Offset();
451
452				} else {
453					// The given device is the base device. We'll write at
454					// offset 0.
455				}
456
457			#else	// !HAIKU_HOST_PLATFORM_LINUX
458
459			// partitions are block devices under Haiku, but not under BeOS
460			#ifndef __HAIKU__
461				fprintf(stderr, "Error: Block devices not supported on this "
462					"platform!\n");
463				exit(1);
464			#endif	// __HAIKU__
465
466			#endif
467		} else {
468			fprintf(stderr, "Error: File type of \"%s\" is not supported.\n",
469				fileName);
470			exit(1);
471		}
472
473		// open the file
474		int fd = open(fileName, O_RDWR);
475		if (fd < 0) {
476			fprintf(stderr, "Error: Failed to open \"%s\": %s\n", fileName,
477				strerror(errno));
478			exit(1);
479		}
480
481		#ifdef __BEOS__
482
483			// get a partition info
484			if (!noPartition
485				&& strlen(fileName) >= 3
486				&& strncmp("raw", fileName + strlen(fileName) - 3, 3)) {
487				partition_info partitionInfo;
488				if (ioctl(fd, B_GET_PARTITION_INFO, &partitionInfo,
489						sizeof(partitionInfo)) == 0) {
490					partitionOffset = partitionInfo.offset;
491				} else {
492					fprintf(stderr, "Error: Failed to get partition info: %s\n",
493						strerror(errno));
494					exit(1);
495				}
496			}
497
498		#endif	// __BEOS__
499
500		// adjust the partition offset in the boot code data
501		// hard coded sector size: 512 bytes
502		*(uint32*)(bootCodeData + kPartitionOffsetOffset)
503			= B_HOST_TO_LENDIAN_INT32((uint32)(partitionOffset / 512));
504
505		// write the boot code
506		printf("Writing boot code to \"%s\" (partition offset: %lld bytes) "
507			"...\n", fileName, partitionOffset);
508
509		write_boot_code_part(fileName, fd, startOffset, bootCodeData, 0,
510			kFirstBootCodePartSize, dryRun);
511		write_boot_code_part(fileName, fd, startOffset, bootCodeData,
512			kSecondBootcodePartOffset, kSecondBootcodePartSize,
513			dryRun);
514
515		close(fd);
516	}
517
518	return 0;
519}
520