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