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
66static const char *kCommandName = "makebootable";
67
68static const int kBootCodeSize				= 1024;
69static const int kFirstBootCodePartSize		= 512;
70static const int kSecondBootCodePartOffset	= 676;
71static const int kSecondBootCodePartSize	= kBootCodeSize
72												- kSecondBootCodePartOffset;
73static const int kPartitionOffsetOffset		= 506;
74
75static int kArgc;
76static const char *const *kArgv;
77
78// usage
79const 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
97static void
98print_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
119static void
120print_usage_and_exit(bool error)
121{
122	print_usage(error);
123	exit(error ? 1 : 0);
124}
125
126
127// read_boot_code_data
128static uint8 *
129read_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
183static void
184write_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
200static status_t
201find_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
220static void
221dump_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
245static void
246get_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
301int
302main(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