root/src/system/boot/loader/partitions.cpp
/*
 * Copyright 2003-2013, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


#include <boot/partitions.h>

#include <errno.h>
#include <unistd.h>
#include <string.h>

#include <boot/FileMapDisk.h>
#include <boot/platform.h>
#include <boot/stage2.h>
#include <boot/stdio.h>
#include <boot/vfs.h>
#include <ddm_modules.h>

#include "RootFileSystem.h"


using namespace boot;

#define TRACE_PARTITIONS
#ifdef TRACE_PARTITIONS
#       define TRACE(x) dprintf x
#else
#       define TRACE(x) ;
#endif


/* supported partition modules */

static const partition_module_info *sPartitionModules[] = {
#ifdef BOOT_SUPPORT_PARTITION_AMIGA
        &gAmigaPartitionModule,
#endif
#ifdef BOOT_SUPPORT_PARTITION_EFI
        &gEFIPartitionModule,
#endif
#ifdef BOOT_SUPPORT_PARTITION_INTEL
        &gIntelPartitionMapModule,
        &gIntelExtendedPartitionModule,
#endif
#ifdef BOOT_SUPPORT_PARTITION_APPLE
        &gApplePartitionModule,
#endif
};
static const int32 sNumPartitionModules = sizeof(sPartitionModules)
        / sizeof(partition_module_info *);

/* supported file system modules */

static file_system_module_info *sFileSystemModules[] = {
#ifdef BOOT_SUPPORT_FILE_SYSTEM_BFS
        &gBFSFileSystemModule,
#endif
#ifdef BOOT_SUPPORT_FILE_SYSTEM_AMIGA_FFS
        &gAmigaFFSFileSystemModule,
#endif
#ifdef BOOT_SUPPORT_FILE_SYSTEM_FAT
        &gFATFileSystemModule,
#endif
#ifdef BOOT_SUPPORT_FILE_SYSTEM_HFS_PLUS
        &gHFSPlusFileSystemModule,
#endif
#ifdef BOOT_SUPPORT_FILE_SYSTEM_TARFS
        &gTarFileSystemModule,
#endif
};
static const int32 sNumFileSystemModules = sizeof(sFileSystemModules)
        / sizeof(file_system_module_info *);

extern NodeList gPartitions;


namespace boot {

/*! A convenience class to automatically close a
        file descriptor upon deconstruction.
*/
class NodeOpener {
public:
        NodeOpener(Node *node, int mode)
        {
                fFD = open_node(node, mode);
        }

        ~NodeOpener()
        {
                close(fFD);
        }

