| // 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 <dirent.h> |
| #include <fcntl.h> |
| #include <stdbool.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <block-client/client.h> |
| #include <chromeos-disk-setup/chromeos-disk-setup.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/array.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/unique_fd.h> |
| #include <fbl/unique_ptr.h> |
| #include <fdio/watcher.h> |
| #include <fs-management/mount.h> |
| #include <fs-management/ramdisk.h> |
| #include <fs/mapped-vmo.h> |
| #include <fvm/fvm-lz4.h> |
| #include <gpt/cros.h> |
| #include <gpt/gpt.h> |
| #include <zircon/device/block.h> |
| #include <zircon/device/device.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| #include <zx/fifo.h> |
| #include <zx/vmo.h> |
| |
| #include "fvm/fvm-sparse.h" |
| #include "fvm/fvm.h" |
| |
| #define FVM_DRIVER_LIB "/boot/driver/fvm.so" |
| #define STRLEN(s) sizeof(s) / sizeof((s)[0]) |
| |
| #define PAVER_PREFIX "paver:" |
| #define ERROR(fmt, ...) fprintf(stderr, PAVER_PREFIX "[%s] " fmt, __FUNCTION__, ##__VA_ARGS__); |
| #define LOG(fmt, ...) fprintf(stdout, PAVER_PREFIX "[%s] " fmt, __FUNCTION__, ##__VA_ARGS__); |
| |
| namespace { |
| |
| constexpr char kBlockDevPath[] = "/dev/class/block"; |
| |
| // Confirm that the file descriptor to the underlying partition exists within an |
| // FVM, not, for example, a GPT or MBR. |
| // |
| // |out| is true if |fd| is a VPartition, else false. |
| zx_status_t fvm_is_vpartition(const fbl::unique_fd& fd, bool* out) { |
| char path[PATH_MAX]; |
| ssize_t r = ioctl_device_get_topo_path(fd.get(), path, sizeof(path)); |
| if (r < 0) { |
| return ZX_ERR_IO; |
| } |
| |
| if (strstr(path, "fvm") != nullptr) { |
| *out = true; |
| } else { |
| *out = false; |
| } |
| return ZX_OK; |
| } |
| |
| // Describes the state of a partition actively being written |
| // out to disk. |
| struct partition_info { |
| fvm::partition_descriptor_t* pd; |
| fbl::unique_fd new_part; |
| fbl::unique_fd old_part; // Or '-1' if this is a new partition |
| }; |
| |
| inline fvm::extent_descriptor_t* get_extent(fvm::partition_descriptor_t* pd, size_t extent) { |
| return reinterpret_cast<fvm::extent_descriptor_t*>( |
| reinterpret_cast<uintptr_t>(pd) + sizeof(fvm::partition_descriptor_t) + |
| extent * sizeof(fvm::extent_descriptor_t)); |
| } |
| |
| zx_status_t register_fast_block_io(const fbl::unique_fd& fd, zx_handle_t vmo, |
| txnid_t* txnid_out, vmoid_t* vmoid_out, |
| fifo_client_t** client_out) { |
| zx::fifo fifo; |
| if (ioctl_block_get_fifos(fd.get(), fifo.reset_and_get_address()) < 0) { |
| ERROR("Couldn't attach fifo to partition\n"); |
| return ZX_ERR_IO; |
| } |
| if (ioctl_block_alloc_txn(fd.get(), txnid_out) < 0) { |
| ERROR("Couldn't allocate transaction\n"); |
| return ZX_ERR_IO; |
| } |
| zx::vmo dup; |
| if (zx_handle_duplicate(vmo, ZX_RIGHT_SAME_RIGHTS, |
| dup.reset_and_get_address()) != ZX_OK) { |
| ERROR("Couldn't duplicate buffer vmo\n"); |
| return ZX_ERR_IO; |
| } |
| zx_handle_t h = dup.release(); |
| if (ioctl_block_attach_vmo(fd.get(), &h, vmoid_out) < 0) { |
| ERROR("Couldn't attach VMO\n"); |
| return ZX_ERR_IO; |
| } |
| if (block_fifo_create_client(fifo.release(), client_out) != ZX_OK) { |
| ERROR("Couldn't create block client\n"); |
| return ZX_ERR_IO; |
| } |
| return ZX_OK; |
| } |
| |
| // Stream an FVM partition to disk. |
| zx_status_t stream_fvm_partition(fvm::SparseReader* reader, partition_info* part, |
| MappedVmo* mvmo, fifo_client_t* client, size_t block_size, |
| block_fifo_request_t* request) { |
| size_t slice_size = reader->Image()->slice_size; |
| const size_t vmo_cap = mvmo->GetSize(); |
| for (size_t e = 0; e < part->pd->extent_count; e++) { |
| LOG("Writing extent %zu... \n", e); |
| fvm::extent_descriptor_t* ext = get_extent(part->pd, e); |
| size_t offset = ext->slice_start * slice_size; |
| size_t bytes_left = ext->extent_length; |
| |
| // Write real data |
| while (bytes_left > 0) { |
| size_t vmo_sz = 0; |
| size_t actual; |
| zx_status_t status = reader->ReadData( |
| &reinterpret_cast<uint8_t*>(mvmo->GetData())[vmo_sz], |
| fbl::min(bytes_left, vmo_cap - vmo_sz), &actual); |
| vmo_sz += actual; |
| bytes_left -= actual; |
| |
| if (vmo_sz == 0) { |
| ERROR("Read nothing from src_fd; %zu bytes left\n", bytes_left); |
| return ZX_ERR_IO; |
| } else if (vmo_sz % block_size != 0) { |
| ERROR("Cannot write non-block size multiple: %zu\n", vmo_sz); |
| return ZX_ERR_IO; |
| } else if (status != ZX_OK) { |
| ERROR("Error reading partition data\n"); |
| return status; |
| } |
| |
| request->length = vmo_sz / block_size; |
| request->vmo_offset = 0; |
| request->dev_offset = offset / block_size; |
| |
| ssize_t r; |
| if ((r = block_fifo_txn(client, request, 1)) != ZX_OK) { |
| ERROR("Error writing partition data\n"); |
| return static_cast<zx_status_t>(r); |
| } |
| |
| offset += vmo_sz; |
| } |
| |
| // Write trailing zeroes (which are implied, but were omitted from |
| // transfer). |
| bytes_left = (ext->slice_count * slice_size) - ext->extent_length; |
| if (bytes_left > 0) { |
| LOG("%zu bytes written, %zu zeroes left\n", ext->extent_length, bytes_left); |
| memset(mvmo->GetData(), 0, vmo_cap); |
| } |
| while (bytes_left > 0) { |
| request->length = fbl::min(bytes_left, vmo_cap) / block_size; |
| request->vmo_offset = 0; |
| request->dev_offset = offset / block_size; |
| |
| zx_status_t status; |
| if ((status = block_fifo_txn(client, request, 1)) != ZX_OK) { |
| ERROR("Error writing trailing zeroes\n"); |
| return status; |
| } |
| |
| offset += request->length * block_size; |
| bytes_left -= request->length * block_size; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| // Stream a raw (non-FVM) partition to disk. |
| zx_status_t stream_partition(MappedVmo* mvmo, fifo_client_t* client, |
| block_fifo_request_t* request, const fbl::unique_fd& src_fd, |
| const block_info_t& info) { |
| const size_t vmo_cap = mvmo->GetSize(); |
| ZX_ASSERT(vmo_cap % info.block_size == 0); |
| size_t offset = 0; |
| |
| while (true) { |
| ssize_t r; |
| size_t vmo_sz = 0; |
| while ((r = read(src_fd.get(), &reinterpret_cast<uint8_t*>(mvmo->GetData())[vmo_sz], |
| vmo_cap - vmo_sz)) > 0) { |
| vmo_sz += r; |
| if (vmo_cap - vmo_sz == 0) { |
| // The buffer is full, let's write to disk. |
| break; |
| } |
| } |
| if (r < 0) { |
| ERROR("Error reading partition data\n"); |
| return static_cast<zx_status_t>(r); |
| } |
| if (vmo_sz == 0) { |
| // Nothing left to write. |
| return ZX_OK; |
| } |
| |
| if ((r == 0) && (vmo_sz % info.block_size)) { |
| // We have a partial block to write. |
| size_t rounded_length = fbl::round_up(vmo_sz, info.block_size); |
| memset(&reinterpret_cast<uint8_t*>(mvmo->GetData())[vmo_sz], 0, |
| rounded_length - vmo_sz); |
| vmo_sz = rounded_length; |
| } |
| |
| request->length = vmo_sz / info.block_size; |
| request->vmo_offset = 0; |
| request->dev_offset = offset / info.block_size; |
| |
| zx_status_t status; |
| if ((status = block_fifo_txn(client, request, 1)) != ZX_OK) { |
| ERROR("Error writing partition data\n"); |
| return status; |
| } |
| |
| if (r == 0) { |
| // We have nothing left to read on the input pipe. |
| return ZX_OK; |
| } |
| |
| offset += vmo_sz; |
| } |
| } |
| |
| // Finds a partition with "FVM type GUID" within a GPT, |
| // and formats the FVM within the GPT if it is not already |
| // formatted. |
| // |
| // On success, returns a file descriptor to an FVM. |
| // On failure, returns -1 |
| fbl::unique_fd fvm_find_or_format(size_t slice_size) { |
| const uint8_t type[GPT_GUID_LEN] = GUID_FVM_VALUE; |
| fbl::unique_fd fd(open_partition(nullptr, type, 0, nullptr)); |
| if (!fd) { |
| ERROR("Couldn't find a GPT partition for FVM\n"); |
| return fbl::unique_fd(); |
| } |
| |
| disk_format_t df = detect_disk_format(fd.get()); |
| if (df != DISK_FORMAT_FVM) { |
| ERROR("Initializing partition as FVM\n"); |
| if (fvm_init(fd.get(), slice_size)) { |
| ERROR("Failed to initialize fvm\n"); |
| return fbl::unique_fd(); |
| } |
| } |
| char path[PATH_MAX]; |
| ssize_t r = ioctl_device_get_topo_path(fd.get(), path, sizeof(path)); |
| if (r < 0) { |
| ERROR("Failed to get topological path\n"); |
| return fbl::unique_fd(); |
| } |
| |
| r = ioctl_device_bind(fd.get(), FVM_DRIVER_LIB, STRLEN(FVM_DRIVER_LIB)); |
| if (r < 0) { |
| ERROR("Could not bind fvm driver\n"); |
| return fbl::unique_fd(); |
| } |
| |
| if (wait_for_driver_bind(path, "fvm")) { |
| ERROR("Error waiting for fvm driver to bind\n"); |
| return fbl::unique_fd(); |
| } |
| strcat(path, "/fvm"); |
| return fbl::unique_fd(open(path, O_RDWR)); |
| } |
| |
| // Returns |ZX_OK| if |part_fd| is a child of |fvm_fd|. |
| zx_status_t fvm_partition_match(const fbl::unique_fd& fvm_fd, const fbl::unique_fd& part_fd) { |
| char fvm_path[PATH_MAX]; |
| char part_path[PATH_MAX]; |
| ssize_t r; |
| if ((r = ioctl_device_get_topo_path(fvm_fd.get(), fvm_path, sizeof(fvm_path))) < 0) { |
| ERROR("Couldn't get topological path of FVM\n"); |
| return static_cast<zx_status_t>(r); |
| } else if ((r = ioctl_device_get_topo_path(part_fd.get(), part_path, sizeof(part_path))) < 0) { |
| ERROR("Couldn't get topological path of partition\n"); |
| return static_cast<zx_status_t>(r); |
| } |
| if (strncmp(fvm_path, part_path, strlen(fvm_path))) { |
| ERROR("Partition does not exist within FVM\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| return ZX_OK; |
| } |
| |
| void recommend_wipe(const char* reason) { |
| ERROR("-----------------------------------------------------\n"); |
| ERROR("\n"); |
| ERROR("%s: Please run 'install-disk-image wipe' to wipe your partitions\n", reason); |
| ERROR("\n"); |
| ERROR("-----------------------------------------------------\n"); |
| } |
| |
| zx_status_t fvm_init_sparse_reader(fbl::unique_fd src_fd, |
| fbl::unique_ptr<fvm::SparseReader>* reader) { |
| zx_status_t status; |
| if ((status = fvm::SparseReader::Create(fbl::move(src_fd), reader)) != ZX_OK) { |
| return status; |
| } |
| |
| fvm::sparse_image_t* hdr = (*reader)->Image(); |
| // Verify the header, then allocate and stream the remaining metadata |
| if (hdr->magic != fvm::kSparseFormatMagic) { |
| ERROR("Bad magic\n"); |
| return ZX_ERR_IO; |
| } else if (hdr->version != fvm::kSparseFormatVersion) { |
| ERROR("Unexpected sparse file version\n"); |
| return ZX_ERR_IO; |
| } |
| |
| return ZX_OK; |
| } |
| |
| // Given an fd representing a "sparse FVM format", fill the FVM with the |
| // provided partitions described by |src_fd|. |
| // |
| // Decides to overwrite or create new partitions based on the type |
| // GUID, not the instance GUID. |
| zx_status_t fvm_stream_partitions(fbl::unique_fd src_fd) { |
| fbl::unique_ptr<fvm::SparseReader> reader; |
| zx_status_t status = fvm_init_sparse_reader(fbl::move(src_fd), &reader); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| LOG("Header Validated - OK\n"); |
| |
| fvm::sparse_image_t* hdr = reader->Image(); |
| // Acquire an fd to the fvm, either by finding one that already |
| // exists, or creating a new one. |
| fbl::unique_fd fvm_fd(fvm_find_or_format(hdr->slice_size)); |
| if (!fvm_fd) { |
| ERROR("Couldn't find FVM partition\n"); |
| return ZX_ERR_IO; |
| } |
| |
| // TODO(smklein): In this case, we could actually unbind the FVM driver, |
| // create a new FVM with the updated slice size, and rebind. |
| |
| size_t block_size = 0; |
| fvm_info_t info; |
| if (ioctl_block_fvm_query(fvm_fd.get(), &info) < 0) { |
| ERROR("Couldn't query underlying FVM\n"); |
| return ZX_ERR_IO; |
| } else if (info.slice_size != hdr->slice_size) { |
| ERROR("Unexpected slice size (%zu vs %zu)\n", info.slice_size, hdr->slice_size); |
| return ZX_ERR_IO; |
| } |
| |
| fbl::Array<partition_info> parts(new partition_info[hdr->partition_count], |
| hdr->partition_count); |
| |
| fvm::partition_descriptor_t* part = reader->Partitions(); |
| |
| for (size_t p = 0; p < hdr->partition_count; p++) { |
| parts[p].pd = part; |
| parts[p].old_part.reset(open_partition(nullptr, part->type, ZX_SEC(2), nullptr)); |
| |
| if (parts[p].pd->magic != fvm::kPartitionDescriptorMagic) { |
| ERROR("Bad partition magic\n"); |
| return ZX_ERR_IO; |
| } |
| |
| if (parts[p].old_part) { |
| bool is_vpartition; |
| if (fvm_is_vpartition(parts[p].old_part, &is_vpartition)) { |
| ERROR("Couldn't confirm old vpartition type\n"); |
| return ZX_ERR_IO; |
| } else if (fvm_partition_match(fvm_fd, parts[p].old_part) != ZX_OK) { |
| recommend_wipe("Streaming a partition type which also exists outside FVM"); |
| return ZX_ERR_BAD_STATE; |
| } else if (!is_vpartition) { |
| recommend_wipe("Streaming a partition type which also exists in a GPT"); |
| return ZX_ERR_BAD_STATE; |
| } |
| } |
| |
| fvm::extent_descriptor_t* ext = get_extent(part, 0); |
| if (ext->magic != fvm::kExtentDescriptorMagic) { |
| ERROR("Bad extent magic\n"); |
| return ZX_ERR_IO; |
| } else if (ext->slice_start != 0) { |
| ERROR("First slice must start at zero\n"); |
| return ZX_ERR_IO; |
| } else if (ext->slice_count == 0) { |
| ERROR("Extents must have > 0 slices\n"); |
| return ZX_ERR_IO; |
| } else if (ext->extent_length > ext->slice_count * hdr->slice_size) { |
| ERROR("Extent length must fit within allocated slice count\n"); |
| return ZX_ERR_IO; |
| } |
| |
| alloc_req_t alloc; |
| // Allocate this partition as inactive so it gets deleted on the next |
| // reboot if this stream fails. |
| alloc.flags = fvm::kVPartFlagInactive; |
| alloc.slice_count = ext->slice_count; |
| memcpy(&alloc.type, parts[p].pd->type, sizeof(alloc.type)); |
| size_t sz; |
| if (zx_cprng_draw(alloc.guid, GPT_GUID_LEN, &sz) != ZX_OK || |
| sz != GPT_GUID_LEN) { |
| ERROR("Couldn't generate unique GUID\n"); |
| return ZX_ERR_IO; |
| } |
| memcpy(&alloc.name, parts[p].pd->name, sizeof(alloc.name)); |
| LOG("Allocating partition %s consisting of %zu slices\n", alloc.name, alloc.slice_count); |
| parts[p].new_part.reset(fvm_allocate_partition(fvm_fd.get(), &alloc)); |
| |
| if (!parts[p].new_part) { |
| ERROR("Couldn't allocate partition\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| block_info_t binfo; |
| if (block_size == 0) { |
| if ((ioctl_block_get_info(parts[p].new_part.get(), &binfo)) < 0) { |
| ERROR("Couldn't get partition block info\n"); |
| return ZX_ERR_IO; |
| } |
| block_size = binfo.block_size; |
| } |
| |
| for (size_t e = 1; e < parts[p].pd->extent_count; e++) { |
| ext = get_extent(parts[p].pd, e); |
| if (ext->magic != fvm::kExtentDescriptorMagic) { |
| ERROR("Bad extent magic\n"); |
| return ZX_ERR_IO; |
| } else if (ext->slice_count == 0) { |
| ERROR("Extents must have > 0 slices\n"); |
| return ZX_ERR_IO; |
| } else if (ext->extent_length > ext->slice_count * hdr->slice_size) { |
| ERROR("Extent must fit within allocated slice count\n"); |
| return ZX_ERR_IO; |
| } |
| |
| extend_request_t request; |
| request.offset = ext->slice_start; |
| request.length = ext->slice_count; |
| LOG("Extending partition[%zu] at offset %zu by length %zu\n", p, request.offset, |
| request.length); |
| if (ioctl_block_fvm_extend(parts[p].new_part.get(), &request) < 0) { |
| ERROR("Failed to extend partition\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| } |
| part = reinterpret_cast<fvm::partition_descriptor*>( |
| reinterpret_cast<uintptr_t>(ext) + sizeof(fvm::extent_descriptor_t)); |
| } |
| |
| LOG("Partition space pre-allocated\n"); |
| |
| const size_t vmo_sz = 1 << 20; |
| |
| fbl::unique_ptr<MappedVmo> mvmo; |
| if ((status = MappedVmo::Create(vmo_sz, "fvm-stream", &mvmo)) != ZX_OK) { |
| ERROR("Failed to create stream VMO\n"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| // Now that all partitions are preallocated, begin streaming data to them. |
| for (size_t p = 0; p < hdr->partition_count; p++) { |
| txnid_t txnid; |
| vmoid_t vmoid; |
| fifo_client_t* client; |
| zx_status_t status = register_fast_block_io(parts[p].new_part, |
| mvmo->GetVmo(), &txnid, |
| &vmoid, &client); |
| if (status != ZX_OK) { |
| ERROR("Failed to register fast block IO\n"); |
| return status; |
| } |
| |
| block_fifo_request_t request; |
| request.txnid = txnid; |
| request.vmoid = vmoid; |
| request.opcode = BLOCKIO_WRITE; |
| |
| LOG("Streaming partition %zu\n", p); |
| status = stream_fvm_partition(reader.get(), &parts[p], mvmo.get(), client, block_size, |
| &request); |
| LOG("Done streaming partition %zu\n", p); |
| block_fifo_release_client(client); |
| if (status != ZX_OK) { |
| ERROR("Failed to stream partition\n"); |
| return status; |
| } |
| } |
| |
| for (size_t p = 0; p < hdr->partition_count; p++) { |
| // Upgrade the old partition (currently active) to the new partition (currently |
| // inactive), so when the new partition becomes active, the old |
| // partition is destroyed. |
| upgrade_req_t upgrade; |
| memset(&upgrade, 0, sizeof(upgrade)); |
| if (parts[p].old_part) { |
| if (ioctl_block_get_partition_guid(parts[p].old_part.get(), |
| &upgrade.old_guid, GUID_LEN) < 0) { |
| ERROR("Failed to get unique GUID of old partition\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| } |
| if (ioctl_block_get_partition_guid(parts[p].new_part.get(), &upgrade.new_guid, GUID_LEN) < 0) { |
| ERROR("Failed to get unique GUID of new partition\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (ioctl_block_fvm_upgrade(fvm_fd.get(), &upgrade) < 0) { |
| ERROR("Failed to upgrade partition\n"); |
| return ZX_ERR_IO; |
| } |
| |
| if (parts[p].old_part) { |
| // This would fail if the old part was on GPT, not FVM. However, |
| // we checked earlier and verified that parts[p].old_part, if it exists, |
| // is a vpartition. |
| ssize_t r; |
| if ((r = ioctl_block_fvm_destroy(parts[p].old_part.get())) < 0) { |
| ERROR("Couldn't destroy partition: %ld\n", r); |
| return static_cast<zx_status_t>(r); |
| } |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| // Find and return the topological path of the GPT which we will pave. |
| // |out_path| must be at least |PATH_MAX| bytes long. |
| zx_status_t find_target_gpt(char* out_path) { |
| DIR* d = opendir(kBlockDevPath); |
| if (d == nullptr) { |
| ERROR("Cannot inspect block devices\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| struct dirent* de; |
| while ((de = readdir(d)) != nullptr) { |
| fbl::unique_fd fd(openat(dirfd(d), de->d_name, O_RDWR)); |
| if (!fd) { |
| continue; |
| } |
| ssize_t r = ioctl_device_get_topo_path(fd.get(), out_path, PATH_MAX); |
| if (r < 0) { |
| continue; |
| } |
| |
| block_info_t info; |
| if ((r = ioctl_block_get_info(fd.get(), &info) < 0)) { |
| continue; |
| } |
| |
| // TODO(ZX-1344): This is a hack, but practically, will work for our |
| // usage. |
| // |
| // The GPT which will contain an FVM should be the first non-removable |
| // block device that isn't a partition itself. |
| if (!(info.flags & BLOCK_FLAG_REMOVABLE) && strstr(out_path, "part-") == nullptr) { |
| closedir(d); |
| return ZX_OK; |
| } |
| } |
| closedir(d); |
| |
| ERROR("No candidate GPT found\n"); |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| // Initialize a GPT object with the gpt_device_t wrapper from ulib/gpt. |
| zx_status_t initialize_gpt(const char* gpt_path, fbl::unique_fd* out_fd, gpt_device_t** out_gpt) { |
| fbl::unique_fd fd(open(gpt_path, O_RDWR)); |
| if (!fd) { |
| ERROR("Failed to open GPT\n"); |
| return ZX_ERR_IO; |
| } |
| block_info_t info; |
| ssize_t rc = ioctl_block_get_info(fd.get(), &info); |
| if (rc < 0) { |
| ERROR("Couldn't get GPT block info\n"); |
| return ZX_ERR_IO; |
| } |
| |
| if (gpt_device_init(fd.get(), info.block_size, info.block_count, out_gpt)) { |
| ERROR("Failed to get GPT info\n"); |
| return ZX_ERR_IO; |
| } else if (!(*out_gpt)->valid) { |
| ERROR("Located GPT is invalid; Attempting to initialize\n"); |
| if (gpt_partition_remove_all(*out_gpt)) { |
| ERROR("Failed to create empty GPT\n"); |
| gpt_device_release(*out_gpt); |
| return ZX_ERR_IO; |
| } else if (gpt_device_sync(*out_gpt)) { |
| ERROR("Failed to sync empty GPT\n"); |
| gpt_device_release(*out_gpt); |
| return ZX_ERR_IO; |
| } else if ((rc = ioctl_block_rr_part(fd.get())) != ZX_OK) { |
| ERROR("Failed to re-read GPT\n"); |
| gpt_device_release(*out_gpt); |
| return static_cast<zx_status_t>(rc); |
| } |
| } |
| *out_fd = fbl::move(fd); |
| return ZX_OK; |
| } |
| |
| struct Partition { |
| size_t start; // Block, inclusive |
| size_t length; // In Blocks |
| }; |
| |
| constexpr size_t kReservedEntryBlocks = (16 * 1024); |
| constexpr size_t kReservedHeaderBlocks(size_t blk_size) { |
| return (kReservedEntryBlocks + 2 * blk_size) / blk_size; |
| }; |
| |
| // Find the first spot that has at least |bytes_requested| of space. |
| // Does not update the GPT. |
| // |
| // Returns the |start_out| block and |length_out| blocks, indicating |
| // how much space was found, on success. This may be larger than |
| // the number of bytes requested. |
| zx_status_t find_first_fit(const gpt_device_t* gpt, const fbl::unique_fd& gpt_fd, |
| size_t bytes_requested, size_t* start_out, size_t* length_out) { |
| LOG("Looking for space\n"); |
| // Gather GPT-related information. |
| block_info_t info; |
| ssize_t rc = ioctl_block_get_info(gpt_fd.get(), &info); |
| if (rc < 0) { |
| ERROR("Cannot acquire GPT info\n"); |
| return static_cast<zx_status_t>(rc); |
| } |
| size_t blocks_requested = (bytes_requested + info.block_size - 1) / info.block_size; |
| |
| // Sort all partitions by starting block. |
| // For simplicity, include the 'start' and 'end' reserved spots as |
| // partitions. |
| size_t partc = 0; |
| Partition partitions[PARTITIONS_COUNT + 2]; |
| const size_t kReservedBlocks = kReservedHeaderBlocks(info.block_size); |
| partitions[partc].start = 0; |
| partitions[partc++].length = kReservedBlocks; |
| partitions[partc].start = info.block_count - kReservedBlocks; |
| partitions[partc++].length = kReservedBlocks; |
| |
| for (size_t i = 0; i < PARTITIONS_COUNT; i++) { |
| gpt_partition_t* p = gpt->partitions[i]; |
| if (!p) { |
| continue; |
| } |
| partitions[partc].start = p->first; |
| partitions[partc].length = p->last - p->first + 1; |
| LOG("Partition seen with start %zu, end %zu (length %zu)\n", p->first, p->last, |
| partitions[partc].length); |
| partc++; |
| } |
| LOG("Sorting\n"); |
| qsort(partitions, partc, sizeof(Partition), [](const void* p1, const void* p2) { |
| ssize_t s1 = static_cast<ssize_t>(static_cast<const Partition*>(p1)->start); |
| ssize_t s2 = static_cast<ssize_t>(static_cast<const Partition*>(p2)->start); |
| return static_cast<int>(s1 - s2); |
| }); |
| |
| // Look for space between the partitions. Since the reserved spots of the |
| // GPT were included in |partitions|, all available space will be located |
| // "between" partitions. |
| for (size_t i = 0; i < partc - 1; i++) { |
| size_t next = partitions[i].start + partitions[i].length; |
| LOG("Partition[%zu] From Block [%zu, %zu) ... (next partition starts at block %zu)\n", |
| i, partitions[i].start, next, partitions[i + 1].start); |
| |
| if (next > partitions[i + 1].start) { |
| ERROR("Corrupted GPT\n"); |
| return ZX_ERR_IO; |
| } |
| size_t free_blocks = partitions[i + 1].start - next; |
| LOG(" There are %zu free blocks (%zu requested)\n", free_blocks, blocks_requested); |
| if (free_blocks >= blocks_requested) { |
| *start_out = next; |
| *length_out = free_blocks; |
| return ZX_OK; |
| } |
| } |
| ERROR("No GPT space found\n"); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| // Returns "true" if the corresponding partition should |
| // be used for paving. |
| using PartitionFilterCb = bool (*)(const block_info_t* info, const gpt_partition_t* part); |
| |
| // Optional callback. |
| // Returns "true" if a new partition should be created. |
| // Only called if one doesn't already exist. |
| // |
| // Additionally, sets the minimum requested size of the partition to allocate. |
| using PartitionCreateCb = bool (*)(uint8_t* type_out, uint64_t* size_bytes_out, |
| const char** name_out); |
| |
| // Optional callback. |
| // Returns "true" if the partition has been updated. |
| // |
| // Allows the partition updater to modify attributes of the |
| // partition (like flags) after writing it to disk. |
| using PartitionFinalizeCb = bool (*)(gpt_partition_t* partition); |
| |
| // Returns a file descriptor to a partition which can be paved, |
| // if one exists. |
| template <PartitionFilterCb filterCb> |
| zx_status_t partition_find(const block_info_t* info, gpt_device_t* gpt, |
| gpt_partition_t** out, fbl::unique_fd* out_fd) { |
| for (size_t i = 0; i < PARTITIONS_COUNT; i++) { |
| gpt_partition_t* p = gpt->partitions[i]; |
| if (!p) { |
| continue; |
| } |
| |
| static_assert(filterCb != nullptr, "Filter callback required to find partition"); |
| if (filterCb(info, p)) { |
| LOG("Found partition in GPT, partition %zu\n", i); |
| if (out) { |
| *out = p; |
| } |
| if (out_fd) { |
| out_fd->reset(open_partition(p->guid, p->type, ZX_SEC(5), nullptr)); |
| if (!*out_fd) { |
| ERROR("Couldn't open partition\n"); |
| return ZX_ERR_IO; |
| } |
| } |
| return ZX_OK; |
| } |
| } |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| // Returns a file descriptor to a partition which can be paved, |
| // creating it. |
| // Assumes that the partition does not already exist. |
| template <PartitionCreateCb createCb> |
| zx_status_t partition_add(gpt_device_t* gpt, fbl::unique_fd gpt_fd, fbl::unique_fd* out_fd) { |
| const char* name; |
| uint8_t type[GPT_GUID_LEN]; |
| size_t minimumSizeBytes = 0; |
| static_assert(createCb != nullptr, "Create callback required to add partition"); |
| if (!createCb(type, &minimumSizeBytes, &name)) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| uint64_t start, length; |
| zx_status_t r; |
| if ((r = find_first_fit(gpt, gpt_fd, minimumSizeBytes, &start, &length)) != ZX_OK) { |
| ERROR("Couldn't find fit\n"); |
| return r; |
| } |
| |
| block_info_t info; |
| ssize_t rc = ioctl_block_get_info(gpt_fd.get(), &info); |
| if (rc < 0) { |
| ERROR("Cannot acquire GPT info\n"); |
| return static_cast<zx_status_t>(rc); |
| } |
| |
| length = (minimumSizeBytes + info.block_size - 1) / info.block_size; |
| size_t sz; |
| uint8_t guid[GPT_GUID_LEN]; |
| if ((r = zx_cprng_draw(guid, GPT_GUID_LEN, &sz)) != ZX_OK) { |
| ERROR("Failed to get random GUID\n"); |
| return r; |
| } else if ((r = gpt_partition_add(gpt, name, type, guid, start, length, 0))) { |
| ERROR("Failed to add partition\n"); |
| return r; |
| } else if ((r = gpt_device_sync(gpt))) { |
| ERROR("Failed to sync GPT\n"); |
| return r; |
| } else if ((r = (int)ioctl_block_rr_part(gpt_fd.get())) < 0) { |
| ERROR("Failed to rebind GPT\n"); |
| return r; |
| } |
| out_fd->reset(open_partition(guid, type, ZX_SEC(5), nullptr)); |
| if (!*out_fd) { |
| return ZX_ERR_IO; |
| } |
| return ZX_OK; |
| } |
| |
| // Assuming the path to the GPT does not already contain an |
| // FVM, find space for an FVM partition, and add it to the GPT. |
| zx_status_t fvm_add_to_gpt(const char* gpt_path) { |
| fbl::unique_fd gpt_fd; |
| gpt_device_t* gpt; |
| zx_status_t status; |
| if ((status = initialize_gpt(gpt_path, &gpt_fd, &gpt)) != ZX_OK) { |
| return status; |
| } |
| |
| block_info_t info; |
| ssize_t rc = ioctl_block_get_info(gpt_fd.get(), &info); |
| if (rc < 0) { |
| ERROR("Cannot acquire GPT info\n"); |
| return static_cast<zx_status_t>(rc); |
| } |
| |
| int r = 0; |
| const size_t kMinimumFVMSizeBytes = 8LU * (1 << 30); |
| const size_t kOptionalReserveBytes = 4LU * (1 << 30); |
| const size_t kOptionalReserveBlocks = kOptionalReserveBytes / info.block_size; |
| size_t start = 0; |
| size_t length = 0; |
| uint8_t type[GPT_GUID_LEN] = GUID_FVM_VALUE; |
| uint8_t guid[GPT_GUID_LEN]; |
| size_t sz; |
| fbl::unique_fd partition_fd; |
| for (size_t i = 0; i < PARTITIONS_COUNT; i++) { |
| gpt_partition_t* p = gpt->partitions[i]; |
| if (!p) { |
| continue; |
| } |
| // If the FVM already exists within the GPT, return early. |
| if (memcmp(p->type, type, GPT_GUID_LEN) == 0) { |
| LOG("FVM partition already exists within GPT\n"); |
| memcpy(guid, p->guid, GPT_GUID_LEN); |
| goto done; |
| } |
| } |
| |
| if ((r = find_first_fit(gpt, gpt_fd, kMinimumFVMSizeBytes, &start, &length)) != ZX_OK) { |
| ERROR("Couldn't find space in GPT: %d\n", r); |
| goto done; |
| } |
| LOG("Found space in GPT - OK %zu @ %zu\n", length, start); |
| |
| // If can fulfill the requested size, and we still have space for the |
| // optional reserve section, then we should shorten the amount of blocks |
| // we're asking for. |
| // |
| // This isn't necessary, but it allows growing the GPT later, if necessary. |
| if (length - kOptionalReserveBlocks > (kMinimumFVMSizeBytes / info.block_size)) { |
| LOG("Space for reserve - OK\n"); |
| length -= kOptionalReserveBlocks; |
| } |
| LOG("Final space in GPT - OK %zu @ %zu\n", length, start); |
| |
| if ((r = zx_cprng_draw(guid, GPT_GUID_LEN, &sz)) != ZX_OK) { |
| ERROR("Failed to get random GUID\n"); |
| goto done; |
| } else if ((r = gpt_partition_add(gpt, "fvm", type, guid, start, length, 0))) { |
| ERROR("Failed to add FVM partition\n"); |
| goto done; |
| } else if ((r = gpt_device_sync(gpt))) { |
| ERROR("Failed to sync GPT\n"); |
| goto done; |
| } else if ((r = (int)ioctl_block_rr_part(gpt_fd.get())) < 0) { |
| ERROR("Failed to rebind GPT\n"); |
| goto done; |
| } |
| |
| LOG("Added partition, waiting for bind\n"); |
| done: |
| if (r == 0) { |
| // Before we return, claiming that the FVM partition is ready, we should |
| // check the GPT partition has actually appeared in devfs. |
| partition_fd.reset(open_partition(guid, type, ZX_SEC(5), nullptr)); |
| if (!partition_fd) { |
| ERROR("Added partition, waiting for bind - NOT FOUND\n"); |
| r = -1; |
| } else { |
| ERROR("Added partition, waiting for bind - OK\n"); |
| r = 0; |
| } |
| } |
| gpt_device_release(gpt); |
| return (r < 0 ? ZX_ERR_BAD_STATE : ZX_OK); |
| } |
| |
| zx_status_t device_specific_disk_prep(const char* gpt_path) { |
| fbl::unique_fd fd; |
| gpt_device_t* gpt; |
| if (initialize_gpt(gpt_path, &fd, &gpt)) { |
| return ZX_ERR_IO; |
| } |
| |
| block_info_t info; |
| ssize_t r; |
| bool modify = false; |
| zx_status_t status = ZX_OK; |
| |
| if (is_cros(gpt)) { |
| if ((r = ioctl_block_get_info(fd.get(), &info)) < 0) { |
| status = static_cast<zx_status_t>(r); |
| goto done; |
| } |
| |
| if (!is_ready_to_pave(gpt, &info, SZ_ZX_PART, SZ_ROOT_PART, true)) { |
| if ((status = config_cros_for_fuchsia(gpt, &info, SZ_ZX_PART, |
| SZ_ROOT_PART, true)) != ZX_OK) { |
| goto done; |
| } |
| modify = true; |
| } |
| } |
| |
| done: |
| if (modify) { |
| gpt_device_sync(gpt); |
| ioctl_block_rr_part(fd.get()); |
| } |
| gpt_device_release(gpt); |
| return status; |
| } |
| |
| // Name used by previous Fuchsia Installer |
| const char* oldEfiName = "EFI"; |
| |
| // Name used for EFI partitions added by paver |
| const char* efiName = "EFI Gigaboot"; |
| |
| #define MB (1LU << 20) |
| |
| bool efi_filter_cb(const block_info_t* info, const gpt_partition_t* part) { |
| uint8_t efi_type[GPT_GUID_LEN] = GUID_EFI_VALUE; |
| char cstring_name[GPT_NAME_LEN]; |
| utf16_to_cstring(cstring_name, (uint16_t*)part->name, GPT_NAME_LEN); |
| // Old EFI: Installed by the legacy Fuchsia installer, identified by |
| // large size and "EFI" label. |
| bool oldEfi = strncmp(cstring_name, oldEfiName, strlen(oldEfiName)) == 0 && |
| ((part->last - part->first + 1) * info->block_size) > (512 * MB); |
| // Disk-paved EFI: Identified by "EFI Gigaboot" label. |
| bool newEfi = strncmp(cstring_name, efiName, strlen(efiName)) == 0; |
| return memcmp(part->type, efi_type, GPT_GUID_LEN) == 0 && (oldEfi || newEfi); |
| } |
| |
| bool efi_create_cb(uint8_t* type_out, uint64_t* size_bytes_out, const char** name_out) { |
| uint8_t efi_type[GPT_GUID_LEN] = GUID_EFI_VALUE; |
| memcpy(type_out, efi_type, GPT_GUID_LEN); |
| *size_bytes_out = 1LU * (1 << 30); |
| *name_out = efiName; |
| return true; |
| } |
| |
| const char* kerncName = "KERN-C"; |
| |
| bool kernc_filter_cb(const block_info_t* info, const gpt_partition_t* part) { |
| uint8_t kernc_type[GPT_GUID_LEN] = GUID_CROS_KERNEL_VALUE; |
| char cstring_name[GPT_NAME_LEN]; |
| utf16_to_cstring(cstring_name, (uint16_t*)part->name, GPT_NAME_LEN); |
| return memcmp(part->type, kernc_type, GPT_GUID_LEN) == 0 && |
| strncmp(cstring_name, kerncName, strlen(kerncName)) == 0; |
| } |
| |
| bool kernc_create_cb(uint8_t* type_out, uint64_t* size_bytes_out, const char** name_out) { |
| uint8_t kernc_type[GPT_GUID_LEN] = GUID_CROS_KERNEL_VALUE; |
| memcpy(type_out, kernc_type, GPT_GUID_LEN); |
| *size_bytes_out = 64LU * (1 << 20); |
| *name_out = kerncName; |
| return true; |
| } |
| |
| bool kernc_finalize_cb(gpt_partition_t* partition) { |
| // Priority set to '3', making Kern C higher priority than |
| // the typical '1' and '2' reserved for Kern A and Kern B. |
| gpt_cros_attr_set_priority(&partition->flags, 3); |
| // Successful set to 'true' to encourage the bootloader to |
| // use this partition. |
| gpt_cros_attr_set_successful(&partition->flags, true); |
| // Maximize the number of attempts to boot this partition before |
| // we fall back to a different kernel. |
| gpt_cros_attr_set_tries(&partition->flags, 15); |
| return true; |
| } |
| |
| } // namespace |
| |
| // Paves a sparse_file to the underlying disk, on top |
| // of a GPT. |
| int fvm_pave(fbl::unique_fd fd) { |
| LOG("Paving FVM\n"); |
| char gpt_path[PATH_MAX]; |
| if (find_target_gpt(gpt_path)) { |
| ERROR("Couldn't find target GPT\n"); |
| return -1; |
| } |
| zx_status_t status = device_specific_disk_prep(gpt_path); |
| if (status != ZX_OK) { |
| ERROR("Failed to complete device-specific prep\n"); |
| return -1; |
| } |
| LOG("Found Target GPT %s - OK\n", gpt_path); |
| if (fvm_add_to_gpt(gpt_path)) { |
| ERROR("Couldn't format FVM partition\n"); |
| return -1; |
| } |
| LOG("Added to GPT - OK\n"); |
| |
| LOG("Streaming partitions...\n"); |
| if ((status = fvm_stream_partitions(fbl::move(fd))) != ZX_OK) { |
| ERROR("Failed to stream partitions: %d\n", status); |
| return -1; |
| } |
| LOG("DONE\n"); |
| return 0; |
| } |
| |
| // Paves an image onto the disk, within the GPT. |
| template <PartitionFilterCb filterCb, PartitionCreateCb createCb, PartitionFinalizeCb finalizeCb> |
| zx_status_t partition_pave(fbl::unique_fd fd) { |
| LOG("Paving a partition to the GPT\n"); |
| char gpt_path[PATH_MAX]; |
| if (find_target_gpt(gpt_path)) { |
| return ZX_ERR_IO; |
| } |
| zx_status_t status = device_specific_disk_prep(gpt_path); |
| if (status != ZX_OK) { |
| ERROR("Failed to complete device-specific prep\n"); |
| return -1; |
| } |
| |
| fbl::unique_fd gpt_fd; |
| gpt_device_t* gpt; |
| if ((status = initialize_gpt(gpt_path, &gpt_fd, &gpt)) != ZX_OK) { |
| return status; |
| } |
| |
| block_info_t info; |
| if ((status = static_cast<zx_status_t>(ioctl_block_get_info(gpt_fd.get(), &info))) < 0) { |
| ERROR("Couldn't get GPT block info\n"); |
| return status; |
| } |
| |
| fbl::unique_fd part_fd; |
| if ((status = partition_find<filterCb>(&info, gpt, nullptr, &part_fd)) != ZX_OK) { |
| if (status != ZX_ERR_NOT_FOUND || (void*)createCb == nullptr) { |
| ERROR("Failure looking for partition: %d\n", status); |
| gpt_device_release(gpt); |
| return status; |
| } |
| if ((status = partition_add<createCb>(gpt, fbl::move(gpt_fd), &part_fd)) != ZX_OK) { |
| ERROR("Failure creating partition: %d\n", status); |
| gpt_device_release(gpt); |
| return status; |
| } |
| } |
| gpt_device_release(gpt); |
| |
| if ((status = static_cast<zx_status_t>(ioctl_block_get_info(part_fd.get(), &info))) < 0) { |
| ERROR("Couldn't get GPT partition block info\n"); |
| return status; |
| } |
| |
| const size_t vmo_sz = fbl::round_up(1LU << 20, info.block_size); |
| fbl::unique_ptr<MappedVmo> mvmo; |
| if ((status = MappedVmo::Create(vmo_sz, "partition-pave", &mvmo)) != ZX_OK) { |
| ERROR("Failed to create stream VMO\n"); |
| return status; |
| } |
| |
| txnid_t txnid; |
| vmoid_t vmoid; |
| fifo_client_t* client; |
| status = register_fast_block_io(part_fd, mvmo->GetVmo(), &txnid, |
| &vmoid, &client); |
| if (status != ZX_OK) { |
| ERROR("Cannot register fast block I/O\n"); |
| return status; |
| } |
| |
| block_fifo_request_t request; |
| request.txnid = txnid; |
| request.vmoid = vmoid; |
| request.opcode = BLOCKIO_WRITE; |
| status = stream_partition(mvmo.get(), client, &request, fd, info); |
| block_fifo_release_client(client); |
| if (status != ZX_OK) { |
| ERROR("Failed to stream partition\n"); |
| return status; |
| } |
| |
| if ((void*)finalizeCb != nullptr) { |
| if ((status = initialize_gpt(gpt_path, &gpt_fd, &gpt)) != ZX_OK) { |
| ERROR("Cannot re-initialize GPT\n"); |
| return status; |
| } |
| gpt_partition_t* partition; |
| if ((status = partition_find<filterCb>(&info, gpt, &partition, nullptr)) != ZX_OK) { |
| ERROR("Cannot re-find partition\n"); |
| return status; |
| } |
| if (finalizeCb(partition)) { |
| gpt_device_sync(gpt); |
| } |
| gpt_device_release(gpt); |
| } |
| |
| LOG("Completed successfully\n"); |
| return ZX_OK; |
| } |
| |
| // Wipes the following partitions: |
| // - System |
| // - Data |
| // - Blobstore |
| // - FVM |
| // - EFI |
| // |
| // From the target GPT, leaving it (hopefully) in a state |
| // ready for a sparse FVM image to be installed. |
| int fvm_clean() { |
| char gpt_path[PATH_MAX]; |
| if (find_target_gpt(gpt_path)) { |
| ERROR("Couldn't find target GPT\n"); |
| return -1; |
| } |
| |
| fbl::unique_fd fd; |
| gpt_device_t* gpt; |
| if (initialize_gpt(gpt_path, &fd, &gpt)) { |
| ERROR("Couldn't initialize GPT\n"); |
| return -1; |
| } |
| |
| block_info_t info; |
| zx_status_t status; |
| if ((status = static_cast<zx_status_t>(ioctl_block_get_info(fd.get(), &info))) < 0) { |
| ERROR("Couldn't get GPT block info\n"); |
| return status; |
| } |
| |
| bool modify = false; |
| for (size_t i = 0; i < PARTITIONS_COUNT; i++) { |
| if (!gpt->partitions[i]) { |
| continue; |
| } |
| const uint8_t system_type[GPT_GUID_LEN] = GUID_SYSTEM_VALUE; |
| const uint8_t data_type[GPT_GUID_LEN] = GUID_DATA_VALUE; |
| const uint8_t install_type[GPT_GUID_LEN] = GUID_INSTALL_VALUE; |
| const uint8_t blobfs_type[GPT_GUID_LEN] = GUID_BLOBFS_VALUE; |
| const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE; |
| |
| char name[GPT_NAME_LEN]; |
| memset(name, 0, sizeof(name)); |
| utf16_to_cstring(name, (uint16_t*)gpt->partitions[i]->name, GPT_NAME_LEN); |
| |
| if (!memcmp(gpt->partitions[i]->type, system_type, GPT_GUID_LEN)) { |
| LOG("Removing system partition\n"); |
| } else if (!memcmp(gpt->partitions[i]->type, data_type, GPT_GUID_LEN)) { |
| LOG("Removing data partition\n"); |
| } else if (!memcmp(gpt->partitions[i]->type, install_type, GPT_GUID_LEN)) { |
| LOG("Removing install partition\n"); |
| } else if (!memcmp(gpt->partitions[i]->type, blobfs_type, GPT_GUID_LEN)) { |
| LOG("Removing blobstore partition\n"); |
| } else if (!memcmp(gpt->partitions[i]->type, fvm_type, GPT_GUID_LEN)) { |
| LOG("Removing FVM partition\n"); |
| } else if (efi_filter_cb(&info, gpt->partitions[i])) { |
| LOG("Removing EFI partition\n"); |
| } else { |
| continue; |
| } |
| modify = true; |
| |
| // Overwrite the first 8k to (hackily) ensure the destroyed partition |
| // doesn't "reappear" in place. |
| char buf[8192]; |
| memset(buf, 0, sizeof(buf)); |
| fbl::unique_fd pfd(open_partition(gpt->partitions[i]->guid, |
| gpt->partitions[i]->type, ZX_SEC(2), |
| nullptr)); |
| if (!pfd) { |
| ERROR("Warning: Could not open partition to overwrite first 8KB\n"); |
| } else { |
| write(pfd.get(), buf, sizeof(buf)); |
| } |
| |
| if (gpt_partition_remove(gpt, gpt->partitions[i]->guid)) { |
| ERROR("Warning: Could not remove partition\n"); |
| } else { |
| // If we successfully clear the partition, then all subsequent |
| // partitions get shifted down. If we just deleted partition 'i', |
| // we now need to look at partition 'i' again, since it's now |
| // occupied by what was in 'i+1'. |
| i--; |
| } |
| } |
| if (modify) { |
| gpt_device_sync(gpt); |
| LOG("GPT updated, reboot strongly recommended immediately\n"); |
| } |
| gpt_device_release(gpt); |
| ioctl_block_rr_part(fd.get()); |
| return 0; |
| } |
| |
| void drain(fbl::unique_fd fd) { |
| char buf[8192]; |
| while (read(fd.get(), &buf, sizeof(buf)) > 0) |
| ; |
| } |
| |
| int usage() { |
| ERROR("install-disk-image [command] <options*>\n"); |
| ERROR("Commands:\n"); |
| ERROR(" install-fvm : Install a sparse FVM to the device\n"); |
| ERROR(" install-efi : Install an EFI partition to the device\n"); |
| ERROR(" install-kernc : Install a KERN-C CrOS partition to the device\n"); |
| ERROR(" wipe : Clean up the install disk\n"); |
| ERROR("Options:\n"); |
| ERROR(" --file <file>: Read from FILE instead of stdin\n"); |
| ERROR(" --force: Install partition even if inappropriate for the device\n"); |
| return -1; |
| } |
| |
| int main(int argc, char** argv) { |
| auto force = false; |
| if (argc < 2) { |
| ERROR("install-disk-image needs a command\n"); |
| return usage(); |
| } |
| argc--; |
| argv++; |
| char* cmd = argv[0]; |
| |
| argc--; |
| argv++; |
| |
| fbl::unique_fd fd(STDIN_FILENO); |
| while (argc > 0) { |
| if (!strcmp(argv[0], "--file")) { |
| argc--; |
| argv++; |
| if (argc < 1) { |
| ERROR("'--file' argument requires a file\n"); |
| return -1; |
| } |
| fd.reset(open(argv[0], O_RDONLY)); |
| if (!fd) { |
| ERROR("Couldn't open supplied file\n"); |
| return -1; |
| } |
| argc--; |
| argv++; |
| } else if (!strcmp(argv[0], "--force")) { |
| argc--; |
| argv++; |
| force = true; |
| } else { |
| return usage(); |
| } |
| } |
| |
| // The following code block computes a heuristic against CROS devices. In |
| // the case where we detect a CROS device, or where we initialized an empty |
| // GPT, we will avoid writing a KERNC partition (essentially, assume EFI |
| // device). |
| bool is_cros_device = false; |
| { |
| char gpt_path[PATH_MAX]; |
| if (!find_target_gpt(gpt_path)) { |
| fbl::unique_fd gpt_fd(open(gpt_path, O_RDWR)); |
| if (!fd) { |
| ERROR("Failed to open GPT\n"); |
| return ZX_ERR_IO; |
| } |
| gpt_device_t* gpt; |
| if (initialize_gpt(gpt_path, &gpt_fd, &gpt)) { |
| return ZX_ERR_IO; |
| } |
| is_cros_device = is_cros(gpt); |
| gpt_device_release(gpt); |
| } |
| } |
| |
| zx_status_t status; |
| if (!strcmp(cmd, "install-efi")) { |
| if (is_cros_device && !force) { |
| LOG("SKIPPING EFI install on CROS device, pass --force if desired.\n"); |
| drain(fbl::move(fd)); |
| return 0; |
| } |
| status = partition_pave<efi_filter_cb, efi_create_cb, nullptr>(fbl::move(fd)); |
| return status == ZX_OK ? 0 : -1; |
| } else if (!strcmp(cmd, "install-kernc")) { |
| if (!is_cros_device && !force) { |
| LOG("SKIPPING KERNC install on non-CROS device, pass --force if desired.\n"); |
| drain(fbl::move(fd)); |
| return 0; |
| } |
| status = partition_pave<kernc_filter_cb, kernc_create_cb, kernc_finalize_cb>(fbl::move(fd)); |
| return status == ZX_OK ? 0 : -1; |
| } else if (!strcmp(cmd, "install-fvm")) { |
| return fvm_pave(fbl::move(fd)); |
| } else if (!strcmp(cmd, "wipe")) { |
| return fvm_clean(); |
| } |
| return usage(); |
| } |