1 /* 2 * Copyright 2007-2011, 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 // check if the check sums match, too 226 for (int32 i = 0; i < NUM_DISK_CHECK_SUMS; i++) { 227 if (disk->device.unknown.check_sums[i].offset == -1) 228 continue; 229 230 if (compute_check_sum(device, 231 disk->device.unknown.check_sums[i].offset) 232 != disk->device.unknown.check_sums[i].sum) { 233 return false; 234 } 235 } 236 break; 237 238 case ATA_DEVICE: 239 case ATAPI_DEVICE: 240 case SCSI_DEVICE: 241 case USB_DEVICE: 242 case FIREWIRE_DEVICE: 243 case FIBRE_DEVICE: 244 // TODO: implement me! 245 break; 246 } 247 248 return true; 249 } 250 251 252 bool 253 DiskBootMethod::IsBootPartition(KPartition* partition, bool& foundForSure) 254 { 255 off_t bootPartitionOffset = fBootVolume.GetInt64( 256 BOOT_VOLUME_PARTITION_OFFSET, 0); 257 258 if (!fBootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)) { 259 // the simple case: we can just boot from the selected boot 260 // device 261 if (partition->Offset() == bootPartitionOffset) { 262 dprintf("Identified boot partition by partition offset.\n"); 263 foundForSure = true; 264 return true; 265 } 266 } else { 267 // For now, unless we can positively identify an anyboot CD, we will 268 // just collect all BFS/ISO9660 volumes. 269 270 if (fMethod == BOOT_METHOD_CD) { 271 // Check for the boot partition of an anyboot CD. We identify it as 272 // such, if it is the only primary partition on the CD, has type 273 // BFS, and the boot partition offset is 0. 274 KDiskDevice* device = partition->Device(); 275 if (IsBootDevice(device, false) && bootPartitionOffset == 0 276 && partition->Parent() == device && device->CountChildren() == 1 277 && device->ContentType() != NULL 278 && strcmp(device->ContentType(), kPartitionTypeIntel) == 0 279 && partition->ContentType() != NULL 280 && strcmp(partition->ContentType(), kPartitionTypeBFS) == 0) { 281 dprintf("Identified anyboot CD.\n"); 282 foundForSure = true; 283 return true; 284 } 285 286 // Ignore non-session partitions, if a boot partition was selected 287 // by the user. 288 if (fBootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false) 289 && partition->Type() != NULL 290 && strcmp(partition->Type(), kPartitionTypeDataSession) != 0) { 291 return false; 292 } 293 } 294 295 if (partition->ContentType() != NULL 296 && (strcmp(partition->ContentType(), kPartitionTypeBFS) == 0 297 || strcmp(partition->ContentType(), kPartitionTypeISO9660) == 0)) { 298 return true; 299 } 300 } 301 302 return false; 303 } 304 305 306 void 307 DiskBootMethod::SortPartitions(KPartition** partitions, int32 count) 308 { 309 qsort(partitions, count, sizeof(KPartition*), 310 fMethod == BOOT_METHOD_CD ? compare_cd_boot : compare_image_boot); 311 } 312 313 314 // #pragma mark - 315 316 317 /*! Make the boot partition (and probably others) available. 318 The partitions that are a boot candidate a put into the /a partitions 319 stack. If the user selected a boot device, there is will only be one 320 entry in this stack; if not, the most likely is put up first. 321 The boot code should then just try them one by one. 322 */ 323 static status_t 324 get_boot_partitions(KMessage& bootVolume, PartitionStack& partitions) 325 { 326 dprintf("get_boot_partitions(): boot volume message:\n"); 327 bootVolume.Dump(&dprintf); 328 329 // create boot method 330 int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT); 331 dprintf("get_boot_partitions(): boot method type: %" B_PRId32 "\n", 332 bootMethodType); 333 334 BootMethod* bootMethod = NULL; 335 switch (bootMethodType) { 336 case BOOT_METHOD_NET: 337 bootMethod = new(nothrow) NetBootMethod(bootVolume, bootMethodType); 338 break; 339 340 case BOOT_METHOD_HARD_DISK: 341 case BOOT_METHOD_CD: 342 default: 343 bootMethod = new(nothrow) DiskBootMethod(bootVolume, 344 bootMethodType); 345 break; 346 } 347 348 status_t status = bootMethod != NULL ? bootMethod->Init() : B_NO_MEMORY; 349 if (status != B_OK) 350 return status; 351 352 KDiskDeviceManager::CreateDefault(); 353 KDiskDeviceManager *manager = KDiskDeviceManager::Default(); 354 355 status = manager->InitialDeviceScan(); 356 if (status != B_OK) { 357 dprintf("KDiskDeviceManager::InitialDeviceScan() failed: %s\n", 358 strerror(status)); 359 return status; 360 } 361 362 if (1 /* dump devices and partitions */) { 363 KDiskDevice *device; 364 int32 cookie = 0; 365 while ((device = manager->NextDevice(&cookie)) != NULL) { 366 device->Dump(true, 0); 367 } 368 } 369 370 struct BootPartitionVisitor : KPartitionVisitor { 371 BootPartitionVisitor(BootMethod* bootMethod, PartitionStack &stack) 372 : fPartitions(stack), 373 fBootMethod(bootMethod) 374 { 375 } 376 377 virtual bool VisitPre(KPartition *partition) 378 { 379 if (!partition->ContainsFileSystem()) 380 return false; 381 382 bool foundForSure = false; 383 if (fBootMethod->IsBootPartition(partition, foundForSure)) 384 fPartitions.Push(partition); 385 386 // if found for sure, we can terminate the search 387 return foundForSure; 388 } 389 390 private: 391 PartitionStack &fPartitions; 392 BootMethod* fBootMethod; 393 } visitor(bootMethod, partitions); 394 395 bool strict = true; 396 397 while (true) { 398 KDiskDevice *device; 399 int32 cookie = 0; 400 while ((device = manager->NextDevice(&cookie)) != NULL) { 401 if (!bootMethod->IsBootDevice(device, strict)) 402 continue; 403 404 if (device->VisitEachDescendant(&visitor) != NULL) 405 break; 406 } 407 408 if (!partitions.IsEmpty() || !strict) 409 break; 410 411 // we couldn't find any potential boot devices, try again less strict 412 strict = false; 413 } 414 415 // sort partition list (e.g.. when booting from CD, CDs should come first in 416 // the list) 417 if (!bootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false)) 418 bootMethod->SortPartitions(partitions.Array(), partitions.CountItems()); 419 420 return B_OK; 421 } 422 423 424 // #pragma mark - 425 426 427 status_t 428 vfs_bootstrap_file_systems(void) 429 { 430 status_t status; 431 432 // bootstrap the root filesystem 433 status = _kern_mount("/", NULL, "rootfs", 0, NULL, 0); 434 if (status < B_OK) 435 panic("error mounting rootfs!\n"); 436 437 _kern_setcwd(-1, "/"); 438 439 // bootstrap the devfs 440 _kern_create_dir(-1, "/dev", 0755); 441 status = _kern_mount("/dev", NULL, "devfs", 0, NULL, 0); 442 if (status < B_OK) 443 panic("error mounting devfs\n"); 444 445 // create directory for the boot volume 446 _kern_create_dir(-1, "/boot", 0755); 447 448 // create some standard links on the rootfs 449 450 for (int32 i = 0; sPredefinedLinks[i].path != NULL; i++) { 451 _kern_create_symlink(-1, sPredefinedLinks[i].path, 452 sPredefinedLinks[i].target, 0); 453 // we don't care if it will succeed or not 454 } 455 456 return B_OK; 457 } 458 459 460 void 461 vfs_mount_boot_file_system(kernel_args* args) 462 { 463 KMessage bootVolume; 464 bootVolume.SetTo(args->boot_volume, args->boot_volume_size); 465 466 PartitionStack partitions; 467 status_t status = get_boot_partitions(bootVolume, partitions); 468 if (status < B_OK) { 469 panic("get_boot_partitions failed!"); 470 } 471 if (partitions.IsEmpty()) { 472 panic("did not find any boot partitions!"); 473 } 474 475 dev_t bootDevice = -1; 476 477 KPartition* bootPartition; 478 while (partitions.Pop(&bootPartition)) { 479 KPath path; 480 if (bootPartition->GetPath(&path) != B_OK) 481 panic("could not get boot device!\n"); 482 483 const char* fsName = NULL; 484 bool readOnly = false; 485 if (strcmp(bootPartition->ContentType(), kPartitionTypeISO9660) == 0) { 486 fsName = "iso9660:write_overlay:attribute_overlay"; 487 readOnly = true; 488 } else if (bootPartition->IsReadOnly() 489 && strcmp(bootPartition->ContentType(), kPartitionTypeBFS) == 0) { 490 fsName = "bfs:write_overlay"; 491 readOnly = true; 492 } 493 494 TRACE(("trying to mount boot partition: %s\n", path.Path())); 495 496 bootDevice = _kern_mount("/boot", path.Path(), fsName, 0, NULL, 0); 497 if (bootDevice >= 0) { 498 dprintf("Mounted boot partition: %s\n", path.Path()); 499 gReadOnlyBootDevice = readOnly; 500 break; 501 } 502 } 503 504 if (bootDevice < B_OK) 505 panic("could not mount boot device!\n"); 506 507 // create link for the name of the boot device 508 509 fs_info info; 510 if (_kern_read_fs_info(bootDevice, &info) == B_OK) { 511 char path[B_FILE_NAME_LENGTH + 1]; 512 snprintf(path, sizeof(path), "/%s", info.volume_name); 513 514 _kern_create_symlink(-1, path, "/boot", 0); 515 } 516 517 // If we're booting off a packaged system, mount packagefs. 518 struct stat st; 519 if (bootVolume.GetBool(BOOT_VOLUME_PACKAGED, false) 520 || (bootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false) 521 && lstat(kSystemPackagesDirectory, &st) == 0)) { 522 static const char* const kPackageFSName = "packagefs"; 523 524 dev_t packageMount = _kern_mount("/boot/system", NULL, kPackageFSName, 525 0, "packages /boot/system/packages; type system", 526 0 /* unused argument length */); 527 if (packageMount < 0) { 528 panic("Failed to mount system packagefs: %s", 529 strerror(packageMount)); 530 } 531 532 packageMount = _kern_mount("/boot/common", NULL, kPackageFSName, 0, 533 "packages /boot/common/packages; type common", 534 0 /* unused argument length */); 535 if (packageMount < 0) { 536 dprintf("Failed to mount common packagefs: %s\n", 537 strerror(packageMount)); 538 } 539 540 packageMount = _kern_mount("/boot/home/config", NULL, kPackageFSName, 0, 541 "packages /boot/home/config/packages; type home", 542 0 /* unused argument length */); 543 if (packageMount < 0) { 544 dprintf("Failed to mount home packagefs: %s\n", 545 strerror(packageMount)); 546 } 547 } 548 549 // Now that packagefs is mounted, the boot volume is really ready. 550 gBootDevice = bootDevice; 551 552 // Do post-boot-volume module initialization. The module code wants to know 553 // whether the module images the boot loader has pre-loaded are the same as 554 // on the boot volume. That is the case when booting from hard disk or CD, 555 // but not via network. 556 int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT); 557 bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK 558 || bootMethodType == BOOT_METHOD_CD; 559 module_init_post_boot_device(bootingFromBootLoaderVolume); 560 561 file_cache_init_post_boot_device(); 562 563 // search for other disk systems 564 KDiskDeviceManager *manager = KDiskDeviceManager::Default(); 565 manager->RescanDiskSystems(); 566 manager->StartMonitoring(); 567 } 568 569