xref: /haiku/src/system/kernel/fs/vfs_boot.cpp (revision 95c9effd68127df2dce202d5e254a7c86560010a)
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 the only primary partition on the CD, has type
279 			// BFS, and the boot partition offset is 0.
280 			KDiskDevice* device = partition->Device();
281 			if (IsBootDevice(device, false) && bootPartitionOffset == 0
282 				&& partition->Parent() == device && device->CountChildren() == 1
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() failed: %s\n",
364 			strerror(status));
365 		return status;
366 	}
367 
368 	if (1 /* dump devices and partitions */) {
369 		KDiskDevice *device;
370 		int32 cookie = 0;
371 		while ((device = manager->NextDevice(&cookie)) != NULL) {
372 			device->Dump(true, 0);
373 		}
374 	}
375 
376 	struct BootPartitionVisitor : KPartitionVisitor {
377 		BootPartitionVisitor(BootMethod* bootMethod, PartitionStack &stack)
378 			: fPartitions(stack),
379 			  fBootMethod(bootMethod)
380 		{
381 		}
382 
383 		virtual bool VisitPre(KPartition *partition)
384 		{
385 			if (!partition->ContainsFileSystem())
386 				return false;
387 
388 			bool foundForSure = false;
389 			if (fBootMethod->IsBootPartition(partition, foundForSure))
390 				fPartitions.Push(partition);
391 
392 			// if found for sure, we can terminate the search
393 			return foundForSure;
394 		}
395 
396 		private:
397 			PartitionStack	&fPartitions;
398 			BootMethod*		fBootMethod;
399 	} visitor(bootMethod, partitions);
400 
401 	bool strict = true;
402 
403 	while (true) {
404 		KDiskDevice *device;
405 		int32 cookie = 0;
406 		while ((device = manager->NextDevice(&cookie)) != NULL) {
407 			if (!bootMethod->IsBootDevice(device, strict))
408 				continue;
409 
410 			if (device->VisitEachDescendant(&visitor) != NULL)
411 				break;
412 		}
413 
414 		if (!partitions.IsEmpty() || !strict)
415 			break;
416 
417 		// we couldn't find any potential boot devices, try again less strict
418 		strict = false;
419 	}
420 
421 	// sort partition list (e.g.. when booting from CD, CDs should come first in
422 	// the list)
423 	if (!bootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false))
424 		bootMethod->SortPartitions(partitions.Array(), partitions.CountItems());
425 
426 	return B_OK;
427 }
428 
429 
430 //	#pragma mark -
431 
432 
433 status_t
434 vfs_bootstrap_file_systems(void)
435 {
436 	status_t status;
437 
438 	// bootstrap the root filesystem
439 	status = _kern_mount("/", NULL, "rootfs", 0, NULL, 0);
440 	if (status < B_OK)
441 		panic("error mounting rootfs!\n");
442 
443 	_kern_setcwd(-1, "/");
444 
445 	// bootstrap the devfs
446 	_kern_create_dir(-1, "/dev", 0755);
447 	status = _kern_mount("/dev", NULL, "devfs", 0, NULL, 0);
448 	if (status < B_OK)
449 		panic("error mounting devfs\n");
450 
451 	// create directory for the boot volume
452 	_kern_create_dir(-1, "/boot", 0755);
453 
454 	// create some standard links on the rootfs
455 
456 	for (int32 i = 0; sPredefinedLinks[i].path != NULL; i++) {
457 		_kern_create_symlink(-1, sPredefinedLinks[i].path,
458 			sPredefinedLinks[i].target, 0777);
459 			// we don't care if it will succeed or not
460 	}
461 
462 	return B_OK;
463 }
464 
465 
466 void
467 vfs_mount_boot_file_system(kernel_args* args)
468 {
469 	KMessage bootVolume;
470 	bootVolume.SetTo(args->boot_volume, args->boot_volume_size);
471 
472 	PartitionStack partitions;
473 	status_t status = get_boot_partitions(bootVolume, partitions);
474 	if (status < B_OK) {
475 		panic("get_boot_partitions failed!");
476 	}
477 	if (partitions.IsEmpty()) {
478 		panic("did not find any boot partitions!");
479 	}
480 
481 	dev_t bootDevice = -1;
482 
483 	KPartition* bootPartition;
484 	while (partitions.Pop(&bootPartition)) {
485 		KPath path;
486 		if (bootPartition->GetPath(&path) != B_OK)
487 			panic("could not get boot device!\n");
488 
489 		const char* fsName = NULL;
490 		bool readOnly = false;
491 		if (strcmp(bootPartition->ContentType(), kPartitionTypeISO9660) == 0) {
492 			fsName = "iso9660:write_overlay:attribute_overlay";
493 			readOnly = true;
494 		} else if (bootPartition->IsReadOnly()
495 			&& strcmp(bootPartition->ContentType(), kPartitionTypeBFS) == 0) {
496 			fsName = "bfs:write_overlay";
497 			readOnly = true;
498 		}
499 
500 		TRACE(("trying to mount boot partition: %s\n", path.Path()));
501 
502 		bootDevice = _kern_mount("/boot", path.Path(), fsName, 0, NULL, 0);
503 		if (bootDevice >= 0) {
504 			dprintf("Mounted boot partition: %s\n", path.Path());
505 			gReadOnlyBootDevice = readOnly;
506 			break;
507 		}
508 	}
509 
510 	if (bootDevice < B_OK)
511 		panic("could not mount boot device!\n");
512 
513 	// create link for the name of the boot device
514 
515 	fs_info info;
516 	if (_kern_read_fs_info(bootDevice, &info) == B_OK) {
517 		char path[B_FILE_NAME_LENGTH + 1];
518 		snprintf(path, sizeof(path), "/%s", info.volume_name);
519 
520 		_kern_create_symlink(-1, path, "/boot", 0777);
521 	}
522 
523 	// If we're booting off a packaged system, mount packagefs.
524 	struct stat st;
525 	if (bootVolume.GetBool(BOOT_VOLUME_PACKAGED, false)
526 		|| (bootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)
527 			&& lstat(kSystemPackagesDirectory, &st) == 0)) {
528 		static const char* const kPackageFSName = "packagefs";
529 
530 		char arguments[256];
531 		strlcpy(arguments, "packages /boot/system/packages; type system",
532 			sizeof(arguments));
533 		if (const char* stateName
534 				= bootVolume.GetString(BOOT_VOLUME_PACKAGES_STATE, NULL)) {
535 			strlcat(arguments, "; state ", sizeof(arguments));
536 			strlcat(arguments, stateName, sizeof(arguments));
537 		}
538 
539 		dev_t packageMount = _kern_mount("/boot/system", NULL, kPackageFSName,
540 			0, arguments, 0 /* unused argument length */);
541 		if (packageMount < 0) {
542 			panic("Failed to mount system packagefs: %s",
543 				strerror(packageMount));
544 		}
545 
546 		packageMount = _kern_mount("/boot/home/config", NULL, kPackageFSName, 0,
547 			"packages /boot/home/config/packages; type home",
548 			0 /* unused argument length */);
549 		if (packageMount < 0) {
550 			dprintf("Failed to mount home packagefs: %s\n",
551 				strerror(packageMount));
552 		}
553 	}
554 
555 	// Now that packagefs is mounted, the boot volume is really ready.
556 	gBootDevice = bootDevice;
557 
558 	// Do post-boot-volume module initialization. The module code wants to know
559 	// whether the module images the boot loader has pre-loaded are the same as
560 	// on the boot volume. That is the case when booting from hard disk or CD,
561 	// but not via network.
562 	int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT);
563 	bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK
564 		|| bootMethodType == BOOT_METHOD_CD;
565 	module_init_post_boot_device(bootingFromBootLoaderVolume);
566 
567 	file_cache_init_post_boot_device();
568 
569 	// search for other disk systems
570 	KDiskDeviceManager *manager = KDiskDeviceManager::Default();
571 	manager->RescanDiskSystems();
572 	manager->StartMonitoring();
573 }
574 
575