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