        int Descriptor() const { return fFD; }

private:
        int             fFD;
};


static int32 sIdCounter = 0;


//      #pragma mark -


Partition::Partition(int fd)
        :
        fParent(NULL),
        fIsFileSystem(false),
        fIsPartitioningSystem(false)
{
        TRACE(("%p Partition::Partition\n", this));

        memset((partition_data *)this, 0, sizeof(partition_data));

        id = atomic_add(&sIdCounter, 1);

        // it's safe to close the file
        fFD = dup(fd);
}


Partition::~Partition()
{
        TRACE(("%p Partition::~Partition\n", this));

        // Tell the children that their parent is gone

        NodeIterator iterator = gPartitions.GetIterator();
        Partition *child;

        while ((child = (Partition *)iterator.Next()) != NULL) {
                if (child->Parent() == this)
                        child->SetParent(NULL);
        }

        close(fFD);
}


Partition *
Partition::Lookup(partition_id id, NodeList *list)
{
        Partition *p;

        if (list == NULL)
                list = &gPartitions;

        NodeIterator iterator = list->GetIterator();

        while ((p = (Partition *)iterator.Next()) != NULL) {
                if (p->id == id)
                        return p;
                if (!p->fChildren.IsEmpty()) {
                        Partition *c = Lookup(id, &p->fChildren);
                        if (c)
                                return c;
                }
        }
        return NULL;
}


void
Partition::SetParent(Partition *parent)
{
        TRACE(("%p Partition::SetParent %p\n", this, parent));
        fParent = parent;
}


Partition *
Partition::Parent() const
{
        //TRACE(("%p Partition::Parent is %p\n", this, fParent));
        return fParent;
}


ssize_t
Partition::ReadAt(void *cookie, off_t position, void *buffer, size_t bufferSize)
{
        if (position > this->size)
                return 0;
        if (position < 0)
                return B_BAD_VALUE;

        if (position + (off_t)bufferSize > this->size)
                bufferSize = this->size - position;

        ssize_t result = read_pos(fFD, this->offset + position, buffer, bufferSize);
        return result < 0 ? errno : result;
}


ssize_t
Partition::WriteAt(void *cookie, off_t position, const void *buffer,
        size_t bufferSize)
{
        if (position > this->size)
                return 0;
        if (position < 0)
                return B_BAD_VALUE;

        if (position + (off_t)bufferSize > this->size)
                bufferSize = this->size - position;

        ssize_t result = write_pos(fFD, this->offset + position, buffer,
                bufferSize);
        return result < 0 ? errno : result;
}


off_t
Partition::Size() const
{
        return size;
}


int32
Partition::Type() const
{
        struct stat stat;
        if (fstat(fFD, &stat) == B_OK)
                return stat.st_mode;

        return Node::Type();
}


Partition *
Partition::AddChild()
{
        Partition *child = new(nothrow) Partition(fFD);
        TRACE(("%p Partition::AddChild %p\n", this, child));
        if (child == NULL)
                return NULL;

        child->SetParent(this);
        child_count++;
        fChildren.Add(child);

        return child;
}


status_t
Partition::_Mount(file_system_module_info *module, Directory **_fileSystem)
{
        TRACE(("%p Partition::_Mount check for file_system: %s\n",
                this, module->pretty_name));

        Directory *fileSystem;
        if (module->get_file_system(this, &fileSystem) == B_OK) {
                gRoot->AddVolume(fileSystem, this);
                if (_fileSystem)
                        *_fileSystem = fileSystem;

                // remember the module that mounted us
                fModuleName = module->module_name;
                this->content_type = module->pretty_name;

                fIsFileSystem = true;

#ifdef BOOT_SUPPORT_FILE_MAP_DISK
                static int fileMapDiskDepth = 0;
                // if we aren't already mounting an image
                if (!fileMapDiskDepth++) {
                        // see if it contains an image file we could mount in turn
                        FileMapDisk *disk = FileMapDisk::FindAnyFileMapDisk(fileSystem);
                        if (disk) {
                                TRACE(("%p Partition::_Mount: found FileMapDisk\n", this));
                                disk->RegisterFileMapBootItem();
                                add_partitions_for(disk, true, false);
                        }
                }
                fileMapDiskDepth--;
#endif

                return B_OK;
        }

        return B_BAD_VALUE;
}


status_t
Partition::Mount(Directory **_fileSystem, bool isBootDevice)
{
        if (isBootDevice && gBootParams.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE,
                        false)) {
                return _Mount(&gTarFileSystemModule, _fileSystem);
        }

        for (int32 i = 0; i < sNumFileSystemModules; i++) {
                status_t status = _Mount(sFileSystemModules[i], _fileSystem);
                if (status == B_OK)
                        return B_OK;
        }

        return B_ENTRY_NOT_FOUND;
}


status_t
Partition::Scan(bool mountFileSystems, bool isBootDevice)
{
        // scan for partitions first (recursively all eventual children as well)

        TRACE(("%p Partition::Scan()\n", this));

        // if we were not booted from the real boot device, we won't scan
        // the device we were booted from (which is likely to be a slow
        // floppy or CD)
        if (isBootDevice && gBootParams.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE,
                        false)) {
                return B_ENTRY_NOT_FOUND;
        }

        const partition_module_info *bestModule = NULL;
        void *bestCookie = NULL;
        float bestPriority = -1;

        for (int32 i = 0; i < sNumPartitionModules; i++) {
                const partition_module_info *module = sPartitionModules[i];
                void *cookie = NULL;
                NodeOpener opener(this, O_RDONLY);

                TRACE(("check for partitioning_system: %s\n", module->pretty_name));

                float priority
                        = module->identify_partition(opener.Descriptor(), this, &cookie);
                if (priority < 0.0)
                        continue;

                TRACE(("  priority: %" B_PRId32 "\n", (int32)(priority * 1000)));
                if (priority <= bestPriority) {
                        // the disk system recognized the partition worse than the currently
                        // best one
                        module->free_identify_partition_cookie(this, cookie);
                        continue;
                }

                // a new winner, replace the previous one
                if (bestModule)
                        bestModule->free_identify_partition_cookie(this, bestCookie);
                bestModule = module;
                bestCookie = cookie;
                bestPriority = priority;
        }

        // find the best FS module
        file_system_module_info *bestFSModule = NULL;
        float bestFSPriority = -1;
        for (int32 i = 0; i < sNumFileSystemModules; i++) {
                if (sFileSystemModules[i]->identify_file_system == NULL)
                        continue;

                float priority = sFileSystemModules[i]->identify_file_system(this);
                if (priority <= 0)
                        continue;

                if (priority > bestFSPriority) {
                        bestFSModule = sFileSystemModules[i];
                        bestFSPriority = priority;
                }
        }

        // now let the best matching disk system scan the partition
        if (bestModule && bestPriority >= bestFSPriority) {
                NodeOpener opener(this, O_RDONLY);
                status_t status = bestModule->scan_partition(opener.Descriptor(), this,
                        bestCookie);
                bestModule->free_identify_partition_cookie(this, bestCookie);

                if (status != B_OK) {
                        dprintf("Partitioning module `%s' recognized the partition, but "
                                "failed to scan it\n", bestModule->pretty_name);
                        return status;
                }

                fIsPartitioningSystem = true;

                content_type = bestModule->pretty_name;
                flags |= B_PARTITION_PARTITIONING_SYSTEM;

                // now that we've found something, check our children
                // out as well!

                NodeIterator iterator = fChildren.GetIterator();
                Partition *child = NULL;

                while ((child = (Partition *)iterator.Next()) != NULL) {
                        TRACE(("%p Partition::Scan(): scan child %p (start = %" B_PRIdOFF
                                ", size = %" B_PRIdOFF ", parent = %p)!\n", this, child,
                                child->offset, child->size, child->Parent()));

                        child->Scan(mountFileSystems);

                        if (!mountFileSystems || child->IsFileSystem()) {
                                // move the partitions containing file systems to the partition
                                // list
                                fChildren.Remove(child);
                                gPartitions.Add(child);
                        }
                }

                // remove all unused children (we keep only file systems)

                while ((child = (Partition *)fChildren.Head()) != NULL) {
                        fChildren.Remove(child);
                        delete child;
                }

                // remember the name of the module that identified us
                fModuleName = bestModule->module.name;

                return B_OK;
        }

        // scan for file systems
        if (mountFileSystems && bestFSModule != NULL)
                return _Mount(bestFSModule, NULL);

        return B_ENTRY_NOT_FOUND;
}

}       // namespace boot


