blob: 69326169eb3ad5715fe5fbb1ad68f4054e4bfd32 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// User of this source code is governed by a BSD-style license that be be found
// in the LICENSE file.
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <zircon/device/block.h>
#include <zircon/syscalls.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "include/installer/installer.h"
/*
* Given a pointer to an array of gpt_partition_t structs, look for a partition
* with the matching type GUID. The table_size argument tells this function how
* many entries there are in the array.
* Returns
* * ZX_ERR_INVALID_ARGS if any pointers are null
* * ZX_ERR_BAD_STATE if the GPT data reports itself as invalid
* * ZX_ERR_NOT_FOUND if the requested partition is not present
* * ZX_OK if the partition is found, in which case the index is assigned
* to part_id_out
*/
zx_status_t find_partition_entries(gpt_partition_t **gpt_table,
const uint8_t (*guid)[GPT_GUID_LEN],
uint16_t table_size, uint16_t *part_id_out) {
assert(gpt_table != NULL);
assert(guid != NULL);
assert(part_id_out != NULL);
for (uint16_t idx = 0; idx < table_size && gpt_table[idx] != NULL; idx++) {
uint8_t *type_ptr = gpt_table[idx]->type;
if (!memcmp(type_ptr, guid, 16)) {
*part_id_out = idx;
return ZX_OK;
}
}
return ZX_ERR_NOT_FOUND;
}
/*
* For the given partition, see if it is at least as large as min_size.
*/
bool check_partition_size(const gpt_partition_t *partition,
const uint64_t min_size, const uint64_t block_size,
const char *partition_name) {
assert(partition != NULL);
assert(partition->last >= partition->first);
assert(partition->name != NULL);
uint64_t block_count = partition->last - partition->first + 1;
uint64_t partition_size =
block_size * (partition->last - partition->first + 1);
printf("%s has %" PRIu64 " blocks and block size of %" PRIu64 "\n",
partition_name, block_count, block_size);
if (partition_size < min_size) {
fprintf(stderr, "%s partition too small, found %" PRIu64 ", but require \
%" PRIu64 "\n",
partition_name, partition_size, min_size);
return false;
} else {
return true;
}
}
/*
* Given an array of GPT partition entries in gpt_table and a partition guid in
* part_guid, validate that the partition is in the array. Further, validate
* that the entry reports that the number of blocks in the partition multiplied
* by the provided block_size meets the min_size. If more than one partition in
* the array passes this test, the first match will be provided. The length of
* the partition information array should be passed in table_size.
*
* If ZX_OK is returned, part_index_out will contain the index in the GPT for
* the requested partition. Additionally, the pointer at *part_info_out will
* point at the gpt_partition_t with information for the requested partition.
*/
zx_status_t find_partition(gpt_partition_t **gpt_table,
const uint8_t (*part_guid)[GPT_GUID_LEN],
uint64_t min_size, uint64_t block_size,
const char *part_name, uint16_t table_size,
uint16_t *part_index_out,
gpt_partition_t **part_info_out) {
// if we find a partition, but it is the wrong size, we want to keep
// looking down the table for something that might match, this offset
// tracks our overall progress in the table
uint16_t part_offset = 0;
zx_status_t rc = ZX_OK;
while (rc == ZX_OK) {
// reduce table_size by the number of entries we've examined
rc = find_partition_entries(gpt_table, part_guid, table_size - part_offset,
part_index_out);
gpt_partition_t *partition;
switch (rc) {
case ZX_ERR_NOT_FOUND:
fprintf(stderr, "No %s partition found.\n", part_name);
break;
case ZX_ERR_INVALID_ARGS:
fprintf(stderr, "Arguments are invalid for %s partition.\n", part_name);
break;
case ZX_ERR_BAD_STATE:
printf("GPT descriptor is invalid.\n");
break;
case ZX_OK:
partition = gpt_table[*part_index_out];
assert(partition->last >= partition->first);
if (check_partition_size(partition, min_size, block_size, part_name)) {
*part_info_out = partition;
// adjust the output index by part_offset
*part_index_out = part_offset + *part_index_out;
return ZX_OK;
} else {
// if the size doesn't check out, keep looking for
// partitions later in the table
uint16_t jump_size = *part_index_out + 1;
// advance the pointer
gpt_table += jump_size;
part_offset += jump_size;
}
break;
default:
fprintf(stderr, "Unrecognized error finding efi parition: %d\n", rc);
break;
}
}
// we didn't find a suitable partition
return ZX_ERR_NOT_FOUND;
}
static int compare(const void *ls, const void *rs) {
return (int)(((part_tuple_t *)ls)->first - ((part_tuple_t *)rs)->first);
}
/*
* Sort an array of gpt_partition_t pointers based on the values of
* gpt_partition_t->first. The returned value will contain an array of pointers
* to partitions in sorted order. This array was allocated on the heap and
* should be freed at some point.
*/
gpt_partition_t **sort_partitions(gpt_partition_t **parts, uint16_t count) {
gpt_partition_t **sorted_parts = malloc(count * sizeof(gpt_partition_t *));
if (sorted_parts == NULL) {
fprintf(stderr, "Unable to sort partitions, out of memory.\n");
return NULL;
}
part_tuple_t *sort_tuples = malloc(count * sizeof(part_tuple_t));
if (sort_tuples == NULL) {
fprintf(stderr, "Unable to sort partitions, out of memory.\n");
free(sorted_parts);
return NULL;
}
for (uint16_t idx = 0; idx < count; idx++, sort_tuples++) {
sort_tuples->index = idx;
sort_tuples->first = parts[idx]->first;
}
sort_tuples -= count;
qsort(sort_tuples, count, sizeof(part_tuple_t), compare);
// create a sorted array of pointers
for (uint16_t idx = 0; idx < count; idx++, sort_tuples++) {
sorted_parts[idx] = parts[sort_tuples->index];
}
sort_tuples -= count;
free(sort_tuples);
return sorted_parts;
}
/*
* Attempt to find an unallocated portion of the specified device that is at
* least blocks_req in size. block_count should contain the total number of
* blocks on the disk. The offset in blocks of the allocation hole will be
* returned or 0 if no space is available. If there is available space, but
* no region is as large as requested, the next largest unallocated region
* will be returned. Callers should check the result_out to see what is found.
*/
void find_available_space(gpt_device_t *device, size_t blocks_req,
size_t block_count, size_t block_size,
part_location_t *result_out) {
assert(result_out != NULL);
assert(device != NULL);
gpt_partition_t **sorted_parts;
// 17K is reserved at the front and back of the disk for the protected MBR
// and the GPT. The front is the primary of these and the back is the backup
const uint32_t blocks_resrvd = SIZE_RESERVED / block_size;
result_out->blk_offset = 0;
result_out->blk_len = 0;
// if the device has no partitions, we can add one after the reserved
// section at the front
if (device->partitions[0] == NULL) {
result_out->blk_len = block_count - blocks_resrvd * 2;
result_out->blk_offset = blocks_resrvd;
return;
}
// check if the GPT is sorted by partition position
bool sorted = true;
size_t count = 0;
for (count = 1;
count < PARTITIONS_COUNT && device->partitions[count] != NULL && sorted;
count++) {
sorted =
device->partitions[count - 1]->first < device->partitions[count]->first;
}
if (!sorted) {
// count the number of valid partitions because in this case we would
// have bailed out of counting early
for (count = 0; device->partitions[count] != NULL; count++)
;
sorted_parts = sort_partitions(device->partitions, count);
if (sorted_parts == NULL) {
printf("Sort failure\n");
return;
}
} else {
sorted_parts = device->partitions;
}
// check to see if we have space at the beginning of the disk
size_t gap = sorted_parts[0]->first - blocks_resrvd;
if (result_out->blk_len < gap) {
result_out->blk_offset = blocks_resrvd;
result_out->blk_len = gap;
}
if (result_out->blk_len >= blocks_req) {
if (!sorted) {
free(sorted_parts);
}
return;
}
// check if there is space between partitions
for (size_t idx = 1; idx < count; idx++) {
gap = sorted_parts[idx]->first - sorted_parts[idx - 1]->last - 1;
if (result_out->blk_len < gap) {
result_out->blk_offset = sorted_parts[idx - 1]->last + 1;
result_out->blk_len = gap;
} else {
continue;
}
if (result_out->blk_len >= blocks_req) {
result_out->blk_offset = sorted_parts[idx - 1]->last + 1;
if (!sorted) {
free(sorted_parts);
}
return;
}
}
if (sorted_parts[count - 1]->last > block_count) {
printf("WARNING: last partition extends beyond end of disk.\n");
}
if (sorted_parts[count - 1]->last + blocks_resrvd + 1 >= block_count) {
gap = 0;
} else {
gap = block_count - sorted_parts[count - 1]->last - blocks_resrvd - 1;
}
if (result_out->blk_len < gap) {
result_out->blk_len = gap;
result_out->blk_offset = sorted_parts[count - 1]->last + 1;
if (!sorted) {
free(sorted_parts);
}
}
if (!sorted) {
free(sorted_parts);
}
return;
}
ssize_t get_next_file_path(DIR *dfd, size_t max_name_len, char *name_out) {
assert(name_out != NULL);
struct dirent *entry;
// skip and '.' or '..' entries
while ((entry = readdir(dfd)) != NULL &&
(!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")))
;
if (entry == NULL) {
return -1;
} else {
size_t len_d_name = strlen(entry->d_name);
ssize_t overrun = len_d_name - max_name_len + 1;
if (overrun > 0) {
assert(overrun <= SSIZE_MAX);
return overrun;
} else {
// copy the string, including the null terminator
memcpy(name_out, entry->d_name, len_d_name + 1);
return 0;
}
}
}
/*
* Attempt to read a GPT from the file descriptor. If successful the returned
* pointer points to the populated gpt_device_t struct, otherwise NULL is
* returned.
*/
gpt_device_t *read_gpt(int fd, uint64_t *blocksize_out) {
assert(blocksize_out != NULL);
block_info_t info;
ssize_t rc = ioctl_block_get_info(fd, &info);
if (rc < 0) {
fprintf(stderr, "error getting block info, ioctl result code: %zd\n", rc);
return NULL;
}
*blocksize_out = info.block_size;
if (*blocksize_out < 1) {
fprintf(stderr, "Device reports block size of %" PRIu64 ", abort!\n",
*blocksize_out);
return NULL;
}
gpt_device_t *gpt;
// gpt_device_init produces output we want to suppress, so kidnap stdout
// TODO(jmatt) Remove this hyjinx when lib-gpt stops writing to stdout
int backup = dup(1);
close(1);
rc = gpt_device_init(fd, info.block_size, info.block_count, &gpt);
// restore stdout
fflush(stdout);
dup2(backup, 1);
close(backup);
if (rc < 0) {
fprintf(stderr, "error reading GPT, result code: %zd \n", rc);
return NULL;
} else if (!gpt->valid) {
return NULL;
}
return gpt;
}
/*
* Attempt to open the given path. If successful, returns the file descriptor
* associated with the opened path in read only mode.
*/
int open_device_ro(const char *dev_path) {
int fd = open(dev_path, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Could not read device at %s, open reported error:%s\n",
dev_path, strerror(errno));
}
return fd;
}
zx_status_t find_disk_by_guid(DIR *dir, const char *dir_path,
uint8_t (*disk_guid)[GPT_GUID_LEN],
gpt_device_t **install_dev_out,
char *disk_path_out, ssize_t max_len) {
strcpy(disk_path_out, dir_path);
const size_t base_len = strlen(disk_path_out);
const size_t buffer_remaining = max_len - base_len - 1;
uint64_t block_size;
gpt_device_t *install_dev = NULL;
uint8_t guid_targ[GPT_GUID_LEN];
*install_dev_out = NULL;
for (ssize_t rc =
get_next_file_path(dir, buffer_remaining, &disk_path_out[base_len]);
rc >= 0; rc = get_next_file_path(dir, buffer_remaining,
&disk_path_out[base_len])) {
if (rc > 0) {
fprintf(stderr, "Device path length overrun by %zd characters\n", rc);
continue;
}
// open device read-only
int fd = open_device_ro(disk_path_out);
if (fd < 0) {
continue;
}
install_dev = read_gpt(fd, &block_size);
close(fd);
if (install_dev != NULL) {
gpt_device_get_header_guid(install_dev, &guid_targ);
if (!memcmp(guid_targ, disk_guid, GPT_GUID_LEN)) {
*install_dev_out = install_dev;
return ZX_OK;
}
gpt_device_release(install_dev);
}
}
strcpy(disk_path_out, "");
return ZX_ERR_NOT_FOUND;
}