1 /*
2 * Copyright 2007-2014, Ingo Weinhold, ingo_weinhold@gmx.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 #include <strings.h>
15
16 #include <fs_info.h>
17 #include <OS.h>
18
19 #include <boot/kernel_args.h>
20 #include <directories.h>
21 #include <disk_device_manager/KDiskDevice.h>
22 #include <disk_device_manager/KDiskDeviceManager.h>
23 #include <disk_device_manager/KPartitionVisitor.h>
24 #include <DiskDeviceTypes.h>
25 #include <file_cache.h>
26 #include <fs/KPath.h>
27 #include <kmodule.h>
28 #include <syscalls.h>
29 #include <util/KMessage.h>
30 #include <util/Stack.h>
31 #include <vfs.h>
32
33 #include "vfs_net_boot.h"
34
35
36 //#define TRACE_VFS
37 #ifdef TRACE_VFS
38 # define TRACE(x) dprintf x
39 #else
40 # define TRACE(x) ;
41 #endif
42
43
44 typedef Stack<KPartition *> PartitionStack;
45
46 static struct {
47 const char *path;
48 const char *target;
49 } sPredefinedLinks[] = {
50 { kGlobalSystemDirectory, kSystemDirectory },
51 { kGlobalBinDirectory, kSystemBinDirectory },
52 { kGlobalEtcDirectory, kSystemEtcDirectory },
53 { kGlobalTempDirectory, kSystemTempDirectory },
54 { kGlobalVarDirectory, kSystemVarDirectory },
55 { kGlobalPackageLinksDirectory, kSystemPackageLinksDirectory },
56 {NULL}
57 };
58
59 // This can be used by other code to see if there is a boot file system already
60 dev_t gBootDevice = -1;
61 bool gReadOnlyBootDevice = false;
62
63
64 /*! No image was chosen - prefer disks with names like "Haiku", or "System"
65 */
66 int
compare_image_boot(const void * _a,const void * _b)67 compare_image_boot(const void* _a, const void* _b)
68 {
69 KPartition* a = *(KPartition**)_a;
70 KPartition* b = *(KPartition**)_b;
71
72 if (a->ContentName() != NULL) {
73 if (b->ContentName() == NULL)
74 return 1;
75 } else if (b->ContentName() != NULL) {
76 return -1;
77 } else
78 return 0;
79
80 int compare = strcasecmp(a->ContentName(), b->ContentName());
81 if (!compare)
82 return 0;
83
84 if (!strcasecmp(a->ContentName(), "Haiku"))
85 return 1;
86 if (!strcasecmp(b->ContentName(), "Haiku"))
87 return -1;
88 if (!strncmp(a->ContentName(), "System", 6))
89 return 1;
90 if (!strncmp(b->ContentName(), "System", 6))
91 return -1;
92
93 return compare;
94 }
95
96
97 /*! The system was booted from CD - prefer CDs over other entries. If there
98 is no CD, fall back to the standard mechanism (as implemented by
99 compare_image_boot().
100 */
101 static int
compare_cd_boot(const void * _a,const void * _b)102 compare_cd_boot(const void* _a, const void* _b)
103 {
104 KPartition* a = *(KPartition**)_a;
105 KPartition* b = *(KPartition**)_b;
106
107 bool aIsCD = a->Type() != NULL
108 && !strcmp(a->Type(), kPartitionTypeDataSession);
109 bool bIsCD = b->Type() != NULL
110 && !strcmp(b->Type(), kPartitionTypeDataSession);
111
112 int compare = (int)aIsCD - (int)bIsCD;
113 if (compare != 0)
114 return compare;
115
116 return compare_image_boot(_a, _b);
117 }
118
119
120 /*! Computes a check sum for the specified block.
121 The check sum is the sum of all data in that block interpreted as an
122 array of uint32 values.
123 Note, this must use the same method as the one used in
124 boot/platform/bios_ia32/devices.cpp (or similar solutions).
125 */
126 static uint32
compute_check_sum(KDiskDevice * device,off_t offset)127 compute_check_sum(KDiskDevice* device, off_t offset)
128 {
129 char buffer[512];
130 ssize_t bytesRead = read_pos(device->FD(), offset, buffer, sizeof(buffer));
131 if (bytesRead < B_OK)
132 return 0;
133
134 if (bytesRead < (ssize_t)sizeof(buffer))
135 memset(buffer + bytesRead, 0, sizeof(buffer) - bytesRead);
136
137 uint32* array = (uint32*)buffer;
138 uint32 sum = 0;
139
140 for (uint32 i = 0;
141 i < (bytesRead + sizeof(uint32) - 1) / sizeof(uint32); i++) {
142 sum += array[i];
143 }
144
145 return sum;
146 }
147
148
149 // #pragma mark - BootMethod
150
151
BootMethod(const KMessage & bootVolume,int32 method)152 BootMethod::BootMethod(const KMessage& bootVolume, int32 method)
153 :
154 fBootVolume(bootVolume),
155 fMethod(method)
156 {
157 }
158
159
~BootMethod()160 BootMethod::~BootMethod()
161 {
162 }
163
164
165 status_t
Init()166 BootMethod::Init()
167 {
168 return B_OK;
169 }
170
171
172 // #pragma mark - DiskBootMethod
173
174
175 class DiskBootMethod : public BootMethod {
176 public:
DiskBootMethod(const KMessage & bootVolume,int32 method)177 DiskBootMethod(const KMessage& bootVolume, int32 method)
178 : BootMethod(bootVolume, method)
179 {
180 }
181
182 virtual bool IsBootDevice(KDiskDevice* device, bool strict);
183 virtual bool IsBootPartition(KPartition* partition, bool& foundForSure);
184 virtual void SortPartitions(KPartition** partitions, int32 count);
185 };
186
187
188 bool
IsBootDevice(KDiskDevice * device,bool strict)189 DiskBootMethod::IsBootDevice(KDiskDevice* device, bool strict)
190 {
191 disk_identifier* disk;
192 int32 diskIdentifierSize;
193 if (fBootVolume.FindData(BOOT_VOLUME_DISK_IDENTIFIER, B_RAW_TYPE,
194 (const void**)&disk, &diskIdentifierSize) != B_OK) {
195 dprintf("DiskBootMethod::IsBootDevice(): no disk identifier!\n");
196 return false;
197 }
198
199 TRACE(("boot device: bus %" B_PRId32 ", device %" B_PRId32 "\n",
200 disk->bus_type, disk->device_type));
201
202 // Assume that CD boots only happen off removable media.
203 if (fMethod == BOOT_METHOD_CD && !device->IsRemovable())
204 return false;
205
206 switch (disk->bus_type) {
207 case PCI_BUS:
208 case LEGACY_BUS:
209 // TODO: implement this! (and then enable this feature in the boot
210 // loader)
211 // (we need a way to get the device_node of a device, then)
212 break;
213
214 case UNKNOWN_BUS:
215 // nothing to do here
216 break;
217 }
218
219 switch (disk->device_type) {
220 case UNKNOWN_DEVICE:
221 // test if the size of the device matches
222 // (the BIOS might have given us the wrong value here, though)
223 if (strict && device->Size() != disk->device.unknown.size)
224 return false;
225
226 // Skip the check sum test for CDs, since we didn't read anything
227 // useful from the disk in the boot loader.
228 if (fMethod == BOOT_METHOD_CD)
229 break;
230
231 // check if the check sums match, too
232 for (int32 i = 0; i < NUM_DISK_CHECK_SUMS; i++) {
233 if (disk->device.unknown.check_sums[i].offset == -1)
234 continue;
235
236 if (compute_check_sum(device,
237 disk->device.unknown.check_sums[i].offset)
238 != disk->device.unknown.check_sums[i].sum) {
239 return false;
240 }
241 }
242 break;
243
244 case ATA_DEVICE:
245 case ATAPI_DEVICE:
246 case SCSI_DEVICE:
247 case USB_DEVICE:
248 case FIREWIRE_DEVICE:
249 case FIBRE_DEVICE:
250 // TODO: implement me!
251 break;
252 }
253
254 return true;
255 }
256
257
258 bool
IsBootPartition(KPartition * partition,bool & foundForSure)259 DiskBootMethod::IsBootPartition(KPartition* partition, bool& foundForSure)
260 {
261 off_t bootPartitionOffset = fBootVolume.GetInt64(
262 BOOT_VOLUME_PARTITION_OFFSET, 0);
263
264 if (!fBootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)) {
265 // the simple case: we can just boot from the selected boot
266 // device
267 if (partition->Offset() == bootPartitionOffset) {
268 dprintf("Identified boot partition by partition offset.\n");
269 foundForSure = true;
270 return true;
271 }
272 } else {
273 // For now, unless we can positively identify an anyboot CD, we will
274 // just collect all BFS/ISO9660 volumes.
275
276 if (fMethod == BOOT_METHOD_CD) {
277 // Check for the boot partition of an anyboot CD. We identify it as
278 // such if it is a partition on the CD, has type BFS, and the boot
279 // partition offset is 0
280 KDiskDevice* device = partition->Device();
281 if (IsBootDevice(device, false)
282 && bootPartitionOffset == 0 && partition->Parent() == device
283 && device->ContentType() != NULL
284 && strcmp(device->ContentType(), kPartitionTypeIntel) == 0
285 && partition->ContentType() != NULL
286 && strcmp(partition->ContentType(), kPartitionTypeBFS) == 0) {
287 dprintf("Identified anyboot CD.\n");
288 foundForSure = true;
289 return true;
290 }
291
292 // Ignore non-session partitions, if a boot partition was selected
293 // by the user.
294 if (fBootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false)
295 && partition->Type() != NULL
296 && strcmp(partition->Type(), kPartitionTypeDataSession) != 0) {
297 return false;
298 }
299 }
300
301 if (partition->ContentType() != NULL
302 && (strcmp(partition->ContentType(), kPartitionTypeBFS) == 0
303 || strcmp(partition->ContentType(), kPartitionTypeISO9660) == 0)) {
304 return true;
305 }
306 }
307
308 return false;
309 }
310
311
312 void
SortPartitions(KPartition ** partitions,int32 count)313 DiskBootMethod::SortPartitions(KPartition** partitions, int32 count)
314 {
315 qsort(partitions, count, sizeof(KPartition*),
316 fMethod == BOOT_METHOD_CD ? compare_cd_boot : compare_image_boot);
317 }
318
319
320 // #pragma mark -
321
322
323 /*! Make the boot partition (and probably others) available.
324 The partitions that are a boot candidate a put into the /a partitions
325 stack. If the user selected a boot device, there is will only be one
326 entry in this stack; if not, the most likely is put up first.
327 The boot code should then just try them one by one.
328 */
329 static status_t
get_boot_partitions(KMessage & bootVolume,PartitionStack & partitions)330 get_boot_partitions(KMessage& bootVolume, PartitionStack& partitions)
331 {
332 dprintf("get_boot_partitions(): boot volume message:\n");
333 bootVolume.Dump(&dprintf);
334
335 // create boot method
336 int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT);
337 dprintf("get_boot_partitions(): boot method type: %" B_PRId32 "\n",
338 bootMethodType);
339
340 BootMethod* bootMethod = NULL;
341 switch (bootMethodType) {
342 case BOOT_METHOD_NET:
343 bootMethod = new(nothrow) NetBootMethod(bootVolume, bootMethodType);
344 break;
345
346 case BOOT_METHOD_HARD_DISK:
347 case BOOT_METHOD_CD:
348 default:
349 bootMethod = new(nothrow) DiskBootMethod(bootVolume,
350 bootMethodType);
351 break;
352 }
353
354 status_t status = bootMethod != NULL ? bootMethod->Init() : B_NO_MEMORY;
355 if (status != B_OK)
356 return status;
357
358 KDiskDeviceManager::CreateDefault();
359 KDiskDeviceManager *manager = KDiskDeviceManager::Default();
360
361 status = manager->InitialDeviceScan();
362 if (status != B_OK) {
363 dprintf("KDiskDeviceManager::InitialDeviceScan() returned error: %s\n",
364 strerror(status));
365 // InitialDeviceScan returns error if one (or more) partitions are
366 // determined to be invalid. The partition we are trying to boot from
367 // may be usuable anyway, so don't fail here.
368 }
369
370 #if KDEBUG
371 // dump devices and partitions
372 KDiskDevice *device;
373 int32 cookie = 0;
374 while ((device = manager->NextDevice(&cookie)) != NULL) {
375 device->Dump(true, 0);
376 }
377 #endif
378
379 struct BootPartitionVisitor : KPartitionVisitor {
380 BootPartitionVisitor(BootMethod* bootMethod, PartitionStack &stack)
381 : fPartitions(stack),
382 fBootMethod(bootMethod)
383 {
384 }
385
386 virtual bool VisitPre(KPartition *partition)
387 {
388 if (!partition->ContainsFileSystem())
389 return false;
390
391 bool foundForSure = false;
392 if (fBootMethod->IsBootPartition(partition, foundForSure))
393 fPartitions.Push(partition);
394
395 // if found for sure, we can terminate the search
396 return foundForSure;
397 }
398
399 private:
400 PartitionStack &fPartitions;
401 BootMethod* fBootMethod;
402 } visitor(bootMethod, partitions);
403
404 bool strict = true;
405
406 while (true) {
407 KDiskDevice *device;
408 int32 cookie = 0;
409 while ((device = manager->NextDevice(&cookie)) != NULL) {
410 if (!bootMethod->IsBootDevice(device, strict))
411 continue;
412
413 if (device->VisitEachDescendant(&visitor) != NULL)
414 break;
415 }
416
417 if (!partitions.IsEmpty() || !strict)
418 break;
419
420 // we couldn't find any potential boot devices, try again less strict
421 strict = false;
422 }
423
424 // sort partition list (e.g.. when booting from CD, CDs should come first in
425 // the list)
426 if (!bootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false))
427 bootMethod->SortPartitions(partitions.Array(), partitions.CountItems());
428
429 return B_OK;
430 }
431
432
433 // #pragma mark -
434
435
436 status_t
vfs_bootstrap_file_systems(void)437 vfs_bootstrap_file_systems(void)
438 {
439 status_t status;
440
441 // bootstrap the root filesystem
442 status = _kern_mount("/", NULL, "rootfs", 0, NULL, 0);
443 if (status < B_OK)
444 panic("error mounting rootfs!\n");
445
446 _kern_setcwd(-1, "/");
447
448 // bootstrap the devfs
449 _kern_create_dir(-1, "/dev", 0755);
450 status = _kern_mount("/dev", NULL, "devfs", 0, NULL, 0);
451 if (status < B_OK)
452 panic("error mounting devfs\n");
453
454 // create directory for the boot volume
455 _kern_create_dir(-1, "/boot", 0755);
456
457 // create some standard links on the rootfs
458
459 for (int32 i = 0; sPredefinedLinks[i].path != NULL; i++) {
460 _kern_create_symlink(-1, sPredefinedLinks[i].path,
461 sPredefinedLinks[i].target, 0777);
462 // we don't care if it will succeed or not
463 }
464
465 return B_OK;
466 }
467
468
469 void
vfs_mount_boot_file_system(kernel_args * args)470 vfs_mount_boot_file_system(kernel_args* args)
471 {
472 KMessage bootVolume;
473 bootVolume.SetTo(args->boot_volume, args->boot_volume_size);
474
475 PartitionStack partitions;
476 status_t status = get_boot_partitions(bootVolume, partitions);
477 if (status < B_OK) {
478 panic("get_boot_partitions failed!");
479 }
480 if (partitions.IsEmpty()) {
481 panic("did not find any boot partitions! @! syslog | tail 15");
482 }
483
484 dev_t bootDevice = -1;
485
486 KPartition* bootPartition;
487 while (partitions.Pop(&bootPartition)) {
488 KPath path;
489 if (bootPartition->GetPath(&path) != B_OK)
490 panic("could not get boot device!\n");
491
492 const char* fsName = NULL;
493 bool readOnly = false;
494 if (strcmp(bootPartition->ContentType(), kPartitionTypeISO9660) == 0) {
495 fsName = "iso9660:write_overlay:attribute_overlay";
496 readOnly = true;
497 } else if (bootPartition->IsReadOnly()
498 && strcmp(bootPartition->ContentType(), kPartitionTypeBFS) == 0) {
499 fsName = "bfs:write_overlay";
500 readOnly = true;
501 }
502
503 TRACE(("trying to mount boot partition: %s\n", path.Path()));
504
505 bootDevice = _kern_mount("/boot", path.Path(), fsName, 0, NULL, 0);
506 if (bootDevice >= 0) {
507 dprintf("Mounted boot partition: %s\n", path.Path());
508 gReadOnlyBootDevice = readOnly;
509 break;
510 }
511 }
512
513 if (bootDevice < B_OK)
514 panic("could not mount boot device!\n");
515
516 // create link for the name of the boot device
517
518 fs_info info;
519 if (_kern_read_fs_info(bootDevice, &info) == B_OK) {
520 char path[B_FILE_NAME_LENGTH + 1];
521 snprintf(path, sizeof(path), "/%s", info.volume_name);
522
523 _kern_create_symlink(-1, path, "/boot", 0777);
524 }
525
526 // If we're booting off a packaged system, mount packagefs.
527 struct stat st;
528 if (bootVolume.GetBool(BOOT_VOLUME_PACKAGED, false)
529 || (bootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)
530 && lstat(kSystemPackagesDirectory, &st) == 0)) {
531 static const char* const kPackageFSName = "packagefs";
532
533 char arguments[256];
534 strlcpy(arguments, "packages /boot/system/packages; type system",
535 sizeof(arguments));
536 if (const char* stateName
537 = bootVolume.GetString(BOOT_VOLUME_PACKAGES_STATE, NULL)) {
538 strlcat(arguments, "; state ", sizeof(arguments));
539 strlcat(arguments, stateName, sizeof(arguments));
540 }
541
542 dev_t packageMount = _kern_mount("/boot/system", NULL, kPackageFSName,
543 0, arguments, 0 /* unused argument length */);
544 if (packageMount < 0) {
545 panic("Failed to mount system packagefs: %s",
546 strerror(packageMount));
547 }
548
549 packageMount = _kern_mount("/boot/home/config", NULL, kPackageFSName, 0,
550 "packages /boot/home/config/packages; type home",
551 0 /* unused argument length */);
552 if (packageMount < 0) {
553 dprintf("Failed to mount home packagefs: %s\n",
554 strerror(packageMount));
555 }
556 }
557
558 // Now that packagefs is mounted, the boot volume is really ready.
559 gBootDevice = bootDevice;
560
561 // Do post-boot-volume module initialization. The module code wants to know
562 // whether the module images the boot loader has pre-loaded are the same as
563 // on the boot volume. That is the case when booting from hard disk or CD,
564 // but not via network.
565 int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT);
566 bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK
567 || bootMethodType == BOOT_METHOD_CD;
568 module_init_post_boot_device(bootingFromBootLoaderVolume);
569
570 file_cache_init_post_boot_device();
571
572 // search for other disk systems
573 KDiskDeviceManager *manager = KDiskDeviceManager::Default();
574 manager->RescanDiskSystems();
575 manager->StartMonitoring();
576 }
577
578