xref: /haiku/src/system/kernel/fs/vfs_boot.cpp (revision a1163de83ea633463a79de234b8742ee106531b2)
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 
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/system/etc"},
51 	{"/var", "/boot/var"},
52 	{"/tmp", "/boot/var/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 = strcmp(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 	switch (disk->bus_type) {
200 		case PCI_BUS:
201 		case LEGACY_BUS:
202 			// TODO: implement this! (and then enable this feature in the boot
203 			// loader)
204 			// (we need a way to get the device_node of a device, then)
205 			break;
206 
207 		case UNKNOWN_BUS:
208 			// nothing to do here
209 			break;
210 	}
211 
212 	switch (disk->device_type) {
213 		case UNKNOWN_DEVICE:
214 			// test if the size of the device matches
215 			// (the BIOS might have given us the wrong value here, though)
216 			if (strict && device->Size() != disk->device.unknown.size)
217 				return false;
218 
219 			// check if the check sums match, too
220 			for (int32 i = 0; i < NUM_DISK_CHECK_SUMS; i++) {
221 				if (disk->device.unknown.check_sums[i].offset == -1)
222 					continue;
223 
224 				if (compute_check_sum(device,
225 						disk->device.unknown.check_sums[i].offset)
226 							!= disk->device.unknown.check_sums[i].sum) {
227 					return false;
228 				}
229 			}
230 			break;
231 
232 		case ATA_DEVICE:
233 		case ATAPI_DEVICE:
234 		case SCSI_DEVICE:
235 		case USB_DEVICE:
236 		case FIREWIRE_DEVICE:
237 		case FIBRE_DEVICE:
238 			// TODO: implement me!
239 			break;
240 	}
241 
242 	return true;
243 }
244 
245 
246 bool
247 DiskBootMethod::IsBootPartition(KPartition* partition, bool& foundForSure)
248 {
249 	if (!fBootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)) {
250 		// the simple case: we can just boot from the selected boot
251 		// device
252 		if (partition->Offset()
253 				== fBootVolume.GetInt64(BOOT_VOLUME_PARTITION_OFFSET, 0)) {
254 			foundForSure = true;
255 			return true;
256 		}
257 	} else {
258 		// for now, we will just collect all BFS/ISO9660 volumes
259 		if (fMethod == BOOT_METHOD_CD
260 			&& fBootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false)
261 			&& partition->Type() != NULL
262 			&& strcmp(partition->Type(), kPartitionTypeDataSession)) {
263 			return false;
264 		}
265 
266 		if (partition->ContentType() != NULL
267 			&& (!strcmp(partition->ContentType(), "Be File System")
268 			|| !strcmp(partition->ContentType(), "ISO9660 File System"))) {
269 			return true;
270 		}
271 	}
272 
273 	return false;
274 }
275 
276 
277 void
278 DiskBootMethod::SortPartitions(KPartition** partitions, int32 count)
279 {
280 	qsort(partitions, count, sizeof(KPartition*),
281 		fMethod == BOOT_METHOD_CD ? compare_cd_boot : compare_image_boot);
282 }
283 
284 
285 // #pragma mark -
286 
287 
288 /*!	Make the boot partition (and probably others) available.
289 	The partitions that are a boot candidate a put into the /a partitions
290 	stack. If the user selected a boot device, there is will only be one
291 	entry in this stack; if not, the most likely is put up first.
292 	The boot code should then just try them one by one.
293 */
294 static status_t
295 get_boot_partitions(kernel_args* args, PartitionStack& partitions)
296 {
297 	const KMessage& bootVolume = args->boot_volume;
298 
299 	dprintf("get_boot_partitions(): boot volume message:\n");
300 	bootVolume.Dump(&dprintf);
301 
302 	// create boot method
303 	int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT);
304 	dprintf("get_boot_partitions(): boot method type: %ld\n", bootMethodType);
305 
306 	BootMethod* bootMethod = NULL;
307 	switch (bootMethodType) {
308 		case BOOT_METHOD_NET:
309 			bootMethod = new(nothrow) NetBootMethod(bootVolume, bootMethodType);
310 			break;
311 
312 		case BOOT_METHOD_HARD_DISK:
313 		case BOOT_METHOD_CD:
314 		default:
315 			bootMethod = new(nothrow) DiskBootMethod(bootVolume,
316 				bootMethodType);
317 			break;
318 	}
319 
320 	status_t status = bootMethod != NULL ? bootMethod->Init() : B_NO_MEMORY;
321 	if (status != B_OK)
322 		return status;
323 
324 	KDiskDeviceManager::CreateDefault();
325 	KDiskDeviceManager *manager = KDiskDeviceManager::Default();
326 
327 	status = manager->InitialDeviceScan();
328 	if (status != B_OK) {
329 		dprintf("KDiskDeviceManager::InitialDeviceScan() failed: %s\n",
330 			strerror(status));
331 		return status;
332 	}
333 
334 	if (1 /* dump devices and partitions */) {
335 		KDiskDevice *device;
336 		int32 cookie = 0;
337 		while ((device = manager->NextDevice(&cookie)) != NULL) {
338 			device->Dump(true, 0);
339 		}
340 	}
341 
342 	struct BootPartitionVisitor : KPartitionVisitor {
343 		BootPartitionVisitor(BootMethod* bootMethod, PartitionStack &stack)
344 			: fPartitions(stack),
345 			  fBootMethod(bootMethod)
346 		{
347 		}
348 
349 		virtual bool VisitPre(KPartition *partition)
350 		{
351 			if (!partition->ContainsFileSystem())
352 				return false;
353 
354 			bool foundForSure = false;
355 			if (fBootMethod->IsBootPartition(partition, foundForSure))
356 				fPartitions.Push(partition);
357 
358 			// if found for sure, we can terminate the search
359 			return foundForSure;
360 		}
361 
362 		private:
363 			PartitionStack	&fPartitions;
364 			BootMethod*		fBootMethod;
365 	} visitor(bootMethod, partitions);
366 
367 	bool strict = true;
368 
369 	while (true) {
370 		KDiskDevice *device;
371 		int32 cookie = 0;
372 		while ((device = manager->NextDevice(&cookie)) != NULL) {
373 			if (!bootMethod->IsBootDevice(device, strict))
374 				continue;
375 
376 			if (device->VisitEachDescendant(&visitor) != NULL)
377 				break;
378 		}
379 
380 		if (!partitions.IsEmpty() || !strict)
381 			break;
382 
383 		// we couldn't find any potential boot devices, try again less strict
384 		strict = false;
385 	}
386 
387 	// sort partition list (e.g.. when booting from CD, CDs should come first in
388 	// the list)
389 	if (!args->boot_volume.GetBool(BOOT_VOLUME_USER_SELECTED, false))
390 		bootMethod->SortPartitions(partitions.Array(), partitions.CountItems());
391 
392 	return B_OK;
393 }
394 
395 
396 //	#pragma mark -
397 
398 
399 status_t
400 vfs_bootstrap_file_systems(void)
401 {
402 	status_t status;
403 
404 	// bootstrap the root filesystem
405 	status = _kern_mount("/", NULL, "rootfs", 0, NULL, 0);
406 	if (status < B_OK)
407 		panic("error mounting rootfs!\n");
408 
409 	_kern_setcwd(-1, "/");
410 
411 	// bootstrap the devfs
412 	_kern_create_dir(-1, "/dev", 0755);
413 	status = _kern_mount("/dev", NULL, "devfs", 0, NULL, 0);
414 	if (status < B_OK)
415 		panic("error mounting devfs\n");
416 
417 	// create directory for the boot volume
418 	_kern_create_dir(-1, "/boot", 0755);
419 
420 	// create some standard links on the rootfs
421 
422 	for (int32 i = 0; sPredefinedLinks[i].path != NULL; i++) {
423 		_kern_create_symlink(-1, sPredefinedLinks[i].path,
424 			sPredefinedLinks[i].target, 0);
425 			// we don't care if it will succeed or not
426 	}
427 
428 	return B_OK;
429 }
430 
431 
432 void
433 vfs_mount_boot_file_system(kernel_args* args)
434 {
435 	PartitionStack partitions;
436 	status_t status = get_boot_partitions(args, partitions);
437 	if (status < B_OK) {
438 		panic("get_boot_partitions failed!");
439 	}
440 	if (partitions.IsEmpty()) {
441 		panic("did not find any boot partitions!");
442 	}
443 
444 	KPartition* bootPartition;
445 	while (partitions.Pop(&bootPartition)) {
446 		KPath path;
447 		if (bootPartition->GetPath(&path) != B_OK)
448 			panic("could not get boot device!\n");
449 
450 		const char *fsName = NULL;
451 		bool readOnly = false;
452 		if (strcmp(bootPartition->ContentType(), "ISO9660 File System") == 0) {
453 			fsName = "iso9660:write_overlay:attribute_overlay";
454 			readOnly = true;
455 		}
456 
457 		TRACE(("trying to mount boot partition: %s\n", path.Path()));
458 		gBootDevice = _kern_mount("/boot", path.Path(), fsName, 0, NULL, 0);
459 		if (gBootDevice >= B_OK) {
460 			gReadOnlyBootDevice = true;
461 			break;
462 		}
463 	}
464 
465 	if (gBootDevice < B_OK)
466 		panic("could not mount boot device!\n");
467 
468 	// create link for the name of the boot device
469 
470 	fs_info info;
471 	if (_kern_read_fs_info(gBootDevice, &info) == B_OK) {
472 		char path[B_FILE_NAME_LENGTH + 1];
473 		snprintf(path, sizeof(path), "/%s", info.volume_name);
474 
475 		_kern_create_symlink(-1, path, "/boot", 0);
476 	}
477 
478 	// Do post-boot-volume module initialization. The module code wants to know
479 	// whether the module images the boot loader has pre-loaded are the same as
480 	// on the boot volume. That is the case when booting from hard disk or CD,
481 	// but not via network.
482 	int32 bootMethodType = args->boot_volume.GetInt32(BOOT_METHOD,
483 		BOOT_METHOD_DEFAULT);
484 	bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK
485 		|| bootMethodType == BOOT_METHOD_CD;
486 	module_init_post_boot_device(bootingFromBootLoaderVolume);
487 
488 	file_cache_init_post_boot_device();
489 
490 	// search for other disk systems
491 	KDiskDeviceManager *manager = KDiskDeviceManager::Default();
492 	manager->RescanDiskSystems();
493 	manager->StartMonitoring();
494 }
495 
496