xref: /haiku/src/bin/makebootable/platform/bios_ia32/makebootable.cpp (revision fc1ca2da5cfcb00ffdf791606d5ae97fdd58a638)
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 
41 static const char *kCommandName = "makebootable";
42 
43 static const int kBootCodeSize				= 1024;
44 static const int kFirstBootCodePartSize		= 512;
45 static const int kSecondBootcodePartOffset	= 676;
46 static const int kSecondBootcodePartSize	= kBootCodeSize
47 												- kSecondBootcodePartOffset;
48 static const int kPartitionOffsetOffset		= 506;
49 
50 static int kArgc;
51 static const char *const *kArgv;
52 
53 // usage
54 const 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
77 static void
78 print_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
99 static void
100 print_usage_and_exit(bool error)
101 {
102 	print_usage(error);
103 	exit(error ? 1 : 0);
104 }
105 
106 
107 // read_boot_code_data
108 static uint8 *
109 read_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
163 static void
164 write_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
179 int
180 main(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