#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;
}
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);
}
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);
}
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();
}