| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/param.h> |
| #include <zircon/assert.h> |
| #include <zircon/device/block.h> |
| #include <zircon/errors.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| #include <chromeos-disk-setup/chromeos-disk-setup.h> |
| #include <gpt/cros.h> |
| #include <gpt/gpt.h> |
| |
| namespace chromeos_disk_setup { |
| using gpt::GptDevice; |
| namespace { |
| |
| constexpr uint8_t kFvmGuid[GPT_GUID_LEN] = GUID_FVM_VALUE; |
| constexpr uint8_t kKernGuid[GPT_GUID_LEN] = GUID_CROS_KERNEL_VALUE; |
| constexpr uint8_t kRootGuid[GPT_GUID_LEN] = GUID_CROS_ROOT_VALUE; |
| constexpr uint8_t kStateGuid[GPT_GUID_LEN] = GUID_CROS_STATE_VALUE; |
| constexpr uint8_t kSysCfgGuid[GPT_GUID_LEN] = GUID_SYS_CONFIG_VALUE; |
| |
| // this value is shared with device-partitioner.cpp |
| constexpr uint64_t kMinFvmSize = 8LU * (1 << 30); |
| |
| constexpr uint64_t kSysCfgSize = 1 << 20; |
| |
| // part_name_eql compares the name field in the given partition to the cstring |
| // name, returning true if the partition name is equal to name and zero-padded. |
| bool part_name_eql(const gpt_partition_t* part, const char* name) { |
| if (part == NULL) { |
| return false; |
| } |
| char buf[GPT_NAME_LEN] = {0}; |
| utf16_to_cstring(&buf[0], (const uint16_t*)part->name, GPT_NAME_LEN / 2); |
| return !strncmp(buf, name, GPT_NAME_LEN); |
| } |
| |
| // part_name_guid_eql returns true if the given partition has the given name and |
| // the given type GUID, false otherwise. |
| bool part_name_guid_eql(const gpt_partition_t* part, const char* name, |
| const uint8_t guid[GPT_GUID_LEN]) { |
| if (part == NULL) { |
| return false; |
| } |
| if (!memcmp(part->type, &guid[0], GPT_GUID_LEN) && part_name_eql(part, name)) { |
| return true; |
| } |
| return false; |
| } |
| |
| // part_size_gte returns true if the partition size is greater than or equal to |
| // the size given, false otherwise. |
| bool part_size_gte(gpt_partition_t* part, uint64_t size, uint64_t block_size) { |
| if (part == NULL) { |
| return false; |
| } |
| uint64_t size_in_blocks = part->last - part->first + 1; |
| return size_in_blocks * block_size >= size; |
| } |
| |
| // find_by_type finds the first partition matching the given type guid. |
| gpt_partition_t* find_by_type(const GptDevice* gpt, const uint8_t type_guid[GPT_GUID_LEN]) { |
| for (uint32_t i = 0; i < gpt::kPartitionCount; ++i) { |
| gpt_partition_t* p = gpt->GetPartition(i); |
| if (p == NULL) { |
| continue; |
| } |
| if (!memcmp(p->type, &type_guid[0], GPT_GUID_LEN)) { |
| return p; |
| } |
| } |
| return NULL; |
| } |
| |
| // find_by_type_and_name finds the first partition matching the given type guid and name. |
| gpt_partition_t* find_by_type_and_name(const GptDevice* gpt, const uint8_t type_guid[GPT_GUID_LEN], |
| const char* name) { |
| for (uint32_t i = 0; i < gpt::kPartitionCount; ++i) { |
| gpt_partition_t* p = gpt->GetPartition(i); |
| if (p == NULL) { |
| continue; |
| } |
| if (part_name_guid_eql(p, name, type_guid)) { |
| return p; |
| } |
| } |
| return NULL; |
| } |
| |
| // Find a contiguous run of free space on the disk at least blocks_req in length. |
| // If space is found, true is returned and out_hole_start and out_hole_end |
| // contain the first free and last free blocks in a contiguous run. |
| bool find_space(GptDevice* gpt, const uint64_t blocks_req, uint64_t* out_hole_start, |
| uint64_t* out_hole_end) { |
| gpt_partition_t* parts[gpt::kPartitionCount] = {0}; |
| for (uint32_t i = 0; i < gpt::kPartitionCount; i++) { |
| parts[i] = gpt->GetPartition(i); |
| } |
| |
| // XXX(raggi): once the lib supports more and less than gpt::kPartitionCount, |
| // this will need to be fixed (as will many loops). |
| gpt_sort_partitions(parts, gpt::kPartitionCount); |
| |
| uint64_t first_usable, last_usable; |
| ZX_ASSERT(gpt->Range(&first_usable, &last_usable) == ZX_OK); |
| |
| uint64_t prev_end = first_usable - 1; |
| for (uint32_t i = 0; i < gpt::kPartitionCount; i++) { |
| if (parts[i] == NULL) { |
| continue; |
| } |
| |
| // TODO(raggi): find out how the tests end up making this state |
| if (parts[i]->first >= last_usable || parts[i]->last >= last_usable) { |
| break; |
| } |
| |
| uint64_t gap = parts[i]->first - (prev_end + 1); |
| if (gap >= blocks_req) { |
| *out_hole_start = prev_end + 1; |
| *out_hole_end = parts[i]->first - 1; |
| |
| return true; |
| } |
| prev_end = parts[i]->last; |
| } |
| |
| if (prev_end < last_usable && last_usable - (prev_end + 1) >= blocks_req) { |
| *out_hole_start = prev_end + 1; |
| *out_hole_end = last_usable; |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // create a GPT entry with the supplied attributes and assign it a random |
| // GUID. May return an error if the GUID can not be generated for the partition |
| // or operations on the gpt_device_t fail. |
| zx_status_t create_gpt_entry(GptDevice* gpt, const uint64_t first, const uint64_t blks, |
| const uint8_t type[GPT_GUID_LEN], const char* name) { |
| uint8_t guid[GPT_GUID_LEN]; |
| zx_cprng_draw(guid, GPT_GUID_LEN); |
| |
| uint8_t tguid[GPT_GUID_LEN]; |
| memcpy(&tguid, type, GPT_GUID_LEN); |
| if (gpt->AddPartition(name, tguid, guid, first, blks, 0) != ZX_OK) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| return ZX_OK; |
| } |
| |
| } // namespace |
| |
| __BEGIN_CDECLS |
| |
| bool is_cros(const GptDevice* gpt) { |
| uint8_t roots = 0; |
| uint8_t kerns = 0; |
| bool state = false; |
| |
| for (uint32_t i = 0; i < gpt::kPartitionCount; ++i) { |
| gpt_partition_t* p = gpt->GetPartition(i); |
| if (p == NULL) { |
| continue; |
| } |
| |
| if (!memcmp(p->type, kRootGuid, GPT_GUID_LEN) && |
| (part_name_eql(p, "ROOT-A") || part_name_eql(p, "ROOT-B"))) { |
| roots++; |
| } else if (!memcmp(p->type, kKernGuid, GPT_GUID_LEN) && |
| (part_name_eql(p, "KERN-A") || part_name_eql(p, "KERN-B"))) { |
| kerns++; |
| } else if (!memcmp(p->type, kStateGuid, GPT_GUID_LEN) && part_name_eql(p, "STATE")) { |
| state = true; |
| } |
| } |
| |
| return state && roots >= 2 && kerns >= 2; |
| } |
| |
| // is_ready_to_pave returns true if there exist partitions for: |
| // * ZIRCON-A is a GUID_CROS_KERNEL_VALUE at least sz_kern in size. |
| // * ZIRCON-B is a GUID_CROS_KERNEL_VALUE at least sz_kern in size. |
| // * ZIRCON-R is a GUID_CROS_KERNEL_VALUE at least sz_kern in size. |
| // * FVM is a GUID_FVM_VALUE at least sz_kern * 8 in size |
| bool is_ready_to_pave(const GptDevice* gpt, const fuchsia_hardware_block_BlockInfo* blk_info, |
| const uint64_t sz_kern) { |
| bool found_zircon_a = false, found_zircon_b = false, found_zircon_r = false; |
| bool found_fvm = false, found_syscfg = false; |
| |
| for (uint32_t i = 0; i < gpt::kPartitionCount; i++) { |
| gpt_partition_t* part = gpt->GetPartition(i); |
| if (part == NULL) { |
| continue; |
| } |
| if (!memcmp(part->type, kFvmGuid, GPT_GUID_LEN)) { |
| if (!part_size_gte(part, kMinFvmSize, blk_info->block_size)) { |
| continue; |
| } |
| found_fvm = true; |
| continue; |
| } |
| if (!memcmp(part->type, kKernGuid, GPT_GUID_LEN)) { |
| if (!part_size_gte(part, sz_kern, blk_info->block_size)) { |
| continue; |
| } |
| if (part_name_eql(part, "ZIRCON-A")) { |
| found_zircon_a = true; |
| } |
| if (part_name_eql(part, "ZIRCON-B")) { |
| found_zircon_b = true; |
| } |
| if (part_name_eql(part, "ZIRCON-R")) { |
| found_zircon_r = true; |
| } |
| } |
| if (!memcmp(part->type, kSysCfgGuid, GPT_GUID_LEN)) { |
| if (!part_size_gte(part, kSysCfgSize, blk_info->block_size)) { |
| continue; |
| } |
| found_syscfg = true; |
| } |
| } |
| if (!found_syscfg) { |
| printf("cros-disk-setup: missing syscfg (or insufficient size)\n"); |
| } |
| if (!found_fvm) { |
| printf("cros-disk-setup: missing FVM (or insufficient size)\n"); |
| } |
| if (!found_zircon_a || !found_zircon_b || !found_zircon_r) { |
| printf("cros-disk-setup: missing one or more kernel partitions\n"); |
| } |
| |
| return found_zircon_a && found_zircon_b && found_zircon_r && found_fvm && found_syscfg; |
| } |
| |
| zx_status_t config_cros_for_fuchsia(GptDevice* gpt, |
| const fuchsia_hardware_block_BlockInfo* blk_info, |
| const uint64_t sz_kern) { |
| // TODO(raggi): this ends up getting called twice, as the canonical user, |
| // the paver, calls is_ready_to_pave itself in order to determine first |
| // whether it will need to sync the gpt. |
| if (is_ready_to_pave(gpt, blk_info, sz_kern)) { |
| return ZX_OK; |
| } |
| |
| // TODO(ZX-1396): The gpt_device_t may not be valid for modification if it |
| // is a newly initialized GPT which has never had gpt_device_finalize or |
| // gpt_device_sync called. |
| if (gpt->Finalize() != ZX_OK) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // Remove the pre-existing Fuchsia partitions as when we were not already |
| // pave-able and we're paving, assume that we want to tend toward a golden |
| // layout. This also avoids any additional complexity that could arise from |
| // intermediate gaps between these partitions. |
| |
| gpt_partition_t* p; |
| if ((p = find_by_type_and_name(gpt, kKernGuid, "ZIRCON-A")) != NULL) { |
| ZX_ASSERT(gpt->RemovePartition(p->guid) == ZX_OK); |
| } |
| if ((p = find_by_type_and_name(gpt, kKernGuid, "ZIRCON-B")) != NULL) { |
| ZX_ASSERT(gpt->RemovePartition(p->guid) == ZX_OK); |
| } |
| if ((p = find_by_type_and_name(gpt, kKernGuid, "ZIRCON-R")) != NULL) { |
| ZX_ASSERT(gpt->RemovePartition(p->guid) == ZX_OK); |
| } |
| if ((p = find_by_type(gpt, kFvmGuid)) != NULL) { |
| ZX_ASSERT(gpt->RemovePartition(p->guid) == ZX_OK); |
| } |
| if ((p = find_by_type_and_name(gpt, kSysCfgGuid, "SYSCFG")) != NULL) { |
| ZX_ASSERT(gpt->RemovePartition(p->guid) == ZX_OK); |
| } |
| |
| // Space is required for 3 kernel partitions and one FVM partition that is |
| // at least 8 kernels in size. |
| uint64_t needed_space = sz_kern * 3 + kMinFvmSize + kSysCfgSize; |
| |
| // see if a contiguous block of space is available for space needed |
| uint64_t blocks_needed = howmany(needed_space, blk_info->block_size); |
| |
| uint64_t hole_start, hole_end; |
| bool found_hole = find_space(gpt, blocks_needed, &hole_start, &hole_end); |
| |
| // TODO(raggi): find a good heuristic to detect "old-paver" behavior, and if |
| // we can detect that, remove the -C's, otherwise leave them alone. |
| |
| // First try removing the kernc and rootc partitions, as they're often a good fit for us: |
| if (!found_hole) { |
| // Some partitions were not large enough. If we found a KERN-C or a ROOT-C, delete them: |
| if ((p = find_by_type_and_name(gpt, kKernGuid, "KERN-C")) != NULL) { |
| ZX_ASSERT(gpt->RemovePartition(p->guid) == ZX_OK); |
| } |
| if ((p = find_by_type_and_name(gpt, kRootGuid, "ROOT-C")) != NULL) { |
| ZX_ASSERT(gpt->RemovePartition(p->guid) == ZX_OK); |
| } |
| |
| found_hole = find_space(gpt, blocks_needed, &hole_start, &hole_end); |
| } |
| |
| // Still not enough contiguous space is available on disk, try shrinking STATE |
| if (!found_hole && (p = find_by_type_and_name(gpt, kStateGuid, "STATE")) != NULL) { |
| uint64_t min_state_sz_blks = howmany(MIN_SZ_STATE, blk_info->block_size); |
| |
| // TODO (TO-607) consider if there is free space on either side of STATE |
| |
| // The STATE partition is expected to be at the end of the GPT in cros, |
| // and can be shrunk in order to make space for use cases such as this. |
| // Here we first try to make the partition half of it's current size |
| // (pinned to a minimum of min_sz_blks). This gives us a roughly equal |
| // share of the free space on the disk. |
| |
| uint64_t state_part_blks = (p->last - p->first) + 1; |
| uint64_t new_state_part_blks = state_part_blks / 2; |
| if (new_state_part_blks < min_state_sz_blks) { |
| new_state_part_blks = min_state_sz_blks; |
| } |
| uint64_t new_free_blks = state_part_blks - new_state_part_blks; |
| |
| if (new_free_blks >= blocks_needed) { |
| p->first = p->first + new_free_blks; |
| |
| // find_space is re-run here as there is often a chunk of free space |
| // left before the STATE partition that is not big enough for us to |
| // fit, but is sensible to use which would not be used simply by |
| // using the state->first offset. |
| found_hole = find_space(gpt, blocks_needed, &hole_start, &hole_end); |
| } |
| } |
| |
| if (!found_hole) { |
| return ZX_ERR_NO_SPACE; |
| } |
| |
| printf("cros-disk-setup: Creating SYSCFG\n"); |
| zx_status_t status; |
| uint64_t sz_syscfg_blks = howmany(kSysCfgSize, blk_info->block_size); |
| if ((status = create_gpt_entry(gpt, hole_start, sz_syscfg_blks, kSysCfgGuid, "SYSCFG")) != |
| ZX_OK) { |
| printf("cros-disk-setup: Error creating SYSCFG: %d\n", status); |
| return ZX_ERR_INTERNAL; |
| } |
| hole_start += sz_syscfg_blks; |
| |
| uint64_t sz_kern_blks = howmany(sz_kern, blk_info->block_size); |
| |
| // Create GPT entries for ZIRCON-A, ZIRCON-B, ZIRCON-R and FVM if needed. |
| const char* kernel_names[3] = {"ZIRCON-A", "ZIRCON-B", "ZIRCON-R"}; |
| for (size_t i = 0; i < 3; ++i) { |
| printf("cros-disk-setup: Creating %s\n", kernel_names[i]); |
| if ((status = create_gpt_entry(gpt, hole_start, sz_kern_blks, kKernGuid, kernel_names[i])) != |
| ZX_OK) { |
| printf("cros-disk-setup: Error creating %s: %d\n", kernel_names[i], status); |
| return ZX_ERR_INTERNAL; |
| } |
| hole_start += sz_kern_blks; |
| } |
| |
| printf("cros-disk-setup: Creating FVM\n"); |
| |
| // TODO(raggi): add this after the test setup supports it. |
| // // clear the FVM superblock to ensure that a new FVM will be created there. |
| // if (gpt_partition_clear(gpt, hole_start, 1) != 0) { |
| // printf("Error clearing FVM superblock.\n"); |
| // return ZX_ERR_INTERNAL; |
| // } |
| |
| // The created FVM partition will fill the available free space. |
| if ((status = create_gpt_entry(gpt, hole_start, (hole_end - hole_start), kFvmGuid, "fvm")) != |
| ZX_OK) { |
| printf("cros-disk-setup: Error creating FVM\n"); |
| return status; |
| } |
| |
| // TODO(raggi): add this once the test setup supports it. |
| // if (!gpt_device_finalize(gpt)) { |
| // printf("Error finalizing GPT\n"); |
| // return ZX_ERR_INTERNAL; |
| // } |
| return ZX_OK; |
| } |
| |
| __END_CDECLS |
| |
| } // namespace chromeos_disk_setup |