blob: 65f5099f4c32dafc96607f032de39d9ebd58c675 [file] [log] [blame]
// 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;
}
}