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 the only primary partition on the CD, has type 279 // BFS, and the boot partition offset is 0. 280 KDiskDevice* device = partition->Device(); 281 if (IsBootDevice(device, false) && bootPartitionOffset == 0 282 && partition->Parent() == device && device->CountChildren() == 1 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() failed: %s\n", 364 strerror(status)); 365 return status; 366 } 367 368 if (1 /* dump devices and partitions */) { 369 KDiskDevice *device; 370 int32 cookie = 0; 371 while ((device = manager->NextDevice(&cookie)) != NULL) { 372 device->Dump(true, 0); 373 } 374 } 375 376 struct BootPartitionVisitor : KPartitionVisitor { 377 BootPartitionVisitor(BootMethod* bootMethod, PartitionStack &stack) 378 : fPartitions(stack), 379 fBootMethod(bootMethod) 380 { 381 } 382 383 virtual bool VisitPre(KPartition *partition) 384 { 385 if (!partition->ContainsFileSystem()) 386 return false; 387 388 bool foundForSure = false; 389 if (fBootMethod->IsBootPartition(partition, foundForSure)) 390 fPartitions.Push(partition); 391 392 // if found for sure, we can terminate the search 393 return foundForSure; 394 } 395 396 private: 397 PartitionStack &fPartitions; 398 BootMethod* fBootMethod; 399 } visitor(bootMethod, partitions); 400 401 bool strict = true; 402 403 while (true) { 404 KDiskDevice *device; 405 int32 cookie = 0; 406 while ((device = manager->NextDevice(&cookie)) != NULL) { 407 if (!bootMethod->IsBootDevice(device, strict)) 408 continue; 409 410 if (device->VisitEachDescendant(&visitor) != NULL) 411 break; 412 } 413 414 if (!partitions.IsEmpty() || !strict) 415 break; 416 417 // we couldn't find any potential boot devices, try again less strict 418 strict = false; 419 } 420 421 // sort partition list (e.g.. when booting from CD, CDs should come first in 422 // the list) 423 if (!bootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false)) 424 bootMethod->SortPartitions(partitions.Array(), partitions.CountItems()); 425 426 return B_OK; 427 } 428 429 430 // #pragma mark - 431 432 433 status_t 434 vfs_bootstrap_file_systems(void) 435 { 436 status_t status; 437 438 // bootstrap the root filesystem 439 status = _kern_mount("/", NULL, "rootfs", 0, NULL, 0); 440 if (status < B_OK) 441 panic("error mounting rootfs!\n"); 442 443 _kern_setcwd(-1, "/"); 444 445 // bootstrap the devfs 446 _kern_create_dir(-1, "/dev", 0755); 447 status = _kern_mount("/dev", NULL, "devfs", 0, NULL, 0); 448 if (status < B_OK) 449 panic("error mounting devfs\n"); 450 451 // create directory for the boot volume 452 _kern_create_dir(-1, "/boot", 0755); 453 454 // create some standard links on the rootfs 455 456 for (int32 i = 0; sPredefinedLinks[i].path != NULL; i++) { 457 _kern_create_symlink(-1, sPredefinedLinks[i].path, 458 sPredefinedLinks[i].target, 0); 459 // we don't care if it will succeed or not 460 } 461 462 return B_OK; 463 } 464 465 466 void 467 vfs_mount_boot_file_system(kernel_args* args) 468 { 469 KMessage bootVolume; 470 bootVolume.SetTo(args->boot_volume, args->boot_volume_size); 471 472 PartitionStack partitions; 473 status_t status = get_boot_partitions(bootVolume, partitions); 474 if (status < B_OK) { 475 panic("get_boot_partitions failed!"); 476 } 477 if (partitions.IsEmpty()) { 478 panic("did not find any boot partitions!"); 479 } 480 481 dev_t bootDevice = -1; 482 483 KPartition* bootPartition; 484 while (partitions.Pop(&bootPartition)) { 485 KPath path; 486 if (bootPartition->GetPath(&path) != B_OK) 487 panic("could not get boot device!\n"); 488 489 const char* fsName = NULL; 490 bool readOnly = false; 491 if (strcmp(bootPartition->ContentType(), kPartitionTypeISO9660) == 0) { 492 fsName = "iso9660:write_overlay:attribute_overlay"; 493 readOnly = true; 494 } else if (bootPartition->IsReadOnly() 495 && strcmp(bootPartition->ContentType(), kPartitionTypeBFS) == 0) { 496 fsName = "bfs:write_overlay"; 497 readOnly = true; 498 } 499 500 TRACE(("trying to mount boot partition: %s\n", path.Path())); 501 502 bootDevice = _kern_mount("/boot", path.Path(), fsName, 0, NULL, 0); 503 if (bootDevice >= 0) { 504 dprintf("Mounted boot partition: %s\n", path.Path()); 505 gReadOnlyBootDevice = readOnly; 506 break; 507 } 508 } 509 510 if (bootDevice < B_OK) 511 panic("could not mount boot device!\n"); 512 513 // create link for the name of the boot device 514 515 fs_info info; 516 if (_kern_read_fs_info(bootDevice, &info) == B_OK) { 517 char path[B_FILE_NAME_LENGTH + 1]; 518 snprintf(path, sizeof(path), "/%s", info.volume_name); 519 520 _kern_create_symlink(-1, path, "/boot", 0); 521 } 522 523 // If we're booting off a packaged system, mount packagefs. 524 struct stat st; 525 if (bootVolume.GetBool(BOOT_VOLUME_PACKAGED, false) 526 || (bootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false) 527 && lstat(kSystemPackagesDirectory, &st) == 0)) { 528 static const char* const kPackageFSName = "packagefs"; 529 530 char arguments[256]; 531 strlcpy(arguments, "packages /boot/system/packages; type system", 532 sizeof(arguments)); 533 if (const char* stateName 534 = bootVolume.GetString(BOOT_VOLUME_PACKAGES_STATE, NULL)) { 535 strlcat(arguments, "; state ", sizeof(arguments)); 536 strlcat(arguments, stateName, sizeof(arguments)); 537 } 538 539 dev_t packageMount = _kern_mount("/boot/system", NULL, kPackageFSName, 540 0, arguments, 0 /* unused argument length */); 541 if (packageMount < 0) { 542 panic("Failed to mount system packagefs: %s", 543 strerror(packageMount)); 544 } 545 546 packageMount = _kern_mount("/boot/home/config", NULL, kPackageFSName, 0, 547 "packages /boot/home/config/packages; type home", 548 0 /* unused argument length */); 549 if (packageMount < 0) { 550 dprintf("Failed to mount home packagefs: %s\n", 551 strerror(packageMount)); 552 } 553 } 554 555 // Now that packagefs is mounted, the boot volume is really ready. 556 gBootDevice = bootDevice; 557 558 // Do post-boot-volume module initialization. The module code wants to know 559 // whether the module images the boot loader has pre-loaded are the same as 560 // on the boot volume. That is the case when booting from hard disk or CD, 561 // but not via network. 562 int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT); 563 bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK 564 || bootMethodType == BOOT_METHOD_CD; 565 module_init_post_boot_device(bootingFromBootLoaderVolume); 566 567 file_cache_init_post_boot_device(); 568 569 // search for other disk systems 570 KDiskDeviceManager *manager = KDiskDeviceManager::Default(); 571 manager->RescanDiskSystems(); 572 manager->StartMonitoring(); 573 } 574 575