1/*
2 * Copyright 2007-2010, Ingo Weinhold, bonefish@cs.tu-berlin.de.
3 * Copyright 2002-2010, Axel Dörfler, axeld@pinc-software.de.
4 * Distributed under the terms of the MIT License.
5 *
6 * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
7 * Distributed under the terms of the NewOS License.
8 */
9
10
11#include "vfs_boot.h"
12
13#include <stdio.h>
14
15#include <fs_info.h>
16#include <OS.h>
17
18#include <boot/kernel_args.h>
19#include <directories.h>
20#include <disk_device_manager/KDiskDevice.h>
21#include <disk_device_manager/KDiskDeviceManager.h>
22#include <disk_device_manager/KPartitionVisitor.h>
23#include <DiskDeviceTypes.h>
24#include <file_cache.h>
25#include <fs/KPath.h>
26#include <kmodule.h>
27#include <syscalls.h>
28#include <util/KMessage.h>
29#include <util/Stack.h>
30#include <vfs.h>
31
32#include "vfs_net_boot.h"
33
34
35//#define TRACE_VFS
36#ifdef TRACE_VFS
37#	define TRACE(x) dprintf x
38#else
39#	define TRACE(x) ;
40#endif
41
42
43typedef Stack<KPartition *> PartitionStack;
44
45static struct {
46	const char *path;
47	const char *target;
48} sPredefinedLinks[] = {
49	{ kGlobalSystemDirectory,	kSystemDirectory },
50	{ kGlobalBinDirectory,		kSystemBinDirectory },
51	{ kGlobalEtcDirectory,		kCommonEtcDirectory },
52	{ kGlobalTempDirectory,		kCommonTempDirectory },
53	{ kGlobalVarDirectory,		kCommonVarDirectory },
54	{NULL}
55};
56
57// This can be used by other code to see if there is a boot file system already
58dev_t gBootDevice = -1;
59bool gReadOnlyBootDevice = false;
60
61
62/*!	No image was chosen - prefer disks with names like "Haiku", or "System"
63 */
64int
65compare_image_boot(const void* _a, const void* _b)
66{
67	KPartition* a = *(KPartition**)_a;
68	KPartition* b = *(KPartition**)_b;
69
70	if (a->ContentName() != NULL) {
71		if (b->ContentName() == NULL)
72			return 1;
73	} else if (b->ContentName() != NULL) {
74		return -1;
75	} else
76		return 0;
77
78	int compare = strcasecmp(a->ContentName(), b->ContentName());
79	if (!compare)
80		return 0;
81
82	if (!strcasecmp(a->ContentName(), "Haiku"))
83		return 1;
84	if (!strcasecmp(b->ContentName(), "Haiku"))
85		return -1;
86	if (!strncmp(a->ContentName(), "System", 6))
87		return 1;
88	if (!strncmp(b->ContentName(), "System", 6))
89		return -1;
90
91	return compare;
92}
93
94
95/*!	The system was booted from CD - prefer CDs over other entries. If there
96	is no CD, fall back to the standard mechanism (as implemented by
97	compare_image_boot().
98*/
99static int
100compare_cd_boot(const void* _a, const void* _b)
101{
102	KPartition* a = *(KPartition**)_a;
103	KPartition* b = *(KPartition**)_b;
104
105	bool aIsCD = a->Type() != NULL
106		&& !strcmp(a->Type(), kPartitionTypeDataSession);
107	bool bIsCD = b->Type() != NULL
108		&& !strcmp(b->Type(), kPartitionTypeDataSession);
109
110	int compare = (int)aIsCD - (int)bIsCD;
111	if (compare != 0)
112		return compare;
113
114	return compare_image_boot(_a, _b);
115}
116
117
118/*!	Computes a check sum for the specified block.
119	The check sum is the sum of all data in that block interpreted as an
120	array of uint32 values.
121	Note, this must use the same method as the one used in
122	boot/platform/bios_ia32/devices.cpp (or similar solutions).
123*/
124static uint32
125compute_check_sum(KDiskDevice* device, off_t offset)
126{
127	char buffer[512];
128	ssize_t bytesRead = read_pos(device->FD(), offset, buffer, sizeof(buffer));
129	if (bytesRead < B_OK)
130		return 0;
131
132	if (bytesRead < (ssize_t)sizeof(buffer))
133		memset(buffer + bytesRead, 0, sizeof(buffer) - bytesRead);
134
135	uint32* array = (uint32*)buffer;
136	uint32 sum = 0;
137
138	for (uint32 i = 0;
139			i < (bytesRead + sizeof(uint32) - 1) / sizeof(uint32); i++) {
140		sum += array[i];
141	}
142
143	return sum;
144}
145
146
147// #pragma mark - BootMethod
148
149
150BootMethod::BootMethod(const KMessage& bootVolume, int32 method)
151	:
152	fBootVolume(bootVolume),
153	fMethod(method)
154{
155}
156
157
158BootMethod::~BootMethod()
159{
160}
161
162
163status_t
164BootMethod::Init()
165{
166	return B_OK;
167}
168
169
170// #pragma mark - DiskBootMethod
171
172
173class DiskBootMethod : public BootMethod {
174public:
175	DiskBootMethod(const KMessage& bootVolume, int32 method)
176		: BootMethod(bootVolume, method)
177	{
178	}
179
180	virtual bool IsBootDevice(KDiskDevice* device, bool strict);
181	virtual bool IsBootPartition(KPartition* partition, bool& foundForSure);
182	virtual void SortPartitions(KPartition** partitions, int32 count);
183};
184
185
186bool
187DiskBootMethod::IsBootDevice(KDiskDevice* device, bool strict)
188{
189	disk_identifier* disk;
190	int32 diskIdentifierSize;
191	if (fBootVolume.FindData(BOOT_VOLUME_DISK_IDENTIFIER, B_RAW_TYPE,
192			(const void**)&disk, &diskIdentifierSize) != B_OK) {
193		dprintf("DiskBootMethod::IsBootDevice(): no disk identifier!\n");
194		return false;
195	}
196
197	TRACE(("boot device: bus %" B_PRId32 ", device %" B_PRId32 "\n",
198		disk->bus_type, disk->device_type));
199
200	// Assume that CD boots only happen off removable media.
201	if (fMethod == BOOT_METHOD_CD && !device->IsRemovable())
202		return false;
203
204	switch (disk->bus_type) {
205		case PCI_BUS:
206		case LEGACY_BUS:
207			// TODO: implement this! (and then enable this feature in the boot
208			// loader)
209			// (we need a way to get the device_node of a device, then)
210			break;
211
212		case UNKNOWN_BUS:
213			// nothing to do here
214			break;
215	}
216
217	switch (disk->device_type) {
218		case UNKNOWN_DEVICE:
219			// test if the size of the device matches
220			// (the BIOS might have given us the wrong value here, though)
221			if (strict && device->Size() != disk->device.unknown.size)
222				return false;
223
224			// check if the check sums match, too
225			for (int32 i = 0; i < NUM_DISK_CHECK_SUMS; i++) {
226				if (disk->device.unknown.check_sums[i].offset == -1)
227					continue;
228
229				if (compute_check_sum(device,
230						disk->device.unknown.check_sums[i].offset)
231							!= disk->device.unknown.check_sums[i].sum) {
232					return false;
233				}
234			}
235			break;
236
237		case ATA_DEVICE:
238		case ATAPI_DEVICE:
239		case SCSI_DEVICE:
240		case USB_DEVICE:
241		case FIREWIRE_DEVICE:
242		case FIBRE_DEVICE:
243			// TODO: implement me!
244			break;
245	}
246
247	return true;
248}
249
250
251bool
252DiskBootMethod::IsBootPartition(KPartition* partition, bool& foundForSure)
253{
254	off_t bootPartitionOffset = fBootVolume.GetInt64(
255		BOOT_VOLUME_PARTITION_OFFSET, 0);
256
257	if (!fBootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)) {
258		// the simple case: we can just boot from the selected boot
259		// device
260		if (partition->Offset() == bootPartitionOffset) {
261			dprintf("Identified boot partition by partition offset.\n");
262			foundForSure = true;
263			return true;
264		}
265	} else {
266		// For now, unless we can positively identify an anyboot CD, we will
267		// just collect all BFS/ISO9660 volumes.
268
269		if (fMethod == BOOT_METHOD_CD) {
270			// Check for the boot partition of an anyboot CD. We identify it as
271			// such, if it is the only primary partition on the CD, has type
272			// BFS, and the boot partition offset is 0.
273			KDiskDevice* device = partition->Device();
274			if (IsBootDevice(device, false) && bootPartitionOffset == 0
275				&& partition->Parent() == device && device->CountChildren() == 1
276				&& device->ContentType() != NULL
277				&& strcmp(device->ContentType(), kPartitionTypeIntel) == 0
278				&& partition->ContentType() != NULL
279				&& strcmp(partition->ContentType(), kPartitionTypeBFS) == 0) {
280				dprintf("Identified anyboot CD.\n");
281				foundForSure = true;
282				return true;
283			}
284
285			// Ignore non-session partitions, if a boot partition was selected
286			// by the user.
287			if (fBootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false)
288				&& partition->Type() != NULL
289				&& strcmp(partition->Type(), kPartitionTypeDataSession) != 0) {
290				return false;
291			}
292		}
293
294		if (partition->ContentType() != NULL
295			&& (strcmp(partition->ContentType(), kPartitionTypeBFS) == 0
296			|| strcmp(partition->ContentType(), kPartitionTypeISO9660) == 0)) {
297			return true;
298		}
299	}
300
301	return false;
302}
303
304
305void
306DiskBootMethod::SortPartitions(KPartition** partitions, int32 count)
307{
308	qsort(partitions, count, sizeof(KPartition*),
309		fMethod == BOOT_METHOD_CD ? compare_cd_boot : compare_image_boot);
310}
311
312
313// #pragma mark -
314
315
316/*!	Make the boot partition (and probably others) available.
317	The partitions that are a boot candidate a put into the /a partitions
318	stack. If the user selected a boot device, there is will only be one
319	entry in this stack; if not, the most likely is put up first.
320	The boot code should then just try them one by one.
321*/
322static status_t
323get_boot_partitions(KMessage& bootVolume, PartitionStack& partitions)
324{
325	dprintf("get_boot_partitions(): boot volume message:\n");
326	bootVolume.Dump(&dprintf);
327
328	// create boot method
329	int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT);
330	dprintf("get_boot_partitions(): boot method type: %" B_PRId32 "\n",
331		bootMethodType);
332
333	BootMethod* bootMethod = NULL;
334	switch (bootMethodType) {
335		case BOOT_METHOD_NET:
336			bootMethod = new(nothrow) NetBootMethod(bootVolume, bootMethodType);
337			break;
338
339		case BOOT_METHOD_HARD_DISK:
340		case BOOT_METHOD_CD:
341		default:
342			bootMethod = new(nothrow) DiskBootMethod(bootVolume,
343				bootMethodType);
344			break;
345	}
346
347	status_t status = bootMethod != NULL ? bootMethod->Init() : B_NO_MEMORY;
348	if (status != B_OK)
349		return status;
350
351	KDiskDeviceManager::CreateDefault();
352	KDiskDeviceManager *manager = KDiskDeviceManager::Default();
353
354	status = manager->InitialDeviceScan();
355	if (status != B_OK) {
356		dprintf("KDiskDeviceManager::InitialDeviceScan() failed: %s\n",
357			strerror(status));
358		return status;
359	}
360
361	if (1 /* dump devices and partitions */) {
362		KDiskDevice *device;
363		int32 cookie = 0;
364		while ((device = manager->NextDevice(&cookie)) != NULL) {
365			device->Dump(true, 0);
366		}
367	}
368
369	struct BootPartitionVisitor : KPartitionVisitor {
370		BootPartitionVisitor(BootMethod* bootMethod, PartitionStack &stack)
371			: fPartitions(stack),
372			  fBootMethod(bootMethod)
373		{
374		}
375
376		virtual bool VisitPre(KPartition *partition)
377		{
378			if (!partition->ContainsFileSystem())
379				return false;
380
381			bool foundForSure = false;
382			if (fBootMethod->IsBootPartition(partition, foundForSure))
383				fPartitions.Push(partition);
384
385			// if found for sure, we can terminate the search
386			return foundForSure;
387		}
388
389		private:
390			PartitionStack	&fPartitions;
391			BootMethod*		fBootMethod;
392	} visitor(bootMethod, partitions);
393
394	bool strict = true;
395
396	while (true) {
397		KDiskDevice *device;
398		int32 cookie = 0;
399		while ((device = manager->NextDevice(&cookie)) != NULL) {
400			if (!bootMethod->IsBootDevice(device, strict))
401				continue;
402
403			if (device->VisitEachDescendant(&visitor) != NULL)
404				break;
405		}
406
407		if (!partitions.IsEmpty() || !strict)
408			break;
409
410		// we couldn't find any potential boot devices, try again less strict
411		strict = false;
412	}
413
414	// sort partition list (e.g.. when booting from CD, CDs should come first in
415	// the list)
416	if (!bootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false))
417		bootMethod->SortPartitions(partitions.Array(), partitions.CountItems());
418
419	return B_OK;
420}
421
422
423//	#pragma mark -
424
425
426status_t
427vfs_bootstrap_file_systems(void)
428{
429	status_t status;
430
431	// bootstrap the root filesystem
432	status = _kern_mount("/", NULL, "rootfs", 0, NULL, 0);
433	if (status < B_OK)
434		panic("error mounting rootfs!\n");
435
436	_kern_setcwd(-1, "/");
437
438	// bootstrap the devfs
439	_kern_create_dir(-1, "/dev", 0755);
440	status = _kern_mount("/dev", NULL, "devfs", 0, NULL, 0);
441	if (status < B_OK)
442		panic("error mounting devfs\n");
443
444	// create directory for the boot volume
445	_kern_create_dir(-1, "/boot", 0755);
446
447	// create some standard links on the rootfs
448
449	for (int32 i = 0; sPredefinedLinks[i].path != NULL; i++) {
450		_kern_create_symlink(-1, sPredefinedLinks[i].path,
451			sPredefinedLinks[i].target, 0);
452			// we don't care if it will succeed or not
453	}
454
455	return B_OK;
456}
457
458
459void
460vfs_mount_boot_file_system(kernel_args* args)
461{
462	KMessage bootVolume;
463	bootVolume.SetTo(args->boot_volume, args->boot_volume_size);
464
465	PartitionStack partitions;
466	status_t status = get_boot_partitions(bootVolume, partitions);
467	if (status < B_OK) {
468		panic("get_boot_partitions failed!");
469	}
470	if (partitions.IsEmpty()) {
471		panic("did not find any boot partitions!");
472	}
473
474	KPartition* bootPartition;
475	while (partitions.Pop(&bootPartition)) {
476		KPath path;
477		if (bootPartition->GetPath(&path) != B_OK)
478			panic("could not get boot device!\n");
479
480		const char* fsName = NULL;
481		bool readOnly = false;
482		if (strcmp(bootPartition->ContentType(), kPartitionTypeISO9660) == 0) {
483			fsName = "iso9660:write_overlay:attribute_overlay";
484			readOnly = true;
485		} else if (bootPartition->IsReadOnly()
486			&& strcmp(bootPartition->ContentType(), kPartitionTypeBFS) == 0) {
487			fsName = "bfs:write_overlay";
488			readOnly = true;
489		}
490
491		TRACE(("trying to mount boot partition: %s\n", path.Path()));
492		gBootDevice = _kern_mount("/boot", path.Path(), fsName, 0, NULL, 0);
493		if (gBootDevice >= 0) {
494			dprintf("Mounted boot partition: %s\n", path.Path());
495			gReadOnlyBootDevice = readOnly;
496			break;
497		}
498	}
499
500	if (gBootDevice < B_OK)
501		panic("could not mount boot device!\n");
502
503	// create link for the name of the boot device
504
505	fs_info info;
506	if (_kern_read_fs_info(gBootDevice, &info) == B_OK) {
507		char path[B_FILE_NAME_LENGTH + 1];
508		snprintf(path, sizeof(path), "/%s", info.volume_name);
509
510		_kern_create_symlink(-1, path, "/boot", 0);
511	}
512
513	// Do post-boot-volume module initialization. The module code wants to know
514	// whether the module images the boot loader has pre-loaded are the same as
515	// on the boot volume. That is the case when booting from hard disk or CD,
516	// but not via network.
517	int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT);
518	bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK
519		|| bootMethodType == BOOT_METHOD_CD;
520	module_init_post_boot_device(bootingFromBootLoaderVolume);
521
522	file_cache_init_post_boot_device();
523
524	// search for other disk systems
525	KDiskDeviceManager *manager = KDiskDeviceManager::Default();
526	manager->RescanDiskSystems();
527	manager->StartMonitoring();
528}
529
530