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 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 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 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 152 BootMethod::BootMethod(const KMessage& bootVolume, int32 method) 153 : 154 fBootVolume(bootVolume), 155 fMethod(method) 156 { 157 } 158 159 160 BootMethod::~BootMethod() 161 { 162 } 163 164 165 status_t 166 BootMethod::Init() 167 { 168 return B_OK; 169 } 170 171 172 // #pragma mark - DiskBootMethod 173 174 175 class DiskBootMethod : public BootMethod { 176 public: 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 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 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 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 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 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 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