xref: /haiku/src/system/kernel/fs/vfs_boot.cpp (revision d3d8b26997fac34a84981e6d2b649521de2cc45a)
1 /*
2  * Copyright 2002-2006, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  *
5  * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
6  * Distributed under the terms of the NewOS License.
7  */
8 
9 
10 #include <OS.h>
11 #include <fs_info.h>
12 
13 #include <disk_device_manager/KDiskDevice.h>
14 #include <disk_device_manager/KDiskDeviceManager.h>
15 #include <disk_device_manager/KPartitionVisitor.h>
16 #include <DiskDeviceTypes.h>
17 
18 #include <vfs.h>
19 #include <file_cache.h>
20 #include <KPath.h>
21 #include <syscalls.h>
22 #include <boot/kernel_args.h>
23 #include <util/Stack.h>
24 
25 #include <stdio.h>
26 
27 
28 //#define TRACE_VFS
29 #ifdef TRACE_VFS
30 #	define TRACE(x) dprintf x
31 #else
32 #	define TRACE(x) ;
33 #endif
34 
35 
36 typedef Stack<KPartition *> PartitionStack;
37 
38 static struct {
39 	const char *path;
40 	const char *target;
41 } sPredefinedLinks[] = {
42 	{"/system", "/boot/beos/system"},
43 	{"/bin", "/boot/beos/bin"},
44 	{"/etc", "/boot/beos/etc"},
45 	{"/var", "/boot/var"},
46 	{"/tmp", "/boot/var/tmp"},
47 	{NULL}
48 };
49 
50 // This can be used by other code to see if there is a boot file system already
51 dev_t gBootDevice = -1;
52 
53 
54 /**	No image was chosen - prefer disks with names like "Haiku", or "System"
55  */
56 
57 static int
58 compare_image_boot(const void *_a, const void *_b)
59 {
60 	KPartition *a = *(KPartition **)_a;
61 	KPartition *b = *(KPartition **)_b;
62 
63 	if (a->ContentName() != NULL) {
64 		if (b->ContentName() == NULL)
65 			return 1;
66 	} else if (b->ContentName() != NULL) {
67 		return -1;
68 	} else
69 		return 0;
70 
71 	int compare = strcmp(a->ContentName(), b->ContentName());
72 	if (!compare)
73 		return 0;
74 
75 	if (!strcasecmp(a->ContentName(), "Haiku"))
76 		return 1;
77 	if (!strcasecmp(b->ContentName(), "Haiku"))
78 		return -1;
79 	if (!strncmp(a->ContentName(), "System", 6))
80 		return 1;
81 	if (!strncmp(b->ContentName(), "System", 6))
82 		return -1;
83 
84 	return compare;
85 }
86 
87 
88 /**	The system was booted from CD - prefer CDs over other entries. If there
89  *	is no CD, fall back to the standard mechanism (as implemented by
90  *	compare_image_boot().
91  */
92 
93 static int
94 compare_cd_boot(const void *_a, const void *_b)
95 {
96 	KPartition *a = *(KPartition **)_a;
97 	KPartition *b = *(KPartition **)_b;
98 
99 	bool aIsCD = a->Type() != NULL && !strcmp(a->Type(), kPartitionTypeDataSession);
100 	bool bIsCD = b->Type() != NULL && !strcmp(b->Type(), kPartitionTypeDataSession);
101 
102 	int compare = (int)aIsCD - (int)bIsCD;
103 	if (compare != 0)
104 		return compare;
105 
106 	return compare_image_boot(_a, _b);
107 }
108 
109 
110 /**	Computes a check sum for the specified block.
111  *	The check sum is the sum of all data in that block interpreted as an
112  *	array of uint32 values.
113  *	Note, this must use the same method as the one used in
114  *	boot/platform/bios_ia32/devices.cpp (or similar solutions).
115  */
116 
117 static uint32
118 compute_check_sum(KDiskDevice *device, off_t offset)
119 {
120 	char buffer[512];
121 	ssize_t bytesRead = read_pos(device->FD(), offset, buffer, sizeof(buffer));
122 	if (bytesRead < B_OK)
123 		return 0;
124 
125 	if (bytesRead < (ssize_t)sizeof(buffer))
126 		memset(buffer + bytesRead, 0, sizeof(buffer) - bytesRead);
127 
128 	uint32 *array = (uint32 *)buffer;
129 	uint32 sum = 0;
130 
131 	for (uint32 i = 0; i < (bytesRead + sizeof(uint32) - 1) / sizeof(uint32); i++) {
132 		sum += array[i];
133 	}
134 
135 	return sum;
136 }
137 
138 
139 /**	Checks if the device matches the boot device as specified by the
140  *	boot loader.
141  */
142 
143 static bool
144 is_boot_device(kernel_args *args, KDiskDevice *device, bool strict)
145 {
146 	disk_identifier &disk = args->boot_disk.identifier;
147 
148 	TRACE(("boot device: bus %ld, device %ld\n", disk.bus_type,
149 		disk.device_type));
150 
151 	switch (disk.bus_type) {
152 		case PCI_BUS:
153 		case LEGACY_BUS:
154 			// TODO: implement this! (and then enable this feature in the boot loader)
155 			// (we need a way to get the device_node of a device, then)
156 			break;
157 
158 		case UNKNOWN_BUS:
159 			// nothing to do here
160 			break;
161 	}
162 
163 	switch (disk.device_type) {
164 		case UNKNOWN_DEVICE:
165 			// test if the size of the device matches
166 			// (the BIOS might have given us the wrong value here, though)
167 			if (strict && device->Size() != disk.device.unknown.size)
168 				return false;
169 
170 			// check if the check sums match, too
171 			for (int32 i = 0; i < NUM_DISK_CHECK_SUMS; i++) {
172 				if (disk.device.unknown.check_sums[i].offset == -1)
173 					continue;
174 
175 				if (compute_check_sum(device, disk.device.unknown.check_sums[i].offset)
176 						!= disk.device.unknown.check_sums[i].sum)
177 					return false;
178 			}
179 			break;
180 
181 		case ATA_DEVICE:
182 		case ATAPI_DEVICE:
183 		case SCSI_DEVICE:
184 		case USB_DEVICE:
185 		case FIREWIRE_DEVICE:
186 		case FIBRE_DEVICE:
187 			// TODO: implement me!
188 			break;
189 	}
190 
191 	return true;
192 }
193 
194 
195 /**	Make the boot partition (and probably others) available.
196  *	The partitions that are a boot candidate a put into the /a partitions
197  *	stack. If the user selected a boot device, there is will only be one
198  *	entry in this stack; if not, the most likely is put up first.
199  *	The boot code should then just try them one by one.
200  */
201 
202 static status_t
203 get_boot_partitions(kernel_args *args, PartitionStack &partitions)
204 {
205 	KDiskDeviceManager::CreateDefault();
206 	KDiskDeviceManager *manager = KDiskDeviceManager::Default();
207 
208 	status_t status = manager->InitialDeviceScan();
209 	if (status != B_OK) {
210 		dprintf("KDiskDeviceManager::InitialDeviceScan() failed: %s\n", strerror(status));
211 		return status;
212 	}
213 
214 	struct BootPartitionVisitor : KPartitionVisitor {
215 		BootPartitionVisitor(kernel_args &args, PartitionStack &stack)
216 			: fArgs(args), fPartitions(stack) {}
217 
218 		virtual bool VisitPre(KPartition *partition)
219 		{
220 			if (!partition->ContainsFileSystem())
221 				return false;
222 
223 			if (!fArgs.boot_disk.booted_from_image) {
224 				// the simple case: we can just boot from the selected boot device
225 				if (partition->Offset() == fArgs.boot_disk.partition_offset) {
226 					fPartitions.Push(partition);
227 					return true;
228 				}
229 			} else {
230 				// for now, we will just collect all BFS volumes
231 				if (fArgs.boot_disk.cd && fArgs.boot_disk.user_selected
232 					&& partition->Type() != NULL
233 					&& strcmp(partition->Type(), kPartitionTypeDataSession))
234 					return false;
235 
236 				if (partition->ContentType() != NULL
237 					&& !strcmp(partition->ContentType(), "Be File System"))
238 					fPartitions.Push(partition);
239 			}
240 			return false;
241 		}
242 		private:
243 			kernel_args		&fArgs;
244 			PartitionStack	&fPartitions;
245 	} visitor(*args, partitions);
246 
247 	bool strict = true;
248 
249 	while (true) {
250 		KDiskDevice *device;
251 		int32 cookie = 0;
252 		while ((device = manager->NextDevice(&cookie)) != NULL) {
253 			if (!is_boot_device(args, device, strict))
254 				continue;
255 
256 			if (device->VisitEachDescendant(&visitor) != NULL)
257 				break;
258 		}
259 
260 		if (!partitions.IsEmpty() || !strict)
261 			break;
262 
263 		// we couldn't find any potential boot devices, try again less strict
264 		strict = false;
265 	}
266 
267 	if (!args->boot_disk.user_selected) {
268 		// sort partition list (ie. when booting from CD, CDs should come first in the list)
269 		qsort(partitions.Array(), partitions.CountItems(), sizeof(KPartition *),
270 			args->boot_disk.cd ? compare_cd_boot : compare_image_boot);
271 	}
272 
273 	return B_OK;
274 }
275 
276 
277 //	#pragma mark -
278 
279 
280 status_t
281 vfs_bootstrap_file_systems(void)
282 {
283 	status_t status;
284 
285 	// bootstrap the root filesystem
286 	status = _kern_mount("/", NULL, "rootfs", 0, NULL, 0);
287 	if (status < B_OK)
288 		panic("error mounting rootfs!\n");
289 
290 	_kern_setcwd(-1, "/");
291 
292 	// bootstrap the devfs
293 	_kern_create_dir(-1, "/dev", 0755);
294 	status = _kern_mount("/dev", NULL, "devfs", 0, NULL, 0);
295 	if (status < B_OK)
296 		panic("error mounting devfs\n");
297 
298 	// bootstrap the pipefs
299 	_kern_create_dir(-1, "/pipe", 0755);
300 	status = _kern_mount("/pipe", NULL, "pipefs", 0, NULL, 0);
301 	if (status < B_OK)
302 		panic("error mounting pipefs\n");
303 
304 	// create directory for the boot volume
305 	_kern_create_dir(-1, "/boot", 0755);
306 
307 	// create some standard links on the rootfs
308 
309 	for (int32 i = 0; sPredefinedLinks[i].path != NULL; i++) {
310 		_kern_create_symlink(-1, sPredefinedLinks[i].path,
311 			sPredefinedLinks[i].target, 0);
312 			// we don't care if it will succeed or not
313 	}
314 
315 	return B_OK;
316 }
317 
318 
319 status_t
320 vfs_mount_boot_file_system(kernel_args *args)
321 {
322 	PartitionStack partitions;
323 	status_t status = get_boot_partitions(args, partitions);
324 	if (status < B_OK) {
325 		panic("Did not get any boot partitions!");
326 		return status;
327 	}
328 
329 	KPartition *bootPartition;
330 	while (partitions.Pop(&bootPartition)) {
331 		KPath path;
332 		if (bootPartition->GetPath(&path) != B_OK)
333 			panic("could not get boot device!\n");
334 
335 		gBootDevice = _kern_mount("/boot", path.Path(), NULL, 0, NULL, 0);
336 		if (gBootDevice >= B_OK)
337 			break;
338 	}
339 
340 	if (gBootDevice < B_OK)
341 		panic("could not mount boot device!\n");
342 
343 	// create link for the name of the boot device
344 
345 	fs_info info;
346 	if (_kern_read_fs_info(gBootDevice, &info) == B_OK) {
347 		char path[B_FILE_NAME_LENGTH + 1];
348 		snprintf(path, sizeof(path), "/%s", info.volume_name);
349 
350 		_kern_create_symlink(-1, path, "/boot", 0);
351 	}
352 
353 	file_cache_init_post_boot_device();
354 	return B_OK;
355 }
356 
357