//      #pragma mark -


/*!     Scans the device passed in for partitioning systems. If none are found,
        a partition containing the whole device is created.
        All created partitions are added to the gPartitions list.
*/
status_t
add_partitions_for(int fd, bool mountFileSystems, bool isBootDevice)
{
        TRACE(("add_partitions_for(fd = %d, mountFS = %s)\n", fd,
                mountFileSystems ? "yes" : "no"));

        Partition *partition = new(nothrow) Partition(fd);

        // set some magic/default values
        partition->block_size = 512;

        struct stat stat;
        if (fstat(fd, &stat) == B_OK)
                partition->size = stat.st_size;

        // add this partition to the list of partitions
        // temporarily for Lookup() to work
        gPartitions.Add(partition);

        // keep it, if it contains or might contain a file system
        if ((partition->Scan(mountFileSystems, isBootDevice) == B_OK
                        && partition->IsFileSystem())
                || (!partition->IsPartitioningSystem() && !mountFileSystems)) {
                return B_OK;
        }

        // if not, we no longer need the partition
        gPartitions.Remove(partition);
        delete partition;
        return B_OK;
}


status_t
add_partitions_for(Node *device, bool mountFileSystems, bool isBootDevice)
{
        TRACE(("add_partitions_for(%p, mountFS = %s)\n", device,
                mountFileSystems ? "yes" : "no"));

        int fd = open_node(device, O_RDONLY);
        if (fd < B_OK)
                return fd;

        status_t status = add_partitions_for(fd, mountFileSystems, isBootDevice);
        if (status < B_OK)
                dprintf("add_partitions_for(%d) failed: %" B_PRIx32 "\n", fd, status);

        close(fd);
        return B_OK;
}


partition_data *
create_child_partition(partition_id id, int32 index, off_t offset, off_t size,
        partition_id childID)
{
        Partition *partition = Partition::Lookup(id);
        if (partition == NULL) {
                dprintf("creating partition failed: could not find partition.\n");
                return NULL;
        }

        Partition *child = partition->AddChild();
        if (child == NULL) {
                dprintf("creating partition failed: no memory\n");
                return NULL;
        }

        child->offset = offset;
        child->size = size;

        // we cannot do anything with the child here, because it was not
        // yet initialized by the partition module.
        TRACE(("new child partition!\n"));

        return child;
}


partition_data *
get_child_partition(partition_id id, int32 index)
{
        // TODO: do we really have to implement this?
        //      The intel partition module doesn't really need this for our mission...
        TRACE(("get_child_partition(id = %" B_PRId32 ", index = %" B_PRId32 ")\n",
                id, index));

        return NULL;
}


partition_data *
get_parent_partition(partition_id id)
{
        Partition *partition = Partition::Lookup(id);
        if (partition == NULL) {
                dprintf("could not find parent partition.\n");
                return NULL;
        }
        return partition->Parent();
}