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 %ld, device %ld\n", disk->bus_type, 198 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(kernel_args* args, PartitionStack& partitions) 324 { 325 const KMessage& bootVolume = args->boot_volume; 326 327 dprintf("get_boot_partitions(): boot volume message:\n"); 328 bootVolume.Dump(&dprintf); 329 330 // create boot method 331 int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT); 332 dprintf("get_boot_partitions(): boot method type: %ld\n", 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 (!args->boot_volume.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 PartitionStack partitions; 464 status_t status = get_boot_partitions(args, partitions); 465 if (status < B_OK) { 466 panic("get_boot_partitions failed!"); 467 } 468 if (partitions.IsEmpty()) { 469 panic("did not find any boot partitions!"); 470 } 471 472 KPartition* bootPartition; 473 while (partitions.Pop(&bootPartition)) { 474 KPath path; 475 if (bootPartition->GetPath(&path) != B_OK) 476 panic("could not get boot device!\n"); 477 478 const char* fsName = NULL; 479 bool readOnly = false; 480 if (strcmp(bootPartition->ContentType(), kPartitionTypeISO9660) == 0) { 481 fsName = "iso9660:write_overlay:attribute_overlay"; 482 readOnly = true; 483 } else if (bootPartition->IsReadOnly() 484 && strcmp(bootPartition->ContentType(), kPartitionTypeBFS) == 0) { 485 fsName = "bfs:write_overlay"; 486 readOnly = true; 487 } 488 489 TRACE(("trying to mount boot partition: %s\n", path.Path())); 490 gBootDevice = _kern_mount("/boot", path.Path(), fsName, 0, NULL, 0); 491 if (gBootDevice >= 0) { 492 dprintf("Mounted boot partition: %s\n", path.Path()); 493 gReadOnlyBootDevice = readOnly; 494 break; 495 } 496 } 497 498 if (gBootDevice < B_OK) 499 panic("could not mount boot device!\n"); 500 501 // create link for the name of the boot device 502 503 fs_info info; 504 if (_kern_read_fs_info(gBootDevice, &info) == B_OK) { 505 char path[B_FILE_NAME_LENGTH + 1]; 506 snprintf(path, sizeof(path), "/%s", info.volume_name); 507 508 _kern_create_symlink(-1, path, "/boot", 0); 509 } 510 511 // Do post-boot-volume module initialization. The module code wants to know 512 // whether the module images the boot loader has pre-loaded are the same as 513 // on the boot volume. That is the case when booting from hard disk or CD, 514 // but not via network. 515 int32 bootMethodType = args->boot_volume.GetInt32(BOOT_METHOD, 516 BOOT_METHOD_DEFAULT); 517 bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK 518 || bootMethodType == BOOT_METHOD_CD; 519 module_init_post_boot_device(bootingFromBootLoaderVolume); 520 521 file_cache_init_post_boot_device(); 522 523 // search for other disk systems 524 KDiskDeviceManager *manager = KDiskDeviceManager::Default(); 525 manager->RescanDiskSystems(); 526 manager->StartMonitoring(); 527 } 528 529