makebootable.cpp revision 04fcc147ca2ab29344fa77509b88bab29a76e2b8
1/*
2 * Copyright 2005-2006, 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 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#endif	// HAIKU_HOST_PLATFORM_LINUX
31
32
33static const char *kCommandName = "makebootable";
34
35static const int kBootCodeSize				= 1024;
36static const int kFirstBootCodePartSize		= 512;
37static const int kSecondBootcodePartOffset	= 676;
38static const int kSecondBootcodePartSize	= kBootCodeSize
39												- kSecondBootcodePartOffset;
40static const int kPartitionOffsetOffset		= 506;
41
42static int kArgc;
43static const char *const *kArgv;
44
45// usage
46const char *kUsage =
47"Usage: %s [ options ] <file> ...\n"
48"\n"
49"Makes the specified BFS partitions/devices bootable by writing boot code\n"
50"into the first two sectors. It doesn't mark the partition(s) active.\n"
51"\n"
52"If a given <file> refers to a directory, the partition/device on which the\n"
53"directory resides will be made bootable. If it refers to a regular file,\n"
54"the file is considered a disk image and the boot code will be written to\n"
55"it.\n"
56"\n"
57"Options:\n"
58"  -h, --help    - Print this help text and exit.\n"
59"  --dry-run     - Do everything but actually writing the boot block to disk.\n"
60"\n"
61"[compatibility]\n"
62"  -alert        - Compatibility option. Ignored.\n"
63"  -full         - Compatibility option. Ignored.\n"
64"  -safe         - Compatibility option. Fail when specified.\n"
65;
66
67// print_usage
68static void
69print_usage(bool error)
70{
71	// get command name
72	const char *commandName = NULL;
73	if (kArgc > 0) {
74		if (const char *lastSlash = strchr(kArgv[0], '/'))
75			commandName = lastSlash + 1;
76		else
77			commandName = kArgv[0];
78	}
79
80	if (!commandName || strlen(commandName) == 0)
81		commandName = kCommandName;
82
83	// print usage
84	fprintf((error ? stderr : stdout), kUsage, commandName, commandName,
85		commandName);
86}
87
88// print_usage_and_exit
89static void
90print_usage_and_exit(bool error)
91{
92	print_usage(error);
93	exit(error ? 1 : 0);
94}
95
96// write_boot_code_part
97static void
98write_boot_code_part(const char *fileName, int fd, const uint8 *bootCodeData,
99	int offset, int size, bool dryRun)
100{
101	printf("writing %d bytes at offset %d%s\n", size, offset,
102		(dryRun ? " (dry run)" : ""));
103
104	if (!dryRun) {
105		ssize_t bytesWritten = write_pos(fd, offset, bootCodeData + offset,
106			size);
107		if (bytesWritten != size) {
108			fprintf(stderr, "Error: Failed to write to \"%s\": %s\n", fileName,
109				strerror(bytesWritten < 0 ? errno : B_ERROR));
110		}
111	}
112}
113
114// main
115int
116main(int argc, const char *const *argv)
117{
118	kArgc = argc;
119	kArgv = argv;
120
121	if (argc < 2)
122		print_usage_and_exit(true);
123
124	// parameters
125	const char **files = new const char*[argc];
126	int fileCount = 0;
127	bool dryRun = false;
128
129	// parse arguments
130	for (int argi = 1; argi < argc;) {
131		const char *arg = argv[argi++];
132
133		if (arg[0] == '-') {
134			if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
135				print_usage_and_exit(false);
136			} else if (strcmp(arg, "--dry-run") == 0) {
137				dryRun = true;
138			} else if (strcmp(arg, "-alert") == 0) {
139				// ignore
140			} else if (strcmp(arg, "-full") == 0) {
141				// ignore
142			} else if (strcmp(arg, "-safe") == 0) {
143				fprintf(stderr, "Error: Sorry, BeOS R3 isn't supported!\n");
144				exit(1);
145			} else {
146				print_usage_and_exit(true);
147			}
148
149		} else {
150			files[fileCount++] = arg;
151		}
152	}
153
154	// we need at least one file
155	if (fileCount == 0)
156		print_usage_and_exit(true);
157
158	// open our executable
159	BFile resourcesFile;
160	status_t error = resourcesFile.SetTo(argv[0], B_READ_ONLY);
161	if (error != B_OK) {
162		fprintf(stderr, "Error: Failed to open my resources: %s\n",
163			strerror(error));
164		exit(1);
165	}
166
167	// open our resources
168	BResources resources;
169	error = resources.SetTo(&resourcesFile);
170	if (error != B_OK) {
171		fprintf(stderr, "Error: Failed to read my resources: %s\n",
172			strerror(error));
173		exit(1);
174	}
175
176	// read the boot block from the resources
177	size_t resourceSize;
178	const void *resourceData = resources.LoadResource(B_RAW_TYPE, 666,
179		&resourceSize);
180	if (!resourceData) {
181		fprintf(stderr,
182			"Error: Failed to read the boot block from my resources!\n");
183		exit(1);
184	}
185
186	if (resourceSize != (size_t)kBootCodeSize) {
187		fprintf(stderr,
188			"Error: Something is fishy with my resources! The boot code "
189			"doesn't have the correct size.\n");
190		exit(1);
191	}
192
193	// clone the boot code data, so that we can modify it
194	uint8 *bootCodeData = new uint8[kBootCodeSize];
195	memcpy(bootCodeData, resourceData, kBootCodeSize);
196
197	// iterate through the files and make them bootable
198	for (int i = 0; i < fileCount; i++) {
199		const char *fileName = files[i];
200		BEntry entry;
201		error = entry.SetTo(fileName, true);
202		if (error != B_OK) {
203			fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
204				fileName, strerror(error));
205			exit(1);
206		}
207
208		// get stat to check the type of the file
209		struct stat st;
210		error = entry.GetStat(&st);
211		if (error != B_OK) {
212			fprintf(stderr, "Error: Failed to stat \"%s\": %s\n",
213				fileName, strerror(error));
214			exit(1);
215		}
216
217		bool noPartition = false;
218		int64 partitionOffset = 0;
219		fs_info info;	// needs to be here (we use the device name later)
220		if (S_ISDIR(st.st_mode)) {
221			#ifdef __BEOS__
222
223				// a directory: get the device
224				error = fs_stat_dev(st.st_dev, &info);
225				if (error != B_OK) {
226					fprintf(stderr, "Error: Failed to determine device for "
227						"\"%s\": %s\n", fileName, strerror(error));
228					exit(1);
229				}
230
231				fileName = info.device_name;
232
233			#else
234
235				(void)info;
236				fprintf(stderr, "Error: Specifying directories not supported "
237					"on this platform!\n");
238				exit(1);
239
240			#endif
241
242		} else if (S_ISREG(st.st_mode)) {
243			// a regular file: fine
244			noPartition = true;
245		} else if (S_ISCHR(st.st_mode)) {
246			// character special: a device or partition under BeOS
247			#ifndef __BEOS__
248
249				fprintf(stderr, "Error: Character special devices not "
250					"supported on this platform.\n");
251				exit(1);
252
253			#endif
254		} else if (S_ISBLK(st.st_mode)) {
255			// block device: a device or partition under Linux
256			#ifdef HAIKU_HOST_PLATFORM_LINUX
257
258				// chop off the trailing number
259				int fileNameLen = strlen(fileName);
260				int baseNameLen = -1;
261				for (int k = fileNameLen - 1; k >= 0; k--) {
262					if (!isdigit(fileName[k])) {
263						baseNameLen = k + 1;
264						break;
265					}
266				}
267
268				if (baseNameLen < 0) {
269					// only digits?
270					fprintf(stderr, "Error: Failed to get base device name.\n");
271					exit(1);
272				}
273
274				if (baseNameLen < fileNameLen) {
275					// get base device name and partition index
276					char baseDeviceName[B_PATH_NAME_LENGTH];
277					int partitionIndex = atoi(fileName + baseNameLen);
278					memcpy(baseDeviceName, fileName, baseNameLen);
279					baseDeviceName[baseNameLen] = '\0';
280
281					// open base device
282					int baseFD = open(baseDeviceName, O_RDONLY);
283					if (baseFD < 0) {
284						fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
285							baseDeviceName, strerror(errno));
286						exit(1);
287					}
288
289					// get device geometry
290					hd_geometry geometry;
291					if (ioctl(baseFD, HDIO_GETGEO, &geometry) < 0) {
292						fprintf(stderr, "Error: Failed to get device geometry "
293							"for \"%s\": %s\n", baseDeviceName,
294							strerror(errno));
295						exit(1);
296					}
297					int64 deviceSize = (int64)geometry.heads * geometry.sectors
298						* geometry.cylinders * 512;
299
300					// parse the partition map
301					PartitionMapParser parser(baseFD, 0, deviceSize, 512);
302					PartitionMap map;
303					error = parser.Parse(NULL, &map);
304					if (error != B_OK) {
305						fprintf(stderr, "Error: Parsing partition table on "
306							"device \"%s\" failed: %s\n", baseDeviceName,
307							strerror(error));
308						exit(1);
309					}
310
311					close(baseFD);
312
313					// check the partition we are supposed to write at
314					Partition *partition = map.PartitionAt(partitionIndex - 1);
315					if (!partition || partition->IsEmpty()) {
316						fprintf(stderr, "Error: Invalid partition index %d.\n",
317							partitionIndex);
318						exit(1);
319					}
320
321					if (partition->IsExtended()) {
322						fprintf(stderr, "Error: Partition %d is an extended "
323							"partition.\n", partitionIndex);
324						exit(1);
325					}
326
327					partitionOffset = partition->Offset();
328
329				} else {
330					// The given device is the base device. We'll write at
331					// offset 0.
332				}
333
334			#else	// !HAIKU_HOST_PLATFORM_LINUX
335
336				fprintf(stderr, "Error: Block devices not supported on this "
337					"platform!\n");
338				exit(1);
339
340			#endif
341		} else {
342			fprintf(stderr, "Error: File type of \"%s\" is not supported.\n",
343				fileName);
344			exit(1);
345		}
346
347		// open the file
348		int fd = open(fileName, O_RDWR);
349		if (fd < 0) {
350			fprintf(stderr, "Error: Failed to open \"%s\": %s\n", fileName,
351				strerror(errno));
352			exit(1);
353		}
354
355		#ifdef __BEOS__
356
357			// get a partition info
358			if (!noPartition) {
359				partition_info partitionInfo;
360				if (ioctl(fd, B_GET_PARTITION_INFO, &partitionInfo) == 0) {
361					// hard coded sector size: 512 bytes
362					partitionOffset = partitionInfo.offset / 512;
363				} else {
364					fprintf(stderr, "Error: Failed to get partition info: %s",
365						strerror(errno));
366					exit(1);
367				}
368			}
369
370		#endif	// __BEOS__
371
372		// adjust the partition offset in the boot code data
373		// hard coded sector size: 512 bytes
374		*(uint32*)(bootCodeData + kPartitionOffsetOffset)
375			= B_HOST_TO_LENDIAN_INT32((uint32)(partitionOffset / 512));
376
377		// write the boot code
378		printf("Writing boot code to \"%s\" (partition offset: %lld bytes) "
379			"...\n", fileName, partitionOffset);
380
381		write_boot_code_part(fileName, fd, bootCodeData, 0,
382			kFirstBootCodePartSize, dryRun);
383		write_boot_code_part(fileName, fd, bootCodeData,
384			kSecondBootcodePartOffset, kSecondBootcodePartSize, dryRun);
385
386		close(fd);
387	}
388
389	return 0;
390}
391