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/beos/system"}, 49 {"/bin", "/boot/beos/bin"}, 50 {"/etc", "/boot/beos/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 return true; 264 } 265 } 266 267 return false; 268 } 269 270 271 void 272 DiskBootMethod::SortPartitions(KPartition** partitions, int32 count) 273 { 274 qsort(partitions, count, sizeof(KPartition *), 275 fMethod == BOOT_METHOD_CD ? compare_cd_boot : compare_image_boot); 276 } 277 278 279 // #pragma mark - 280 281 282 /*! Make the boot partition (and probably others) available. 283 The partitions that are a boot candidate a put into the /a partitions 284 stack. If the user selected a boot device, there is will only be one 285 entry in this stack; if not, the most likely is put up first. 286 The boot code should then just try them one by one. 287 */ 288 static status_t 289 get_boot_partitions(kernel_args *args, PartitionStack &partitions) 290 { 291 const KMessage& bootVolume = args->boot_volume; 292 293 dprintf("get_boot_partitions(): boot volume message:\n"); 294 bootVolume.Dump(&dprintf); 295 296 // create boot method 297 int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, 298 BOOT_METHOD_DEFAULT); 299 dprintf("get_boot_partitions(): boot method type: %ld\n", bootMethodType); 300 BootMethod* bootMethod = NULL; 301 switch (bootMethodType) { 302 case BOOT_METHOD_NET: 303 bootMethod = new(nothrow) NetBootMethod(bootVolume, bootMethodType); 304 break; 305 306 case BOOT_METHOD_HARD_DISK: 307 case BOOT_METHOD_CD: 308 default: 309 bootMethod = new(nothrow) DiskBootMethod(bootVolume, 310 bootMethodType); 311 break; 312 } 313 314 status_t status = bootMethod != NULL ? bootMethod->Init() : B_NO_MEMORY; 315 if (status != B_OK) 316 return status; 317 318 KDiskDeviceManager::CreateDefault(); 319 KDiskDeviceManager *manager = KDiskDeviceManager::Default(); 320 321 status = manager->InitialDeviceScan(); 322 if (status != B_OK) { 323 dprintf("KDiskDeviceManager::InitialDeviceScan() failed: %s\n", 324 strerror(status)); 325 return status; 326 } 327 328 if (1 /* dump devices and partitions */) { 329 KDiskDevice *device; 330 int32 cookie = 0; 331 while ((device = manager->NextDevice(&cookie)) != NULL) { 332 device->Dump(true, 0); 333 } 334 } 335 336 struct BootPartitionVisitor : KPartitionVisitor { 337 BootPartitionVisitor(BootMethod* bootMethod, PartitionStack &stack) 338 : fPartitions(stack), 339 fBootMethod(bootMethod) 340 { 341 } 342 343 virtual bool VisitPre(KPartition *partition) 344 { 345 if (!partition->ContainsFileSystem()) 346 return false; 347 348 bool foundForSure = false; 349 if (fBootMethod->IsBootPartition(partition, foundForSure)) 350 fPartitions.Push(partition); 351 352 // if found for sure, we can terminate the search 353 return foundForSure; 354 } 355 356 private: 357 PartitionStack &fPartitions; 358 BootMethod* fBootMethod; 359 } visitor(bootMethod, partitions); 360 361 bool strict = true; 362 363 while (true) { 364 KDiskDevice *device; 365 int32 cookie = 0; 366 while ((device = manager->NextDevice(&cookie)) != NULL) { 367 if (!bootMethod->IsBootDevice(device, strict)) 368 continue; 369 370 if (device->VisitEachDescendant(&visitor) != NULL) 371 break; 372 } 373 374 if (!partitions.IsEmpty() || !strict) 375 break; 376 377 // we couldn't find any potential boot devices, try again less strict 378 strict = false; 379 } 380 381 // sort partition list (e.g.. when booting from CD, CDs should come first in 382 // the list) 383 if (!args->boot_volume.GetBool(BOOT_VOLUME_USER_SELECTED, false)) 384 bootMethod->SortPartitions(partitions.Array(), partitions.CountItems()); 385 386 return B_OK; 387 } 388 389 390 // #pragma mark - 391 392 393 status_t 394 vfs_bootstrap_file_systems(void) 395 { 396 status_t status; 397 398 // bootstrap the root filesystem 399 status = _kern_mount("/", NULL, "rootfs", 0, NULL, 0); 400 if (status < B_OK) 401 panic("error mounting rootfs!\n"); 402 403 _kern_setcwd(-1, "/"); 404 405 // bootstrap the devfs 406 _kern_create_dir(-1, "/dev", 0755); 407 status = _kern_mount("/dev", NULL, "devfs", 0, NULL, 0); 408 if (status < B_OK) 409 panic("error mounting devfs\n"); 410 411 // create directory for the boot volume 412 _kern_create_dir(-1, "/boot", 0755); 413 414 // create some standard links on the rootfs 415 416 for (int32 i = 0; sPredefinedLinks[i].path != NULL; i++) { 417 _kern_create_symlink(-1, sPredefinedLinks[i].path, 418 sPredefinedLinks[i].target, 0); 419 // we don't care if it will succeed or not 420 } 421 422 return B_OK; 423 } 424 425 426 void 427 vfs_mount_boot_file_system(kernel_args *args) 428 { 429 PartitionStack partitions; 430 status_t status = get_boot_partitions(args, partitions); 431 if (status < B_OK) { 432 panic("get_boot_partitions failed!"); 433 } 434 if (partitions.IsEmpty()) { 435 panic("did not find any boot partitions!"); 436 } 437 438 KPartition *bootPartition; 439 while (partitions.Pop(&bootPartition)) { 440 KPath path; 441 if (bootPartition->GetPath(&path) != B_OK) 442 panic("could not get boot device!\n"); 443 444 TRACE(("trying to mount boot partition: %s\n", path.Path())); 445 gBootDevice = _kern_mount("/boot", path.Path(), NULL, 0, NULL, 0); 446 if (gBootDevice >= B_OK) 447 break; 448 } 449 450 if (gBootDevice < B_OK) 451 panic("could not mount boot device!\n"); 452 453 // create link for the name of the boot device 454 455 fs_info info; 456 if (_kern_read_fs_info(gBootDevice, &info) == B_OK) { 457 char path[B_FILE_NAME_LENGTH + 1]; 458 snprintf(path, sizeof(path), "/%s", info.volume_name); 459 460 _kern_create_symlink(-1, path, "/boot", 0); 461 } 462 463 // Do post-boot-volume module initialization. The module code wants to know 464 // whether the module images the boot loader has pre-loaded are the same as 465 // on the boot volume. That is the case when booting from hard disk or CD, 466 // but not via network. 467 int32 bootMethodType = args->boot_volume.GetInt32(BOOT_METHOD, 468 BOOT_METHOD_DEFAULT); 469 bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK 470 || bootMethodType == BOOT_METHOD_CD; 471 module_init_post_boot_device(bootingFromBootLoaderVolume); 472 473 file_cache_init_post_boot_device(); 474 475 // search for other disk systems 476 KDiskDeviceManager *manager = KDiskDeviceManager::Default(); 477 manager->RescanDiskSystems(); 478 manager->StartMonitoring(); 479 } 480 481