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