root/src/add-ons/kernel/partitioning_systems/intel/intel.cpp
/*
 * Copyright 2003-2009, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Ingo Weinhold, bonefish@cs.tu-berlin.de
 *              Tomas Kucera, kucerat@centrum.cz
 */

/*!
        \file intel.cpp
        \brief partitioning system module for "intel" style partitions.
*/

// TODO: The implementation is very strict right now. It rejects a partition
// completely, if it finds an error in its partition tables. We should see,
// what error can be handled gracefully, e.g. by ignoring the partition
// descriptor or the whole partition table sector.

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <new>

#include <KernelExport.h>

#include <AutoDeleter.h>
#include <disk_device_manager/ddm_modules.h>

#include "intel.h"
#include "PartitionLocker.h"
#include "PartitionMap.h"
#include "PartitionMapParser.h"


//#define TRACE_INTEL 1
#ifdef _BOOT_MODE
#       include <boot/partitions.h>
#       include <util/kernel_cpp.h>
#       undef TRACE_INTEL
#else
#       include <DiskDeviceTypes.h>
#       include "write_support.h"
#endif

#if TRACE_INTEL
#       define TRACE(x...)              dprintf("intel: " x)
#else
#       define TRACE(x...) ;
#endif


// module names
#define INTEL_PARTITION_MODULE_NAME "partitioning_systems/intel/map/v1"
#define INTEL_EXTENDED_PARTITION_MODULE_NAME \
        "partitioning_systems/intel/extended/v1"


using std::nothrow;


#ifndef _BOOT_MODE

// get_type_for_content_type (for both pm_* and ep_*)
static status_t
get_type_for_content_type(const char* contentType, char* type)
{
        TRACE("get_type_for_content_type(%s)\n", contentType);

        if (!contentType || !type)
                return B_BAD_VALUE;

        PartitionType ptype;
        ptype.SetContentType(contentType);
        if (!ptype.IsValid())
                return B_NAME_NOT_FOUND;

        ptype.GetTypeString(type);
        return B_OK;
}

#endif


// #pragma mark - Intel Partition Map Module


static status_t
pm_std_ops(int32 op, ...)
{
        TRACE("pm_std_ops(0x%" B_PRIx32 ")\n", op);
        switch(op) {
                case B_MODULE_INIT:
                case B_MODULE_UNINIT:
                        return B_OK;
        }
        return B_ERROR;
}


static float
pm_identify_partition(int fd, partition_data* partition, void** cookie)
{
        // check parameters
        if (fd < 0 || !partition || !cookie)
                return -1;

        TRACE("pm_identify_partition(%d, %" B_PRId32 ": %" B_PRId64 ", "
                "%" B_PRId64 ", %" B_PRId32 ")\n", fd, partition->id, partition->offset,
                partition->size, partition->block_size);

        // reject extended partitions
        if (partition->type != NULL
                        && !strcmp(partition->type, kPartitionTypeIntelExtended)) {
                return -1;
        }

        // allocate a PartitionMap
        PartitionMapCookie* map = new(nothrow) PartitionMapCookie;
        if (!map)
                return -1;

        // read the partition structure
        PartitionMapParser parser(fd, 0, partition->size, partition->block_size);
        status_t error = parser.Parse(NULL, map);
        if (error != B_OK) {
                // cleanup, if not detected
                delete map;
                return -1;
        }

        *cookie = map;

        // Depending on whether we actually have recognized child partitions and
        // whether we are installed directly on a device (the by far most common
        // setup), we determine the priority.
        bool hasChildren = (map->CountNonEmptyPartitions() > 0);
        bool hasParent = (get_parent_partition(partition->id) != NULL);

        if (!hasParent) {
                if (hasChildren) {
                        // This value overrides BFS.
                        return 0.81;
                }

                // No children -- might be a freshly initialized disk. But it could
                // also be an image file. So we give BFS a chance to override us.
                return 0.5;
        }

// NOTE: It seems supporting nested partition maps makes more trouble than it
// has useful applications ATM. So it is disabled for the time being.
#if 0
        // We have a parent. That's a very unlikely setup.
        if (hasChildren)
                return 0.4;

        // No children. Extremely unlikely, that this is desired. But if no one
        // else claims the partition, we take it anyway.
        return 0.1;
#endif
        return -1;
}


