xref: /haiku/src/bin/makebootable/platform/bios_ia32/makebootable.cpp (revision 5e9fd9f60d6a4f62bf88b67465e88990413355c8)
1 /*
2  * Copyright 2005-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <sys/stat.h>
14 
15 #include <ByteOrder.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/fs.h>
26 #	include <linux/hdreg.h>
27 #	include <sys/ioctl.h>
28 
29 #	define USE_PARTITION_MAP 1
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 #	define USE_PARTITION_MAP 1
37 #elif HAIKU_HOST_PLATFORM_DARWIN
38 #	include <ctype.h>
39 #	include <sys/disk.h>
40 #	include <sys/ioctl.h>
41 
42 #	define USE_PARTITION_MAP 1
43 #endif
44 
45 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
46 #	include <image.h>
47 
48 #	include <DiskDevice.h>
49 #	include <DiskDeviceRoster.h>
50 #	include <Drivers.h>
51 #	include <Partition.h>
52 #	include <Path.h>
53 
54 #	include "bfs_control.h"
55 #endif
56 
57 #if USE_PARTITION_MAP
58 #	include "guid.h"
59 #	include "gpt_known_guids.h"
60 #	include "Header.h"
61 #	include "PartitionMap.h"
62 #	include "PartitionMapParser.h"
63 #endif
64 
65 
66 static const char *kCommandName = "makebootable";
67 
68 static const int kBootCodeSize				= 1024;
69 static const int kFirstBootCodePartSize		= 512;
70 static const int kSecondBootCodePartOffset	= 676;
71 static const int kSecondBootCodePartSize	= kBootCodeSize
72 												- kSecondBootCodePartOffset;
73 static const int kPartitionOffsetOffset		= 506;
74 
75 static int kArgc;
76 static const char *const *kArgv;
77 
78 // usage
79 const char *kUsage =
80 "Usage: %s [ options ] <file> ...\n"
81 "\n"
82 "Makes the specified BFS partitions/devices bootable by writing boot code\n"
83 "into the first two sectors. It doesn't mark the partition(s) active.\n"
84 "\n"
85 "If a given <file> refers to a directory, the partition/device on which the\n"
86 "directory resides will be made bootable. If it refers to a regular file,\n"
87 "the file is considered a disk image and the boot code will be written to\n"
88 "it.\n"
89 "\n"
90 "Options:\n"
91 "  -h, --help    - Print this help text and exit.\n"
92 "  --dry-run     - Do everything but actually writing the boot block to disk.\n"
93 ;
94 
95 
96 // print_usage
97 static void
98 print_usage(bool error)
99 {
100 	// get command name
101 	const char *commandName = NULL;
102 	if (kArgc > 0) {
103 		if (const char *lastSlash = strchr(kArgv[0], '/'))
104 			commandName = lastSlash + 1;
105 		else
106 			commandName = kArgv[0];
107 	}
108 
109 	if (!commandName || strlen(commandName) == 0)
110 		commandName = kCommandName;
111 
112 	// print usage
113 	fprintf((error ? stderr : stdout), kUsage, commandName, commandName,
114 		commandName);
115 }
116 
117 
118 // print_usage_and_exit
119 static void
120 print_usage_and_exit(bool error)
121 {
122 	print_usage(error);
123 	exit(error ? 1 : 0);
124 }
125 
126 
127 // read_boot_code_data
128 static uint8 *
129 read_boot_code_data(const char* programPath)
130 {
131 	// open our executable
132 	BFile executableFile;
133 	status_t error = executableFile.SetTo(programPath, B_READ_ONLY);
134 	if (error != B_OK) {
135 		fprintf(stderr, "Error: Failed to open my executable file (\"%s\": "
136 			"%s\n", programPath, strerror(error));
137 		exit(1);
138 	}
139 
140 	uint8 *bootCodeData = new uint8[kBootCodeSize];
141 
142 	// open our resources
143 	BResources resources;
144 	error = resources.SetTo(&executableFile);
145 	const void *resourceData = NULL;
146 	if (error == B_OK) {
147 		// read the boot block from the resources
148 		size_t resourceSize;
149 		resourceData = resources.LoadResource(B_RAW_TYPE, 666, &resourceSize);
150 
151 		if (resourceData && resourceSize != (size_t)kBootCodeSize) {
152 			resourceData = NULL;
153 			printf("Warning: Something is fishy with my resources! The boot "
154 				"code doesn't have the correct size. Trying the attribute "
155 				"instead ...\n");
156 		}
157 	}
158 
159 	if (resourceData) {
160 		// found boot data in the resources
161 		memcpy(bootCodeData, resourceData, kBootCodeSize);
162 	} else {
163 		// no boot data in the resources; try the attribute
164 		ssize_t bytesRead = executableFile.ReadAttr("BootCode", B_RAW_TYPE,
165 			0, bootCodeData, kBootCodeSize);
166 		if (bytesRead < 0) {
167 			fprintf(stderr, "Error: Failed to read boot code from resources "
168 				"or attribute.\n");
169 			exit(1);
170 		}
171 		if (bytesRead != kBootCodeSize) {
172 			fprintf(stderr, "Error: Failed to read boot code from resources, "
173 				"and the boot code in the attribute has the wrong size!\n");
174 			exit(1);
175 		}
176 	}
177 
178 	return bootCodeData;
179 }
180 
181 
182 // write_boot_code_part
183 static void
184 write_boot_code_part(const char *fileName, int fd, off_t imageOffset,
185 	const uint8 *bootCodeData, int offset, int size, bool dryRun)
186 {
187 	if (!dryRun) {
188 		ssize_t bytesWritten = write_pos(fd, imageOffset + offset,
189 			bootCodeData + offset, size);
190 		if (bytesWritten != size) {
191 			fprintf(stderr, "Error: Failed to write to \"%s\": %s\n", fileName,
192 				strerror(bytesWritten < 0 ? errno : B_ERROR));
193 		}
194 	}
195 }
196 
197 
198 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
199 
200 static status_t
201 find_own_image(image_info *info)
202 {
203 	int32 cookie = 0;
204 	while (get_next_image_info(B_CURRENT_TEAM, &cookie, info) == B_OK) {
205 		if (((addr_t)info->text <= (addr_t)find_own_image
206 			&& (addr_t)info->text + info->text_size
207 				> (addr_t)find_own_image)) {
208 			return B_OK;
209 		}
210 	}
211 
212 	return B_NAME_NOT_FOUND;
213 }
214 
215 #endif
216 
217 
218 #if USE_PARTITION_MAP
219 
220 static void
221 dump_partition_map(const PartitionMap& map)
222 {
223 	fprintf(stderr, "partitions:\n");
224 	int32 count = map.CountPartitions();
225 	for (int i = 0; i < count; i++) {
226 		const Partition* partition = map.PartitionAt(i);
227 		fprintf(stderr, "%2d: ", i);
228 		if (partition == NULL) {
229 			fprintf(stderr, "<null>\n");
230 			continue;
231 		}
232 
233 		if (partition->IsEmpty()) {
234 			fprintf(stderr, "<empty>\n");
235 			continue;
236 		}
237 
238 		fprintf(stderr, "offset: %16" B_PRIdOFF ", size: %16" B_PRIdOFF
239 			", type: %x%s\n", partition->Offset(), partition->Size(),
240 			partition->Type(), partition->IsExtended() ? " (extended)" : "");
241 	}
242 }
243 
244 
245 static void
246 get_partition_offset(int deviceFD, off_t deviceStart, off_t deviceSize,
247 		int blockSize, int partitionIndex, char *deviceName,
248 		int64 &_partitionOffset)
249 {
250 	PartitionMapParser parser(deviceFD, deviceStart, deviceSize, blockSize);
251 	PartitionMap map;
252 	status_t error = parser.Parse(NULL, &map);
253 	if (error == B_OK) {
254 		Partition *partition = map.PartitionAt(partitionIndex - 1);
255 		if (!partition || partition->IsEmpty()) {
256 			fprintf(stderr, "Error: Invalid partition index %d.\n",
257 				partitionIndex);
258 			dump_partition_map(map);
259 			exit(1);
260 		}
261 
262 		if (partition->IsExtended()) {
263 			fprintf(stderr, "Error: Partition %d is an extended "
264 				"partition.\n", partitionIndex);
265 			dump_partition_map(map);
266 			exit(1);
267 		}
268 
269 		_partitionOffset = partition->Offset();
270 	} else {
271 		// try again using GPT instead
272 		EFI::Header gptHeader(deviceFD, deviceSize, blockSize);
273 		error = gptHeader.InitCheck();
274 		if (error == B_OK && partitionIndex < gptHeader.EntryCount()) {
275 			gpt_partition_entry partition = gptHeader.EntryAt(partitionIndex - 1);
276 
277 			static_guid bfs_uuid = {0x42465331, 0x3BA3, 0x10F1,
278 				0x802A4861696B7521LL};
279 
280 			if (!(bfs_uuid == partition.partition_type)) {
281 				fprintf(stderr, "Error: Partition %d does not have the "
282 					"BFS UUID.\n", partitionIndex);
283 				exit(1);
284 			}
285 
286 			_partitionOffset = partition.StartBlock() * blockSize;
287 		} else {
288 			fprintf(stderr, "Error: Parsing partition table on device "
289 				"\"%s\" failed: %s\n", deviceName, strerror(error));
290 			exit(1);
291 		}
292 	}
293 
294 	close(deviceFD);
295 }
296 
297 #endif
298 
299 
300 // main
301 int
302 main(int argc, const char *const *argv)
303 {
304 	kArgc = argc;
305 	kArgv = argv;
306 
307 	if (argc < 2)
308 		print_usage_and_exit(true);
309 
310 	// parameters
311 	const char **files = new const char*[argc];
312 	int fileCount = 0;
313 	bool dryRun = false;
314 	off_t startOffset = 0;
315 
316 	// parse arguments
317 	for (int argi = 1; argi < argc;) {
318 		const char *arg = argv[argi++];
319 
320 		if (arg[0] == '-') {
321 			if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
322 				print_usage_and_exit(false);
323 			} else if (strcmp(arg, "--dry-run") == 0) {
324 				dryRun = true;
325 			} else if (strcmp(arg, "--start-offset") == 0) {
326 				if (argi >= argc)
327 					print_usage_and_exit(true);
328 				startOffset = strtoll(argv[argi++], NULL, 0);
329 			} else {
330 				print_usage_and_exit(true);
331 			}
332 
333 		} else {
334 			files[fileCount++] = arg;
335 		}
336 	}
337 
338 	// we need at least one file
339 	if (fileCount == 0)
340 		print_usage_and_exit(true);
341 
342 	// read the boot code
343 	uint8 *bootCodeData = NULL;
344 #ifndef HAIKU_TARGET_PLATFORM_HAIKU
345 	bootCodeData = read_boot_code_data(argv[0]);
346 #else
347 	image_info info;
348 	if (find_own_image(&info) == B_OK)
349 		bootCodeData = read_boot_code_data(info.name);
350 #endif
351 	if (!bootCodeData) {
352 		fprintf(stderr, "Error: Failed to read \n");
353 		exit(1);
354 	}
355 
356 	// iterate through the files and make them bootable
357 	status_t error;
358 	for (int i = 0; i < fileCount; i++) {
359 		const char *fileName = files[i];
360 		BEntry entry;
361 		error = entry.SetTo(fileName, true);
362 		if (error != B_OK) {
363 			fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
364 				fileName, strerror(error));
365 			exit(1);
366 		}
367 
368 		// get stat to check the type of the file
369 		struct stat st;
370 		error = entry.GetStat(&st);
371 		if (error != B_OK) {
372 			fprintf(stderr, "Error: Failed to stat \"%s\": %s\n",
373 				fileName, strerror(error));
374 			exit(1);
375 		}
376 
377 		bool noPartition = false;
378 		int64 partitionOffset = 0;
379 		fs_info info;	// needs to be here (we use the device name later)
380 		if (S_ISDIR(st.st_mode)) {
381 			#ifdef HAIKU_TARGET_PLATFORM_HAIKU
382 
383 				// a directory: get the device
384 				error = fs_stat_dev(st.st_dev, &info);
385 				if (error != B_OK) {
386 					fprintf(stderr, "Error: Failed to determine device for "
387 						"\"%s\": %s\n", fileName, strerror(error));
388 					exit(1);
389 				}
390 
391 				fileName = info.device_name;
392 
393 			#else
394 
395 				(void)info;
396 				fprintf(stderr, "Error: Specifying directories not supported "
397 					"on this platform!\n");
398 				exit(1);
399 
400 			#endif
401 
402 		} else if (S_ISREG(st.st_mode)) {
403 			// a regular file: fine
404 			noPartition = true;
405 		} else if (S_ISCHR(st.st_mode)) {
406 			// character special: a device or partition under BeOS
407 			// or under FreeBSD
408 			#if !defined(HAIKU_TARGET_PLATFORM_HAIKU) \
409 				&& !defined(HAIKU_HOST_PLATFORM_FREEBSD)
410 
411 				fprintf(stderr, "Error: Character special devices not "
412 					"supported on this platform.\n");
413 				exit(1);
414 
415 			#endif
416 
417 			#ifdef HAIKU_HOST_PLATFORM_FREEBSD
418 
419 				// chop off the trailing number
420 				int fileNameLen = strlen(fileName);
421 				int baseNameLen = -1;
422 				for (int k = fileNameLen - 1; k >= 0; k--) {
423 					if (!isdigit(fileName[k])) {
424 						baseNameLen = k + 1;
425 						break;
426 					}
427 				}
428 
429 				// Remove de 's' from 'ad2s2' slice device (partition for DOS
430 				// users) to get 'ad2' base device
431 				baseNameLen--;
432 
433 				if (baseNameLen < 0) {
434 					// only digits?
435 					fprintf(stderr, "Error: Failed to get base device name.\n");
436 					exit(1);
437 				}
438 
439 				if (baseNameLen < fileNameLen) {
440 					// get base device name and partition index
441 					char baseDeviceName[B_PATH_NAME_LENGTH];
442 					int partitionIndex = atoi(fileName + baseNameLen + 1);
443 						// Don't forget the 's' of slice :)
444 					memcpy(baseDeviceName, fileName, baseNameLen);
445 					baseDeviceName[baseNameLen] = '\0';
446 
447 					// open base device
448 					int baseFD = open(baseDeviceName, O_RDONLY);
449 					if (baseFD < 0) {
450 						fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
451 							baseDeviceName, strerror(errno));
452 						exit(1);
453 					}
454 
455 					// get device size
456 					int64 deviceSize;
457 					if (ioctl(baseFD, DIOCGMEDIASIZE, &deviceSize) == -1) {
458 						fprintf(stderr, "Error: Failed to get device geometry "
459 							"for \"%s\": %s\n", baseDeviceName,
460 							strerror(errno));
461 						exit(1);
462 					}
463 
464 					// parse the partition map
465 					// TODO: block size!
466 					get_partition_offset(baseFD, 0, deviceSize, 512,
467 						partitionIndex, baseDeviceName, partitionOffset);
468 				} else {
469 					// The given device is the base device. We'll write at
470 					// offset 0.
471 				}
472 
473 			#endif // HAIKU_HOST_PLATFORM_FREEBSD
474 
475 		} else if (S_ISBLK(st.st_mode)) {
476 			// block device: a device or partition under Linux or Darwin
477 			#ifdef HAIKU_HOST_PLATFORM_LINUX
478 
479 				// chop off the trailing number
480 				int fileNameLen = strlen(fileName);
481 				int baseNameLen = -1;
482 				for (int k = fileNameLen - 1; k >= 0; k--) {
483 					if (!isdigit(fileName[k])) {
484 						baseNameLen = k + 1;
485 						break;
486 					}
487 				}
488 
489 				if (baseNameLen < 0) {
490 					// only digits?
491 					fprintf(stderr, "Error: Failed to get base device name.\n");
492 					exit(1);
493 				}
494 
495 				if (baseNameLen < fileNameLen) {
496 					// get base device name and partition index
497 					char baseDeviceName[B_PATH_NAME_LENGTH];
498 					int partitionIndex = atoi(fileName + baseNameLen);
499 					memcpy(baseDeviceName, fileName, baseNameLen);
500 					baseDeviceName[baseNameLen] = '\0';
501 
502 					// open base device
503 					int baseFD = open(baseDeviceName, O_RDONLY);
504 					if (baseFD < 0) {
505 						fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
506 							baseDeviceName, strerror(errno));
507 						exit(1);
508 					}
509 
510 					// get device size -- try BLKGETSIZE64, but, if it doesn't
511 					// work, fall back to the obsolete HDIO_GETGEO
512 					int64 deviceSize;
513 					hd_geometry geometry;
514 					if (ioctl(baseFD, BLKGETSIZE64, &deviceSize) == 0
515 						&& deviceSize > 0) {
516 						// looks good
517 					} else if (ioctl(baseFD, HDIO_GETGEO, &geometry) == 0) {
518 						deviceSize = (int64)geometry.heads * geometry.sectors
519 							* geometry.cylinders * 512;
520 					} else {
521 						fprintf(stderr, "Error: Failed to get device geometry "
522 							"for \"%s\": %s\n", baseDeviceName,
523 							strerror(errno));
524 						exit(1);
525 					}
526 
527 					// parse the partition map
528 					// TODO: block size!
529 					get_partition_offset(baseFD, 0, deviceSize, 512,
530 						partitionIndex, baseDeviceName, partitionOffset);
531 				} else {
532 					// The given device is the base device. We'll write at
533 					// offset 0.
534 				}
535 
536 			#elif defined(HAIKU_HOST_PLATFORM_DARWIN)
537 				// chop off the trailing number
538 				int fileNameLen = strlen(fileName);
539 				int baseNameLen = fileNameLen - 2;
540 
541 				// get base device name and partition index
542 				char baseDeviceName[B_PATH_NAME_LENGTH];
543 				int partitionIndex = atoi(fileName + baseNameLen + 1);
544 				memcpy(baseDeviceName, fileName, baseNameLen);
545 				baseDeviceName[baseNameLen] = '\0';
546 
547 				// open base device
548 				int baseFD = open(baseDeviceName, O_RDONLY);
549 				if (baseFD < 0) {
550 					fprintf(stderr, "Error: Failed to open \"%s\": %s\n",
551 							baseDeviceName, strerror(errno));
552 					exit(1);
553 				}
554 
555 				// get device size
556 				int64 blockSize;
557 				int64 blockCount;
558 				int64 deviceSize;
559 				if (ioctl(baseFD, DKIOCGETBLOCKSIZE, &blockSize) == -1) {
560 					fprintf(stderr, "Error: Failed to get block size "
561 							"for \"%s\": %s\n", baseDeviceName,
562 							strerror(errno));
563 					exit(1);
564 				}
565 				if (ioctl(baseFD, DKIOCGETBLOCKCOUNT, &blockCount) == -1) {
566 					fprintf(stderr, "Error: Failed to get block count "
567 							"for \"%s\": %s\n", baseDeviceName,
568 							strerror(errno));
569 					exit(1);
570 				}
571 
572 				deviceSize = blockSize * blockCount;
573 
574 				// parse the partition map
575 				get_partition_offset(baseFD, 0, deviceSize, 512,
576 						partitionIndex, baseDeviceName, partitionOffset);
577 			#else
578 			// partitions are block devices under Haiku, but not under BeOS
579 			#ifdef HAIKU_TARGET_PLATFORM_HAIKU
580 				fprintf(stderr, "Error: Block devices not supported on this "
581 					"platform!\n");
582 				exit(1);
583 			#endif	// HAIKU_TARGET_PLATFORM_HAIKU
584 
585 			#endif
586 		} else {
587 			fprintf(stderr, "Error: File type of \"%s\" is not supported.\n",
588 				fileName);
589 			exit(1);
590 		}
591 
592 		// open the file
593 		int fd = open(fileName, O_RDWR);
594 		if (fd < 0) {
595 			fprintf(stderr, "Error: Failed to open \"%s\": %s\n", fileName,
596 				strerror(errno));
597 			exit(1);
598 		}
599 
600 		#ifdef HAIKU_TARGET_PLATFORM_HAIKU
601 
602 			// get a partition info
603 			if (!noPartition
604 				&& strlen(fileName) >= 3
605 				&& strncmp("raw", fileName + strlen(fileName) - 3, 3)) {
606 				partition_info partitionInfo;
607 				if (ioctl(fd, B_GET_PARTITION_INFO, &partitionInfo,
608 						sizeof(partitionInfo)) == 0) {
609 					partitionOffset = partitionInfo.offset;
610 				} else {
611 					fprintf(stderr, "Error: Failed to get partition info: %s\n",
612 						strerror(errno));
613 					exit(1);
614 				}
615 			}
616 
617 		#endif	// HAIKU_TARGET_PLATFORM_HAIKU
618 
619 		// adjust the partition offset in the boot code data
620 		// hard coded sector size: 512 bytes
621 		*(uint32*)(bootCodeData + kPartitionOffsetOffset)
622 			= B_HOST_TO_LENDIAN_INT32((uint32)(partitionOffset / 512));
623 
624 		// write the boot code
625 		printf("Writing boot code to \"%s\" (partition offset: %" B_PRId64
626 			" bytes, start offset = %" B_PRIdOFF ") "
627 			"...\n", fileName, partitionOffset, startOffset);
628 
629 		write_boot_code_part(fileName, fd, startOffset, bootCodeData, 0,
630 			kFirstBootCodePartSize, dryRun);
631 		write_boot_code_part(fileName, fd, startOffset, bootCodeData,
632 			kSecondBootCodePartOffset, kSecondBootCodePartSize,
633 			dryRun);
634 
635 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
636 		// check if this partition is mounted
637 		BDiskDeviceRoster roster;
638 		BPartition* partition;
639 		BDiskDevice device;
640 		status_t status = roster.GetPartitionForPath(fileName, &device,
641 			&partition);
642 		if (status != B_OK) {
643 			status = roster.GetFileDeviceForPath(fileName, &device);
644 			if (status == B_OK)
645 				partition = &device;
646 		}
647 		if (status == B_OK && partition->IsMounted() && !dryRun) {
648 			// This partition is mounted, we need to tell BFS to update its
649 			// boot block (we are using part of the same logical block).
650 			BPath path;
651 			status = partition->GetMountPoint(&path);
652 			if (status == B_OK) {
653 				update_boot_block update;
654 				update.offset = kSecondBootCodePartOffset - 512;
655 				update.data = bootCodeData + kSecondBootCodePartOffset;
656 				update.length = kSecondBootCodePartSize;
657 
658 				int mountFD = open(path.Path(), O_RDONLY);
659 				if (ioctl(mountFD, BFS_IOCTL_UPDATE_BOOT_BLOCK, &update,
660 						sizeof(update_boot_block)) != 0) {
661 					fprintf(stderr, "Could not update BFS boot block: %s\n",
662 						strerror(errno));
663 				}
664 				close(mountFD);
665 			} else {
666 				fprintf(stderr, "Could not update BFS boot code while the "
667 					"partition is mounted!\n");
668 			}
669 		}
670 #endif	// HAIKU_TARGET_PLATFORM_HAIKU
671 
672 		close(fd);
673 	}
674 
675 	delete[] files;
676 
677 	return 0;
678 }
679