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