static status_t
pm_scan_partition(int fd, partition_data* partition, void* cookie)
{
        // check parameters
        if (fd < 0 || !partition || !cookie)
                return B_ERROR;

        TRACE("pm_scan_partition(%d, %" B_PRId32 ": %" B_PRId64 ", "
                "%" B_PRId64 ", %" B_PRId32 ")\n", fd, partition->id, partition->offset,
                partition->size, partition->block_size);

        PartitionMapCookie* map = (PartitionMapCookie*)cookie;
        // fill in the partition_data structure
        partition->status = B_PARTITION_VALID;
        partition->flags |= B_PARTITION_PARTITIONING_SYSTEM;
        partition->content_size = partition->size;
        // (no content_name and content_parameters)
        // (content_type is set by the system)

        partition->content_cookie = map;
        // children
        status_t error = B_OK;
        int32 index = 0;
        for (int32 i = 0; i < 4; i++) {
                PrimaryPartition* primary = map->PrimaryPartitionAt(i);
                if (!primary->IsEmpty()) {
                        partition_data* child = create_child_partition(partition->id,
                                index, partition->offset + primary->Offset(), primary->Size(),
                                -1);
                        index++;
                        if (!child) {
                                // something went wrong
                                error = B_ERROR;
                                break;
                        }

                        child->block_size = partition->block_size;

                        // (no name)
                        char type[B_FILE_NAME_LENGTH];
                        primary->GetTypeString(type);
                        child->type = strdup(type);
                        // parameters
                        char buffer[128];
                        sprintf(buffer, "type = %u ; active = %d", primary->Type(),
                                primary->Active());
                        child->parameters = strdup(buffer);
                        child->cookie = primary;
                        // check for allocation problems
                        if (!child->type || !child->parameters) {
                                error = B_NO_MEMORY;
                                break;
                        }
                }
        }

        // keep map on success or cleanup on error
        if (error == B_OK) {
                atomic_add(&map->ref_count, 1);
        } else {
                partition->content_cookie = NULL;
                for (int32 i = 0; i < partition->child_count; i++) {
                        if (partition_data* child = get_child_partition(partition->id, i))
                                child->cookie = NULL;
                }
        }

        return error;
}


static void
pm_free_identify_partition_cookie(partition_data*/* partition*/, void* cookie)
{
        if (cookie) {
                PartitionMapCookie* map = (PartitionMapCookie*)cookie;
                if (atomic_add(&map->ref_count, -1) == 1)
                        delete map;
        }
}


static void
pm_free_partition_cookie(partition_data* partition)
{
        // called for the primary partitions: the PrimaryPartition is allocated
        // by the partition containing the partition map
        if (partition)
                partition->cookie = NULL;
}


static void
pm_free_partition_content_cookie(partition_data* partition)
{
        if (partition && partition->content_cookie) {
                pm_free_identify_partition_cookie(partition, partition->content_cookie);
                partition->content_cookie = NULL;
        }
}


// #pragma mark - Intel Extended Partition Module


static status_t
ep_std_ops(int32 op, ...)
{
        TRACE("ep_std_ops(0x%" B_PRIx32 ")\n", op);
        switch(op) {
                case B_MODULE_INIT:
                case B_MODULE_UNINIT:
                        return B_OK;
        }
        return B_ERROR;
}


static float
ep_identify_partition(int fd, partition_data* partition, void** cookie)
{
        // check parameters
        if (fd < 0 || !partition || !cookie || !partition->cookie)
                return -1;

        TRACE("ep_identify_partition(%d, %" B_PRId64 ", %" B_PRId64 ", "
                "%" B_PRId32 ")\n", fd, partition->offset, partition->size,
                partition->block_size);

        // our parent must be a intel partition map partition and we must have
        // extended partition type
        if (!partition->type
                || strcmp(partition->type, kPartitionTypeIntelExtended)) {
                return -1;
        }
        partition_data* parent = get_parent_partition(partition->id);
        if (!parent || !parent->content_type
                || strcmp(parent->content_type, kPartitionTypeIntel)) {
                return -1;
        }

        // things seem to be in order
        return 0.95;
}


