/* * Copyright 2009, Michael Lotz, mmlr@mlotz.ch. All rights reserved. * Copyright 2007-2013, Axel Dörfler, axeld@pinc-software.de. * * Distributed under the terms of the MIT License. */ #include "gpt.h" #include #include #include #ifdef _BOOT_MODE # include #else # include # include "PartitionLocker.h" #endif #include #include #include #include #include #ifndef _BOOT_MODE #include "uuid.h" #endif #include "Header.h" #include "utility.h" #define TRACE_EFI_GPT #ifdef TRACE_EFI_GPT # define TRACE(x) dprintf x #else # define TRACE(x) ; #endif #define EFI_PARTITION_MODULE_NAME "partitioning_systems/efi_gpt/v1" #ifndef _BOOT_MODE static off_t block_align(partition_data* partition, off_t offset, bool upwards) { // Take HDs into account that hide the fact they are using a // block size of 4096 bytes, and round to that. uint32 blockSize = max_c(partition->block_size, 4096); if (upwards) return ((offset + blockSize - 1) / blockSize) * blockSize; return (offset / blockSize) * blockSize; } #endif // !_BOOT_MODE // #pragma mark - public module interface static status_t efi_gpt_std_ops(int32 op, ...) { switch (op) { case B_MODULE_INIT: case B_MODULE_UNINIT: return B_OK; } return B_ERROR; } static float efi_gpt_identify_partition(int fd, partition_data* partition, void** _cookie) { EFI::Header* header = new (std::nothrow) EFI::Header(fd, (partition->size - 1) / partition->block_size, partition->block_size); status_t status = header->InitCheck(); if (status != B_OK) { delete header; return -1; } *_cookie = header; if (header->IsDirty()) { // Either the main or the backup table is missing, it looks like someone // tried to erase the GPT with something else. Let's lower the priority, // so that other partitioning systems which use either only the start or // only the end of the drive, have a chance to run instead. return 0.75; } return 0.96; // This must be higher as Intel partitioning, as EFI can contain this // partitioning for compatibility } static status_t efi_gpt_scan_partition(int fd, partition_data* partition, void* _cookie) { TRACE(("efi_gpt_scan_partition(cookie = %p)\n", _cookie)); EFI::Header* header = (EFI::Header*)_cookie; partition->status = B_PARTITION_VALID; partition->flags |= B_PARTITION_PARTITIONING_SYSTEM; partition->content_size = partition->size; partition->content_cookie = header; // scan all children uint32 index = 0; for (uint32 i = 0; i < header->EntryCount(); i++) { const gpt_partition_entry& entry = header->EntryAt(i); if (entry.partition_type == kEmptyGUID) continue; if (entry.EndBlock() * partition->block_size > (uint64)partition->size) { TRACE(("efi_gpt: child partition exceeds existing space (ends at " "block %" B_PRIu64 ")\n", entry.EndBlock())); continue; } if (entry.StartBlock() * partition->block_size == 0) { TRACE(("efi_gpt: child partition starts at 0 (recursive entry)\n")); continue; } partition_data* child = create_child_partition(partition->id, index++, partition->offset + entry.StartBlock() * partition->block_size, entry.BlockCount() * partition->block_size, -1); if (child == NULL) { TRACE(("efi_gpt: Creating child at index %" B_PRIu32 " failed\n", index - 1)); return B_ERROR; } char name[B_OS_NAME_LENGTH]; to_utf8(entry.name, EFI_PARTITION_NAME_LENGTH, name, sizeof(name)); child->name = strdup(name); child->type = strdup(get_partition_type(entry.partition_type)); child->block_size = partition->block_size; child->cookie = (void*)(addr_t)i; child->content_cookie = header; } return B_OK; } static void efi_gpt_free_identify_partition_cookie(partition_data* partition, void* _cookie) { // Cookie is freed in efi_gpt_free_partition_content_cookie(). } static void efi_gpt_free_partition_content_cookie(partition_data* partition) { delete (EFI::Header*)partition->content_cookie; } #ifndef _BOOT_MODE static uint32 efi_gpt_get_supported_operations(partition_data* partition, uint32 mask) { uint32 flags = B_DISK_SYSTEM_SUPPORTS_INITIALIZING | B_DISK_SYSTEM_SUPPORTS_SETTING_CONTENT_NAME | B_DISK_SYSTEM_SUPPORTS_MOVING | B_DISK_SYSTEM_SUPPORTS_RESIZING | B_DISK_SYSTEM_SUPPORTS_CREATING_CHILD; // TODO: check for available entries and partitionable space and only // add creating child support if both is valid return flags; } static uint32 efi_gpt_get_supported_child_operations(partition_data* partition, partition_data* child, uint32 mask) { return B_DISK_SYSTEM_SUPPORTS_MOVING_CHILD | B_DISK_SYSTEM_SUPPORTS_RESIZING_CHILD | B_DISK_SYSTEM_SUPPORTS_SETTING_TYPE | B_DISK_SYSTEM_SUPPORTS_DELETING_CHILD; } static bool efi_gpt_is_sub_system_for(partition_data* partition) { // a GUID Partition Table doesn't usually live inside another partition return false; } static bool efi_gpt_validate_resize(partition_data* partition, off_t* size) { off_t newSize = *size; if (newSize == partition->size) return true; if (newSize < 0) newSize = 0; else newSize = block_align(partition, newSize, false); // growing if (newSize > partition->size) { *size = newSize; return true; } // shrinking, only so that no child would be truncated off_t newEnd = partition->offset + newSize; for (int32 i = 0; i < partition->child_count; i++) { partition_data* child = get_child_partition(partition->id, i); if (child == NULL) continue; if (child->offset + child->size > newEnd) newEnd = child->offset + child->size; } newSize = block_align(partition, newEnd - partition->offset, true); *size = newSize; return true; } static bool efi_gpt_validate_resize_child(partition_data* partition, partition_data* child, off_t* size) { off_t newSize = *size; if (newSize == child->size) return true; // shrinking if (newSize < child->size) { if (newSize < 0) newSize = 0; *size = block_align(partition, newSize, false); return true; } // growing, but only so much that the child doesn't get bigger than // the parent if (child->offset + newSize > partition->offset + partition->size) newSize = partition->offset + partition->size - child->offset; // make sure that the child doesn't overlap any sibling partitions off_t newEnd = child->offset + newSize; for (int32 i = 0; i < partition->child_count; i++) { partition_data* other = get_child_partition(partition->id, i); if (other == NULL || other->id == child->id || other->offset < child->offset) continue; if (newEnd > other->offset) newEnd = other->offset; } *size = block_align(partition, newEnd - child->offset, false); return true; } static bool efi_gpt_validate_move(partition_data* partition, off_t* start) { // nothing to do return true; } static bool efi_gpt_validate_move_child(partition_data* partition, partition_data* child, off_t* start) { off_t newStart = *start; if (newStart < 0) newStart = 0; if (newStart + child->size > partition->size) newStart = partition->size - child->size; newStart = block_align(partition, newStart, false); if (newStart > child->offset) { for (int32 i = 0; i < partition->child_count; i++) { partition_data* other = get_child_partition(partition->id, i); if (other == NULL || other->id == child->id || other->offset < child->offset) continue; if (other->offset < newStart + child->size) newStart = other->offset - child->size; } newStart = block_align(partition, newStart, false); } else { for (int32 i = 0; i < partition->child_count; i++) { partition_data* other = get_child_partition(partition->id, i); if (other == NULL || other->id == child->id || other->offset > child->offset) continue; if (other->offset + other->size > newStart) newStart = other->offset + other->size; } newStart = block_align(partition, newStart, true); } *start = newStart; return true; } static bool efi_gpt_validate_set_name(partition_data* partition, char* name) { // TODO: should validate that the utf-8 -> ucs-2 is valid // TODO: should count actual utf-8 chars if (strlen(name) > EFI_PARTITION_NAME_LENGTH) name[EFI_PARTITION_NAME_LENGTH - 1] = 0; return true; } static bool efi_gpt_validate_set_type(partition_data* partition, const char* type) { guid_t typeGUID; return get_guid_for_partition_type(type, typeGUID); } static bool efi_gpt_validate_initialize(partition_data* partition, char* name, const char* parameters) { if ((efi_gpt_get_supported_operations(partition, ~0) & B_DISK_SYSTEM_SUPPORTS_INITIALIZING) == 0) return false; // name and parameters are ignored if (name != NULL) name[0] = 0; return true; } static bool efi_gpt_validate_create_child(partition_data* partition, off_t* start, off_t* size, const char* type, const char* name, const char* parameters, int32* index) { if ((efi_gpt_get_supported_operations(partition, ~0) & B_DISK_SYSTEM_SUPPORTS_CREATING_CHILD) == 0) return false; if (!efi_gpt_validate_set_type(partition, type)) return false; EFI::Header* header = (EFI::Header*)partition->content_cookie; int32 entryIndex = -1; for (uint32 i = 0; i < header->EntryCount(); i++) { const gpt_partition_entry& entry = header->EntryAt(i); if (entry.partition_type == kEmptyGUID) { entryIndex = i; break; } } if (entryIndex < 0) return false; *index = entryIndex; // ensure that child lies between first and last usable block off_t firstUsable = header->FirstUsableBlock() * partition->block_size; if (*start < firstUsable) *start = firstUsable; off_t lastUsable = header->LastUsableBlock() * partition->block_size; if (*start + *size > lastUsable) { if (*start > lastUsable) return false; *size = lastUsable - *start; } // ensure that we don't overlap any siblings for (int32 i = 0; i < partition->child_count; i++) { partition_data* other = get_child_partition(partition->id, i); if (other == NULL) continue; if (other->offset < *start && other->offset + other->size > *start) *start = other->offset + other->size; if (other->offset > *start && other->offset < *start + *size) *size = other->offset - *start; } *start = block_align(partition, *start, true); *size = block_align(partition, *size, false); // TODO: support parameters return true; } static status_t efi_gpt_get_partitionable_spaces(partition_data* partition, partitionable_space_data* buffer, int32 count, int32* actualCount) { // TODO: implement return B_ERROR; } static status_t efi_gpt_get_next_supported_type(partition_data* partition, int32* cookie, char* type) { // TODO: implement return B_ERROR; } static status_t efi_gpt_shadow_changed(partition_data* partition, partition_data* child, uint32 operation) { // TODO: implement return B_ERROR; } static status_t efi_gpt_repair(int fd, partition_id partition, bool checkOnly, disk_job_id job) { // TODO: implement, validate CRCs and restore from backup area if corrupt return B_ERROR; } static status_t efi_gpt_resize(int fd, partition_id partitionID, off_t size, disk_job_id job) { if (fd < 0) return B_ERROR; PartitionWriteLocker locker(partitionID); if (!locker.IsLocked()) return B_ERROR; partition_data* partition = get_partition(partitionID); if (partition == NULL) return B_BAD_VALUE; off_t validatedSize = size; if (!efi_gpt_validate_resize(partition, &validatedSize)) return B_BAD_VALUE; update_disk_device_job_progress(job, 0.0); partition->size = validatedSize; partition->content_size = validatedSize; update_disk_device_job_progress(job, 1.0); partition_modified(partitionID); return B_OK; } static status_t efi_gpt_resize_child(int fd, partition_id partitionID, off_t size, disk_job_id job) { if (fd < 0) return B_ERROR; PartitionWriteLocker locker(partitionID); if (!locker.IsLocked()) return B_ERROR; partition_data* child = get_partition(partitionID); if (child == NULL) return B_BAD_VALUE; partition_data* partition = get_parent_partition(partitionID); if (partition == NULL) return B_BAD_VALUE; EFI::Header* header = (EFI::Header*)partition->content_cookie; if (header == NULL) return B_BAD_VALUE; uint32 entryIndex = (uint32)(addr_t)child->cookie; if (entryIndex >= header->EntryCount()) return B_BAD_VALUE; off_t validatedSize = size; if (!efi_gpt_validate_resize_child(partition, child, &validatedSize)) return B_BAD_VALUE; if (child->size == validatedSize) return B_OK; update_disk_device_job_progress(job, 0.0); gpt_partition_entry& entry = header->EntryAt(entryIndex); entry.SetBlockCount(validatedSize / partition->block_size); status_t result = header->WriteEntry(fd, entryIndex); if (result != B_OK) { entry.SetBlockCount(child->size / partition->block_size); return result; } child->size = validatedSize; update_disk_device_job_progress(job, 1.0); partition_modified(partitionID); return B_OK; } static status_t efi_gpt_move(int fd, partition_id partition, off_t offset, disk_job_id job) { // nothing to do here return B_OK; } static status_t efi_gpt_move_child(int fd, partition_id partitionID, partition_id childID, off_t offset, disk_job_id job) { if (fd < 0) return B_ERROR; PartitionWriteLocker locker(partitionID); if (!locker.IsLocked()) return B_ERROR; partition_data* partition = get_partition(partitionID); if (partition == NULL) return B_BAD_VALUE; partition_data* child = get_partition(childID); if (child == NULL) return B_BAD_VALUE; EFI::Header* header = (EFI::Header*)partition->content_cookie; if (header == NULL) return B_BAD_VALUE; uint32 entryIndex = (uint32)(addr_t)child->cookie; if (entryIndex >= header->EntryCount()) return B_BAD_VALUE; off_t validatedOffset = offset; if (!efi_gpt_validate_move_child(partition, child, &validatedOffset)) return B_BAD_VALUE; if (child->offset == validatedOffset) return B_OK; // TODO: implement actual moving, need to move the partition content // (the raw data) here and need to take overlap into account return B_ERROR; update_disk_device_job_progress(job, 0.0); gpt_partition_entry& entry = header->EntryAt(entryIndex); uint64 blockCount = entry.BlockCount(); entry.SetStartBlock((validatedOffset - partition->offset) / partition->block_size); entry.SetBlockCount(blockCount); status_t result = header->WriteEntry(fd, entryIndex); if (result != B_OK) { // fatal error: the data has been moved but the partition table could // not be updated to reflect that change! return result; } child->offset = validatedOffset; update_disk_device_job_progress(job, 1.0); partition_modified(childID); return B_OK; } static status_t efi_gpt_set_name(int fd, partition_id partitionID, const char* name, disk_job_id job) { if (fd < 0) return B_ERROR; PartitionWriteLocker locker(partitionID); if (!locker.IsLocked()) return B_ERROR; partition_data* child = get_partition(partitionID); if (child == NULL) return B_BAD_VALUE; partition_data* partition = get_parent_partition(partitionID); if (partition == NULL) return B_BAD_VALUE; EFI::Header* header = (EFI::Header*)partition->content_cookie; if (header == NULL) return B_BAD_VALUE; uint32 entryIndex = (uint32)(addr_t)child->cookie; if (entryIndex >= header->EntryCount()) return B_BAD_VALUE; update_disk_device_job_progress(job, 0.0); gpt_partition_entry& entry = header->EntryAt(entryIndex); to_ucs2(name, strlen(name), entry.name, EFI_PARTITION_NAME_LENGTH); status_t result = header->WriteEntry(fd, entryIndex); if (result != B_OK) return result; char newName[B_OS_NAME_LENGTH]; to_utf8(entry.name, EFI_PARTITION_NAME_LENGTH, newName, sizeof(newName)); child->name = strdup(newName); update_disk_device_job_progress(job, 1.0); partition_modified(partitionID); return B_OK; } static status_t efi_gpt_set_type(int fd, partition_id partitionID, const char* type, disk_job_id job) { if (fd < 0) return B_ERROR; PartitionWriteLocker locker(partitionID); if (!locker.IsLocked()) return B_ERROR; partition_data* child = get_partition(partitionID); if (child == NULL) return B_BAD_VALUE; partition_data* partition = get_parent_partition(partitionID); if (partition == NULL) return B_BAD_VALUE; EFI::Header* header = (EFI::Header*)partition->content_cookie; if (header == NULL) return B_BAD_VALUE; uint32 entryIndex = (uint32)(addr_t)child->cookie; if (entryIndex >= header->EntryCount()) return B_BAD_VALUE; guid_t typeGUID; if (!get_guid_for_partition_type(type, typeGUID)) return B_BAD_VALUE; update_disk_device_job_progress(job, 0.0); gpt_partition_entry& entry = header->EntryAt(entryIndex); entry.partition_type = typeGUID; status_t result = header->WriteEntry(fd, entryIndex); if (result != B_OK) return result; child->type = strdup(type); update_disk_device_job_progress(job, 1.0); partition_modified(partitionID); return B_OK; } static status_t efi_gpt_initialize(int fd, partition_id partitionID, const char* name, const char* parameters, off_t partitionSize, disk_job_id job) { if (fd < 0) return B_ERROR; partition_data* partition = get_partition(partitionID); if (partition == NULL) return B_BAD_VALUE; update_disk_device_job_progress(job, 0.0); EFI::Header header((partitionSize - 1) / partition->block_size, partition->block_size); status_t result = header.InitCheck(); if (result != B_OK) return result; result = header.Write(fd); if (result != B_OK) return result; result = scan_partition(partitionID); if (result != B_OK) return result; update_disk_device_job_progress(job, 1.0); partition_modified(partitionID); return B_OK; } static status_t efi_gpt_uninitialize(int fd, partition_id partitionID, off_t partitionSize, uint32 blockSize, disk_job_id job) { if (fd < 0) return B_ERROR; partition_data* partition = get_partition(partitionID); if (partition == NULL) return B_BAD_VALUE; update_disk_device_job_progress(job, 0.0); const int headerSize = partition->block_size * 3; // The first block is the protective MBR // The second block is the GPT header // The third block is the start of the partition list (it can span more // blocks, but that doesn't matter as soon as the header is erased). uint8 buffer[headerSize]; memset(buffer, 0xE5, sizeof(buffer)); // Erase the first blocks if (write_pos(fd, 0, &buffer, headerSize) < 0) return errno; // Erase the last blocks // Only 2 blocks, as there is no protective MBR if (write_pos(fd, partitionSize - 2 * partition->block_size, &buffer, 2 * partition->block_size) < 0) { return errno; } update_disk_device_job_progress(job, 1.0); return B_OK; } static status_t efi_gpt_create_child(int fd, partition_id partitionID, off_t offset, off_t size, const char* type, const char* name, const char* parameters, disk_job_id job, partition_id* childID) { if (fd < 0) return B_ERROR; PartitionWriteLocker locker(partitionID); if (!locker.IsLocked()) return B_ERROR; partition_data* partition = get_partition(partitionID); if (partition == NULL) return B_BAD_VALUE; EFI::Header* header = (EFI::Header*)partition->content_cookie; if (header == NULL) return B_BAD_VALUE; off_t validatedOffset = offset; off_t validatedSize = size; uint32 entryIndex = 0; if (!efi_gpt_validate_create_child(partition, &validatedOffset, &validatedSize, type, name, parameters, (int32*)&entryIndex)) return B_BAD_VALUE; guid_t typeGUID; if (!get_guid_for_partition_type(type, typeGUID)) return B_BAD_VALUE; update_disk_device_job_progress(job, 0.0); partition_data* child = create_child_partition(partition->id, entryIndex, validatedOffset, validatedSize, *childID); if (child == NULL) return B_ERROR; gpt_partition_entry& entry = header->EntryAt(entryIndex); entry.partition_type = typeGUID; uuid_t uuid; uuid_generate_random(uuid); memcpy((uint8*)&entry.unique_guid, uuid, sizeof(guid_t)); to_ucs2(name, strlen(name), entry.name, EFI_PARTITION_NAME_LENGTH); entry.SetStartBlock((validatedOffset - partition->offset) / partition->block_size); entry.SetBlockCount(validatedSize / partition->block_size); entry.SetAttributes(0); // TODO status_t result = header->WriteEntry(fd, entryIndex); if (result != B_OK) { delete_partition(child->id); return result; } *childID = child->id; child->block_size = partition->block_size; child->name = strdup(name); child->type = strdup(type); child->parameters = strdup(parameters); child->cookie = (void*)(addr_t)entryIndex; if (child->type == NULL || child->parameters == NULL) { delete_partition(child->id); return B_NO_MEMORY; } update_disk_device_job_progress(job, 1.0); partition_modified(partitionID); return B_OK; } static status_t efi_gpt_delete_child(int fd, partition_id partitionID, partition_id childID, disk_job_id job) { if (fd < 0) return B_ERROR; PartitionWriteLocker locker(partitionID); if (!locker.IsLocked()) return B_ERROR; partition_data* partition = get_partition(partitionID); if (partition == NULL) return B_BAD_VALUE; partition_data* child = get_partition(childID); if (child == NULL) return B_BAD_VALUE; EFI::Header* header = (EFI::Header*)partition->content_cookie; if (header == NULL) return B_BAD_VALUE; uint32 entryIndex = (uint32)(addr_t)child->cookie; if (entryIndex >= header->EntryCount()) return B_BAD_VALUE; update_disk_device_job_progress(job, 0.0); if (!delete_partition(childID)) return B_ERROR; gpt_partition_entry& entry = header->EntryAt(entryIndex); memset(&entry, 0, sizeof(gpt_partition_entry)); entry.partition_type = kEmptyGUID; status_t result = header->WriteEntry(fd, entryIndex); if (result != B_OK) return result; update_disk_device_job_progress(job, 1.0); partition_modified(partitionID); return B_OK; } #endif // !_BOOT_MODE #ifndef _BOOT_MODE static partition_module_info sEFIPartitionModule = { #else partition_module_info gEFIPartitionModule = { #endif { EFI_PARTITION_MODULE_NAME, 0, efi_gpt_std_ops }, "gpt", // short_name EFI_PARTITION_NAME, // pretty_name 0 // flags | B_DISK_SYSTEM_SUPPORTS_INITIALIZING | B_DISK_SYSTEM_SUPPORTS_MOVING | B_DISK_SYSTEM_SUPPORTS_RESIZING | B_DISK_SYSTEM_SUPPORTS_SETTING_TYPE | B_DISK_SYSTEM_SUPPORTS_MOVING_CHILD | B_DISK_SYSTEM_SUPPORTS_RESIZING_CHILD | B_DISK_SYSTEM_SUPPORTS_CREATING_CHILD | B_DISK_SYSTEM_SUPPORTS_DELETING_CHILD | B_DISK_SYSTEM_SUPPORTS_SETTING_NAME | B_DISK_SYSTEM_SUPPORTS_NAME , // scanning efi_gpt_identify_partition, efi_gpt_scan_partition, efi_gpt_free_identify_partition_cookie, NULL, // free_partition_cookie efi_gpt_free_partition_content_cookie, #ifndef _BOOT_MODE // querying efi_gpt_get_supported_operations, efi_gpt_get_supported_child_operations, NULL, // supports_initializing_child efi_gpt_is_sub_system_for, efi_gpt_validate_resize, efi_gpt_validate_resize_child, efi_gpt_validate_move, efi_gpt_validate_move_child, efi_gpt_validate_set_name, NULL, // validate_set_content_name efi_gpt_validate_set_type, NULL, // validate_set_parameters NULL, // validate_set_content_parameters efi_gpt_validate_initialize, efi_gpt_validate_create_child, efi_gpt_get_partitionable_spaces, efi_gpt_get_next_supported_type, NULL, // get_type_for_content_type // shadow partition modification efi_gpt_shadow_changed, // writing efi_gpt_repair, efi_gpt_resize, efi_gpt_resize_child, efi_gpt_move, efi_gpt_move_child, efi_gpt_set_name, NULL, // set_content_name efi_gpt_set_type, NULL, // set_parameters NULL, // set_content_parameters efi_gpt_initialize, efi_gpt_uninitialize, efi_gpt_create_child, efi_gpt_delete_child #else NULL #endif // _BOOT_MODE }; #ifndef _BOOT_MODE partition_module_info* modules[] = { &sEFIPartitionModule, NULL }; #endif