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