* Copyright 2007-2014, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2002-2010, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*
* Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
* Distributed under the terms of the NewOS License.
*/
#include "vfs_boot.h"
#include <stdio.h>
#include <strings.h>
#include <fs_info.h>
#include <OS.h>
#include <boot/kernel_args.h>
#include <directories.h>
#include <disk_device_manager/KDiskDevice.h>
#include <disk_device_manager/KDiskDeviceManager.h>
#include <disk_device_manager/KPartitionVisitor.h>
#include <DiskDeviceTypes.h>
#include <file_cache.h>
#include <fs/KPath.h>
#include <kmodule.h>
#include <syscalls.h>
#include <util/KMessage.h>
#include <util/Stack.h>
#include <vfs.h>
#include "vfs_net_boot.h"
#ifdef TRACE_VFS
# define TRACE(x) dprintf x
#else
# define TRACE(x) ;
#endif
typedef Stack<KPartition *> PartitionStack;
static struct {
const char *path;
const char *target;
} sPredefinedLinks[] = {
{ kGlobalSystemDirectory, kSystemDirectory },
{ kGlobalBinDirectory, kSystemBinDirectory },
{ kGlobalEtcDirectory, kSystemEtcDirectory },
{ kGlobalTempDirectory, kSystemTempDirectory },
{ kGlobalVarDirectory, kSystemVarDirectory },
{ kGlobalPackageLinksDirectory, kSystemPackageLinksDirectory },
{NULL}
};
dev_t gBootDevice = -1;
bool gReadOnlyBootDevice = false;
*/
int
compare_image_boot(const void* _a, const void* _b)
{
KPartition* a = *(KPartition**)_a;
KPartition* b = *(KPartition**)_b;
if (a->ContentName() != NULL) {
if (b->ContentName() == NULL)
return 1;
} else if (b->ContentName() != NULL) {
return -1;
} else
return 0;
int compare = strcasecmp(a->ContentName(), b->ContentName());
if (!compare)
return 0;
if (!strcasecmp(a->ContentName(), "Haiku"))
return 1;
if (!strcasecmp(b->ContentName(), "Haiku"))
return -1;
if (!strncmp(a->ContentName(), "System", 6))
return 1;
if (!strncmp(b->ContentName(), "System", 6))
return -1;
return compare;
}
is no CD, fall back to the standard mechanism (as implemented by
compare_image_boot().
*/
static int
compare_cd_boot(const void* _a, const void* _b)
{
KPartition* a = *(KPartition**)_a;
KPartition* b = *(KPartition**)_b;
bool aIsCD = a->Type() != NULL
&& !strcmp(a->Type(), kPartitionTypeDataSession);
bool bIsCD = b->Type() != NULL
&& !strcmp(b->Type(), kPartitionTypeDataSession);
int compare = (int)aIsCD - (int)bIsCD;
if (compare != 0)
return compare;
return compare_image_boot(_a, _b);
}
The check sum is the sum of all data in that block interpreted as an
array of uint32 values.
Note, this must use the same method as the one used in
boot/platform/bios_ia32/devices.cpp (or similar solutions).
*/
static uint32
compute_check_sum(KDiskDevice* device, off_t offset)
{
char buffer[512];
ssize_t bytesRead = read_pos(device->FD(), offset, buffer, sizeof(buffer));
if (bytesRead < B_OK)
return 0;
if (bytesRead < (ssize_t)sizeof(buffer))
memset(buffer + bytesRead, 0, sizeof(buffer) - bytesRead);
uint32* array = (uint32*)buffer;
uint32 sum = 0;
for (uint32 i = 0;
i < (bytesRead + sizeof(uint32) - 1) / sizeof(uint32); i++) {
sum += array[i];
}
return sum;
}
BootMethod::BootMethod(const KMessage& bootVolume, int32 method)
:
fBootVolume(bootVolume),
fMethod(method)
{
}
BootMethod::~BootMethod()
{
}
status_t
BootMethod::Init()
{
return B_OK;
}
class DiskBootMethod : public BootMethod {
public:
DiskBootMethod(const KMessage& bootVolume, int32 method)
: BootMethod(bootVolume, method)
{
}
virtual bool IsBootDevice(KDiskDevice* device, bool strict);
virtual bool IsBootPartition(KPartition* partition, bool& foundForSure);
virtual void SortPartitions(KPartition** partitions, int32 count);
};
bool
DiskBootMethod::IsBootDevice(KDiskDevice* device, bool strict)
{
disk_identifier* disk;
int32 diskIdentifierSize;
if (fBootVolume.FindData(BOOT_VOLUME_DISK_IDENTIFIER, B_RAW_TYPE,
(const void**)&disk, &diskIdentifierSize) != B_OK) {
dprintf("DiskBootMethod::IsBootDevice(): no disk identifier!\n");
return false;
}
TRACE(("boot device: bus %" B_PRId32 ", device %" B_PRId32 "\n",
disk->bus_type, disk->device_type));
if (fMethod == BOOT_METHOD_CD && !device->IsRemovable())
return false;
switch (disk->bus_type) {
case PCI_BUS:
case LEGACY_BUS:
break;
case UNKNOWN_BUS:
break;
}
switch (disk->device_type) {
case UNKNOWN_DEVICE:
if (strict && device->Size() != disk->device.unknown.size)
return false;
if (fMethod == BOOT_METHOD_CD)
break;
for (int32 i = 0; i < NUM_DISK_CHECK_SUMS; i++) {
if (disk->device.unknown.check_sums[i].offset == -1)
continue;
if (compute_check_sum(device,
disk->device.unknown.check_sums[i].offset)
!= disk->device.unknown.check_sums[i].sum) {
return false;
}
}
break;
case ATA_DEVICE:
case ATAPI_DEVICE:
case SCSI_DEVICE:
case USB_DEVICE:
case FIREWIRE_DEVICE:
case FIBRE_DEVICE:
break;
}
return true;
}
bool
DiskBootMethod::IsBootPartition(KPartition* partition, bool& foundForSure)
{
off_t bootPartitionOffset = fBootVolume.GetInt64(
BOOT_VOLUME_PARTITION_OFFSET, 0);
if (!fBootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)) {
if (partition->Offset() == bootPartitionOffset) {
dprintf("Identified boot partition by partition offset.\n");
foundForSure = true;
return true;
}
} else {
if (fMethod == BOOT_METHOD_CD) {
KDiskDevice* device = partition->Device();
if (IsBootDevice(device, false)
&& bootPartitionOffset == 0 && partition->Parent() == device
&& device->ContentType() != NULL
&& strcmp(device->ContentType(), kPartitionTypeIntel) == 0
&& partition->ContentType() != NULL
&& strcmp(partition->ContentType(), kPartitionTypeBFS) == 0) {
dprintf("Identified anyboot CD.\n");
foundForSure = true;
return true;
}
if (fBootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false)
&& partition->Type() != NULL
&& strcmp(partition->Type(), kPartitionTypeDataSession) != 0) {
return false;
}
}
if (partition->ContentType() != NULL
&& (strcmp(partition->ContentType(), kPartitionTypeBFS) == 0
|| strcmp(partition->ContentType(), kPartitionTypeISO9660) == 0)) {
return true;
}
}
return false;
}
void
DiskBootMethod::SortPartitions(KPartition** partitions, int32 count)
{
qsort(partitions, count, sizeof(KPartition*),
fMethod == BOOT_METHOD_CD ? compare_cd_boot : compare_image_boot);
}
The partitions that are a boot candidate a put into the /a partitions
stack. If the user selected a boot device, there is will only be one
entry in this stack; if not, the most likely is put up first.
The boot code should then just try them one by one.
*/
static status_t
get_boot_partitions(KMessage& bootVolume, PartitionStack& partitions)
{
dprintf("get_boot_partitions(): boot volume message:\n");
bootVolume.Dump(&dprintf);
int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT);
dprintf("get_boot_partitions(): boot method type: %" B_PRId32 "\n",
bootMethodType);
BootMethod* bootMethod = NULL;
switch (bootMethodType) {
case BOOT_METHOD_NET:
bootMethod = new(nothrow) NetBootMethod(bootVolume, bootMethodType);
break;
case BOOT_METHOD_HARD_DISK:
case BOOT_METHOD_CD:
default:
bootMethod = new(nothrow) DiskBootMethod(bootVolume,
bootMethodType);
break;
}
status_t status = bootMethod != NULL ? bootMethod->Init() : B_NO_MEMORY;
if (status != B_OK)
return status;
KDiskDeviceManager::CreateDefault();
KDiskDeviceManager *manager = KDiskDeviceManager::Default();
status = manager->InitialDeviceScan();
if (status != B_OK) {
dprintf("KDiskDeviceManager::InitialDeviceScan() returned error: %s\n",
strerror(status));
}
#if KDEBUG
KDiskDevice *device;
int32 cookie = 0;
while ((device = manager->NextDevice(&cookie)) != NULL) {
device->Dump(true, 0);
}
#endif
struct BootPartitionVisitor : KPartitionVisitor {
BootPartitionVisitor(BootMethod* bootMethod, PartitionStack &stack)
: fPartitions(stack),
fBootMethod(bootMethod)
{
}
virtual bool VisitPre(KPartition *partition)
{
if (!partition->ContainsFileSystem())
return false;
bool foundForSure = false;
if (fBootMethod->IsBootPartition(partition, foundForSure))
fPartitions.Push(partition);
return foundForSure;
}
private:
PartitionStack &fPartitions;
BootMethod* fBootMethod;
} visitor(bootMethod, partitions);
bool strict = true;
while (true) {
KDiskDevice *device;
int32 cookie = 0;
while ((device = manager->NextDevice(&cookie)) != NULL) {
if (!bootMethod->IsBootDevice(device, strict))
continue;
if (device->VisitEachDescendant(&visitor) != NULL)
break;
}
if (!partitions.IsEmpty() || !strict)
break;
strict = false;
}
if (!bootVolume.GetBool(BOOT_VOLUME_USER_SELECTED, false))
bootMethod->SortPartitions(partitions.Array(), partitions.CountItems());
return B_OK;
}
status_t
vfs_bootstrap_file_systems(void)
{
status_t status;
status = _kern_mount("/", NULL, "rootfs", 0, NULL, 0);
if (status < B_OK)
panic("error mounting rootfs!\n");
_kern_setcwd(-1, "/");
_kern_create_dir(-1, "/dev", 0755);
status = _kern_mount("/dev", NULL, "devfs", 0, NULL, 0);
if (status < B_OK)
panic("error mounting devfs\n");
_kern_create_dir(-1, "/boot", 0755);
for (int32 i = 0; sPredefinedLinks[i].path != NULL; i++) {
_kern_create_symlink(-1, sPredefinedLinks[i].path,
sPredefinedLinks[i].target, 0777);
}
return B_OK;
}
void
vfs_mount_boot_file_system(kernel_args* args)
{
KMessage bootVolume;
bootVolume.SetTo(args->boot_volume, args->boot_volume_size);
PartitionStack partitions;
status_t status = get_boot_partitions(bootVolume, partitions);
if (status < B_OK) {
panic("get_boot_partitions failed!");
}
if (partitions.IsEmpty()) {
panic("did not find any boot partitions! @! syslog | tail 15");
}
dev_t bootDevice = -1;
KPartition* bootPartition;
while (partitions.Pop(&bootPartition)) {
KPath path;
if (bootPartition->GetPath(&path) != B_OK)
panic("could not get boot device!\n");
const char* fsName = NULL;
bool readOnly = false;
if (strcmp(bootPartition->ContentType(), kPartitionTypeISO9660) == 0) {
fsName = "iso9660:write_overlay:attribute_overlay";
readOnly = true;
} else if (bootPartition->IsReadOnly()
&& strcmp(bootPartition->ContentType(), kPartitionTypeBFS) == 0) {
fsName = "bfs:write_overlay";
readOnly = true;
}
TRACE(("trying to mount boot partition: %s\n", path.Path()));
bootDevice = _kern_mount("/boot", path.Path(), fsName, 0, NULL, 0);
if (bootDevice >= 0) {
dprintf("Mounted boot partition: %s\n", path.Path());
gReadOnlyBootDevice = readOnly;
break;
}
}
if (bootDevice < B_OK)
panic("could not mount boot device!\n");
fs_info info;
if (_kern_read_fs_info(bootDevice, &info) == B_OK) {
char path[B_FILE_NAME_LENGTH + 1];
snprintf(path, sizeof(path), "/%s", info.volume_name);
_kern_create_symlink(-1, path, "/boot", 0777);
}
struct stat st;
if (bootVolume.GetBool(BOOT_VOLUME_PACKAGED, false)
|| (bootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)
&& lstat(kSystemPackagesDirectory, &st) == 0)) {
static const char* const kPackageFSName = "packagefs";
char arguments[256];
strlcpy(arguments, "packages /boot/system/packages; type system",
sizeof(arguments));
if (const char* stateName
= bootVolume.GetString(BOOT_VOLUME_PACKAGES_STATE, NULL)) {
strlcat(arguments, "; state ", sizeof(arguments));
strlcat(arguments, stateName, sizeof(arguments));
}
dev_t packageMount = _kern_mount("/boot/system", NULL, kPackageFSName,
0, arguments, 0 );
if (packageMount < 0) {
panic("Failed to mount system packagefs: %s",
strerror(packageMount));
}
packageMount = _kern_mount("/boot/home/config", NULL, kPackageFSName, 0,
"packages /boot/home/config/packages; type home",
0 );
if (packageMount < 0) {
dprintf("Failed to mount home packagefs: %s\n",
strerror(packageMount));
}
}
gBootDevice = bootDevice;
int32 bootMethodType = bootVolume.GetInt32(BOOT_METHOD, BOOT_METHOD_DEFAULT);
bool bootingFromBootLoaderVolume = bootMethodType == BOOT_METHOD_HARD_DISK
|| bootMethodType == BOOT_METHOD_CD;
module_init_post_boot_device(bootingFromBootLoaderVolume);
file_cache_init_post_boot_device();
KDiskDeviceManager *manager = KDiskDeviceManager::Default();
manager->RescanDiskSystems();
manager->StartMonitoring();
}