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