xref: /haiku/src/system/kernel/fs/vfs_boot.cpp (revision f2b4344867e97c3f4e742a1b4a15e6879644601a)
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 %ld, device %ld\n", disk->bus_type,
198 		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(kernel_args* args, PartitionStack& partitions)
324 {
325 	const KMessage& bootVolume = args->boot_volume;
326 
327 	dprintf("get_boot_partitions(): boot volume message:\n");
328 	bootVolume.Dump(&dprintf);
329 
330 	// create boot method
331 	int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT);
332 	dprintf("get_boot_partitions(): boot method type: %ld\n", 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 (!args->boot_volume.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 	PartitionStack partitions;
464 	status_t status = get_boot_partitions(args, partitions);
465 	if (status < B_OK) {
466 		panic("get_boot_partitions failed!");
467 	}
468 	if (partitions.IsEmpty()) {
469 		panic("did not find any boot partitions!");
470 	}
471 
472 	KPartition* bootPartition;
473 	while (partitions.Pop(&bootPartition)) {
474 		KPath path;
475 		if (bootPartition->GetPath(&path) != B_OK)
476 			panic("could not get boot device!\n");
477 
478 		const char* fsName = NULL;
479 		bool readOnly = false;
480 		if (strcmp(bootPartition->ContentType(), kPartitionTypeISO9660) == 0) {
481 			fsName = "iso9660:write_overlay:attribute_overlay";
482 			readOnly = true;
483 		} else if (bootPartition->IsReadOnly()
484 			&& strcmp(bootPartition->ContentType(), kPartitionTypeBFS) == 0) {
485 			fsName = "bfs:write_overlay";
486 			readOnly = true;
487 		}
488 
489 		TRACE(("trying to mount boot partition: %s\n", path.Path()));
490 		gBootDevice = _kern_mount("/boot", path.Path(), fsName, 0, NULL, 0);
491 		if (gBootDevice >= 0) {
492 			dprintf("Mounted boot partition: %s\n", path.Path());
493 			gReadOnlyBootDevice = readOnly;
494 			break;
495 		}
496 	}
497 
498 	if (gBootDevice < B_OK)
499 		panic("could not mount boot device!\n");
500 
501 	// create link for the name of the boot device
502 
503 	fs_info info;
504 	if (_kern_read_fs_info(gBootDevice, &info) == B_OK) {
505 		char path[B_FILE_NAME_LENGTH + 1];
506 		snprintf(path, sizeof(path), "/%s", info.volume_name);
507 
508 		_kern_create_symlink(-1, path, "/boot", 0);
509 	}
510 
511 	// Do post-boot-volume module initialization. The module code wants to know
512 	// whether the module images the boot loader has pre-loaded are the same as
513 	// on the boot volume. That is the case when booting from hard disk or CD,
514 	// but not via network.
515 	int32 bootMethodType = args->boot_volume.GetInt32(BOOT_METHOD,
516 		BOOT_METHOD_DEFAULT);
517 	bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK
518 		|| bootMethodType == BOOT_METHOD_CD;
519 	module_init_post_boot_device(bootingFromBootLoaderVolume);
520 
521 	file_cache_init_post_boot_device();
522 
523 	// search for other disk systems
524 	KDiskDeviceManager *manager = KDiskDeviceManager::Default();
525 	manager->RescanDiskSystems();
526 	manager->StartMonitoring();
527 }
528 
529