1/*
2 * Copyright 2008-2011, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Axel D��rfler, axeld@pinc-software.de
7 *		Michael Pfeiffer <laplace@users.sourceforge.net>
8 */
9
10
11#include "LegacyBootMenu.h"
12
13#include <new>
14
15#include <errno.h>
16#include <stdio.h>
17
18#include <Catalog.h>
19#include <DataIO.h>
20#include <DiskDevice.h>
21#include <DiskDeviceTypes.h>
22#include <DiskDeviceRoster.h>
23#include <DiskDeviceVisitor.h>
24#include <Drivers.h>
25#include <File.h>
26#include <Partition.h>
27#include <Path.h>
28#include <String.h>
29#include <UTF8.h>
30
31#include "BootDrive.h"
32#include "BootLoader.h"
33
34
35/*
36	Note: for testing, the best way is to create a small file image, and
37	register it, for example via the "diskimage" tool (mountvolume might also
38	work).
39	You can then create partitions on it via DriveSetup, or simply copy an
40	existing drive to it, for example via:
41		$ dd if=/dev/disk/ata/0/master/raw of=test.image bs=1M count=10
42
43	It will automatically appear in BootManager once it is registered, and,
44	depending on its parition layout, you can then install the boot menu on
45	it, and directly test it via qemu.
46*/
47
48
49#undef B_TRANSLATION_CONTEXT
50#define B_TRANSLATION_CONTEXT "LegacyBootMenu"
51
52
53struct MasterBootRecord {
54	uint8 bootLoader[440];
55	uint8 diskSignature[4];
56	uint8 reserved[2];
57	uint8 partition[64];
58	uint8 signature[2];
59};
60
61
62class LittleEndianMallocIO : public BMallocIO {
63public:
64			bool				WriteInt8(int8 value);
65			bool				WriteInt16(int16 value);
66			bool				WriteInt32(int32 value);
67			bool				WriteInt64(int64 value);
68			bool				WriteString(const char* value);
69			bool				Align(int16 alignment);
70			bool				Fill(int16 size, int8 fillByte);
71};
72
73
74class PartitionVisitor : public BDiskDeviceVisitor {
75public:
76								PartitionVisitor();
77
78	virtual	bool				Visit(BDiskDevice* device);
79	virtual	bool				Visit(BPartition* partition, int32 level);
80
81			bool				HasPartitions() const;
82			off_t				FirstOffset() const;
83
84private:
85			off_t				fFirstOffset;
86};
87
88
89class PartitionRecorder : public BDiskDeviceVisitor {
90public:
91								PartitionRecorder(BMessage& settings,
92									int8 biosDrive);
93
94	virtual	bool				Visit(BDiskDevice* device);
95	virtual	bool				Visit(BPartition* partition, int32 level);
96
97			bool				FoundPartitions() const;
98
99private:
100			BMessage&			fSettings;
101			int32				fUnnamedIndex;
102			int8				fBIOSDrive;
103			bool				fFound;
104};
105
106
107static const uint32 kBlockSize = 512;
108static const uint32 kNumberOfBootLoaderBlocks = 4;
109	// The number of blocks required to store the
110	// MBR including the Haiku boot loader.
111
112static const uint32 kMBRSignature = 0xAA55;
113
114static const int32 kMaxBootMenuItemLength = 70;
115
116
117bool
118LittleEndianMallocIO::WriteInt8(int8 value)
119{
120	return Write(&value, sizeof(value)) == sizeof(value);
121}
122
123
124bool
125LittleEndianMallocIO::WriteInt16(int16 value)
126{
127	return WriteInt8(value & 0xff)
128		&& WriteInt8(value >> 8);
129}
130
131
132bool
133LittleEndianMallocIO::WriteInt32(int32 value)
134{
135	return WriteInt8(value & 0xff)
136		&& WriteInt8(value >> 8)
137		&& WriteInt8(value >> 16)
138		&& WriteInt8(value >> 24);
139}
140
141
142bool
143LittleEndianMallocIO::WriteInt64(int64 value)
144{
145	return WriteInt32(value) && WriteInt32(value >> 32);
146}
147
148
149bool
150LittleEndianMallocIO::WriteString(const char* value)
151{
152	int len = strlen(value) + 1;
153	return WriteInt8(len)
154		&& Write(value, len) == len;
155}
156
157
158bool
159LittleEndianMallocIO::Align(int16 alignment)
160{
161	if ((Position() % alignment) == 0)
162		return true;
163	return Fill(alignment - (Position() % alignment), 0);
164}
165
166
167bool
168LittleEndianMallocIO::Fill(int16 size, int8 fillByte)
169{
170	for (int i = 0; i < size; i ++) {
171		if (!WriteInt8(fillByte))
172			return false;
173	}
174	return true;
175}
176
177
178// #pragma mark -
179
180
181PartitionVisitor::PartitionVisitor()
182	:
183	fFirstOffset(LONGLONG_MAX)
184{
185}
186
187
188bool
189PartitionVisitor::Visit(BDiskDevice* device)
190{
191	return false;
192}
193
194
195bool
196PartitionVisitor::Visit(BPartition* partition, int32 level)
197{
198	if (partition->Offset() < fFirstOffset)
199		fFirstOffset = partition->Offset();
200
201	return false;
202}
203
204
205bool
206PartitionVisitor::HasPartitions() const
207{
208	return fFirstOffset != LONGLONG_MAX;
209}
210
211
212off_t
213PartitionVisitor::FirstOffset() const
214{
215	return fFirstOffset;
216}
217
218
219// #pragma mark -
220
221
222PartitionRecorder::PartitionRecorder(BMessage& settings, int8 biosDrive)
223	:
224	fSettings(settings),
225	fUnnamedIndex(0),
226	fBIOSDrive(biosDrive),
227	fFound(false)
228{
229}
230
231
232bool
233PartitionRecorder::Visit(BDiskDevice* device)
234{
235	return false;
236}
237
238
239bool
240PartitionRecorder::Visit(BPartition* partition, int32 level)
241{
242	if (partition->ContainsPartitioningSystem())
243		return false;
244
245	BPath partitionPath;
246	partition->GetPath(&partitionPath);
247
248	BString buffer;
249	BString name = partition->ContentName();
250	if (name.Length() == 0) {
251		BString number;
252		number << ++fUnnamedIndex;
253		buffer << B_TRANSLATE_COMMENT("Unnamed %d",
254			"Default name of a partition whose name could not be read from "
255			"disk; characters in codepage 437 are allowed only");
256		buffer.ReplaceFirst("%d", number);
257		name = buffer.String();
258	}
259
260	const char* type = partition->Type();
261	if (type == NULL)
262		type = B_TRANSLATE_COMMENT("Unknown", "Text is shown for an unknown "
263			"partition type");
264
265	BMessage message;
266	// Data as required by BootLoader.h
267	message.AddBool("show", true);
268	message.AddString("name", name);
269	message.AddString("type", type);
270	message.AddString("path", partitionPath.Path());
271	if (fBIOSDrive != 0)
272		message.AddInt8("drive", fBIOSDrive);
273	message.AddInt64("size", partition->Size());
274	message.AddInt64("offset", partition->Offset());
275
276	fSettings.AddMessage("partition", &message);
277	fFound = true;
278
279	return false;
280}
281
282
283bool
284PartitionRecorder::FoundPartitions() const
285{
286	return fFound;
287}
288
289
290// #pragma mark -
291
292
293LegacyBootMenu::LegacyBootMenu()
294{
295}
296
297
298LegacyBootMenu::~LegacyBootMenu()
299{
300}
301
302
303bool
304LegacyBootMenu::IsInstalled(const BootDrive& drive)
305{
306	// TODO: detect bootman
307	return false;
308}
309
310
311status_t
312LegacyBootMenu::CanBeInstalled(const BootDrive& drive)
313{
314	BDiskDevice device;
315	status_t status = drive.GetDiskDevice(device);
316	if (status != B_OK)
317		return status;
318
319	PartitionVisitor visitor;
320	device.VisitEachDescendant(&visitor);
321
322	if (!visitor.HasPartitions()
323			|| strcmp(device.ContentType(), kPartitionTypeIntel) != 0)
324		return B_ENTRY_NOT_FOUND;
325
326	// Enough space to write boot menu to drive?
327	if (visitor.FirstOffset() < (int)sizeof(kBootLoader))
328		return B_PARTITION_TOO_SMALL;
329
330	if (device.IsReadOnlyMedia())
331		return B_READ_ONLY_DEVICE;
332
333	return B_OK;
334}
335
336
337status_t
338LegacyBootMenu::CollectPartitions(const BootDrive& drive, BMessage& settings)
339{
340	status_t status = B_ERROR;
341
342	// Remove previous partitions, if any
343	settings.RemoveName("partition");
344
345	BDiskDeviceRoster diskDeviceRoster;
346	BDiskDevice device;
347	bool partitionsFound = false;
348
349	while (diskDeviceRoster.GetNextDevice(&device) == B_OK) {
350		BPath path;
351		status_t status = device.GetPath(&path);
352		if (status != B_OK)
353			continue;
354
355		// Skip not from BIOS bootable drives that are not the target disk
356		int8 biosDrive = 0;
357		if (path != drive.Path()
358			&& _GetBIOSDrive(path.Path(), biosDrive) != B_OK)
359			continue;
360
361		PartitionRecorder recorder(settings, biosDrive);
362		device.VisitEachDescendant(&recorder);
363
364		partitionsFound |= recorder.FoundPartitions();
365	}
366
367	return partitionsFound ? B_OK : status;
368}
369
370
371status_t
372LegacyBootMenu::Install(const BootDrive& drive, BMessage& settings)
373{
374	int32 defaultPartitionIndex;
375	if (settings.FindInt32("defaultPartition", &defaultPartitionIndex) != B_OK)
376		return B_BAD_VALUE;
377
378	int32 timeout;
379	if (settings.FindInt32("timeout", &timeout) != B_OK)
380		return B_BAD_VALUE;
381
382	int fd = open(drive.Path(), O_RDWR);
383	if (fd < 0)
384		return B_IO_ERROR;
385
386	MasterBootRecord oldMBR;
387	if (read(fd, &oldMBR, sizeof(oldMBR)) != sizeof(oldMBR)) {
388		close(fd);
389		return B_IO_ERROR;
390	}
391
392	if (!_IsValid(&oldMBR)) {
393		close(fd);
394		return B_BAD_VALUE;
395	}
396
397	LittleEndianMallocIO newBootLoader;
398	ssize_t size = sizeof(kBootLoader);
399	if (newBootLoader.Write(kBootLoader, size) != size) {
400		close(fd);
401		return B_NO_MEMORY;
402	}
403
404	MasterBootRecord* newMBR = (MasterBootRecord*)newBootLoader.Buffer();
405	_CopyPartitionTable(newMBR, &oldMBR);
406
407	int menuEntries = 0;
408	int defaultMenuEntry = 0;
409	BMessage partition;
410	int32 index;
411	for (index = 0; settings.FindMessage("partition", index,
412			&partition) == B_OK; index ++) {
413		bool show;
414		partition.FindBool("show", &show);
415		if (!show)
416			continue;
417		if (index == defaultPartitionIndex)
418			defaultMenuEntry = menuEntries;
419
420		menuEntries ++;
421	}
422	newBootLoader.WriteInt16(menuEntries);
423	newBootLoader.WriteInt16(defaultMenuEntry);
424	newBootLoader.WriteInt16(timeout);
425
426	for (index = 0; settings.FindMessage("partition", index,
427			&partition) == B_OK; index ++) {
428		bool show;
429		BString name;
430		BString path;
431		int64 offset;
432		int8 drive;
433		partition.FindBool("show", &show);
434		partition.FindString("name", &name);
435		partition.FindString("path", &path);
436		// LegacyBootMenu specific data
437		partition.FindInt64("offset", &offset);
438		partition.FindInt8("drive", &drive);
439		if (!show)
440			continue;
441
442		BString biosName;
443		_ConvertToBIOSText(name.String(), biosName);
444
445		newBootLoader.WriteString(biosName.String());
446		newBootLoader.WriteInt8(drive);
447		newBootLoader.WriteInt64(offset / kBlockSize);
448	}
449
450	if (!newBootLoader.Align(kBlockSize)) {
451		close(fd);
452		return B_ERROR;
453	}
454
455	lseek(fd, 0, SEEK_SET);
456	const uint8* buffer = (const uint8*)newBootLoader.Buffer();
457	status_t status = _WriteBlocks(fd, buffer, newBootLoader.Position());
458	close(fd);
459	return status;
460}
461
462
463status_t
464LegacyBootMenu::SaveMasterBootRecord(BMessage* settings, BFile* file)
465{
466	BString path;
467
468	if (settings->FindString("disk", &path) != B_OK)
469		return B_BAD_VALUE;
470
471	int fd = open(path.String(), O_RDONLY);
472	if (fd < 0)
473		return B_IO_ERROR;
474
475	ssize_t size = kBlockSize * kNumberOfBootLoaderBlocks;
476	uint8* buffer = new(std::nothrow) uint8[size];
477	if (buffer == NULL) {
478		close(fd);
479		return B_NO_MEMORY;
480	}
481
482	status_t status = _ReadBlocks(fd, buffer, size);
483	if (status != B_OK) {
484		close(fd);
485		delete[] buffer;
486		return B_IO_ERROR;
487	}
488
489	MasterBootRecord* mbr = (MasterBootRecord*)buffer;
490	if (!_IsValid(mbr)) {
491		close(fd);
492		delete[] buffer;
493		return B_BAD_VALUE;
494	}
495
496	if (file->Write(buffer, size) != size)
497		status = B_IO_ERROR;
498	delete[] buffer;
499	close(fd);
500	return status;
501}
502
503
504status_t
505LegacyBootMenu::RestoreMasterBootRecord(BMessage* settings, BFile* file)
506{
507	BString path;
508	if (settings->FindString("disk", &path) != B_OK)
509		return B_BAD_VALUE;
510
511	int fd = open(path.String(), O_RDWR);
512	if (fd < 0)
513		return B_IO_ERROR;
514
515	MasterBootRecord oldMBR;
516	if (read(fd, &oldMBR, sizeof(oldMBR)) != sizeof(oldMBR)) {
517		close(fd);
518		return B_IO_ERROR;
519	}
520	if (!_IsValid(&oldMBR)) {
521		close(fd);
522		return B_BAD_VALUE;
523	}
524
525	lseek(fd, 0, SEEK_SET);
526
527	size_t size = kBlockSize * kNumberOfBootLoaderBlocks;
528	uint8* buffer = new(std::nothrow) uint8[size];
529	if (buffer == NULL) {
530		close(fd);
531		return B_NO_MEMORY;
532	}
533
534	if (file->Read(buffer, size) != (ssize_t)size) {
535		close(fd);
536		delete[] buffer;
537		return B_IO_ERROR;
538	}
539
540	MasterBootRecord* newMBR = (MasterBootRecord*)buffer;
541	if (!_IsValid(newMBR)) {
542		close(fd);
543		delete[] buffer;
544		return B_BAD_VALUE;
545	}
546
547	_CopyPartitionTable(newMBR, &oldMBR);
548
549	status_t status = _WriteBlocks(fd, buffer, size);
550	delete[] buffer;
551	close(fd);
552	return status;
553}
554
555
556status_t
557LegacyBootMenu::GetDisplayText(const char* text, BString& displayText)
558{
559	BString biosText;
560	if (!_ConvertToBIOSText(text, biosText)) {
561		displayText = "???";
562		return B_ERROR;
563	}
564
565	// convert back to UTF-8
566	int32 biosTextLength = biosText.Length();
567	int32 bufferLength = strlen(text);
568	char* buffer = displayText.LockBuffer(bufferLength + 1);
569	int32 state = 0;
570	if (convert_to_utf8(B_MS_DOS_CONVERSION,
571		biosText.String(), &biosTextLength,
572		buffer, &bufferLength, &state) != B_OK) {
573		displayText.UnlockBuffer(0);
574		displayText = "???";
575		return B_ERROR;
576	}
577
578	buffer[bufferLength] = '\0';
579	displayText.UnlockBuffer(bufferLength);
580	return B_OK;
581}
582
583
584bool
585LegacyBootMenu::_ConvertToBIOSText(const char* text, BString& biosText)
586{
587	// convert text in UTF-8 to 'code page 437'
588	int32 textLength = strlen(text);
589
590	int32 biosTextLength = textLength;
591	char* buffer = biosText.LockBuffer(biosTextLength + 1);
592	if (buffer == NULL) {
593		biosText.UnlockBuffer(0);
594		return false;
595	}
596
597	int32 state = 0;
598	if (convert_from_utf8(B_MS_DOS_CONVERSION, text, &textLength,
599		buffer, &biosTextLength, &state) != B_OK) {
600		biosText.UnlockBuffer(0);
601		return false;
602	}
603
604	buffer[biosTextLength] = '\0';
605	biosText.UnlockBuffer(biosTextLength);
606	return biosTextLength < kMaxBootMenuItemLength;
607}
608
609
610status_t
611LegacyBootMenu::_GetBIOSDrive(const char* device, int8& drive)
612{
613	int fd = open(device, O_RDONLY);
614	if (fd < 0)
615		return errno;
616
617	status_t status = ioctl(fd, B_GET_BIOS_DRIVE_ID, &drive, 1);
618	close(fd);
619	return status;
620}
621
622
623status_t
624LegacyBootMenu::_ReadBlocks(int fd, uint8* buffer, size_t size)
625{
626	if (size % kBlockSize != 0) {
627		fprintf(stderr, "_ReadBlocks buffer size must be a multiple of %d\n",
628			(int)kBlockSize);
629		return B_BAD_VALUE;
630	}
631	const size_t blocks = size / kBlockSize;
632	uint8* block = buffer;
633	for (size_t i = 0; i < blocks; i ++, block += kBlockSize) {
634		if (read(fd, block, kBlockSize) != (ssize_t)kBlockSize)
635			return B_IO_ERROR;
636	}
637	return B_OK;
638}
639
640
641status_t
642LegacyBootMenu::_WriteBlocks(int fd, const uint8* buffer, size_t size)
643{
644	if (size % kBlockSize != 0) {
645		fprintf(stderr, "_WriteBlocks buffer size must be a multiple of %d\n",
646			(int)kBlockSize);
647		return B_BAD_VALUE;
648	}
649	const size_t blocks = size / kBlockSize;
650	const uint8* block = buffer;
651	for (size_t i = 0; i < blocks; i ++, block += kBlockSize) {
652		if (write(fd, block, kBlockSize) != (ssize_t)kBlockSize)
653			return B_IO_ERROR;
654	}
655	return B_OK;
656}
657
658
659void
660LegacyBootMenu::_CopyPartitionTable(MasterBootRecord* destination,
661		const MasterBootRecord* source)
662{
663	memcpy(destination->diskSignature, source->diskSignature,
664		sizeof(source->diskSignature) + sizeof(source->reserved)
665			+ sizeof(source->partition));
666}
667
668
669bool
670LegacyBootMenu::_IsValid(const MasterBootRecord* mbr)
671{
672	return mbr->signature[0] == (kMBRSignature & 0xff)
673		&& mbr->signature[1] == (kMBRSignature >> 8);
674}
675