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