blob: 742b1d0c1dfd2fa2d88ed5fe45c594aebda020e1 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "images.h"
#include <limits.h>
#include <android-base/file.h>
#include <android-base/unique_fd.h>
#include <sparse/sparse.h>
#include "reader.h"
#include "utility.h"
#include "writer.h"
namespace android {
namespace fs_mgr {
std::unique_ptr<LpMetadata> ReadFromImageFile(int fd) {
LpMetadataGeometry geometry;
if (!ReadLogicalPartitionGeometry(fd, &geometry)) {
return nullptr;
}
if (SeekFile64(fd, LP_METADATA_GEOMETRY_SIZE, SEEK_SET) < 0) {
PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << LP_METADATA_GEOMETRY_SIZE;
return nullptr;
}
return ParseMetadata(geometry, fd);
}
std::unique_ptr<LpMetadata> ReadFromImageBlob(const void* data, size_t bytes) {
if (bytes < LP_METADATA_GEOMETRY_SIZE) {
LERROR << __PRETTY_FUNCTION__ << ": " << bytes << " is smaller than geometry header";
return nullptr;
}
LpMetadataGeometry geometry;
if (!ParseGeometry(data, &geometry)) {
return nullptr;
}
const uint8_t* metadata_buffer =
reinterpret_cast<const uint8_t*>(data) + LP_METADATA_GEOMETRY_SIZE;
size_t metadata_buffer_size = bytes - LP_METADATA_GEOMETRY_SIZE;
return ParseMetadata(geometry, metadata_buffer, metadata_buffer_size);
}
std::unique_ptr<LpMetadata> ReadFromImageFile(const char* file) {
android::base::unique_fd fd(open(file, O_RDONLY));
if (fd < 0) {
PERROR << __PRETTY_FUNCTION__ << "open failed: " << file;
return nullptr;
}
return ReadFromImageFile(fd);
}
bool WriteToImageFile(int fd, const LpMetadata& input) {
std::string geometry = SerializeGeometry(input.geometry);
std::string metadata = SerializeMetadata(input);
std::string everything = geometry + metadata;
if (!android::base::WriteFully(fd, everything.data(), everything.size())) {
PERROR << __PRETTY_FUNCTION__ << "write " << everything.size() << " bytes failed";
return false;
}
return true;
}
bool WriteToImageFile(const char* file, const LpMetadata& input) {
android::base::unique_fd fd(open(file, O_CREAT | O_RDWR | O_TRUNC, 0644));
if (fd < 0) {
PERROR << __PRETTY_FUNCTION__ << "open failed: " << file;
return false;
}
return WriteToImageFile(fd, input);
}
// We use an object to build the sparse file since it requires that data
// pointers be held alive until the sparse file is destroyed. It's easier
// to do this when the data pointers are all in one place.
class SparseBuilder {
public:
SparseBuilder(const LpMetadata& metadata, uint32_t block_size,
const std::map<std::string, std::string>& images);
bool Build();
bool Export(const char* file);
bool IsValid() const { return file_ != nullptr; }
private:
bool AddData(const std::string& blob, uint64_t sector);
bool AddPartitionImage(const LpMetadataPartition& partition, const std::string& file);
int OpenImageFile(const std::string& file);
bool SectorToBlock(uint64_t sector, uint32_t* block);
const LpMetadata& metadata_;
const LpMetadataGeometry& geometry_;
uint32_t block_size_;
std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)> file_;
std::string primary_blob_;
std::string backup_blob_;
std::map<std::string, std::string> images_;
std::vector<android::base::unique_fd> temp_fds_;
};
SparseBuilder::SparseBuilder(const LpMetadata& metadata, uint32_t block_size,
const std::map<std::string, std::string>& images)
: metadata_(metadata),
geometry_(metadata.geometry),
block_size_(block_size),
file_(sparse_file_new(block_size_, geometry_.block_device_size), sparse_file_destroy),
images_(images) {}
bool SparseBuilder::Export(const char* file) {
android::base::unique_fd fd(open(file, O_CREAT | O_RDWR | O_TRUNC, 0644));
if (fd < 0) {
PERROR << "open failed: " << file;
return false;
}
// No gzip compression; sparseify; no checksum.
int ret = sparse_file_write(file_.get(), fd, false, true, false);
if (ret != 0) {
LERROR << "sparse_file_write failed (error code " << ret << ")";
return false;
}
return true;
}
bool SparseBuilder::AddData(const std::string& blob, uint64_t sector) {
uint32_t block;
if (!SectorToBlock(sector, &block)) {
return false;
}
void* data = const_cast<char*>(blob.data());
int ret = sparse_file_add_data(file_.get(), data, blob.size(), block);
if (ret != 0) {
LERROR << "sparse_file_add_data failed (error code " << ret << ")";
return false;
}
return true;
}
bool SparseBuilder::SectorToBlock(uint64_t sector, uint32_t* block) {
// The caller must ensure that the metadata has an alignment that is a
// multiple of the block size. liblp will take care of the rest, ensuring
// that all partitions are on an aligned boundary. Therefore all writes
// should be block-aligned, and if they are not, the table was misconfigured.
// Note that the default alignment is 1MiB, which is a multiple of the
// default block size (4096).
if ((sector * LP_SECTOR_SIZE) % block_size_ != 0) {
LERROR << "sector " << sector << " is not aligned to block size " << block_size_;
return false;
}
*block = (sector * LP_SECTOR_SIZE) / block_size_;
return true;
}
bool SparseBuilder::Build() {
std::string geometry_blob = SerializeGeometry(geometry_);
std::string metadata_blob = SerializeMetadata(metadata_);
metadata_blob.resize(geometry_.metadata_max_size);
std::string all_metadata;
for (size_t i = 0; i < geometry_.metadata_slot_count; i++) {
all_metadata += metadata_blob;
}
// Metadata immediately follows geometry, and we write the same metadata
// to all slots. Note that we don't bother trying to write skip chunks
// here since it's a small amount of data.
primary_blob_ = geometry_blob + all_metadata;
if (!AddData(primary_blob_, 0)) {
return false;
}
for (const auto& partition : metadata_.partitions) {
auto iter = images_.find(GetPartitionName(partition));
if (iter == images_.end()) {
continue;
}
if (!AddPartitionImage(partition, iter->second)) {
return false;
}
images_.erase(iter);
}
if (!images_.empty()) {
LERROR << "Partition image was specified but no partition was found.";
return false;
}
// The backup area contains all metadata slots, and then geometry. Similar
// to before we write the metadata to every slot.
int64_t backup_offset = GetBackupMetadataOffset(geometry_, 0);
uint64_t backups_start = geometry_.block_device_size + backup_offset;
uint64_t backup_sector = backups_start / LP_SECTOR_SIZE;
backup_blob_ = all_metadata + geometry_blob;
if (!AddData(backup_blob_, backup_sector)) {
return false;
}
return true;
}
static inline bool HasFillValue(uint32_t* buffer, size_t count) {
uint32_t fill_value = buffer[0];
for (size_t i = 1; i < count; i++) {
if (fill_value != buffer[i]) {
return false;
}
}
return true;
}
bool SparseBuilder::AddPartitionImage(const LpMetadataPartition& partition,
const std::string& file) {
if (partition.num_extents != 1) {
LERROR << "Partition for new tables should not have more than one extent: "
<< GetPartitionName(partition);
return false;
}
const LpMetadataExtent& extent = metadata_.extents[partition.first_extent_index];
if (extent.target_type != LP_TARGET_TYPE_LINEAR) {
LERROR << "Partition should only have linear extents: " << GetPartitionName(partition);
return false;
}
int fd = OpenImageFile(file);
if (fd < 0) {
LERROR << "Could not open image for partition: " << GetPartitionName(partition);
return false;
}
// Make sure the image does not exceed the partition size.
uint64_t file_length;
if (!GetDescriptorSize(fd, &file_length)) {
LERROR << "Could not compute image size";
return false;
}
if (file_length > extent.num_sectors * LP_SECTOR_SIZE) {
LERROR << "Image for partition '" << GetPartitionName(partition)
<< "' is greater than its size";
return false;
}
if (SeekFile64(fd, 0, SEEK_SET)) {
PERROR << "lseek failed";
return false;
}
uint32_t output_block;
if (!SectorToBlock(extent.target_data, &output_block)) {
return false;
}
uint64_t pos = 0;
uint64_t remaining = file_length;
while (remaining) {
uint32_t buffer[block_size_ / sizeof(uint32_t)];
size_t read_size = remaining >= sizeof(buffer) ? sizeof(buffer) : size_t(remaining);
if (!android::base::ReadFully(fd, buffer, sizeof(buffer))) {
PERROR << "read failed";
return false;
}
if (read_size != sizeof(buffer) || !HasFillValue(buffer, read_size / sizeof(uint32_t))) {
int rv = sparse_file_add_fd(file_.get(), fd, pos, read_size, output_block);
if (rv) {
LERROR << "sparse_file_add_fd failed with code: " << rv;
return false;
}
} else {
int rv = sparse_file_add_fill(file_.get(), buffer[0], read_size, output_block);
if (rv) {
LERROR << "sparse_file_add_fill failed with code: " << rv;
return false;
}
}
pos += read_size;
remaining -= read_size;
output_block++;
}
return true;
}
int SparseBuilder::OpenImageFile(const std::string& file) {
android::base::unique_fd source_fd(open(file.c_str(), O_RDONLY));
if (source_fd < 0) {
PERROR << "open image file failed: " << file;
return -1;
}
std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)> source(
sparse_file_import(source_fd, true, true), sparse_file_destroy);
if (!source) {
int fd = source_fd.get();
temp_fds_.push_back(std::move(source_fd));
return fd;
}
char temp_file[PATH_MAX];
snprintf(temp_file, sizeof(temp_file), "%s/imageXXXXXX", P_tmpdir);
android::base::unique_fd temp_fd(mkstemp(temp_file));
if (temp_fd < 0) {
PERROR << "mkstemp failed";
return -1;
}
if (unlink(temp_file) < 0) {
PERROR << "unlink failed";
return -1;
}
// We temporarily unsparse the file, rather than try to merge its chunks.
int rv = sparse_file_write(source.get(), temp_fd, false, false, false);
if (rv) {
LERROR << "sparse_file_write failed with code: " << rv;
return -1;
}
temp_fds_.push_back(std::move(temp_fd));
return temp_fds_.back().get();
}
bool WriteToSparseFile(const char* file, const LpMetadata& metadata, uint32_t block_size,
const std::map<std::string, std::string>& images) {
if (block_size % LP_SECTOR_SIZE != 0) {
LERROR << "Block size must be a multiple of the sector size, " << LP_SECTOR_SIZE;
return false;
}
if (metadata.geometry.block_device_size % block_size != 0) {
LERROR << "Device size must be a multiple of the block size, " << block_size;
return false;
}
uint64_t num_blocks = metadata.geometry.block_device_size % block_size;
if (num_blocks >= UINT_MAX) {
// libsparse counts blocks in unsigned 32-bit integers, so we check to
// make sure we're not going to overflow.
LERROR << "Block device is too large to encode with libsparse.";
return false;
}
SparseBuilder builder(metadata, block_size, images);
if (!builder.IsValid()) {
LERROR << "Could not allocate sparse file of size " << metadata.geometry.block_device_size;
return false;
}
if (!builder.Build()) {
return false;
}
return builder.Export(file);
}
} // namespace fs_mgr
} // namespace android