xref: /haiku/src/system/kernel/fs/vfs_boot.cpp (revision adb0d19d561947362090081e81d90dde59142026)
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 
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 			|| !strcmp(partition->ContentType(), "ISO9660 File System"))) {
264 			return true;
265 		}
266 	}
267 
268 	return false;
269 }
270 
271 
272 void
273 DiskBootMethod::SortPartitions(KPartition** partitions, int32 count)
274 {
275 	qsort(partitions, count, sizeof(KPartition *),
276 		fMethod == BOOT_METHOD_CD ? compare_cd_boot : compare_image_boot);
277 }
278 
279 
280 // #pragma mark -
281 
282 
283 /*!	Make the boot partition (and probably others) available.
284 	The partitions that are a boot candidate a put into the /a partitions
285 	stack. If the user selected a boot device, there is will only be one
286 	entry in this stack; if not, the most likely is put up first.
287 	The boot code should then just try them one by one.
288 */
289 static status_t
290 get_boot_partitions(kernel_args *args, PartitionStack &partitions)
291 {
292 	const KMessage& bootVolume = args->boot_volume;
293 
294 	dprintf("get_boot_partitions(): boot volume message:\n");
295 	bootVolume.Dump(&dprintf);
296 
297 	// create boot method
298 	int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD,
299 		BOOT_METHOD_DEFAULT);
300 dprintf("get_boot_partitions(): boot method type: %ld\n", bootMethodType);
301 	BootMethod* bootMethod = NULL;
302 	switch (bootMethodType) {
303 		case BOOT_METHOD_NET:
304 			bootMethod = new(nothrow) NetBootMethod(bootVolume, bootMethodType);
305 			break;
306 
307 		case BOOT_METHOD_HARD_DISK:
308 		case BOOT_METHOD_CD:
309 		default:
310 			bootMethod = new(nothrow) DiskBootMethod(bootVolume,
311 				bootMethodType);
312 			break;
313 	}
314 
315 	status_t status = bootMethod != NULL ? bootMethod->Init() : B_NO_MEMORY;
316 	if (status != B_OK)
317 		return status;
318 
319 	KDiskDeviceManager::CreateDefault();
320 	KDiskDeviceManager *manager = KDiskDeviceManager::Default();
321 
322 	status = manager->InitialDeviceScan();
323 	if (status != B_OK) {
324 		dprintf("KDiskDeviceManager::InitialDeviceScan() failed: %s\n",
325 			strerror(status));
326 		return status;
327 	}
328 
329 	if (1 /* dump devices and partitions */) {
330 		KDiskDevice *device;
331 		int32 cookie = 0;
332 		while ((device = manager->NextDevice(&cookie)) != NULL) {
333 			device->Dump(true, 0);
334 		}
335 	}
336 
337 	struct BootPartitionVisitor : KPartitionVisitor {
338 		BootPartitionVisitor(BootMethod* bootMethod, PartitionStack &stack)
339 			: fPartitions(stack),
340 			  fBootMethod(bootMethod)
341 		{
342 		}
343 
344 		virtual bool VisitPre(KPartition *partition)
345 		{
346 			if (!partition->ContainsFileSystem())
347 				return false;
348 
349 			bool foundForSure = false;
350 			if (fBootMethod->IsBootPartition(partition, foundForSure))
351 				fPartitions.Push(partition);
352 
353 			// if found for sure, we can terminate the search
354 			return foundForSure;
355 		}
356 
357 		private:
358 			PartitionStack	&fPartitions;
359 			BootMethod*		fBootMethod;
360 	} visitor(bootMethod, partitions);
361 
362 	bool strict = true;
363 
364 	while (true) {
365 		KDiskDevice *device;
366 		int32 cookie = 0;
367 		while ((device = manager->NextDevice(&cookie)) != NULL) {
368 			if (!bootMethod->IsBootDevice(device, strict))
369 				continue;
370 
371 			if (device->VisitEachDescendant(&visitor) != NULL)
372 				break;
373 		}
374 
375 		if (!partitions.IsEmpty() || !strict)
376 			break;
377 
378 		// we couldn't find any potential boot devices, try again less strict
379 		strict = false;
380 	}
381 
382 	// sort partition list (e.g.. when booting from CD, CDs should come first in
383 	// the list)
384 	if (!args->boot_volume.GetBool(BOOT_VOLUME_USER_SELECTED, false))
385 		bootMethod->SortPartitions(partitions.Array(), partitions.CountItems());
386 
387 	return B_OK;
388 }
389 
390 
391 //	#pragma mark -
392 
393 
394 status_t
395 vfs_bootstrap_file_systems(void)
396 {
397 	status_t status;
398 
399 	// bootstrap the root filesystem
400 	status = _kern_mount("/", NULL, "rootfs", 0, NULL, 0);
401 	if (status < B_OK)
402 		panic("error mounting rootfs!\n");
403 
404 	_kern_setcwd(-1, "/");
405 
406 	// bootstrap the devfs
407 	_kern_create_dir(-1, "/dev", 0755);
408 	status = _kern_mount("/dev", NULL, "devfs", 0, NULL, 0);
409 	if (status < B_OK)
410 		panic("error mounting devfs\n");
411 
412 	// create directory for the boot volume
413 	_kern_create_dir(-1, "/boot", 0755);
414 
415 	// create some standard links on the rootfs
416 
417 	for (int32 i = 0; sPredefinedLinks[i].path != NULL; i++) {
418 		_kern_create_symlink(-1, sPredefinedLinks[i].path,
419 			sPredefinedLinks[i].target, 0);
420 			// we don't care if it will succeed or not
421 	}
422 
423 	return B_OK;
424 }
425 
426 
427 void
428 vfs_mount_boot_file_system(kernel_args *args)
429 {
430 	PartitionStack partitions;
431 	status_t status = get_boot_partitions(args, partitions);
432 	if (status < B_OK) {
433 		panic("get_boot_partitions failed!");
434 	}
435 	if (partitions.IsEmpty()) {
436 		panic("did not find any boot partitions!");
437 	}
438 
439 	KPartition *bootPartition;
440 	while (partitions.Pop(&bootPartition)) {
441 		KPath path;
442 		if (bootPartition->GetPath(&path) != B_OK)
443 			panic("could not get boot device!\n");
444 
445 		const char *fsName = NULL;
446 		if (strcmp(bootPartition->ContentType(), "ISO9660 File System") == 0)
447 			fsName = "iso9660:write_overlay:attribute_overlay";
448 
449 		TRACE(("trying to mount boot partition: %s\n", path.Path()));
450 		gBootDevice = _kern_mount("/boot", path.Path(), fsName, 0, NULL, 0);
451 		if (gBootDevice >= B_OK)
452 			break;
453 	}
454 
455 	if (gBootDevice < B_OK)
456 		panic("could not mount boot device!\n");
457 
458 	// create link for the name of the boot device
459 
460 	fs_info info;
461 	if (_kern_read_fs_info(gBootDevice, &info) == B_OK) {
462 		char path[B_FILE_NAME_LENGTH + 1];
463 		snprintf(path, sizeof(path), "/%s", info.volume_name);
464 
465 		_kern_create_symlink(-1, path, "/boot", 0);
466 	}
467 
468 	// Do post-boot-volume module initialization. The module code wants to know
469 	// whether the module images the boot loader has pre-loaded are the same as
470 	// on the boot volume. That is the case when booting from hard disk or CD,
471 	// but not via network.
472 	int32 bootMethodType = args->boot_volume.GetInt32(BOOT_METHOD,
473 		BOOT_METHOD_DEFAULT);
474 	bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK
475 		|| bootMethodType == BOOT_METHOD_CD;
476 	module_init_post_boot_device(bootingFromBootLoaderVolume);
477 
478 	file_cache_init_post_boot_device();
479 
480 	// search for other disk systems
481 	KDiskDeviceManager *manager = KDiskDeviceManager::Default();
482 	manager->RescanDiskSystems();
483 	manager->StartMonitoring();
484 }
485 
486