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