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