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