static status_t
ep_scan_partition(int fd, partition_data* partition, void* cookie)
{
        // check parameters
        if (fd < 0 || !partition || !partition->cookie)
                return B_ERROR;

        TRACE("ep_scan_partition(%d, %" B_PRId64 ", %" B_PRId64 ", "
                "%" B_PRId32 ")\n", fd, partition->offset, partition->size,
                partition->block_size);

        partition_data* parent = get_parent_partition(partition->id);
        if (!parent)
                return B_ERROR;

        PrimaryPartition* primary = (PrimaryPartition*)partition->cookie;
        // fill in the partition_data structure
        partition->status = B_PARTITION_VALID;
        partition->flags |= B_PARTITION_PARTITIONING_SYSTEM;
        partition->content_size = partition->size;
        // (no content_name and content_parameters)
        // (content_type is set by the system)

        partition->content_cookie = primary;
        // children
        status_t error = B_OK;
        int32 index = 0;
        for (int32 i = 0; i < primary->CountLogicalPartitions(); i++) {
                LogicalPartition* logical = primary->LogicalPartitionAt(i);
                partition_data* child = create_child_partition(partition->id, index,
                        parent->offset + logical->Offset(), logical->Size(), -1);
                index++;
                if (!child) {
                        // something went wrong
                        TRACE("ep_scan_partition(): failed to create child partition\n");
                        error = B_ERROR;
                        break;
                }
                child->block_size = partition->block_size;

                // (no name)
                char type[B_FILE_NAME_LENGTH];
                logical->GetTypeString(type);
                child->type = strdup(type);

                // parameters
                char buffer[128];
                sprintf(buffer, "active %s ;\npartition_table_offset %" B_PRId64 " ;\n",
                        logical->Active() ? "true" : "false",
                        logical->PartitionTableOffset());
                child->parameters = strdup(buffer);
                child->cookie = logical;
                // check for allocation problems
                if (!child->type || !child->parameters) {
                        TRACE("ep_scan_partition(): failed to allocation type "
                                "or parameters\n");
                        error = B_NO_MEMORY;
                        break;
                }
        }

        // cleanup on error
        if (error != B_OK) {
                partition->content_cookie = NULL;
                for (int32 i = 0; i < partition->child_count; i++) {
                        if (partition_data* child = get_child_partition(partition->id, i))
                                child->cookie = NULL;
                }
        }
        return error;
}


static void
ep_free_identify_partition_cookie(partition_data* partition, void* cookie)
{
        // nothing to do
}


static void
ep_free_partition_cookie(partition_data* partition)
{
        // the logical partition's cookie belongs to the partition map partition
        if (partition)
                partition->cookie = NULL;
}


static void
ep_free_partition_content_cookie(partition_data* partition)
{
        // the extended partition's cookie belongs to the partition map partition
        if (partition)
                partition->content_cookie = NULL;
}


// #pragma mark - modules


#ifdef _BOOT_MODE
partition_module_info gIntelPartitionMapModule =
#else
static partition_module_info intel_partition_map_module =
#endif
{
        {
                INTEL_PARTITION_MODULE_NAME,
                0,
                pm_std_ops
        },
        "intel",                                                        // short_name
        INTEL_PARTITION_NAME,                           // pretty_name

        // flags
        0
//      | B_DISK_SYSTEM_SUPPORTS_CHECKING
//      | B_DISK_SYSTEM_SUPPORTS_REPAIRING
        | B_DISK_SYSTEM_SUPPORTS_RESIZING
        | B_DISK_SYSTEM_SUPPORTS_MOVING
//      | B_DISK_SYSTEM_SUPPORTS_SETTING_CONTENT_NAME
        | B_DISK_SYSTEM_SUPPORTS_SETTING_CONTENT_PARAMETERS
        | B_DISK_SYSTEM_SUPPORTS_INITIALIZING
//      | B_DISK_SYSTEM_SUPPORTS_CONTENT_NAME

        | B_DISK_SYSTEM_SUPPORTS_RESIZING_CHILD
        | B_DISK_SYSTEM_SUPPORTS_MOVING_CHILD
//      | B_DISK_SYSTEM_SUPPORTS_SETTING_NAME
        | B_DISK_SYSTEM_SUPPORTS_SETTING_TYPE
//      | B_DISK_SYSTEM_SUPPORTS_SETTING_PARAMETERS
        | B_DISK_SYSTEM_SUPPORTS_CREATING_CHILD
        | B_DISK_SYSTEM_SUPPORTS_DELETING_CHILD
//      | B_DISK_SYSTEM_SUPPORTS_NAME
        ,

        // scanning
        pm_identify_partition,                          // identify_partition
        pm_scan_partition,                                      // scan_partition
        pm_free_identify_partition_cookie,      // free_identify_partition_cookie
        pm_free_partition_cookie,                       // free_partition_cookie
        pm_free_partition_content_cookie,       // free_partition_content_cookie

#ifndef _BOOT_MODE
        // querying
        pm_get_supported_operations,            // get_supported_operations
        pm_get_supported_child_operations,      // get_supported_child_operations
        NULL,                                                           // supports_initializing_child
        pm_is_sub_system_for,                           // is_sub_system_for

        pm_validate_resize,                                     // validate_resize
        pm_validate_resize_child,                       // validate_resize_child
        pm_validate_move,                                       // validate_move
        pm_validate_move_child,                         // validate_move_child
        NULL,                                                           // validate_set_name
        NULL,                                                           // validate_set_content_name
        pm_validate_set_type,                           // validate_set_type
        NULL,                                                           // validate_set_parameters
        NULL,                                                           // validate_set_content_parameters
        pm_validate_initialize,                         // validate_initialize
        pm_validate_create_child,                       // validate_create_child
        pm_get_partitionable_spaces,            // get_partitionable_spaces
        pm_get_next_supported_type,                     // get_next_supported_type
        get_type_for_content_type,                      // get_type_for_content_type

        // shadow partition modification
        pm_shadow_changed,                                      // shadow_changed

        // writing
        NULL,                                                           // repair
        pm_resize,                                                      // resize
        pm_resize_child,                                        // resize_child
        pm_move,                                                        // move
        pm_move_child,                                          // move_child
        NULL,                                                           // set_name
        NULL,                                                           // set_content_name
        pm_set_type,                                            // set_type
        pm_set_parameters,                                      // set_parameters
        pm_set_parameters,                                      // set_content_parameters
        pm_initialize,                                          // initialize
        pm_uninitialize,                                        // uninitialize
        pm_create_child,                                        // create_child
        pm_delete_child,                                        // delete_child
#else
        NULL
#endif  // _BOOT_MODE
};


