| // 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 <chromeos-disk-setup/chromeos-disk-setup.h> |
| #include <gpt/cros.h> |
| #include <gpt/gpt.h> |
| #include <zircon/device/block.h> |
| #include <zircon/errors.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| bool is_cros(const gpt_device_t* gpt) { |
| uint8_t kern_guid[GPT_GUID_LEN] = GUID_CROS_KERNEL_VALUE; |
| uint8_t root_guid[GPT_GUID_LEN] = GUID_CROS_ROOT_VALUE; |
| uint8_t state_guid[GPT_GUID_LEN] = GUID_CROS_STATE_VALUE; |
| gpt_partition_t* const* parts = gpt->partitions; |
| |
| uint8_t roots = 0; |
| uint8_t kerns = 0; |
| bool state = false; |
| char buf[GPT_NAME_LEN / 2 + 1]; |
| |
| for (int i = 0; parts[i] != NULL; ++i) { |
| const gpt_partition_t* p = parts[i]; |
| |
| memset(buf, 0, GPT_NAME_LEN / 2 + 1); |
| utf16_to_cstring(buf, (uint16_t*)p->name, GPT_NAME_LEN / 2); |
| if (!memcmp(p->type, root_guid, GPT_GUID_LEN) && |
| (!memcmp("ROOT-A", buf, 7) || !memcmp("ROOT-B", buf, 7))) { |
| roots++; |
| } else if (!memcmp(p->type, kern_guid, GPT_GUID_LEN) && |
| (!memcmp("KERN-A", buf, 7) || !memcmp("KERN-B", buf, 7))) { |
| kerns++; |
| } else if (!memcmp(p->type, state_guid, GPT_GUID_LEN) && |
| !memcmp("STATE", buf, 6)) { |
| state = true; |
| } |
| } |
| |
| return state && roots == 2 && kerns == 2; |
| } |
| |
| // Attempt to expand the target partition without moving it. |
| // parts - the table of all disk partitions, sorted by start block index |
| // part_idx - partition that we want to expand |
| // sz - the size in bytes the partition wants to be |
| // num_parts - total number of partitions of the disk |
| // disk_info - information about the disk hosting the partitions |
| // |
| // Returns a boolean indicating whether the resize was successful or not. If it |
| // was successful, the partition record is updated, otherwise it is untouched. |
| static bool expand_in_place(gpt_partition_t** parts, const int16_t part_idx, |
| const uint64_t sz, const uint16_t num_parts, |
| const block_info_t* disk_info) { |
| if (part_idx < 0) { |
| return false; |
| } |
| |
| gpt_partition_t* targ = parts[part_idx]; |
| uint64_t new_first = targ->first; |
| uint64_t new_last = targ->last; |
| uint64_t blocks_needed = howmany(sz, disk_info->block_size); |
| blocks_needed -= (targ->last - targ->first + 1); |
| uint64_t prev_part_end; |
| if (part_idx > 0) { |
| prev_part_end = parts[part_idx - 1]->last; |
| } else { |
| prev_part_end = gpt_device_get_size_blocks(disk_info->block_size) - 1; |
| } |
| |
| uint64_t gap = targ->first - prev_part_end - 1; |
| if (gap >= blocks_needed) { |
| new_first -= blocks_needed; |
| blocks_needed = 0; |
| } else { |
| new_first -= gap; |
| blocks_needed -= gap; |
| } |
| |
| uint64_t next_part_start; |
| // see if we can grow the partition at the end |
| if (num_parts > part_idx + 1) { |
| next_part_start = parts[part_idx + 1]->first; |
| } else { |
| next_part_start = disk_info->block_count - |
| gpt_device_get_size_blocks(disk_info->block_size); |
| } |
| |
| gap = next_part_start - parts[part_idx]->last - 1; |
| if (gap >= blocks_needed) { |
| new_last = targ->last + blocks_needed; |
| blocks_needed = 0; |
| } else { |
| new_last = targ->last + gap; |
| blocks_needed -= gap; |
| } |
| |
| // see if the new location of beginning and end of partition |
| // are enough, if so, modify our in-memory GPT, otherwise we |
| // can't expand in-place |
| if ((new_last - new_first + 1) * disk_info->block_size >= sz) { |
| targ->first = new_first; |
| targ->last = new_last; |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| // Find parition indexes based on GUID. If the partition is not found, the |
| // corresponding out-variable will be set to -1. |
| static void find_partitions(gpt_partition_t* const* parts, int16_t* kern_out, |
| int16_t* root_out, int16_t* fvm_out, |
| int16_t* state_out, const uint16_t part_count) { |
| uint8_t fvm_guid[GPT_GUID_LEN] = GUID_FVM_VALUE; |
| uint8_t kern_guid[GPT_GUID_LEN] = GUID_CROS_KERNEL_VALUE; |
| uint8_t root_guid[GPT_GUID_LEN] = GUID_CROS_ROOT_VALUE; |
| uint8_t state_guid[GPT_GUID_LEN] = GUID_CROS_STATE_VALUE; |
| |
| *root_out = -1; |
| *kern_out = -1; |
| *fvm_out = -1; |
| *state_out = -1; |
| char buf[GPT_NAME_LEN / 2 + 1]; |
| |
| for (int i = 0; i < part_count && parts[i] != NULL && |
| (*root_out < 0 || *kern_out < 0 || *fvm_out < 0 || *state_out < 0); |
| ++i) { |
| const gpt_partition_t* part = parts[i]; |
| |
| memset(buf, 0, GPT_NAME_LEN / 2 + 1); |
| utf16_to_cstring(buf, (uint16_t*)part->name, GPT_NAME_LEN / 2); |
| if (!memcmp(part->type, root_guid, GPT_GUID_LEN) && |
| !memcmp("ROOT-C", buf, 7)) { |
| *root_out = i; |
| } else if (!memcmp(part->type, kern_guid, GPT_GUID_LEN) && |
| !memcmp("KERN-C", buf, 7)) { |
| *kern_out = i; |
| } else if (!memcmp(part->type, fvm_guid, GPT_GUID_LEN)) { |
| *fvm_out = i; |
| } else if (!memcmp(part->type, state_guid, GPT_GUID_LEN) && |
| !memcmp("STATE", buf, 6)) { |
| *state_out = i; |
| } |
| } |
| } |
| |
| // Find requested amount of space. Find the lowest block offset that |
| // has that amount of available space. If space is not found, 0 is returned. |
| // 0 is not a valid value since the GPT itself lives in the early part of |
| static uint64_t find_space(gpt_partition_t* const* parts, |
| const uint16_t part_count, const uint64_t blocks_req, |
| const block_info_t* d_info) { |
| uint64_t prev_end = gpt_device_get_size_blocks(d_info->block_size) - 1; |
| for (uint16_t i = 0; i < part_count; i++) { |
| uint64_t gap = parts[i]->first - prev_end - 1; |
| if (gap >= blocks_req) { |
| return prev_end + 1; |
| } |
| prev_end = parts[i]->last; |
| } |
| |
| uint64_t last_usable = |
| d_info->block_count - gpt_device_get_size_blocks(d_info->block_size) - 1; |
| if (prev_end <= last_usable && last_usable - prev_end >= blocks_req) { |
| return prev_end + 1; |
| } |
| |
| return 0; |
| } |
| |
| bool is_ready_to_pave(const gpt_device_t* gptd, const block_info_t* blk_info, |
| const uint64_t sz_kern, const uint64_t sz_root, |
| const bool fvm_req) { |
| |
| bool root = false; |
| bool kern = false; |
| bool fvm = !fvm_req; |
| uint64_t max_blks_state = howmany(MIN_SZ_STATE, blk_info->block_size); |
| |
| int16_t k_idx = -1; |
| int16_t r_idx = -1; |
| int16_t f_idx = -1; |
| int16_t s_idx = -1; |
| uint16_t count = 0; |
| for (; gptd->partitions[count] != NULL; count++) |
| ; |
| |
| find_partitions(gptd->partitions, &k_idx, &r_idx, &f_idx, &s_idx, count); |
| |
| gpt_partition_t* kern_part = NULL; |
| if (k_idx > -1) { |
| kern_part = gptd->partitions[k_idx]; |
| } |
| gpt_partition_t* root_part = NULL; |
| if (r_idx > -1) { |
| root_part = gptd->partitions[r_idx]; |
| } |
| gpt_partition_t* fvm_part = NULL; |
| if (f_idx > -1) { |
| fvm_part = gptd->partitions[f_idx]; |
| } |
| |
| if (kern_part != NULL && |
| (kern_part->last - kern_part->first + 1) * blk_info->block_size >= sz_kern) { |
| kern = true; |
| } |
| |
| if (root_part != NULL && |
| (root_part->last - root_part->first + 1) * blk_info->block_size >= sz_root) { |
| root = true; |
| } |
| |
| if (fvm_part != NULL) { |
| fvm = true; |
| } |
| |
| if (fvm && root && kern) { |
| return true; |
| } |
| |
| if (!root || !kern) { |
| return false; |
| } |
| |
| char buf[GPT_NAME_LEN / 2 + 1]; |
| uint8_t state_guid[GPT_GUID_LEN] = GUID_CROS_STATE_VALUE; |
| // the fvm partition does not exist, make sure the state partition is |
| // as no larger than the allowed size to allow as much space as possible |
| // for fvm. |
| for (uint16_t i = 0; !fvm && gptd->partitions[i] != NULL; i++) { |
| gpt_partition_t* part = gptd->partitions[i]; |
| memset(buf, 0, GPT_NAME_LEN / 2 + 1); |
| utf16_to_cstring(buf, (uint16_t*)part->name, GPT_NAME_LEN / 2); |
| if (!memcmp(part->type, state_guid, GPT_GUID_LEN) && |
| !memcmp("STATE", buf, 6) && |
| part->last - part->first + 1 <= max_blks_state) { |
| fvm = true; |
| } |
| } |
| |
| return root && kern && fvm; |
| } |
| |
| // 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. |
| static zx_status_t create_gpt_entry(gpt_device_t* dev, const uint64_t first, |
| const uint64_t sz, const uint32_t blk_sz, |
| uint8_t* type, const char* name) { |
| uint64_t blks = howmany(sz, blk_sz); |
| uint8_t guid[GPT_GUID_LEN]; |
| zx_cprng_draw(guid, GPT_GUID_LEN); |
| |
| // The gpt_device_t may not be valid for use with gpt_partition_add if |
| // it is a newly initialized GPT which has never had gpt_device_finalize |
| // or gpt_device_sync on, call gpt_device_finalize to be safe. |
| // Remove when ZX-1396 is fixed. |
| if (gpt_device_finalize(dev)) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| if (gpt_partition_add(dev, name, type, guid, first, blks, 0)) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t config_cros_for_fuchsia(gpt_device_t* gpt, |
| const block_info_t* blk_info, |
| const uint64_t sz_kern, |
| const uint64_t sz_root, |
| const bool fvm_req) { |
| uint8_t kern_guid[GPT_GUID_LEN] = GUID_CROS_KERNEL_VALUE; |
| uint8_t root_guid[GPT_GUID_LEN] = GUID_CROS_ROOT_VALUE; |
| |
| if (is_ready_to_pave(gpt, blk_info, sz_kern, sz_root, fvm_req)) { |
| return ZX_OK; |
| } |
| |
| uint64_t sz_state = MIN_SZ_STATE; |
| |
| // mark everything as unknown |
| bool root = false; |
| bool kern = false; |
| bool fvm = !fvm_req; |
| |
| uint16_t num_parts = 0; |
| for (; gpt->partitions[num_parts] != NULL; ++num_parts) |
| ; |
| gpt_partition_t** parts = malloc(num_parts * sizeof(gpt_partition_t*)); |
| if (parts == NULL) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| gpt_sort_partitions(gpt->partitions, parts, num_parts); |
| |
| int16_t kern_idx = -1; |
| int16_t root_idx = -1; |
| int16_t fvm_idx = -1; |
| int16_t state_idx = -1; |
| find_partitions(parts, &kern_idx, &root_idx, &fvm_idx, &state_idx, num_parts); |
| |
| // see which partitions already have an entry |
| gpt_partition_t* kern_part = NULL; |
| if (kern_idx > -1) { |
| kern_part = parts[kern_idx]; |
| } |
| gpt_partition_t* root_part = NULL; |
| if (root_idx > -1) { |
| root_part = parts[root_idx]; |
| } |
| gpt_partition_t* fvm_part = NULL; |
| if (fvm_idx > -1) { |
| fvm_part = parts[fvm_idx]; |
| } |
| |
| // check which existing partitions are large enough as they are |
| if (kern_part != NULL && |
| (kern_part->last - kern_part->first + 1) * blk_info->block_size >= sz_kern) { |
| kern = true; |
| } |
| |
| if (root_part != NULL && |
| (root_part->last - root_part->first + 1) * blk_info->block_size >= sz_root) { |
| root = true; |
| } |
| |
| if (fvm_part != NULL) { |
| fvm = true; |
| } |
| |
| if (fvm && root && kern) { |
| free(parts); |
| return ZX_OK; |
| } |
| |
| // some partitions we're unavailable or not large enough |
| uint64_t needed_space = 0; |
| |
| // see which partitions can be expanded in place and which need to be moved |
| if (!kern) { |
| if (kern_part != NULL && expand_in_place(parts, kern_idx, sz_kern, |
| num_parts, blk_info)) { |
| kern = true; |
| } else { |
| needed_space += sz_kern; |
| } |
| } |
| |
| if (!root) { |
| if (root_part != NULL && expand_in_place(parts, root_idx, sz_root, |
| num_parts, blk_info)) { |
| root = true; |
| } else { |
| needed_space += sz_root; |
| } |
| } |
| |
| // some partitions don't exist or couldn't be expanded in place |
| if (needed_space > 0) { |
| // 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 = find_space(parts, num_parts, blocks_needed, blk_info); |
| |
| // not enough contiguous space is available on disk, try shrinking STATE |
| if (hole == 0 && state_idx > -1) { |
| gpt_partition_t* state_part = parts[state_idx]; |
| uint64_t min_sz_blks = howmany(sz_state, blk_info->block_size); |
| // TODO (TO-607) consider if there is free space on either side of STATE |
| if (state_part->last - state_part->first + 1 >= |
| min_sz_blks + blocks_needed) { |
| hole = state_part->first; |
| state_part->first += blocks_needed; |
| } |
| } |
| |
| // we found or made enough available space |
| if (hole > 0) { |
| // see if we need to create GPT entries for either root-c or kern-c |
| if (kern_part == NULL) { |
| const char* k_name = "KERN-C"; |
| if (create_gpt_entry(gpt, hole, sz_kern, blk_info->block_size, |
| kern_guid, k_name) != ZX_OK) { |
| free(parts); |
| return ZX_ERR_INTERNAL; |
| } |
| kern = true; |
| needed_space -= sz_kern; |
| hole += howmany(sz_kern, blk_info->block_size); |
| } |
| |
| if (root_part == NULL) { |
| const char* r_name = "ROOT-C"; |
| if (create_gpt_entry(gpt, hole, sz_root, blk_info->block_size, |
| root_guid, r_name) != ZX_OK) { |
| free(parts); |
| return ZX_ERR_INTERNAL; |
| } |
| root = true; |
| needed_space -= sz_root; |
| hole += howmany(sz_root, blk_info->block_size); |
| } |
| |
| if (!kern) { |
| kern_part->first = hole; |
| kern_part->last = hole + howmany(sz_kern, blk_info->block_size) |
| - 1; |
| hole = kern_part->last + 1; |
| needed_space -= sz_kern; |
| kern = true; |
| } |
| |
| if (!root) { |
| root_part->first = hole; |
| root_part->last = hole + howmany(sz_root, blk_info->block_size) |
| - 1; |
| hole = root_part->last + 1; |
| needed_space -= sz_root; |
| root = true; |
| } |
| } |
| } |
| |
| if (!fvm && state_idx > -1) { |
| gpt_partition_t* part = parts[state_idx]; |
| uint64_t state_blks = howmany(sz_state, blk_info->block_size); |
| if (part->last - part->first + 1 > state_blks) { |
| part->first = part->last - state_blks + 1; |
| } |
| fvm = true; |
| } |
| |
| free(parts); |
| |
| if (fvm && root && kern) { |
| return ZX_OK; |
| } else { |
| return ZX_ERR_BAD_STATE; |
| } |
| } |