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