blob: 97e4f6cca6837ba0393f390b6cbdfb063f4bffa2 [file] [log] [blame]
// Copyright 2016 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 <fs-management/mount.h>
#include <gpt/gpt.h>
#include <inttypes.h>
#include <limits.h>
#include <lz4/lz4.h>
#include <lz4/lz4frame.h>
#include <zircon/device/block.h>
#include <zircon/listnode.h>
#include <zircon/syscalls.h>
#include <fdio/io.h>
#include <fdio/util.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <installer/installer.h>
#include <installer/sparse.h>
#define DEFAULT_BLOCKDEV "/dev/class/block/000"
#define CHECK_BIT(var, pos) ((var) & (1 << (pos)))
#define PATH_VOLUMES "/volume"
// 4GB
#define MIN_SIZE_SYSTEM_PART (((uint64_t)1024u) * 1024u * 1024u * 4u)
// 1GB
#define MIN_SIZE_EFI_PART (((uint64_t)1024u) * 1024u * 1024u)
// data must be at least 200MB
#define MIN_SIZE_DATA (((uint64_t)1024u) * 1024u * 200)
// we'd like data to be 8GB
#define PREFERRED_SIZE_DATA (((uint64_t)1024u) * 1024u * 1024u * 8u)
#define PATH_MAX 4096
#define NUM_INSTALL_PARTS 2
// The size of memory blocks to use while decompressing the LZ4 file.
// The LZ4 compressed file is expected to have 64K blocks. If the file being
// decompressed is a sparsed file the 64K block may contain a sparse file header
// and therefore the data in the decompressed section may not align to boundaries
// of the block device we're writing to. If this is true, then we need to
// keep a partial device block's worth of data and decompress a new section
// from the LZ4 file. At most we expect device blocks to be 4K and therefore this
// is the most we'd have left over
#define DECOMP_BLOCK_SIZE ((64 + 4) * 1024)
// TODO(jmatt): it is gross that we're hard-coding this here, we should take
// from the user or somehow set in the environment
#define IMG_SYSTEM_PATH "/system/installer/user_fs.lz4"
#define IMG_EFI_PATH "/system/installer/efi_fs.lz4"
// use for the partition mask sent to parition_for_install
typedef enum { PART_EFI = 1 << 0, PART_SYSTEM = 1 << 1 } partition_flags;
typedef struct disk_rec {
list_node_t disk_node;
gpt_device_t *device;
char *path;
uint16_t part_count;
} disk_rec_t;
static const uint8_t guid_system_part[GPT_GUID_LEN] = GUID_SYSTEM_VALUE;
static const uint8_t guid_efi_part[GPT_GUID_LEN] = GUID_EFI_VALUE;
static uint16_t count_partitions(gpt_device_t *device) {
assert(device != NULL);
if (device == NULL) {
return 0;
}
uint16_t counter;
for (counter = 0; device->partitions[counter] != NULL; counter++)
;
return counter;
}
/*
* Search the path at search_dir for partitions whose ID (NOT type) GUIDs
* match the ID GUIDs in the gpt_partition_t array pointed to by part_info.
* num_parts should match both the length of the part_info array and the
* part_out char* array. If the call reports ZX_OK, path_out will contain
* an array of character pointers to paths to the partitions, these paths will
* be relative to the directory represented by search_dir. The path_out array
* is ordered the same as the part_info array. If some partitions are not found
* their entries will contain just a null terminator. An error will be returned
* if we encounter an error looking through the partition information.
*/
static zx_status_t find_partition_path(gpt_partition_t *const *part_info,
char **path_out, DIR *search_dir,
int num_parts) {
if (num_parts == 0) {
printf("No partitions requested.\n");
return ZX_OK;
}
int found_parts = 0;
int dir_fd = dirfd(search_dir);
if (dir_fd < 0) {
fprintf(stderr, "Could not get descriptor for directory, '%s'.\n",
strerror(errno));
return ZX_ERR_IO;
}
// initialize the path output so we can check this sentinel value later
for (int idx = 0; idx < num_parts; idx++) {
if (path_out[idx] != NULL) {
// this makes a 0-length string
path_out[idx][0] = '\0';
}
}
struct dirent *entry;
while ((entry = readdir(search_dir)) != NULL) {
// get a file descriptor for the entry
int file_fd = openat(dir_fd, entry->d_name, O_RDONLY);
if (file_fd < 0) {
fprintf(stderr, "Error opening descriptor for %s, error:'%s'\n",
entry->d_name, strerror(errno));
continue;
}
uint8_t partition_guid[16];
ssize_t rc = ioctl_block_get_partition_guid(file_fd, partition_guid, 16);
if (rc >= 0) {
for (int idx = 0; idx < num_parts; idx++) {
gpt_partition_t *part_targ = part_info[idx];
char *path_targ = path_out[idx];
if (part_targ == NULL || path_targ == NULL) {
continue;
}
if (!memcmp(partition_guid, part_targ->guid, 16)) {
if (strlen(path_targ) == 0) {
strcpy(path_targ, entry->d_name);
found_parts++;
} else {
fprintf(stderr, "Error, non-unique partition GUIDs!!\n");
close(file_fd);
return ZX_ERR_NOT_FOUND;
}
}
}
} else {
fprintf(stderr, "Warning: ioctl failed getting GUID for %s, error:(%zi) '%s'\n",
entry->d_name, rc, strerror(errno));
}
close(file_fd);
}
if (found_parts != num_parts) {
// this isn't an error per se, everything worked but we didn't find all
// the requested pieces.
printf("Some partitions were not found.\n");
}
return ZX_OK;
}
/*
* Give GPT information, check if the table contains entries for the partitions
* represented by part_mask. For constructing the part_mask, see the PART_*
* definitions. This also checks the partition sizes match or exceed the defined
* minimums. gpt_data must not be NULL and gpt_data->valid must be true.
*
* Returns value is a mask for missing partitions, or 0 if all partitions are
* found and otherwise valid. Upon return the part_paths_out array will contain
* absolute paths to the partitions to use for install. The part_paths_out array
* should be the same length as the number of partitions as represented in
* partition_flags. The order of the paths will be in ascending order of the
* flag value used to request that partition, so if you're looking for the
* system and efi partitions, the efi partition will be first and then
* system.
*
* The EFI partition is only considered valid if it is not the first
* partition on the device since we assume the first partition of the device
* contains the 'native' EFI partition for the device.
*/
static partition_flags find_install_partitions(gpt_device_t *gpt_data,
const uint64_t block_size,
partition_flags part_flags,
size_t max_path_len,
char *part_paths_out[]) {
assert(gpt_data != NULL);
assert(gpt_data->valid);
static_assert(NUM_INSTALL_PARTS == 2,
"Install partition count is unexpected, expected 2.");
if (!gpt_data->valid) {
return part_flags;
}
DIR *block_dir = NULL;
gpt_partition_t *part_info[NUM_INSTALL_PARTS] = {NULL, NULL};
uint8_t parts_found = 0;
int part_masks[NUM_INSTALL_PARTS] = {0, 0};
uint8_t parts_requested = 0;
uint16_t part_id = 0;
if (part_flags & PART_EFI) {
// look for a match until we exhaust partitions
zx_status_t rc = ZX_OK;
while (part_info[parts_requested] == NULL && rc == ZX_OK) {
uint16_t part_limit = countof(gpt_data->partitions) - part_id;
rc = find_partition((gpt_partition_t **)&gpt_data->partitions[part_id],
&guid_efi_part, MIN_SIZE_EFI_PART, block_size, "ESP",
part_limit, &part_id, &part_info[parts_requested]);
if (rc == ZX_OK) {
// check if this is the first partition on disk, we could sort
// but that seems overly involved for our simple requirements
// for this use case
bool first = true;
for (int idx = 0;
idx < PARTITIONS_COUNT && gpt_data->partitions[idx] != NULL;
idx++) {
if (gpt_data->partitions[part_id]->first >
gpt_data->partitions[idx]->first) {
first = false;
break;
}
}
if (!first) {
part_masks[parts_requested] = PART_EFI;
parts_found++;
} else {
printf("found an EFI partition, but it is the first; ");
printf("assume we want to keep this one intact.\n");
// reset part info
part_info[parts_requested] = NULL;
part_id++;
}
}
}
parts_requested++;
}
if (part_flags & PART_SYSTEM) {
zx_status_t rc = find_partition(
(gpt_partition_t **)&gpt_data->partitions, &guid_system_part,
MIN_SIZE_SYSTEM_PART, block_size, "System",
countof(gpt_data->partitions), &part_id, &part_info[parts_requested]);
if (rc == ZX_OK) {
part_masks[parts_requested] = PART_SYSTEM;
parts_found++;
}
parts_requested++;
}
if (parts_found == 0) {
return part_flags;
}
block_dir = opendir(PATH_BLOCKDEVS);
if (block_dir != NULL) {
zx_status_t rc = find_partition_path(part_info, part_paths_out, block_dir,
parts_requested);
if (rc == ZX_OK) {
size_t base_len = strlen(PATH_BLOCKDEVS);
for (int idx = 0; idx < parts_requested; idx++) {
char *str_targ = part_paths_out[idx];
// we didn't find this partition
if (part_masks[idx] == 0) {
str_targ[0] = '\0';
continue;
}
// construct paths for partitions
if (strlen(str_targ) + base_len + 2 > max_path_len) {
fprintf(stderr, "Path %s/%s does not fit in provided buffer.\n",
PATH_BLOCKDEVS, str_targ);
continue;
}
memmove(&str_targ[base_len + 1], str_targ, strlen(str_targ) + 1);
memcpy(str_targ, PATH_BLOCKDEVS, base_len);
memcpy(&str_targ[base_len], "/", 1);
part_flags &= ~part_masks[idx];
}
}
closedir(block_dir);
} else {
fprintf(stderr, "Failure reading directory %s, error: %s\n", PATH_BLOCKDEVS,
strerror(errno));
}
return part_flags;
}
/*
* Attempt to unmount all known mount paths.
*/
static zx_status_t unmount_all(void) {
const char *static_paths[1] = {"/data"};
zx_status_t result = ZX_OK;
for (uint16_t idx = 0; idx < countof(static_paths); idx++) {
zx_status_t rc = umount(static_paths[idx]);
if (rc != ZX_OK && rc != ZX_ERR_NOT_FOUND) {
// why not return failure? we're just making a best effort attempt,
// the system can return an error from this unmount call
result = rc;
printf("Warning: Unmounting filesystem at %s failed.\n",
static_paths[idx]);
}
}
char path[PATH_MAX];
DIR *vols = opendir(PATH_VOLUMES);
if (vols == NULL) {
fprintf(stderr, "Couldn't open volumes directory for reading!\n");
return ZX_ERR_IO;
}
struct dirent *entry = NULL;
strcpy(path, PATH_VOLUMES);
strcat(path, "/");
int path_len = strlen(path);
while ((entry = readdir(vols)) != NULL) {
if (!strncmp(".", entry->d_name, strlen(entry->d_name)) ||
!strncmp("..", entry->d_name, strlen(entry->d_name))) {
continue;
}
strncpy(path + path_len, entry->d_name, PATH_MAX - path_len);
zx_status_t rc = umount(path);
if (rc != ZX_OK) {
printf("Warning: Unmounting filesystem at %s failed.\n", path);
}
if (result == ZX_OK && rc != ZX_OK) {
result = rc;
}
}
closedir(vols);
// take a power nap, the system may take a moment to free resources after
// unmounting
sleep(1);
return result;
}
static zx_status_t write_partition(int src, int dest, size_t *bytes_copied) {
uint8_t read_buffer[DECOMP_BLOCK_SIZE];
uint8_t decomp_buffer[DECOMP_BLOCK_SIZE];
*bytes_copied = 0;
LZ4F_decompressionContext_t dc_context;
LZ4F_errorCode_t err =
LZ4F_createDecompressionContext(&dc_context, LZ4F_VERSION);
if (LZ4F_isError(err)) {
printf("Error creating decompression context: %s\n",
LZ4F_getErrorName(err));
return ZX_ERR_INTERNAL;
}
// we set special initial read parameters so we can read just the header
// of the first frame to provide hints about how to proceed
size_t to_read = 4;
size_t to_expand = DECOMP_BLOCK_SIZE;
ssize_t to_consume;
size_t MB_10s = 0;
const uint32_t divisor = 1024 * 1024 * 10;
unsparse_ctx_t write_ctx;
init_unsparse_ctx(&write_ctx);
// remainder will be the amount of data decompressed, but not written out and
// therefore leftover in the decompression buffer
ssize_t remainder = 0;
while ((to_consume = read(src, read_buffer, to_read)) > 0) {
ssize_t consumed_count = 0;
size_t chunk_size = 0;
if (*bytes_copied > 0) {
size_t new_val = *bytes_copied / divisor;
if (new_val != MB_10s) {
printf(" %zd0MB written.\r", new_val);
fflush(stdout);
MB_10s = new_val;
}
}
while (consumed_count < to_consume) {
// space available in the decompression buffer
to_expand = DECOMP_BLOCK_SIZE - remainder;
// bytes read from disk yet to decompressed
size_t req_size = to_consume - consumed_count;
chunk_size =
LZ4F_decompress(dc_context, decomp_buffer + remainder, &to_expand,
read_buffer + consumed_count, &req_size, NULL);
if (LZ4F_isError(chunk_size)) {
fprintf(stderr, "Error decompressing volume file.\n");
return ZX_ERR_INTERNAL;
}
if (to_expand > 0) {
// newly decompressed data, plus any left in decompression buffer from
// previous iteration
size_t unsparse_data = to_expand + remainder;
// unsparse the data and write it out, checking to see how much of the
// buffer was consumed
ssize_t written = unsparse_buf(decomp_buffer, unsparse_data,
&write_ctx, dest);
remainder = (ssize_t) unsparse_data - written;
if (written < 0) {
fprintf(stderr, "Error writing to partition, it may be corrupt %zi. %zu %zu %s\n", *bytes_copied, unsparse_data, remainder,
strerror(errno));
LZ4F_freeDecompressionContext(dc_context);
return ZX_ERR_IO;
} else if ((size_t) written < unsparse_data) {
// unsparse_buf didn't consume the whole buffer, move remaining data
// to front of buffer
memmove(decomp_buffer, decomp_buffer + written, remainder);
}
*bytes_copied += (size_t) written;
}
consumed_count += req_size;
}
// set the next read request size
if (chunk_size > DECOMP_BLOCK_SIZE) {
to_read = DECOMP_BLOCK_SIZE;
} else {
to_read = chunk_size;
}
}
LZ4F_freeDecompressionContext(dc_context);
// go to the next line so we don't overwrite the last data size print out
printf("\n");
if (to_consume < 0) {
fprintf(stderr, "Error decompressing file: %s.\n", strerror(errno));
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t add_partition(gpt_device_t *device, uint64_t offset_blocks,
uint64_t size_blocks, uint8_t *guid_type,
const char *name) {
uint8_t guid_id[GPT_GUID_LEN];
size_t rand_size = 0;
zx_status_t rc = zx_cprng_draw(guid_id, GPT_GUID_LEN, &rand_size);
if (rc != ZX_OK || rand_size != GPT_GUID_LEN) {
fprintf(stderr, "Sys call failed to set all random bytes, err: %s\n",
strerror(errno));
return rc;
}
int gpt_result = gpt_partition_add(device, name, guid_type, guid_id,
offset_blocks, size_blocks, 0);
if (gpt_result < 0) {
fprintf(stderr, "Error adding partition code: %i\n", gpt_result);
return ZX_ERR_INTERNAL;
}
gpt_result = gpt_device_sync(device);
if (gpt_result < 0) {
fprintf(stderr, "Error writing partition table, code: %i\n", gpt_result);
return ZX_ERR_IO;
}
return ZX_OK;
}
/*
* Take a directory stream of devices, the path to that directory, and a bit
* mask describing which partitions are being looked for and determines which
* partitions are available, what their device path is, and loads the
* gpt_device_t struct for the device containing the partition(s). The
* unfound_parts_out bit mask has bits set for any partitions not found. The
* part_paths_out array is indexed by the position of the corresponding bit
* in the requested_parts bit mask and size of the array passed in should
* match the number of partitions requested. If the requested_parts bit mask
* is 0b1011 the array should be length three. If the unfound_parts_out mask
* were set to 0b0010 then the path array would be null at index 1 while index
* 0 and 2 would point to a string.
*
* If successful dev_path_out will contain the path to the device that hosts
* the found partitions. max_len should specify the size of the buffer that
* dev_path_out points to.
*/
zx_status_t find_install_device(DIR *dir, const char *dir_path,
partition_flags requested_parts,
partition_flags *unfound_parts_out,
char *part_paths_out[],
gpt_device_t **device_out, char *dev_path_out,
size_t max_len) {
strcpy(dev_path_out, dir_path);
const size_t base_len = strlen(dev_path_out);
const size_t buffer_remaining = max_len - base_len - 1;
uint64_t block_size;
gpt_device_t *install_dev = NULL;
for (ssize_t rc =
get_next_file_path(dir, buffer_remaining, &dev_path_out[base_len]);
rc >= 0; rc = get_next_file_path(dir, buffer_remaining,
&dev_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(dev_path_out);
if (fd < 0) {
continue;
}
install_dev = read_gpt(fd, &block_size);
close(fd);
// if we read a GPT, see if it has the entry we want
if (install_dev != NULL && install_dev->valid) {
*unfound_parts_out = find_install_partitions(
install_dev, block_size, requested_parts, PATH_MAX, part_paths_out);
if (*unfound_parts_out != 0) {
gpt_device_release(install_dev);
install_dev = NULL;
} else {
*device_out = install_dev;
break;
}
}
}
if (install_dev != NULL) {
return ZX_OK;
} else {
return ZX_ERR_NOT_FOUND;
}
}
/*
* Write out the install data from the source paths into the destination
* paths. A partition is only written if its bit is set in both parts_requested
* and parts_available masks. The paths_src array should be indexed based the
* position of the bit in the masks while the paths_dest array should be
* indexed based on how many requested partitions there are.
*/
zx_status_t write_install_data(partition_flags parts_requested,
partition_flags parts_available,
char *paths_src[], char *paths_dest[]) {
if (unmount_all() != ZX_OK) {
// this isn't necessarily a failure, some of the paths that we tried
// to unmount not exist or might not actually correspond to devices
// we want to write to. We'll try to open the devices we want to
// write to and see what happens
printf("Warning, devices might not be unmounted.\n");
}
uint8_t part_idx = -1;
// scan through the requested partitions bitmask to see which
// partitions we want to write to and find the corresponding path for
// the disk image for that partition
for (int idx = 0; idx < 32; idx++) {
// see if this was a requested part, if so move the index we use to
// access the part_paths array because that array is ordered based
// on index of the order of bits in the bitmask sent to
// partition_for_install
if (CHECK_BIT(parts_requested, idx)) {
part_idx++;
}
// if either we weren't interested or we were, but we didn't find
// the partition, skip
if (!CHECK_BIT(parts_requested, idx) ||
(CHECK_BIT(parts_requested, idx) && CHECK_BIT(parts_available, idx))) {
continue;
}
// do install
size_t bytes_written;
int fd_dst = open(paths_dest[part_idx], O_RDWR);
if (fd_dst == -1) {
fprintf(stderr, "ERROR: Could not open output device for writing, %s\n",
strerror(errno));
return ZX_ERR_IO;
}
printf("writing content to '%s'\n", paths_dest[part_idx]);
int fd_src = open(paths_src[idx], O_RDONLY);
if (fd_src == -1) {
fprintf(stderr, "ERROR: Could not open disk image, %s, is this"
" the installer build?\n",
strerror(errno));
close(fd_dst);
return ZX_ERR_IO;
}
time_t start;
time(&start);
zx_status_t rc = write_partition(fd_src, fd_dst, &bytes_written);
time_t end;
time(&end);
printf("%.f secs taken to write %zd bytes\n", difftime(end, start),
bytes_written);
close(fd_dst);
close(fd_src);
if (rc != ZX_OK) {
fprintf(stderr, "ERROR: Problem writing partition code: %i\n", rc);
return rc;
}
}
return ZX_OK;
}
/*
* Given a directory, assume its contents represent block devices. Look at
* each entry to see if it contains a GPT and if it does, see if the GPT
* reports that space_required contiguous bytes are available. If a suitable
* place is found device_path_out and offset_out will be set to valid values,
* otherwise they will be left unchanged.
*/
void find_device_with_space(DIR *dir, char *dir_path, uint64_t space_required,
char *device_path_out, size_t *offset_out) {
char path_buffer[PATH_MAX];
strcpy(path_buffer, dir_path);
size_t base_len = strlen(path_buffer);
size_t buffer_remaining = PATH_MAX - base_len - 1;
uint64_t block_size;
// no device looks configured the way we want for install, see if we can
// partition a device and make it suitable
for (ssize_t rc =
get_next_file_path(dir, buffer_remaining, &path_buffer[base_len]);
rc >= 0;
rc = get_next_file_path(dir, buffer_remaining, &path_buffer[base_len])) {
if (rc > 0) {
fprintf(stderr, "Device path length overrun by %zd characters\n", rc);
continue;
}
// open device read-only
int device_fd = open_device_ro(&path_buffer[0]);
if (device_fd < 0) {
fprintf(stderr, "Error reading directory");
continue;
}
block_info_t info;
rc = ioctl_block_get_info(device_fd, &info);
if (rc < 0) {
fprintf(stderr, "Unable to get block info for '%s'\n", path_buffer);
close(device_fd);
continue;
}
block_size = info.block_size;
gpt_device_t *install_dev = read_gpt(device_fd, &block_size);
if (install_dev == NULL) {
close(device_fd);
continue;
} else if (!install_dev->valid) {
fprintf(stderr, "Read GPT for %s, but it is invalid\n", path_buffer);
gpt_device_release(install_dev);
close(device_fd);
continue;
}
part_location_t space_offset;
find_available_space(install_dev, space_required / block_size,
info.block_count, block_size, &space_offset);
gpt_device_release(install_dev);
close(device_fd);
if (space_offset.blk_len * block_size >= space_required) {
strcpy(device_path_out, path_buffer);
*offset_out = space_offset.blk_offset;
return;
}
}
}
/*
* Create the system partition and ESP on the specified device, starting at the
* specified block offset.
*/
zx_status_t create_partitions(char *dev_path, uint64_t block_offset) {
printf("Adding partitions...\n");
// open a read/write fd for the block device
int rw_dev = open(dev_path, O_RDWR);
if (rw_dev < 0) {
fprintf(stderr, "couldn't open device read/write\n");
return ZX_ERR_IO;
}
uint64_t block_size;
gpt_device_t *gpt_edit = read_gpt(rw_dev, &block_size);
// TODO(jmatt): consider asking the user what device to partition
// install_dev should point to the device we want to modify
uint64_t size_blocks = MIN_SIZE_SYSTEM_PART / block_size;
uint8_t type_system[GPT_GUID_LEN] = GUID_SYSTEM_VALUE;
zx_status_t rc =
add_partition(gpt_edit, block_offset, size_blocks, type_system, "system");
if (rc != ZX_OK) {
gpt_device_release(gpt_edit);
close(rw_dev);
return rc;
}
uint64_t size_blocks_efi = MIN_SIZE_EFI_PART / block_size;
uint8_t type_efi[GPT_GUID_LEN] = GUID_EFI_VALUE;
rc = add_partition(gpt_edit, block_offset + size_blocks, size_blocks_efi,
type_efi, "EFI");
if (rc != ZX_OK) {
gpt_device_release(gpt_edit);
close(rw_dev);
return rc;
}
gpt_device_release(gpt_edit);
// force a re-read of the block device so the new partitions are
// properly picked up
ioctl_block_rr_part(rw_dev);
close(rw_dev);
return ZX_OK;
}
/*
* Given a file descriptor open on a GPT device, checks if that GPT has an
* entry whose type GUID matches the provided GUID.
* Returns:
* ZX_OK if the data partition is found
* ZX_ERR_NOT_FOUND if we were able to look for the partition, but couldn't find
* it.
* ZX_ERR_IO if we were unable to read the partition table from device_fd
*/
static zx_status_t check_for_partition(int device_fd,
uint8_t (*guid)[GPT_GUID_LEN]) {
gpt_device_t *gpt_edit;
uint64_t block_size;
gpt_edit = read_gpt(device_fd, &block_size);
if (gpt_edit == NULL) {
fprintf(stderr, "Unable to read GPT from device.\n");
return ZX_ERR_IO;
}
uint16_t part_count = count_partitions(gpt_edit);
uint16_t part_idx = 0;
zx_status_t rc = find_partition_entries(
(gpt_partition_t **)&gpt_edit->partitions, guid, part_count, &part_idx);
return rc;
}
/*
* Give a partition table struct and a file descriptor pointing to a disk,
* find the block offset and appropriate number of blocks for the partition.
* The size returned in len_out will be at least size_min bytes and at most size
* size_pref bytes.
*
* If metadata can not be read from the disk or the disk contains less than
* size_min free space, ZX_ERR_NOT_FOUND is returned.
*/
static zx_status_t get_part_size(gpt_device_t *dev, int device_fd,
uint64_t size_pref, uint64_t size_min,
size_t *offset_out, size_t *len_out) {
part_location_t part_data;
part_data.blk_offset = 0;
part_data.blk_len = 0;
block_info_t info;
ssize_t rc = ioctl_block_get_info(device_fd, &info);
if (rc < 0) {
return ZX_ERR_NOT_FOUND;
}
uint64_t num_blocks_pref = size_pref / info.block_size;
uint64_t num_blocks_min = size_min / info.block_size;
find_available_space(dev, num_blocks_pref, info.block_count, info.block_size,
&part_data);
if (part_data.blk_len < num_blocks_min) {
return ZX_ERR_NOT_FOUND;
}
*len_out = part_data.blk_len >= num_blocks_pref ? num_blocks_pref
: part_data.blk_len;
*offset_out = part_data.blk_offset;
return ZX_OK;
}
/*
* Given a device struct, a file descriptor that is open on it's block device,
* a block location, and number of blocks, create a partition entry in the GPT
* for the partition and format that partition as the requested format with the
* supplied label.
*/
static zx_status_t make_part(int device_fd, const char *dev_dir_path,
size_t offset, size_t length,
uint8_t (*guid)[GPT_GUID_LEN], disk_format_t format,
const char *label) {
uint64_t block_size;
uint8_t disk_guid[GPT_GUID_LEN];
// ADD the data partition of the requested size that the requested location
gpt_device_t *gpt_edit = read_gpt(device_fd, &block_size);
if (gpt_edit == NULL) {
fprintf(stderr, "Couldn't read GPT from device.\n");
return ZX_ERR_IO;
}
gpt_device_get_header_guid(gpt_edit, &disk_guid);
zx_status_t rc = add_partition(gpt_edit, offset, length, *guid, label);
gpt_device_release(gpt_edit);
if (rc != ZX_OK) {
fprintf(stderr, "Partition entry could not be added to GPT.\n");
return ZX_ERR_IO;
}
ssize_t ioctl_rc = ioctl_block_rr_part(device_fd);
if (ioctl_rc < 0) {
fprintf(stderr, "Unknown error re-reading GPT.\n");
return ZX_ERR_IO;
}
// a brief pause is required while the system absorbs the GPT change
sleep(1);
unmount_all();
// find the new path of the device
DIR* dirfp = opendir(dev_dir_path);
if (dirfp == NULL) {
fprintf(stderr, "Couldn't open devices directory to read\n");
return ZX_ERR_IO;
}
char disk_path[PATH_MAX];
rc = find_disk_by_guid(dirfp, dev_dir_path, &disk_guid, &gpt_edit, disk_path,
PATH_MAX);
closedir(dirfp);
if (rc != ZX_OK) {
fprintf(stderr, "Couldn't locate disk after adding partition.\n");
return rc;
}
device_fd = open(disk_path, O_RDWR);
if (device_fd < 0) {
fprintf(stderr, "Couldn't open rebound device.\n");
return ZX_ERR_IO;
}
gpt_edit = read_gpt(device_fd, &block_size);
close(device_fd);
if (gpt_edit == NULL) {
fprintf(stderr, "Couldn't read GPT after partition addition.\n");
return ZX_ERR_IO;
}
// count the number of partitions we have
uint16_t part_count = count_partitions(gpt_edit);
// locate the metadata for the partition just created
uint16_t part_idx = 0;
rc = find_partition_entries((gpt_partition_t **)&gpt_edit->partitions, guid,
part_count, &part_idx);
if (rc != ZX_OK) {
fprintf(stderr, "Partition that was just created is not found.\n");
gpt_device_release(gpt_edit);
return ZX_ERR_NOT_FOUND;
}
// find the partition in the block device directory
char part_path[PATH_MAX];
char *path_holder[1] = {part_path};
DIR *dir = opendir(PATH_BLOCKDEVS);
gpt_partition_t *const *ptr_cpy = &gpt_edit->partitions[part_idx];
rc = find_partition_path(ptr_cpy, path_holder, dir, 1);
gpt_device_release(gpt_edit);
if (rc != ZX_OK) {
fprintf(stderr, "Problem finding partition path.\n");
return ZX_ERR_INTERNAL;
}
// construct the full path in-place now that we know which device it is
size_t len_temp = strlen(part_path) + 1;
size_t total_len = strlen(part_path) + strlen(dev_dir_path) + 1;
// check that the total length required does not exceed available space AND
// that accounting for the length of dev_dir_path we can copy part_path
// around without source and destination regions not overlapping for memcpy
if (total_len > PATH_MAX) {
fprintf(stderr, "Device path is too long!\n");
return ZX_ERR_INTERNAL;
}
// move the device-specific part to make space for the prefix
memmove(&part_path[strlen(dev_dir_path)], part_path, len_temp);
memcpy(part_path, dev_dir_path, strlen(dev_dir_path));
// kick off formatting of the device
rc = mkfs(part_path, format, launch_stdio_sync, &default_mkfs_options);
if (rc != ZX_OK) {
fprintf(stderr, "ERROR: Partition formatting failed.\n");
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
static zx_status_t format_existing(int device_fd, char *dev_dir_path,
uint8_t (*guid)[GPT_GUID_LEN],
disk_format_t disk_format) {
lseek(device_fd, 0, SEEK_SET);
uint64_t block_size;
gpt_device_t *gpt_device = read_gpt(device_fd, &block_size);
if (gpt_device == NULL) {
fprintf(stderr, "WARNING: Couldn't read GPT to format partition.\n");
return ZX_ERR_INTERNAL;
}
uint16_t part_id;
uint16_t part_count = count_partitions(gpt_device);
int rc = find_partition_entries((gpt_partition_t**)&gpt_device->partitions,
guid, part_count, &part_id);
if (rc != ZX_OK) {
gpt_device_release(gpt_device);
fprintf(stderr, "WARNING: Couldn't find partition to format.\n");
return ZX_ERR_INTERNAL;
}
char part_path[PATH_MAX];
// find_partition_path wants an array of pointers, so we pass a pointer
// to the address of the start of the array
char *indir = &part_path[0];
DIR *dev_dir;
dev_dir = opendir(dev_dir_path);
if (dev_dir == NULL) {
fprintf(stderr, "WARNING: Couldn't open device directory.\n");
gpt_device_release(gpt_device);
return ZX_ERR_INTERNAL;
}
rc = find_partition_path((gpt_partition_t**)&gpt_device->partitions[part_id],
&indir, dev_dir, 1);
if (rc != ZX_OK) {
gpt_device_release(gpt_device);
fprintf(stderr, "WARNING: Couldn't locate partition path.\n");
return ZX_ERR_INTERNAL;
}
// construct the full path in-place now that we know which device it is
size_t len_temp = strlen(part_path) + 1;
size_t total_len = strlen(part_path) + strlen(dev_dir_path) + 1;
// check that the total length required does not exceed available space AND
// that accounting for the length of dev_dir_path we can copy part_path
// around without source and destination regions not overlapping for memcpy
if (total_len > PATH_MAX) {
gpt_device_release(gpt_device);
fprintf(stderr, "WARNING: Device path is too long!\n");
return ZX_ERR_INTERNAL;
}
// move the device-specific part to make space for the prefix
memmove(&part_path[strlen(dev_dir_path)], part_path, len_temp);
memcpy(part_path, dev_dir_path, strlen(dev_dir_path));
gpt_device_release(gpt_device);
return mkfs(part_path, disk_format, launch_stdio_sync, &default_mkfs_options);
}
/*
* Given a GPT device struct and a path to the disk device, check to see if
* there is already a partition with the supplied GUID. If not, try to create
* that partition with the given size and format.
*
* This returns ZX_OK if there already is a partition or if there is enough
* space to create one and it is successfully created and formatted, otherwise
* returns an error.
*/
static zx_status_t make_empty_partition(gpt_device_t *install_dev,
char *device_path, char *dev_dir_path,
uint8_t (*guid)[GPT_GUID_LEN],
uint64_t size_pref, uint64_t size_min,
disk_format_t disk_format, const char *name,
bool reformat) {
int device_fd = open(device_path, O_RDWR);
if (device_fd < 0) {
printf("WARNING: Problem opening device, '%s' partition not created.\n",
name);
return ZX_ERR_IO;
}
zx_status_t rc;
if ((rc = check_for_partition(device_fd, guid)) == ZX_ERR_NOT_FOUND) {
size_t blk_off;
size_t blk_len;
if ((get_part_size(install_dev, device_fd, size_pref, size_min, &blk_off,
&blk_len) != ZX_OK) ||
(make_part(device_fd, dev_dir_path, blk_off, blk_len, guid, disk_format, name) !=
ZX_OK)) {
close(device_fd);
return ZX_ERR_INTERNAL;
}
} else if (rc != ZX_OK) {
fprintf(stderr, "Unexpected error '%i' looking for '%s' partition\n", rc,
name);
close(device_fd);
return rc;
} else if (rc == ZX_OK && reformat) {
lseek(device_fd, 0, SEEK_SET);
rc = format_existing(device_fd, dev_dir_path, guid, disk_format);
if (rc != ZX_OK) {
printf("WARNING: couldn't format existing partition\n");
close(device_fd);
return rc;
}
}
close(device_fd);
return ZX_OK;
}
static void get_input(char *buf, size_t max_input) {
// converted to signed since idx could possible be negative
ssize_t max_input_s = max_input;
for (ssize_t idx = 0; idx < max_input_s; idx++) {
int c = getc(stdin);
// if the user hit backspace, erase the previous char and position the
// index before the erased char so the next loop will fill in that space
if (c == 8) {
if (idx > 0) {
printf("%c", c);
fflush(stdout);
idx--;
buf[idx] = ' ';
}
idx--;
continue;
}
sprintf(buf + idx, "%c", c);
printf("%c", c);
fflush(stdout);
if (buf[idx] == '\n') {
buf[idx] = '\0';
return;
}
}
buf[max_input - 1] = '\0';
}
/*
* Checks that the given string converts to a number and that the number
* converted back to a string matches the original input string.
*/
static bool check_input(char *input, int *out_value) {
char *parsed = NULL;
// according to strtol() docs, set errno before calling
errno = 0;
long int tmp = strtol(input, &parsed, 10);
// check that there was no parse error, that the value is within the integer
// range, and that all of the input was consumed in parsing
if (!errno && tmp <= INT_MAX && parsed == input + strlen(input)) {
*out_value = (int)tmp;
return true;
} else {
return false;
}
}
static void release_disk_list(list_node_t *list) {
disk_rec_t *rec;
while ((rec = list_remove_head_type(list, disk_rec_t, disk_node)) != NULL) {
if (rec->path != NULL) {
free(rec->path);
}
if (rec->device != NULL) {
gpt_device_release(rec->device);
}
free(rec);
}
}
/*
* Given a size in bytes, compute how many gibibytes (2^30) and tenths of a
* gibibyte this represents. Note that the tenths are computed by TRUNCATING
* rather than rounding, so what would accurately be 2.46GiB will be returned
* as 2.4GiB.
*/
static void get_gib_and_tenths(uint64_t size, uint64_t *gb_out,
uint64_t *tenths_out) {
*gb_out = size >> 30;
*tenths_out = ((size - (*gb_out << 30)) * 10) >> 30;
}
static void print_gpt(gpt_device_t *device, uint64_t block_size,
uint16_t part_count) {
for (int part_idx = 0; part_idx < part_count; part_idx++) {
gpt_partition_t *part_targ = device->partitions[part_idx];
uint64_t size_bytes = block_size * (part_targ->last - part_targ->first + 1);
uint64_t size_gib;
uint64_t remainder;
get_gib_and_tenths(size_bytes, &size_gib, &remainder);
char name[GPT_GUID_STRLEN];
memset(name, 0, GPT_GUID_STRLEN);
utf16_to_cstring(name, (const uint16_t *)part_targ->name,
GPT_GUID_STRLEN - 1);
name[GPT_GUID_STRLEN - 1] = '\0';
printf(" Partition %d %16s %" PRIu64 ".%" PRIu64
"GB at block %" PRIu64 "\n",
part_idx, name, size_gib, remainder, part_targ->first);
}
}
/*
* Takes a file descriptor pointing to a disk, attempting to read a GPT from the
* disk and construct a disk_rec_t, a pointer to which will be returned through
* rec_out.
* Returns
* ZX_ERR_NO_MEMORY if memory can't be allocated for the record data
* ZX_ERR_IO if the device does not contain a GPT or otherwise can not be read
* ZX_OK if all goes well
*/
static zx_status_t build_disk_record(int device_fd, char *path,
uint64_t *block_size_out,
disk_rec_t **rec_out) {
disk_rec_t *disk_rec = malloc(sizeof(disk_rec_t));
if (disk_rec == NULL) {
fprintf(stderr, "No memory available to add disk entry.\n");
return ZX_ERR_NO_MEMORY;
}
// see if this block device has a GPT we can read and get disk
// size
gpt_device_t *target_dev = read_gpt(device_fd, block_size_out);
if (target_dev == NULL || !target_dev->valid) {
if (target_dev != NULL) {
gpt_device_release(target_dev);
}
free(disk_rec);
return ZX_ERR_IO;
}
disk_rec->device = target_dev;
// record path to the device
disk_rec->path = malloc((strlen(path) + 1) * sizeof(char));
if (disk_rec->path == NULL) {
fprintf(stderr, "Out of memory when writing disk path.\n");
free(disk_rec);
gpt_device_release(target_dev);
return ZX_ERR_NO_MEMORY;
} else {
strcpy(disk_rec->path, path);
}
disk_rec->part_count = count_partitions(target_dev);
*rec_out = disk_rec;
return ZX_OK;
}
void print_disk_info(int disk_fd, uint16_t disk_num, char *dev_path) {
block_info_t info;
uint64_t disk_size;
ssize_t rc = ioctl_block_get_info(disk_fd, &info);
if (rc < 0) {
printf("WARNING: Unable to read disk size, reporting zero.\n");
disk_size = 0;
} else {
disk_size = info.block_size * info.block_count;
}
uint64_t disk_size_gib;
uint64_t tenths_of_gib;
get_gib_and_tenths(disk_size, &disk_size_gib, &tenths_of_gib);
printf("Disk %d (%s) %" PRIu64 ".%" PRIu64 "GB\n", disk_num, dev_path,
disk_size_gib, tenths_of_gib);
}
static bool build_disk_list(DIR *dev_dir, char *dev_path, size_t path_buf_sz,
list_node_t *disk_list, uint16_t *disk_num,
bool print) {
size_t base_len = strlen(dev_path);
size_t buffer_remaining = path_buf_sz - base_len - 1;
uint64_t block_size;
for (ssize_t rc =
get_next_file_path(dev_dir, buffer_remaining, &dev_path[base_len]);
rc >= 0; rc = get_next_file_path(dev_dir, buffer_remaining,
&dev_path[base_len])) {
// confirm path to device is not too long
if (rc > 0) {
fprintf(stderr, "Device path length overrun by %zd characters\n", rc);
continue;
}
int device_fd = open_device_ro(dev_path);
if (device_fd < 0) {
fprintf(stderr, "Could not read device entry.\n");
continue;
}
disk_rec_t *disk_rec;
rc = build_disk_record(device_fd, dev_path, &block_size, &disk_rec);
if (rc == ZX_OK) {
list_add_tail(disk_list, &disk_rec->disk_node);
} else if (rc == ZX_ERR_IO) {
// this just wasn't a GPT device or device couldn't be read,
// continue on to other possible devices
close(device_fd);
continue;
} else {
close(device_fd);
fprintf(stderr, "Out of memory or other unexpected error, aborting.\n");
return false;
}
// TODO (jmatt) make print a callback
if (print) {
print_disk_info(device_fd, *disk_num, dev_path);
print_gpt(disk_rec->device, block_size, disk_rec->part_count);
}
close(device_fd);
(*disk_num)++;
}
return true;
}
static bool ask_for_disk_part(list_node_t *list, int num_disks,
disk_rec_t **disk_out, int *part_idx_out) {
printf("Delete a partition on which disk (0-%i blank to cancel)?\n",
(num_disks - 1));
char buffer[512];
get_input(buffer, sizeof(buffer));
int req_disk;
if (!check_input(buffer, &req_disk)) {
printf("Disk selection is not understood.\n");
return false;
}
// check that specified disk number is in range
if (req_disk < 0 || req_disk >= num_disks) {
printf("Specified disk is invalid, please choose 0-%i\n", num_disks - 1);
return false;
}
disk_rec_t *selected_disk = list_peek_head_type(list, disk_rec_t, disk_node);
for (int idx = 0; idx < req_disk; idx++) {
selected_disk =
list_next_type(list, &selected_disk->disk_node, disk_rec_t, disk_node);
}
printf("Which partition would you like to remove? (0-%i)\n",
selected_disk->part_count - 1);
get_input(buffer, sizeof(buffer));
if (!check_input(buffer, &req_disk)) {
printf("Invalid input\n");
return false;
}
if (req_disk < 0 || req_disk >= selected_disk->part_count) {
printf("Partition index is out of range, please choose 0-%i\n",
selected_disk->part_count - 1);
return false;
}
*disk_out = selected_disk;
*part_idx_out = req_disk;
return true;
}
static bool remove_partition(int device_fd, int part_idx) {
uint64_t block_size;
gpt_device_t *dev = read_gpt(device_fd, &block_size);
if (dev == NULL) {
printf("Unable to remove partition, couldn't read GPT.\n");
return false;
}
if (!dev->valid) {
printf("Unable to remove partition, GPT is invalid\n");
gpt_device_release(dev);
return false;
}
if (dev->partitions[part_idx] == NULL ||
gpt_partition_remove(dev, dev->partitions[part_idx]->guid)) {
gpt_device_release(dev);
printf("Unable to remove partition, partition not found!\n");
return false;
}
if (gpt_device_sync(dev)) {
printf("Unable to remove partition, GPT could not be written.\n");
gpt_device_release(dev);
return false;
}
gpt_device_release(dev);
return true;
}
static bool ask_for_space(void) {
DIR *dev_dir;
char dev_path[PATH_MAX] = PATH_BLOCKDEVS;
size_t base_len = strlen(dev_path);
assert(base_len > 0);
dev_path[base_len] = '/';
dev_path[++base_len] = '\0';
dev_dir = opendir(PATH_BLOCKDEVS);
if (dev_dir == NULL) {
fprintf(stderr, "Could not open device directory.\n");
return false;
}
list_node_t disk_list;
list_initialize(&disk_list);
uint16_t disk_num = 0;
if (!build_disk_list(dev_dir, dev_path, PATH_MAX, &disk_list, &disk_num,
true)) {
release_disk_list(&disk_list);
closedir(dev_dir);
return false;
}
closedir(dev_dir);
// no disks, nothing to do
if (disk_num < 1) {
return false;
}
disk_rec_t *selected_disk;
int req_part;
if (!ask_for_disk_part(&disk_list, disk_num, &selected_disk, &req_part)) {
release_disk_list(&disk_list);
return false;
}
int device_fd = open(selected_disk->path, O_RDWR);
if (device_fd < 0) {
printf("Unable to remove partition, could not open GPT for writing.\n");
release_disk_list(&disk_list);
return false;
}
bool result = remove_partition(device_fd, req_part);
close(device_fd);
release_disk_list(&disk_list);
return result;
}
static int print_summary(bool install_dev_found, bool req_data_written,
bool part_data_avail, bool part_blob_avail) {
bool total_success = install_dev_found && req_data_written &&
part_data_avail && part_blob_avail;
printf("\n===================================\n");
printf("INSTALL SUMMARY: %s\n", (total_success ? "SUCCESS" : "FAILURE"));
printf(" Drive found? %s\n", (install_dev_found ? "YES" : "NO"));
printf(" ESP+SYS written? %s\n", (req_data_written ? "YES" : "NO"));
printf(" /data ready? %s\n", (part_data_avail ? "YES" : "NO"));
printf(" /blobstore ready? %s\n", (part_blob_avail ? "YES" : "NO"));
return total_success ? 0 : -1;
}
int main(int argc, char **argv) {
static_assert(NUM_INSTALL_PARTS == 2,
"Install partition count is unexpected, expected 2.");
static_assert(PATH_MAX >= sizeof(PATH_BLOCKDEVS),
"File path max length is too short for path to block devices.");
bool wipe = false;
if (argc == 2 && strcmp("-w", argv[1]) == 0) {
printf("running with wipe");
wipe = true;
}
// setup the base path in the path buffer
static char path_buffer[sizeof(PATH_BLOCKDEVS) + 2];
strcpy(path_buffer, PATH_BLOCKDEVS);
strcat(path_buffer, "/");
// set up structures for hold source and destination paths for partition
// data
char system_path[PATH_MAX];
char efi_path[PATH_MAX];
char *part_paths[NUM_INSTALL_PARTS] = {efi_path, system_path};
char system_img_path[PATH_MAX] = IMG_SYSTEM_PATH;
char efi_img_path[PATH_MAX] = IMG_EFI_PATH;
char *disk_img_paths[NUM_INSTALL_PARTS] = {efi_img_path, system_img_path};
// device to install on
gpt_device_t *install_dev = NULL;
partition_flags ready_for_install = 0;
partition_flags requested_parts = PART_EFI | PART_SYSTEM;
uint8_t data_guid[GPT_GUID_LEN] = GUID_DATA_VALUE;
uint8_t blobfs_guid[GPT_GUID_LEN] = GUID_BLOBFS_VALUE;
bool install_dev_found = false;
bool req_data_written = false;
bool part_data_avail = false;
bool part_blob_avail = false;
printf("Messages tagged \"ERROR\" are fatal, others are informational.\n");
// the dirty bit is set to true whenever the devices directory is in a
// state where it it unknown if installation can proceed
bool retry;
do {
retry = false;
// first read the directory of block devices
DIR *dir = opendir(PATH_BLOCKDEVS);
if (dir == NULL) {
fprintf(stderr, "Open failed for directory: '%s' with error %s\n",
PATH_BLOCKDEVS, strerror(errno));
return print_summary(install_dev_found, req_data_written, part_data_avail,
part_blob_avail);
}
char disk_path[PATH_MAX];
zx_status_t rc = find_install_device(dir, path_buffer, requested_parts,
&ready_for_install, part_paths,
&install_dev, disk_path, PATH_MAX);
closedir(dir);
if (rc == ZX_OK && install_dev->valid) {
install_dev_found = true;
rc = write_install_data(requested_parts, ready_for_install,
disk_img_paths, part_paths);
// Check for a data and blobfs partitions, creating if necessary.
// Having these partitions is highly desireable, but we can live
// without it if needed
if (rc == ZX_OK) {
req_data_written = true;
// store the guid of the disk we're using
uint8_t disk_guid[GPT_GUID_LEN];
gpt_device_get_header_guid(install_dev, &disk_guid);
strcpy(path_buffer, PATH_BLOCKDEVS);
strcat(path_buffer, "/");
if (make_empty_partition(install_dev, disk_path, path_buffer, &data_guid,
PREFERRED_SIZE_DATA, MIN_SIZE_DATA,
DISK_FORMAT_MINFS, "data", wipe) != ZX_OK) {
printf("WARNING: Problem locating or creating data partition.\n");
} else {
part_data_avail = true;
}
// find the device path of the disk we're using, it will have changed
// if we created a data partition
strcpy(path_buffer, PATH_BLOCKDEVS);
strcat(path_buffer, "/");
dir = opendir(path_buffer);
if (dir == NULL) {
printf("Unable to re-open block device directory, can not make\
blobfs partition");
return print_summary(install_dev_found, req_data_written,
part_data_avail, part_blob_avail);
}
gpt_device_release(install_dev);
find_disk_by_guid(dir, path_buffer, &disk_guid, &install_dev, disk_path,
PATH_MAX);
closedir(dir);
// add a blobfs partition
if (make_empty_partition(install_dev, disk_path, path_buffer,
&blobfs_guid, PREFERRED_SIZE_DATA,
MIN_SIZE_DATA, DISK_FORMAT_BLOBFS, "blobfs", wipe)
!= ZX_OK) {
printf("WARNING: Problem locating or creating blobfs partition.\n");
} else {
part_blob_avail = true;
}
} else {
gpt_device_release(install_dev);
fprintf(stderr, "Failure writing install data, aborting.\n");
return print_summary(install_dev_found, req_data_written,
part_data_avail, part_blob_avail);
}
gpt_device_release(install_dev);
// we ignore whether or not we could make the data partition since
// it is desired, but not required
return print_summary(install_dev_found, req_data_written, part_data_avail,
part_blob_avail);
} else {
dir = opendir(PATH_BLOCKDEVS);
if (dir == NULL) {
fprintf(stderr, "Open failed for directory: '%s' with error %s\n",
PATH_BLOCKDEVS, strerror(errno));
return print_summary(install_dev_found, req_data_written,
part_data_avail, part_blob_avail);
}
strcpy(path_buffer, PATH_BLOCKDEVS);
strcat(path_buffer, "/");
char device_path[PATH_MAX];
size_t space_offset = 0;
find_device_with_space(dir, path_buffer,
MIN_SIZE_SYSTEM_PART + MIN_SIZE_EFI_PART,
device_path, &space_offset);
closedir(dir);
if (space_offset == 0) {
// TODO don't give up, try removing one or more partitions
retry = ask_for_space();
continue;
}
// if partition creation succeeds, set the dirty bit
retry = create_partitions(device_path, space_offset) == ZX_OK;
// if we're going to try again, give the system a moment to absorb
// newly created partitions
if (retry) {
sleep(1);
}
}
} while (retry);
return print_summary(install_dev_found, req_data_written, part_data_avail,
part_blob_avail);
}