xref: /haiku/src/system/kernel/fs/vfs_boot.cpp (revision 52c4471a3024d2eb81fe88e2c3982b9f8daa5e56)
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 
44 typedef Stack<KPartition *> PartitionStack;
45 
46 static 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
60 dev_t gBootDevice = -1;
61 bool gReadOnlyBootDevice = false;
62 
63 
64 /*!	No image was chosen - prefer disks with names like "Haiku", or "System"
65  */
66 int
67 compare_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 */
101 static int
102 compare_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 */
126 static uint32
127 compute_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 
152 BootMethod::BootMethod(const KMessage& bootVolume, int32 method)
153 	:
154 	fBootVolume(bootVolume),
155 	fMethod(method)
156 {
157 }
158 
159 
160 BootMethod::~BootMethod()
161 {
162 }
163 
164 
165 status_t
166 BootMethod::Init()
167 {
168 	return B_OK;
169 }
170 
171 
172 // #pragma mark - DiskBootMethod
173 
174 
175 class DiskBootMethod : public BootMethod {
176 public:
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 
188 bool
189 DiskBootMethod::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 
258 bool
259 DiskBootMethod::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 
312 void
313 DiskBootMethod::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 */
329 static status_t
330 get_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 
436 status_t
437 vfs_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 
469 void
470 vfs_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