#ifdef _BOOT_MODE
partition_module_info gIntelExtendedPartitionModule =
#else
static partition_module_info intel_extended_partition_module =
#endif
{
        {
                INTEL_EXTENDED_PARTITION_MODULE_NAME,
                0,
                ep_std_ops
        },
        "intel_extended",                                       // short_name
        INTEL_EXTENDED_PARTITION_NAME,          // pretty_name

        // flags
        0
//      | B_DISK_SYSTEM_SUPPORTS_CHECKING
//      | B_DISK_SYSTEM_SUPPORTS_REPAIRING
        | B_DISK_SYSTEM_SUPPORTS_RESIZING
        | B_DISK_SYSTEM_SUPPORTS_MOVING
//      | B_DISK_SYSTEM_SUPPORTS_SETTING_CONTENT_NAME
        | B_DISK_SYSTEM_SUPPORTS_SETTING_CONTENT_PARAMETERS
        | B_DISK_SYSTEM_SUPPORTS_INITIALIZING
//      | B_DISK_SYSTEM_SUPPORTS_CONTENT_NAME

        | B_DISK_SYSTEM_SUPPORTS_RESIZING_CHILD
        | B_DISK_SYSTEM_SUPPORTS_MOVING_CHILD
//      | B_DISK_SYSTEM_SUPPORTS_SETTING_NAME
        | B_DISK_SYSTEM_SUPPORTS_SETTING_TYPE
//      | B_DISK_SYSTEM_SUPPORTS_SETTING_PARAMETERS
        | B_DISK_SYSTEM_SUPPORTS_CREATING_CHILD
        | B_DISK_SYSTEM_SUPPORTS_DELETING_CHILD
//      | B_DISK_SYSTEM_SUPPORTS_NAME
        ,

        // scanning
        ep_identify_partition,                          // identify_partition
        ep_scan_partition,                                      // scan_partition
        ep_free_identify_partition_cookie,      // free_identify_partition_cookie
        ep_free_partition_cookie,                       // free_partition_cookie
        ep_free_partition_content_cookie,       // free_partition_content_cookie

#ifndef _BOOT_MODE
        // querying
        ep_get_supported_operations,            // get_supported_operations
        ep_get_supported_child_operations,      // get_supported_child_operations
        NULL,                                                           // supports_initializing_child
        ep_is_sub_system_for,                           // is_sub_system_for

        ep_validate_resize,                                     // validate_resize
        ep_validate_resize_child,                       // validate_resize_child
        ep_validate_move,                                       // validate_move
        ep_validate_move_child,                         // validate_move_child
        NULL,                                                           // validate_set_name
        NULL,                                                           // validate_set_content_name
        ep_validate_set_type,                           // validate_set_type
        NULL,                                                           // validate_set_parameters
        NULL,                                                           // validate_set_content_parameters
        ep_validate_initialize,                         // validate_initialize
        ep_validate_create_child,                       // validate_create_child
        ep_get_partitionable_spaces,            // get_partitionable_spaces
        ep_get_next_supported_type,                     // get_next_supported_type
        get_type_for_content_type,                      // get_type_for_content_type

        // shadow partition modification
        ep_shadow_changed,                                      // shadow_changed

        // writing
        NULL,                                                           // repair
        ep_resize,                                                      // resize
        ep_resize_child,                                        // resize_child
        ep_move,                                                        // move
        ep_move_child,                                          // move_child
        NULL,                                                           // set_name
        NULL,                                                           // set_content_name
        ep_set_type,                                            // set_type
        NULL,                                                           // set_parameters
        NULL,                                                           // set_content_parameters
        ep_initialize,                                          // initialize
        NULL,                                                           // uninitialize
        ep_create_child,                                        // create_child
        ep_delete_child,                                        // delete_child
#else   // _BOOT_MODE
        NULL
#endif  // _BOOT_MODE
};


#ifndef _BOOT_MODE
extern "C" partition_module_info* modules[];
_EXPORT partition_module_info* modules[] =
{
        &intel_partition_map_module,
        &intel_extended_partition_module,
        NULL
};
#endif