xref: /haiku/src/system/kernel/fs/vfs_boot.cpp (revision d06cbe081b7ea043aea2012359744091de6d604d)
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 
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 			// Skip the check sum test for CDs, since we didn't read anything
226 			// useful from the disk in the boot loader.
227 			if (fMethod == BOOT_METHOD_CD)
228 				break;
229 
230 			// check if the check sums match, too
231 			for (int32 i = 0; i < NUM_DISK_CHECK_SUMS; i++) {
232 				if (disk->device.unknown.check_sums[i].offset == -1)
233 					continue;
234 
235 				if (compute_check_sum(device,
236 						disk->device.unknown.check_sums[i].offset)
237 							!= disk->device.unknown.check_sums[i].sum) {
238 					return false;
239 				}
240 			}
241 			break;
242 
243 		case ATA_DEVICE:
244 		case ATAPI_DEVICE:
245 		case SCSI_DEVICE:
246 		case USB_DEVICE:
247 		case FIREWIRE_DEVICE:
248 		case FIBRE_DEVICE:
249 			// TODO: implement me!
250 			break;
251 	}
252 
253 	return true;
254 }
255 
256 
257 bool
258 DiskBootMethod::IsBootPartition(KPartition* partition, bool& foundForSure)
259 {
260 	off_t bootPartitionOffset = fBootVolume.GetInt64(
261 		BOOT_VOLUME_PARTITION_OFFSET, 0);
262 
263 	if (!fBootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)) {
264 		// the simple case: we can just boot from the selected boot
265 		// device
266 		if (partition->Offset() == bootPartitionOffset) {
267 			dprintf("Identified boot partition by partition offset.\n");
268 			foundForSure = true;
269 			return true;
270 		}
271 	} else {
272 		// For now, unless we can positively identify an anyboot CD, we will
273 		// just collect all BFS/ISO9660 volumes.
274 
275 		if (fMethod == BOOT_METHOD_CD) {
276 			// Check for the boot partition of an anyboot CD. We identify it as
277 			// such, if it is the only primary partition on the CD, has type
278 			// BFS, and the boot partition offset is 0.
279 			KDiskDevice* device = partition->Device();
280 			if (IsBootDevice(device, false) && bootPartitionOffset == 0
281 				&& partition->Parent() == device && device->CountChildren() == 1
282 				&& device->ContentType() != NULL
283 				&& strcmp(device->ContentType(), kPartitionTypeIntel) == 0
284 				&& partition->ContentType() != NULL
285 				&& strcmp(partition->ContentType(), kPartitionTypeBFS) == 0) {
286 				dprintf("Identified anyboot CD.\n");
287 				foundForSure = true;
288 				return true;
289 			}
290 
291 			// Ignore non-session partitions, if a boot partition was selected
292 			// by the user.
293 			if (fBootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false)
294 				&& partition->Type() != NULL
295 				&& strcmp(partition->Type(), kPartitionTypeDataSession) != 0) {
296 				return false;
297 			}
298 		}
299 
300 		if (partition->ContentType() != NULL
301 			&& (strcmp(partition->ContentType(), kPartitionTypeBFS) == 0
302 			|| strcmp(partition->ContentType(), kPartitionTypeISO9660) == 0)) {
303 			return true;
304 		}
305 	}
306 
307 	return false;
308 }
309 
310 
311 void
312 DiskBootMethod::SortPartitions(KPartition** partitions, int32 count)
313 {
314 	qsort(partitions, count, sizeof(KPartition*),
315 		fMethod == BOOT_METHOD_CD ? compare_cd_boot : compare_image_boot);
316 }
317 
318 
319 // #pragma mark -
320 
321 
322 /*!	Make the boot partition (and probably others) available.
323 	The partitions that are a boot candidate a put into the /a partitions
324 	stack. If the user selected a boot device, there is will only be one
325 	entry in this stack; if not, the most likely is put up first.
326 	The boot code should then just try them one by one.
327 */
328 static status_t
329 get_boot_partitions(KMessage& bootVolume, PartitionStack& partitions)
330 {
331 	dprintf("get_boot_partitions(): boot volume message:\n");
332 	bootVolume.Dump(&dprintf);
333 
334 	// create boot method
335 	int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT);
336 	dprintf("get_boot_partitions(): boot method type: %" B_PRId32 "\n",
337 		bootMethodType);
338 
339 	BootMethod* bootMethod = NULL;
340 	switch (bootMethodType) {
341 		case BOOT_METHOD_NET:
342 			bootMethod = new(nothrow) NetBootMethod(bootVolume, bootMethodType);
343 			break;
344 
345 		case BOOT_METHOD_HARD_DISK:
346 		case BOOT_METHOD_CD:
347 		default:
348 			bootMethod = new(nothrow) DiskBootMethod(bootVolume,
349 				bootMethodType);
350 			break;
351 	}
352 
353 	status_t status = bootMethod != NULL ? bootMethod->Init() : B_NO_MEMORY;
354 	if (status != B_OK)
355 		return status;
356 
357 	KDiskDeviceManager::CreateDefault();
358 	KDiskDeviceManager *manager = KDiskDeviceManager::Default();
359 
360 	status = manager->InitialDeviceScan();
361 	if (status != B_OK) {
362 		dprintf("KDiskDeviceManager::InitialDeviceScan() failed: %s\n",
363 			strerror(status));
364 		return status;
365 	}
366 
367 	if (1 /* dump devices and partitions */) {
368 		KDiskDevice *device;
369 		int32 cookie = 0;
370 		while ((device = manager->NextDevice(&cookie)) != NULL) {
371 			device->Dump(true, 0);
372 		}
373 	}
374 
375 	struct BootPartitionVisitor : KPartitionVisitor {
376 		BootPartitionVisitor(BootMethod* bootMethod, PartitionStack &stack)
377 			: fPartitions(stack),
378 			  fBootMethod(bootMethod)
379 		{
380 		}
381 
382 		virtual bool VisitPre(KPartition *partition)
383 		{
384 			if (!partition->ContainsFileSystem())
385 				return false;
386 
387 			bool foundForSure = false;
388 			if (fBootMethod->IsBootPartition(partition, foundForSure))
389 				fPartitions.Push(partition);
390 
391 			// if found for sure, we can terminate the search
392 			return foundForSure;
393 		}
394 
395 		private:
396 			PartitionStack	&fPartitions;
397 			BootMethod*		fBootMethod;
398 	} visitor(bootMethod, partitions);
399 
400 	bool strict = true;
401 
402 	while (true) {
403 		KDiskDevice *device;
404 		int32 cookie = 0;
405 		while ((device = manager->NextDevice(&cookie)) != NULL) {
406 			if (!bootMethod->IsBootDevice(device, strict))
407 				continue;
408 
409 			if (device->VisitEachDescendant(&visitor) != NULL)
410 				break;
411 		}
412 
413 		if (!partitions.IsEmpty() || !strict)
414 			break;
415 
416 		// we couldn't find any potential boot devices, try again less strict
417 		strict = false;
418 	}
419 
420 	// sort partition list (e.g.. when booting from CD, CDs should come first in
421 	// the list)
422 	if (!bootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false))
423 		bootMethod->SortPartitions(partitions.Array(), partitions.CountItems());
424 
425 	return B_OK;
426 }
427 
428 
429 //	#pragma mark -
430 
431 
432 status_t
433 vfs_bootstrap_file_systems(void)
434 {
435 	status_t status;
436 
437 	// bootstrap the root filesystem
438 	status = _kern_mount("/", NULL, "rootfs", 0, NULL, 0);
439 	if (status < B_OK)
440 		panic("error mounting rootfs!\n");
441 
442 	_kern_setcwd(-1, "/");
443 
444 	// bootstrap the devfs
445 	_kern_create_dir(-1, "/dev", 0755);
446 	status = _kern_mount("/dev", NULL, "devfs", 0, NULL, 0);
447 	if (status < B_OK)
448 		panic("error mounting devfs\n");
449 
450 	// create directory for the boot volume
451 	_kern_create_dir(-1, "/boot", 0755);
452 
453 	// create some standard links on the rootfs
454 
455 	for (int32 i = 0; sPredefinedLinks[i].path != NULL; i++) {
456 		_kern_create_symlink(-1, sPredefinedLinks[i].path,
457 			sPredefinedLinks[i].target, 0);
458 			// we don't care if it will succeed or not
459 	}
460 
461 	return B_OK;
462 }
463 
464 
465 void
466 vfs_mount_boot_file_system(kernel_args* args)
467 {
468 	KMessage bootVolume;
469 	bootVolume.SetTo(args->boot_volume, args->boot_volume_size);
470 
471 	PartitionStack partitions;
472 	status_t status = get_boot_partitions(bootVolume, partitions);
473 	if (status < B_OK) {
474 		panic("get_boot_partitions failed!");
475 	}
476 	if (partitions.IsEmpty()) {
477 		panic("did not find any boot partitions!");
478 	}
479 
480 	dev_t bootDevice = -1;
481 
482 	KPartition* bootPartition;
483 	while (partitions.Pop(&bootPartition)) {
484 		KPath path;
485 		if (bootPartition->GetPath(&path) != B_OK)
486 			panic("could not get boot device!\n");
487 
488 		const char* fsName = NULL;
489 		bool readOnly = false;
490 		if (strcmp(bootPartition->ContentType(), kPartitionTypeISO9660) == 0) {
491 			fsName = "iso9660:write_overlay:attribute_overlay";
492 			readOnly = true;
493 		} else if (bootPartition->IsReadOnly()
494 			&& strcmp(bootPartition->ContentType(), kPartitionTypeBFS) == 0) {
495 			fsName = "bfs:write_overlay";
496 			readOnly = true;
497 		}
498 
499 		TRACE(("trying to mount boot partition: %s\n", path.Path()));
500 
501 		bootDevice = _kern_mount("/boot", path.Path(), fsName, 0, NULL, 0);
502 		if (bootDevice >= 0) {
503 			dprintf("Mounted boot partition: %s\n", path.Path());
504 			gReadOnlyBootDevice = readOnly;
505 			break;
506 		}
507 	}
508 
509 	if (bootDevice < B_OK)
510 		panic("could not mount boot device!\n");
511 
512 	// create link for the name of the boot device
513 
514 	fs_info info;
515 	if (_kern_read_fs_info(bootDevice, &info) == B_OK) {
516 		char path[B_FILE_NAME_LENGTH + 1];
517 		snprintf(path, sizeof(path), "/%s", info.volume_name);
518 
519 		_kern_create_symlink(-1, path, "/boot", 0);
520 	}
521 
522 	// If we're booting off a packaged system, mount packagefs.
523 	struct stat st;
524 	if (bootVolume.GetBool(BOOT_VOLUME_PACKAGED, false)
525 		|| (bootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)
526 			&& lstat(kSystemPackagesDirectory, &st) == 0)) {
527 		static const char* const kPackageFSName = "packagefs";
528 
529 		char arguments[256];
530 		strlcpy(arguments, "packages /boot/system/packages; type system",
531 			sizeof(arguments));
532 		if (const char* stateName
533 				= bootVolume.GetString(BOOT_VOLUME_PACKAGES_STATE, NULL)) {
534 			strlcat(arguments, "; state ", sizeof(arguments));
535 			strlcat(arguments, stateName, sizeof(arguments));
536 		}
537 
538 		dev_t packageMount = _kern_mount("/boot/system", NULL, kPackageFSName,
539 			0, arguments, 0 /* unused argument length */);
540 		if (packageMount < 0) {
541 			panic("Failed to mount system packagefs: %s",
542 				strerror(packageMount));
543 		}
544 
545 		packageMount = _kern_mount("/boot/home/config", NULL, kPackageFSName, 0,
546 			"packages /boot/home/config/packages; type home",
547 			0 /* unused argument length */);
548 		if (packageMount < 0) {
549 			dprintf("Failed to mount home packagefs: %s\n",
550 				strerror(packageMount));
551 		}
552 	}
553 
554 	// Now that packagefs is mounted, the boot volume is really ready.
555 	gBootDevice = bootDevice;
556 
557 	// Do post-boot-volume module initialization. The module code wants to know
558 	// whether the module images the boot loader has pre-loaded are the same as
559 	// on the boot volume. That is the case when booting from hard disk or CD,
560 	// but not via network.
561 	int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT);
562 	bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK
563 		|| bootMethodType == BOOT_METHOD_CD;
564 	module_init_post_boot_device(bootingFromBootLoaderVolume);
565 
566 	file_cache_init_post_boot_device();
567 
568 	// search for other disk systems
569 	KDiskDeviceManager *manager = KDiskDeviceManager::Default();
570 	manager->RescanDiskSystems();
571 	manager->StartMonitoring();
572 }
573 
574