xref: /haiku/src/system/kernel/fs/vfs_boot.cpp (revision 7749d0bb0c358a3279b1b9cc76d8376e900130a5)
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 <disk_device_manager/KDiskDevice.h>
20 #include <disk_device_manager/KDiskDeviceManager.h>
21 #include <disk_device_manager/KPartitionVisitor.h>
22 #include <DiskDeviceTypes.h>
23 #include <file_cache.h>
24 #include <fs/KPath.h>
25 #include <kmodule.h>
26 #include <syscalls.h>
27 #include <util/KMessage.h>
28 #include <util/Stack.h>
29 #include <vfs.h>
30 
31 #include "vfs_net_boot.h"
32 
33 
34 //#define TRACE_VFS
35 #ifdef TRACE_VFS
36 #	define TRACE(x) dprintf x
37 #else
38 #	define TRACE(x) ;
39 #endif
40 
41 
42 typedef Stack<KPartition *> PartitionStack;
43 
44 static struct {
45 	const char *path;
46 	const char *target;
47 } sPredefinedLinks[] = {
48 	{"/system", "/boot/system"},
49 	{"/bin", "/boot/system/bin"},
50 	{"/etc", "/boot/common/etc"},
51 	{"/var", "/boot/common/var"},
52 	{"/tmp", "/boot/common/cache/tmp"},
53 	{NULL}
54 };
55 
56 // This can be used by other code to see if there is a boot file system already
57 dev_t gBootDevice = -1;
58 bool gReadOnlyBootDevice = false;
59 
60 
61 /*!	No image was chosen - prefer disks with names like "Haiku", or "System"
62  */
63 int
64 compare_image_boot(const void* _a, const void* _b)
65 {
66 	KPartition* a = *(KPartition**)_a;
67 	KPartition* b = *(KPartition**)_b;
68 
69 	if (a->ContentName() != NULL) {
70 		if (b->ContentName() == NULL)
71 			return 1;
72 	} else if (b->ContentName() != NULL) {
73 		return -1;
74 	} else
75 		return 0;
76 
77 	int compare = strcasecmp(a->ContentName(), b->ContentName());
78 	if (!compare)
79 		return 0;
80 
81 	if (!strcasecmp(a->ContentName(), "Haiku"))
82 		return 1;
83 	if (!strcasecmp(b->ContentName(), "Haiku"))
84 		return -1;
85 	if (!strncmp(a->ContentName(), "System", 6))
86 		return 1;
87 	if (!strncmp(b->ContentName(), "System", 6))
88 		return -1;
89 
90 	return compare;
91 }
92 
93 
94 /*!	The system was booted from CD - prefer CDs over other entries. If there
95 	is no CD, fall back to the standard mechanism (as implemented by
96 	compare_image_boot().
97 */
98 static int
99 compare_cd_boot(const void* _a, const void* _b)
100 {
101 	KPartition* a = *(KPartition**)_a;
102 	KPartition* b = *(KPartition**)_b;
103 
104 	bool aIsCD = a->Type() != NULL
105 		&& !strcmp(a->Type(), kPartitionTypeDataSession);
106 	bool bIsCD = b->Type() != NULL
107 		&& !strcmp(b->Type(), kPartitionTypeDataSession);
108 
109 	int compare = (int)aIsCD - (int)bIsCD;
110 	if (compare != 0)
111 		return compare;
112 
113 	return compare_image_boot(_a, _b);
114 }
115 
116 
117 /*!	Computes a check sum for the specified block.
118 	The check sum is the sum of all data in that block interpreted as an
119 	array of uint32 values.
120 	Note, this must use the same method as the one used in
121 	boot/platform/bios_ia32/devices.cpp (or similar solutions).
122 */
123 static uint32
124 compute_check_sum(KDiskDevice* device, off_t offset)
125 {
126 	char buffer[512];
127 	ssize_t bytesRead = read_pos(device->FD(), offset, buffer, sizeof(buffer));
128 	if (bytesRead < B_OK)
129 		return 0;
130 
131 	if (bytesRead < (ssize_t)sizeof(buffer))
132 		memset(buffer + bytesRead, 0, sizeof(buffer) - bytesRead);
133 
134 	uint32* array = (uint32*)buffer;
135 	uint32 sum = 0;
136 
137 	for (uint32 i = 0;
138 			i < (bytesRead + sizeof(uint32) - 1) / sizeof(uint32); i++) {
139 		sum += array[i];
140 	}
141 
142 	return sum;
143 }
144 
145 
146 // #pragma mark - BootMethod
147 
148 
149 BootMethod::BootMethod(const KMessage& bootVolume, int32 method)
150 	:
151 	fBootVolume(bootVolume),
152 	fMethod(method)
153 {
154 }
155 
156 
157 BootMethod::~BootMethod()
158 {
159 }
160 
161 
162 status_t
163 BootMethod::Init()
164 {
165 	return B_OK;
166 }
167 
168 
169 // #pragma mark - DiskBootMethod
170 
171 
172 class DiskBootMethod : public BootMethod {
173 public:
174 	DiskBootMethod(const KMessage& bootVolume, int32 method)
175 		: BootMethod(bootVolume, method)
176 	{
177 	}
178 
179 	virtual bool IsBootDevice(KDiskDevice* device, bool strict);
180 	virtual bool IsBootPartition(KPartition* partition, bool& foundForSure);
181 	virtual void SortPartitions(KPartition** partitions, int32 count);
182 };
183 
184 
185 bool
186 DiskBootMethod::IsBootDevice(KDiskDevice* device, bool strict)
187 {
188 	disk_identifier* disk;
189 	int32 diskIdentifierSize;
190 	if (fBootVolume.FindData(BOOT_VOLUME_DISK_IDENTIFIER, B_RAW_TYPE,
191 			(const void**)&disk, &diskIdentifierSize) != B_OK) {
192 		dprintf("DiskBootMethod::IsBootDevice(): no disk identifier!\n");
193 		return false;
194 	}
195 
196 	TRACE(("boot device: bus %ld, device %ld\n", disk->bus_type,
197 		disk->device_type));
198 
199 	// Assume that CD boots only happen off removable media.
200 	if (fMethod == BOOT_METHOD_CD && !device->IsRemovable())
201 		return false;
202 
203 	switch (disk->bus_type) {
204 		case PCI_BUS:
205 		case LEGACY_BUS:
206 			// TODO: implement this! (and then enable this feature in the boot
207 			// loader)
208 			// (we need a way to get the device_node of a device, then)
209 			break;
210 
211 		case UNKNOWN_BUS:
212 			// nothing to do here
213 			break;
214 	}
215 
216 	switch (disk->device_type) {
217 		case UNKNOWN_DEVICE:
218 			// test if the size of the device matches
219 			// (the BIOS might have given us the wrong value here, though)
220 			if (strict && device->Size() != disk->device.unknown.size)
221 				return false;
222 
223 			// check if the check sums match, too
224 			for (int32 i = 0; i < NUM_DISK_CHECK_SUMS; i++) {
225 				if (disk->device.unknown.check_sums[i].offset == -1)
226 					continue;
227 
228 				if (compute_check_sum(device,
229 						disk->device.unknown.check_sums[i].offset)
230 							!= disk->device.unknown.check_sums[i].sum) {
231 					return false;
232 				}
233 			}
234 			break;
235 
236 		case ATA_DEVICE:
237 		case ATAPI_DEVICE:
238 		case SCSI_DEVICE:
239 		case USB_DEVICE:
240 		case FIREWIRE_DEVICE:
241 		case FIBRE_DEVICE:
242 			// TODO: implement me!
243 			break;
244 	}
245 
246 	return true;
247 }
248 
249 
250 bool
251 DiskBootMethod::IsBootPartition(KPartition* partition, bool& foundForSure)
252 {
253 	off_t bootPartitionOffset = fBootVolume.GetInt64(
254 		BOOT_VOLUME_PARTITION_OFFSET, 0);
255 
256 	if (!fBootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)) {
257 		// the simple case: we can just boot from the selected boot
258 		// device
259 		if (partition->Offset() == bootPartitionOffset) {
260 			dprintf("Identified boot partition by partition offset.\n");
261 			foundForSure = true;
262 			return true;
263 		}
264 	} else {
265 		// For now, unless we can positively identify an anyboot CD, we will
266 		// just collect all BFS/ISO9660 volumes.
267 
268 		if (fMethod == BOOT_METHOD_CD) {
269 			// Check for the boot partition of an anyboot CD. We identify it as
270 			// such, if it is the only primary partition on the CD, has type
271 			// BFS, and the boot partition offset is 0.
272 			KDiskDevice* device = partition->Device();
273 			if (IsBootDevice(device, false) && bootPartitionOffset == 0
274 				&& partition->Parent() == device && device->CountChildren() == 1
275 				&& device->ContentType() != NULL
276 				&& strcmp(device->ContentType(), kPartitionTypeIntel) == 0
277 				&& partition->ContentType() != NULL
278 				&& strcmp(partition->ContentType(), kPartitionTypeBFS) == 0) {
279 				dprintf("Identified anyboot CD.\n");
280 				foundForSure = true;
281 				return true;
282 			}
283 
284 			// Ignore non-session partitions, if a boot partition was selected
285 			// by the user.
286 			if (fBootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false)
287 				&& partition->Type() != NULL
288 				&& strcmp(partition->Type(), kPartitionTypeDataSession) != 0) {
289 				return false;
290 			}
291 		}
292 
293 		if (partition->ContentType() != NULL
294 			&& (strcmp(partition->ContentType(), kPartitionTypeBFS) == 0
295 			|| strcmp(partition->ContentType(), kPartitionTypeISO9660) == 0)) {
296 			return true;
297 		}
298 	}
299 
300 	return false;
301 }
302 
303 
304 void
305 DiskBootMethod::SortPartitions(KPartition** partitions, int32 count)
306 {
307 	qsort(partitions, count, sizeof(KPartition*),
308 		fMethod == BOOT_METHOD_CD ? compare_cd_boot : compare_image_boot);
309 }
310 
311 
312 // #pragma mark -
313 
314 
315 /*!	Make the boot partition (and probably others) available.
316 	The partitions that are a boot candidate a put into the /a partitions
317 	stack. If the user selected a boot device, there is will only be one
318 	entry in this stack; if not, the most likely is put up first.
319 	The boot code should then just try them one by one.
320 */
321 static status_t
322 get_boot_partitions(kernel_args* args, PartitionStack& partitions)
323 {
324 	const KMessage& bootVolume = args->boot_volume;
325 
326 	dprintf("get_boot_partitions(): boot volume message:\n");
327 	bootVolume.Dump(&dprintf);
328 
329 	// create boot method
330 	int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT);
331 	dprintf("get_boot_partitions(): boot method type: %ld\n", 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 (!args->boot_volume.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 	PartitionStack partitions;
463 	status_t status = get_boot_partitions(args, partitions);
464 	if (status < B_OK) {
465 		panic("get_boot_partitions failed!");
466 	}
467 	if (partitions.IsEmpty()) {
468 		panic("did not find any boot partitions!");
469 	}
470 
471 	KPartition* bootPartition;
472 	while (partitions.Pop(&bootPartition)) {
473 		KPath path;
474 		if (bootPartition->GetPath(&path) != B_OK)
475 			panic("could not get boot device!\n");
476 
477 		const char* fsName = NULL;
478 		bool readOnly = false;
479 		if (strcmp(bootPartition->ContentType(), kPartitionTypeISO9660) == 0) {
480 			fsName = "iso9660:write_overlay:attribute_overlay";
481 			readOnly = true;
482 		} else if (bootPartition->IsReadOnly()
483 			&& strcmp(bootPartition->ContentType(), kPartitionTypeBFS) == 0) {
484 			fsName = "bfs:write_overlay";
485 			readOnly = true;
486 		}
487 
488 		TRACE(("trying to mount boot partition: %s\n", path.Path()));
489 		gBootDevice = _kern_mount("/boot", path.Path(), fsName, 0, NULL, 0);
490 		if (gBootDevice >= 0) {
491 			dprintf("Mounted boot partition: %s\n", path.Path());
492 			gReadOnlyBootDevice = readOnly;
493 			break;
494 		}
495 	}
496 
497 	if (gBootDevice < B_OK)
498 		panic("could not mount boot device!\n");
499 
500 	// create link for the name of the boot device
501 
502 	fs_info info;
503 	if (_kern_read_fs_info(gBootDevice, &info) == B_OK) {
504 		char path[B_FILE_NAME_LENGTH + 1];
505 		snprintf(path, sizeof(path), "/%s", info.volume_name);
506 
507 		_kern_create_symlink(-1, path, "/boot", 0);
508 	}
509 
510 	// Do post-boot-volume module initialization. The module code wants to know
511 	// whether the module images the boot loader has pre-loaded are the same as
512 	// on the boot volume. That is the case when booting from hard disk or CD,
513 	// but not via network.
514 	int32 bootMethodType = args->boot_volume.GetInt32(BOOT_METHOD,
515 		BOOT_METHOD_DEFAULT);
516 	bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK
517 		|| bootMethodType == BOOT_METHOD_CD;
518 	module_init_post_boot_device(bootingFromBootLoaderVolume);
519 
520 	file_cache_init_post_boot_device();
521 
522 	// search for other disk systems
523 	KDiskDeviceManager *manager = KDiskDeviceManager::Default();
524 	manager->RescanDiskSystems();
525 	manager->StartMonitoring();
526 }
527 
528