1 /* 2 * Copyright 2007, Ingo Weinhold, bonefish@cs.tu-berlin.de. 3 * Copyright 2002-2007, 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/system/etc"}, 51 {"/var", "/boot/var"}, 52 {"/tmp", "/boot/var/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 = strcmp(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 switch (disk->bus_type) { 200 case PCI_BUS: 201 case LEGACY_BUS: 202 // TODO: implement this! (and then enable this feature in the boot 203 // loader) 204 // (we need a way to get the device_node of a device, then) 205 break; 206 207 case UNKNOWN_BUS: 208 // nothing to do here 209 break; 210 } 211 212 switch (disk->device_type) { 213 case UNKNOWN_DEVICE: 214 // test if the size of the device matches 215 // (the BIOS might have given us the wrong value here, though) 216 if (strict && device->Size() != disk->device.unknown.size) 217 return false; 218 219 // check if the check sums match, too 220 for (int32 i = 0; i < NUM_DISK_CHECK_SUMS; i++) { 221 if (disk->device.unknown.check_sums[i].offset == -1) 222 continue; 223 224 if (compute_check_sum(device, 225 disk->device.unknown.check_sums[i].offset) 226 != disk->device.unknown.check_sums[i].sum) { 227 return false; 228 } 229 } 230 break; 231 232 case ATA_DEVICE: 233 case ATAPI_DEVICE: 234 case SCSI_DEVICE: 235 case USB_DEVICE: 236 case FIREWIRE_DEVICE: 237 case FIBRE_DEVICE: 238 // TODO: implement me! 239 break; 240 } 241 242 return true; 243 } 244 245 246 bool 247 DiskBootMethod::IsBootPartition(KPartition* partition, bool& foundForSure) 248 { 249 if (!fBootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)) { 250 // the simple case: we can just boot from the selected boot 251 // device 252 if (partition->Offset() 253 == fBootVolume.GetInt64(BOOT_VOLUME_PARTITION_OFFSET, 0)) { 254 foundForSure = true; 255 return true; 256 } 257 } else { 258 // for now, we will just collect all BFS/ISO9660 volumes 259 if (fMethod == BOOT_METHOD_CD 260 && fBootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false) 261 && partition->Type() != NULL 262 && strcmp(partition->Type(), kPartitionTypeDataSession)) { 263 return false; 264 } 265 266 if (partition->ContentType() != NULL 267 && (!strcmp(partition->ContentType(), "Be File System") 268 || !strcmp(partition->ContentType(), "ISO9660 File System"))) { 269 return true; 270 } 271 } 272 273 return false; 274 } 275 276 277 void 278 DiskBootMethod::SortPartitions(KPartition** partitions, int32 count) 279 { 280 qsort(partitions, count, sizeof(KPartition*), 281 fMethod == BOOT_METHOD_CD ? compare_cd_boot : compare_image_boot); 282 } 283 284 285 // #pragma mark - 286 287 288 /*! Make the boot partition (and probably others) available. 289 The partitions that are a boot candidate a put into the /a partitions 290 stack. If the user selected a boot device, there is will only be one 291 entry in this stack; if not, the most likely is put up first. 292 The boot code should then just try them one by one. 293 */ 294 static status_t 295 get_boot_partitions(kernel_args* args, PartitionStack& partitions) 296 { 297 const KMessage& bootVolume = args->boot_volume; 298 299 dprintf("get_boot_partitions(): boot volume message:\n"); 300 bootVolume.Dump(&dprintf); 301 302 // create boot method 303 int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT); 304 dprintf("get_boot_partitions(): boot method type: %ld\n", bootMethodType); 305 306 BootMethod* bootMethod = NULL; 307 switch (bootMethodType) { 308 case BOOT_METHOD_NET: 309 bootMethod = new(nothrow) NetBootMethod(bootVolume, bootMethodType); 310 break; 311 312 case BOOT_METHOD_HARD_DISK: 313 case BOOT_METHOD_CD: 314 default: 315 bootMethod = new(nothrow) DiskBootMethod(bootVolume, 316 bootMethodType); 317 break; 318 } 319 320 status_t status = bootMethod != NULL ? bootMethod->Init() : B_NO_MEMORY; 321 if (status != B_OK) 322 return status; 323 324 KDiskDeviceManager::CreateDefault(); 325 KDiskDeviceManager *manager = KDiskDeviceManager::Default(); 326 327 status = manager->InitialDeviceScan(); 328 if (status != B_OK) { 329 dprintf("KDiskDeviceManager::InitialDeviceScan() failed: %s\n", 330 strerror(status)); 331 return status; 332 } 333 334 if (1 /* dump devices and partitions */) { 335 KDiskDevice *device; 336 int32 cookie = 0; 337 while ((device = manager->NextDevice(&cookie)) != NULL) { 338 device->Dump(true, 0); 339 } 340 } 341 342 struct BootPartitionVisitor : KPartitionVisitor { 343 BootPartitionVisitor(BootMethod* bootMethod, PartitionStack &stack) 344 : fPartitions(stack), 345 fBootMethod(bootMethod) 346 { 347 } 348 349 virtual bool VisitPre(KPartition *partition) 350 { 351 if (!partition->ContainsFileSystem()) 352 return false; 353 354 bool foundForSure = false; 355 if (fBootMethod->IsBootPartition(partition, foundForSure)) 356 fPartitions.Push(partition); 357 358 // if found for sure, we can terminate the search 359 return foundForSure; 360 } 361 362 private: 363 PartitionStack &fPartitions; 364 BootMethod* fBootMethod; 365 } visitor(bootMethod, partitions); 366 367 bool strict = true; 368 369 while (true) { 370 KDiskDevice *device; 371 int32 cookie = 0; 372 while ((device = manager->NextDevice(&cookie)) != NULL) { 373 if (!bootMethod->IsBootDevice(device, strict)) 374 continue; 375 376 if (device->VisitEachDescendant(&visitor) != NULL) 377 break; 378 } 379 380 if (!partitions.IsEmpty() || !strict) 381 break; 382 383 // we couldn't find any potential boot devices, try again less strict 384 strict = false; 385 } 386 387 // sort partition list (e.g.. when booting from CD, CDs should come first in 388 // the list) 389 if (!args->boot_volume.GetBool(BOOT_VOLUME_USER_SELECTED, false)) 390 bootMethod->SortPartitions(partitions.Array(), partitions.CountItems()); 391 392 return B_OK; 393 } 394 395 396 // #pragma mark - 397 398 399 status_t 400 vfs_bootstrap_file_systems(void) 401 { 402 status_t status; 403 404 // bootstrap the root filesystem 405 status = _kern_mount("/", NULL, "rootfs", 0, NULL, 0); 406 if (status < B_OK) 407 panic("error mounting rootfs!\n"); 408 409 _kern_setcwd(-1, "/"); 410 411 // bootstrap the devfs 412 _kern_create_dir(-1, "/dev", 0755); 413 status = _kern_mount("/dev", NULL, "devfs", 0, NULL, 0); 414 if (status < B_OK) 415 panic("error mounting devfs\n"); 416 417 // create directory for the boot volume 418 _kern_create_dir(-1, "/boot", 0755); 419 420 // create some standard links on the rootfs 421 422 for (int32 i = 0; sPredefinedLinks[i].path != NULL; i++) { 423 _kern_create_symlink(-1, sPredefinedLinks[i].path, 424 sPredefinedLinks[i].target, 0); 425 // we don't care if it will succeed or not 426 } 427 428 return B_OK; 429 } 430 431 432 void 433 vfs_mount_boot_file_system(kernel_args* args) 434 { 435 PartitionStack partitions; 436 status_t status = get_boot_partitions(args, partitions); 437 if (status < B_OK) { 438 panic("get_boot_partitions failed!"); 439 } 440 if (partitions.IsEmpty()) { 441 panic("did not find any boot partitions!"); 442 } 443 444 KPartition* bootPartition; 445 while (partitions.Pop(&bootPartition)) { 446 KPath path; 447 if (bootPartition->GetPath(&path) != B_OK) 448 panic("could not get boot device!\n"); 449 450 const char *fsName = NULL; 451 bool readOnly = false; 452 if (strcmp(bootPartition->ContentType(), "ISO9660 File System") == 0) { 453 fsName = "iso9660:write_overlay:attribute_overlay"; 454 readOnly = true; 455 } 456 457 TRACE(("trying to mount boot partition: %s\n", path.Path())); 458 gBootDevice = _kern_mount("/boot", path.Path(), fsName, 0, NULL, 0); 459 if (gBootDevice >= B_OK) { 460 gReadOnlyBootDevice = true; 461 break; 462 } 463 } 464 465 if (gBootDevice < B_OK) 466 panic("could not mount boot device!\n"); 467 468 // create link for the name of the boot device 469 470 fs_info info; 471 if (_kern_read_fs_info(gBootDevice, &info) == B_OK) { 472 char path[B_FILE_NAME_LENGTH + 1]; 473 snprintf(path, sizeof(path), "/%s", info.volume_name); 474 475 _kern_create_symlink(-1, path, "/boot", 0); 476 } 477 478 // Do post-boot-volume module initialization. The module code wants to know 479 // whether the module images the boot loader has pre-loaded are the same as 480 // on the boot volume. That is the case when booting from hard disk or CD, 481 // but not via network. 482 int32 bootMethodType = args->boot_volume.GetInt32(BOOT_METHOD, 483 BOOT_METHOD_DEFAULT); 484 bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK 485 || bootMethodType == BOOT_METHOD_CD; 486 module_init_post_boot_device(bootingFromBootLoaderVolume); 487 488 file_cache_init_post_boot_device(); 489 490 // search for other disk systems 491 KDiskDeviceManager *manager = KDiskDeviceManager::Default(); 492 manager->RescanDiskSystems(); 493 manager->StartMonitoring(); 494 } 495 496