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