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 <disk_device_manager/KDiskDevice.h> 20 #include <disk_device_manager/KDiskDeviceManager.h> 21 #include <disk_device_manager/KPartitionVisitor.h> 22 #include <DiskDeviceTypes.h> 23 #include <file_cache.h> 24 #include <fs/KPath.h> 25 #include <kmodule.h> 26 #include <syscalls.h> 27 #include <util/KMessage.h> 28 #include <util/Stack.h> 29 #include <vfs.h> 30 31 #include "vfs_net_boot.h" 32 33 34 //#define TRACE_VFS 35 #ifdef TRACE_VFS 36 # define TRACE(x) dprintf x 37 #else 38 # define TRACE(x) ; 39 #endif 40 41 42 typedef Stack<KPartition *> PartitionStack; 43 44 static struct { 45 const char *path; 46 const char *target; 47 } sPredefinedLinks[] = { 48 {"/system", "/boot/system"}, 49 {"/bin", "/boot/system/bin"}, 50 {"/etc", "/boot/common/etc"}, 51 {"/var", "/boot/common/var"}, 52 {"/tmp", "/boot/common/cache/tmp"}, 53 {NULL} 54 }; 55 56 // This can be used by other code to see if there is a boot file system already 57 dev_t gBootDevice = -1; 58 bool gReadOnlyBootDevice = false; 59 60 61 /*! No image was chosen - prefer disks with names like "Haiku", or "System" 62 */ 63 int 64 compare_image_boot(const void* _a, const void* _b) 65 { 66 KPartition* a = *(KPartition**)_a; 67 KPartition* b = *(KPartition**)_b; 68 69 if (a->ContentName() != NULL) { 70 if (b->ContentName() == NULL) 71 return 1; 72 } else if (b->ContentName() != NULL) { 73 return -1; 74 } else 75 return 0; 76 77 int compare = strcasecmp(a->ContentName(), b->ContentName()); 78 if (!compare) 79 return 0; 80 81 if (!strcasecmp(a->ContentName(), "Haiku")) 82 return 1; 83 if (!strcasecmp(b->ContentName(), "Haiku")) 84 return -1; 85 if (!strncmp(a->ContentName(), "System", 6)) 86 return 1; 87 if (!strncmp(b->ContentName(), "System", 6)) 88 return -1; 89 90 return compare; 91 } 92 93 94 /*! The system was booted from CD - prefer CDs over other entries. If there 95 is no CD, fall back to the standard mechanism (as implemented by 96 compare_image_boot(). 97 */ 98 static int 99 compare_cd_boot(const void* _a, const void* _b) 100 { 101 KPartition* a = *(KPartition**)_a; 102 KPartition* b = *(KPartition**)_b; 103 104 bool aIsCD = a->Type() != NULL 105 && !strcmp(a->Type(), kPartitionTypeDataSession); 106 bool bIsCD = b->Type() != NULL 107 && !strcmp(b->Type(), kPartitionTypeDataSession); 108 109 int compare = (int)aIsCD - (int)bIsCD; 110 if (compare != 0) 111 return compare; 112 113 return compare_image_boot(_a, _b); 114 } 115 116 117 /*! Computes a check sum for the specified block. 118 The check sum is the sum of all data in that block interpreted as an 119 array of uint32 values. 120 Note, this must use the same method as the one used in 121 boot/platform/bios_ia32/devices.cpp (or similar solutions). 122 */ 123 static uint32 124 compute_check_sum(KDiskDevice* device, off_t offset) 125 { 126 char buffer[512]; 127 ssize_t bytesRead = read_pos(device->FD(), offset, buffer, sizeof(buffer)); 128 if (bytesRead < B_OK) 129 return 0; 130 131 if (bytesRead < (ssize_t)sizeof(buffer)) 132 memset(buffer + bytesRead, 0, sizeof(buffer) - bytesRead); 133 134 uint32* array = (uint32*)buffer; 135 uint32 sum = 0; 136 137 for (uint32 i = 0; 138 i < (bytesRead + sizeof(uint32) - 1) / sizeof(uint32); i++) { 139 sum += array[i]; 140 } 141 142 return sum; 143 } 144 145 146 // #pragma mark - BootMethod 147 148 149 BootMethod::BootMethod(const KMessage& bootVolume, int32 method) 150 : 151 fBootVolume(bootVolume), 152 fMethod(method) 153 { 154 } 155 156 157 BootMethod::~BootMethod() 158 { 159 } 160 161 162 status_t 163 BootMethod::Init() 164 { 165 return B_OK; 166 } 167 168 169 // #pragma mark - DiskBootMethod 170 171 172 class DiskBootMethod : public BootMethod { 173 public: 174 DiskBootMethod(const KMessage& bootVolume, int32 method) 175 : BootMethod(bootVolume, method) 176 { 177 } 178 179 virtual bool IsBootDevice(KDiskDevice* device, bool strict); 180 virtual bool IsBootPartition(KPartition* partition, bool& foundForSure); 181 virtual void SortPartitions(KPartition** partitions, int32 count); 182 }; 183 184 185 bool 186 DiskBootMethod::IsBootDevice(KDiskDevice* device, bool strict) 187 { 188 disk_identifier* disk; 189 int32 diskIdentifierSize; 190 if (fBootVolume.FindData(BOOT_VOLUME_DISK_IDENTIFIER, B_RAW_TYPE, 191 (const void**)&disk, &diskIdentifierSize) != B_OK) { 192 dprintf("DiskBootMethod::IsBootDevice(): no disk identifier!\n"); 193 return false; 194 } 195 196 TRACE(("boot device: bus %ld, device %ld\n", disk->bus_type, 197 disk->device_type)); 198 199 // Assume that CD boots only happen off removable media. 200 if (fMethod == BOOT_METHOD_CD && !device->IsRemovable()) 201 return false; 202 203 switch (disk->bus_type) { 204 case PCI_BUS: 205 case LEGACY_BUS: 206 // TODO: implement this! (and then enable this feature in the boot 207 // loader) 208 // (we need a way to get the device_node of a device, then) 209 break; 210 211 case UNKNOWN_BUS: 212 // nothing to do here 213 break; 214 } 215 216 switch (disk->device_type) { 217 case UNKNOWN_DEVICE: 218 // test if the size of the device matches 219 // (the BIOS might have given us the wrong value here, though) 220 if (strict && device->Size() != disk->device.unknown.size) 221 return false; 222 223 // check if the check sums match, too 224 for (int32 i = 0; i < NUM_DISK_CHECK_SUMS; i++) { 225 if (disk->device.unknown.check_sums[i].offset == -1) 226 continue; 227 228 if (compute_check_sum(device, 229 disk->device.unknown.check_sums[i].offset) 230 != disk->device.unknown.check_sums[i].sum) { 231 return false; 232 } 233 } 234 break; 235 236 case ATA_DEVICE: 237 case ATAPI_DEVICE: 238 case SCSI_DEVICE: 239 case USB_DEVICE: 240 case FIREWIRE_DEVICE: 241 case FIBRE_DEVICE: 242 // TODO: implement me! 243 break; 244 } 245 246 return true; 247 } 248 249 250 bool 251 DiskBootMethod::IsBootPartition(KPartition* partition, bool& foundForSure) 252 { 253 off_t bootPartitionOffset = fBootVolume.GetInt64( 254 BOOT_VOLUME_PARTITION_OFFSET, 0); 255 256 if (!fBootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)) { 257 // the simple case: we can just boot from the selected boot 258 // device 259 if (partition->Offset() == bootPartitionOffset) { 260 dprintf("Identified boot partition by partition offset.\n"); 261 foundForSure = true; 262 return true; 263 } 264 } else { 265 // For now, unless we can positively identify an anyboot CD, we will 266 // just collect all BFS/ISO9660 volumes. 267 268 if (fMethod == BOOT_METHOD_CD) { 269 // Check for the boot partition of an anyboot CD. We identify it as 270 // such, if it is the only primary partition on the CD, has type 271 // BFS, and the boot partition offset is 0. 272 KDiskDevice* device = partition->Device(); 273 if (IsBootDevice(device, false) && bootPartitionOffset == 0 274 && partition->Parent() == device && device->CountChildren() == 1 275 && device->ContentType() != NULL 276 && strcmp(device->ContentType(), kPartitionTypeIntel) == 0 277 && partition->ContentType() != NULL 278 && strcmp(partition->ContentType(), kPartitionTypeBFS) == 0) { 279 dprintf("Identified anyboot CD.\n"); 280 foundForSure = true; 281 return true; 282 } 283 284 // Ignore non-session partitions, if a boot partition was selected 285 // by the user. 286 if (fBootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false) 287 && partition->Type() != NULL 288 && strcmp(partition->Type(), kPartitionTypeDataSession) != 0) { 289 return false; 290 } 291 } 292 293 if (partition->ContentType() != NULL 294 && (strcmp(partition->ContentType(), kPartitionTypeBFS) == 0 295 || strcmp(partition->ContentType(), kPartitionTypeISO9660) == 0)) { 296 return true; 297 } 298 } 299 300 return false; 301 } 302 303 304 void 305 DiskBootMethod::SortPartitions(KPartition** partitions, int32 count) 306 { 307 qsort(partitions, count, sizeof(KPartition*), 308 fMethod == BOOT_METHOD_CD ? compare_cd_boot : compare_image_boot); 309 } 310 311 312 // #pragma mark - 313 314 315 /*! Make the boot partition (and probably others) available. 316 The partitions that are a boot candidate a put into the /a partitions 317 stack. If the user selected a boot device, there is will only be one 318 entry in this stack; if not, the most likely is put up first. 319 The boot code should then just try them one by one. 320 */ 321 static status_t 322 get_boot_partitions(kernel_args* args, PartitionStack& partitions) 323 { 324 const KMessage& bootVolume = args->boot_volume; 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: %ld\n", 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 (!args->boot_volume.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 PartitionStack partitions; 463 status_t status = get_boot_partitions(args, partitions); 464 if (status < B_OK) { 465 panic("get_boot_partitions failed!"); 466 } 467 if (partitions.IsEmpty()) { 468 panic("did not find any boot partitions!"); 469 } 470 471 KPartition* bootPartition; 472 while (partitions.Pop(&bootPartition)) { 473 KPath path; 474 if (bootPartition->GetPath(&path) != B_OK) 475 panic("could not get boot device!\n"); 476 477 const char* fsName = NULL; 478 bool readOnly = false; 479 if (strcmp(bootPartition->ContentType(), kPartitionTypeISO9660) == 0) { 480 fsName = "iso9660:write_overlay:attribute_overlay"; 481 readOnly = true; 482 } else if (bootPartition->IsReadOnly() 483 && strcmp(bootPartition->ContentType(), kPartitionTypeBFS) == 0) { 484 fsName = "bfs:write_overlay"; 485 readOnly = true; 486 } 487 488 TRACE(("trying to mount boot partition: %s\n", path.Path())); 489 gBootDevice = _kern_mount("/boot", path.Path(), fsName, 0, NULL, 0); 490 if (gBootDevice >= 0) { 491 dprintf("Mounted boot partition: %s\n", path.Path()); 492 gReadOnlyBootDevice = readOnly; 493 break; 494 } 495 } 496 497 if (gBootDevice < B_OK) 498 panic("could not mount boot device!\n"); 499 500 // create link for the name of the boot device 501 502 fs_info info; 503 if (_kern_read_fs_info(gBootDevice, &info) == B_OK) { 504 char path[B_FILE_NAME_LENGTH + 1]; 505 snprintf(path, sizeof(path), "/%s", info.volume_name); 506 507 _kern_create_symlink(-1, path, "/boot", 0); 508 } 509 510 // Do post-boot-volume module initialization. The module code wants to know 511 // whether the module images the boot loader has pre-loaded are the same as 512 // on the boot volume. That is the case when booting from hard disk or CD, 513 // but not via network. 514 int32 bootMethodType = args->boot_volume.GetInt32(BOOT_METHOD, 515 BOOT_METHOD_DEFAULT); 516 bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK 517 || bootMethodType == BOOT_METHOD_CD; 518 module_init_post_boot_device(bootingFromBootLoaderVolume); 519 520 file_cache_init_post_boot_device(); 521 522 // search for other disk systems 523 KDiskDeviceManager *manager = KDiskDeviceManager::Default(); 524 manager->RescanDiskSystems(); 525 manager->StartMonitoring(); 526 } 527 528