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