/* * 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 #include #include "reader.h" #include "utility.h" #include "writer.h" namespace android { namespace fs_mgr { using android::base::unique_fd; #if defined(_WIN32) static const int O_NOFOLLOW = 0; #endif std::unique_ptr ReadFromImageFile(int fd) { std::unique_ptr buffer = std::make_unique(LP_METADATA_GEOMETRY_SIZE); if (SeekFile64(fd, 0, SEEK_SET) < 0) { PERROR << __PRETTY_FUNCTION__ << " lseek failed"; return nullptr; } if (!android::base::ReadFully(fd, buffer.get(), LP_METADATA_GEOMETRY_SIZE)) { PERROR << __PRETTY_FUNCTION__ << " read failed"; return nullptr; } LpMetadataGeometry geometry; if (!ParseGeometry(buffer.get(), &geometry)) { return nullptr; } return ParseMetadata(geometry, fd); } std::unique_ptr 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(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 ReadFromImageFile(const std::string& image_file) { unique_fd fd = GetControlFileOrOpen(image_file.c_str(), O_RDONLY | O_CLOEXEC); if (fd < 0) { PERROR << __PRETTY_FUNCTION__ << " open failed: " << image_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) { unique_fd fd(open(file, O_CREAT | O_RDWR | O_TRUNC | O_CLOEXEC, 0644)); if (fd < 0) { PERROR << __PRETTY_FUNCTION__ << " open failed: " << file; return false; } return WriteToImageFile(fd, input); } ImageBuilder::ImageBuilder(const LpMetadata& metadata, uint32_t block_size, const std::map& images, bool sparsify) : metadata_(metadata), geometry_(metadata.geometry), block_size_(block_size), sparsify_(sparsify), images_(images) { uint64_t total_size = GetTotalSuperPartitionSize(metadata); if (block_size % LP_SECTOR_SIZE != 0) { LERROR << "Block size must be a multiple of the sector size, " << LP_SECTOR_SIZE; return; } if (total_size % block_size != 0) { LERROR << "Device size must be a multiple of the block size, " << block_size; return; } if (metadata.geometry.metadata_max_size % block_size != 0) { LERROR << "Metadata max size must be a multiple of the block size, " << block_size; return; } if (LP_METADATA_GEOMETRY_SIZE % block_size != 0) { LERROR << "Geometry size is not a multiple of the block size, " << block_size; return; } if (LP_PARTITION_RESERVED_BYTES % block_size != 0) { LERROR << "Reserved size is not a multiple of the block size, " << block_size; return; } uint64_t num_blocks = total_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; } for (const auto& block_device : metadata.block_devices) { SparsePtr file(sparse_file_new(block_size_, block_device.size), sparse_file_destroy); if (!file) { LERROR << "Could not allocate sparse file of size " << block_device.size; return; } device_images_.emplace_back(std::move(file)); } } bool ImageBuilder::IsValid() const { return device_images_.size() == metadata_.block_devices.size(); } bool ImageBuilder::Export(const char* file) { unique_fd fd(open(file, O_CREAT | O_RDWR | O_TRUNC | O_CLOEXEC, 0644)); if (fd < 0) { PERROR << "open failed: " << file; return false; } if (device_images_.size() > 1) { LERROR << "Cannot export to a single image on retrofit builds."; return false; } // No gzip compression; no checksum. int ret = sparse_file_write(device_images_[0].get(), fd, false, sparsify_, false); if (ret != 0) { LERROR << "sparse_file_write failed (error code " << ret << ")"; return false; } return true; } bool ImageBuilder::ExportFiles(const std::string& output_dir) { for (size_t i = 0; i < device_images_.size(); i++) { std::string name = GetBlockDevicePartitionName(metadata_.block_devices[i]); std::string file_name = "super_" + name + ".img"; std::string file_path = output_dir + "/" + file_name; static const int kOpenFlags = O_CREAT | O_RDWR | O_TRUNC | O_CLOEXEC | O_NOFOLLOW; unique_fd fd(open(file_path.c_str(), kOpenFlags, 0644)); if (fd < 0) { PERROR << "open failed: " << file_path; return false; } // No gzip compression; no checksum. int ret = sparse_file_write(device_images_[i].get(), fd, false, sparsify_, false); if (ret != 0) { LERROR << "sparse_file_write failed (error code " << ret << ")"; return false; } } return true; } bool ImageBuilder::AddData(sparse_file* file, const std::string& blob, uint64_t sector) { uint32_t block; if (!SectorToBlock(sector, &block)) { return false; } void* data = const_cast(blob.data()); int ret = sparse_file_add_data(file, data, blob.size(), block); if (ret != 0) { LERROR << "sparse_file_add_data failed (error code " << ret << ")"; return false; } return true; } bool ImageBuilder::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; } uint64_t ImageBuilder::BlockToSector(uint64_t block) const { return (block * block_size_) / LP_SECTOR_SIZE; } bool ImageBuilder::Build() { if (sparse_file_add_fill(device_images_[0].get(), 0, LP_PARTITION_RESERVED_BYTES, 0) < 0) { LERROR << "Could not add initial sparse block for reserved zeroes"; return false; } std::string geometry_blob = SerializeGeometry(geometry_); std::string metadata_blob = SerializeMetadata(metadata_); metadata_blob.resize(geometry_.metadata_max_size); // Two copies of geometry, then two copies of each metadata slot. all_metadata_ += geometry_blob + geometry_blob; for (size_t i = 0; i < geometry_.metadata_slot_count * 2; i++) { all_metadata_ += metadata_blob; } uint64_t first_sector = LP_PARTITION_RESERVED_BYTES / LP_SECTOR_SIZE; if (!AddData(device_images_[0].get(), all_metadata_, first_sector)) { return false; } if (!CheckExtentOrdering()) { 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; } 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 ImageBuilder::AddPartitionImage(const LpMetadataPartition& partition, const std::string& file) { // Track which extent we're processing. uint32_t extent_index = partition.first_extent_index; const LpMetadataExtent& extent = metadata_.extents[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; } uint64_t partition_size = ComputePartitionSize(partition); if (file_length > partition_size) { LERROR << "Image for partition '" << GetPartitionName(partition) << "' is greater than its size (" << file_length << ", expected " << partition_size << ")"; return false; } if (SeekFile64(fd, 0, SEEK_SET)) { PERROR << "lseek failed"; return false; } // We track the current logical sector and the position the current extent // ends at. uint64_t output_sector = 0; uint64_t extent_last_sector = extent.num_sectors; // We also track the output device and the current output block within that // device. uint32_t output_block; if (!SectorToBlock(extent.target_data, &output_block)) { return false; } sparse_file* output_device = device_images_[extent.target_source].get(); // Proceed to read the file and build sparse images. uint64_t pos = 0; uint64_t remaining = file_length; while (remaining) { // Check if we need to advance to the next extent. if (output_sector == extent_last_sector) { extent_index++; if (extent_index >= partition.first_extent_index + partition.num_extents) { LERROR << "image is larger than extent table"; return false; } const LpMetadataExtent& extent = metadata_.extents[extent_index]; extent_last_sector += extent.num_sectors; output_device = device_images_[extent.target_source].get(); if (!SectorToBlock(extent.target_data, &output_block)) { return false; } } 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(output_device, 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(output_device, 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_sector += block_size_ / LP_SECTOR_SIZE; output_block++; } return true; } uint64_t ImageBuilder::ComputePartitionSize(const LpMetadataPartition& partition) const { uint64_t sectors = 0; for (size_t i = 0; i < partition.num_extents; i++) { sectors += metadata_.extents[partition.first_extent_index + i].num_sectors; } return sectors * LP_SECTOR_SIZE; } // For simplicity, we don't allow serializing any configuration: extents must // be ordered, such that any extent at position I in the table occurs *before* // any extent after position I, for the same block device. We validate that // here. // // Without this, it would be more difficult to find the appropriate extent for // an output block. With this guarantee it is a linear walk. bool ImageBuilder::CheckExtentOrdering() { std::vector last_sectors(metadata_.block_devices.size()); for (const auto& extent : metadata_.extents) { if (extent.target_type != LP_TARGET_TYPE_LINEAR) { LERROR << "Extents must all be type linear."; return false; } if (extent.target_data <= last_sectors[extent.target_source]) { LERROR << "Extents must appear in increasing order."; return false; } if ((extent.num_sectors * LP_SECTOR_SIZE) % block_size_ != 0) { LERROR << "Extents must be aligned to the block size."; return false; } last_sectors[extent.target_source] = extent.target_data; } return true; } int ImageBuilder::OpenImageFile(const std::string& file) { android::base::unique_fd source_fd = GetControlFileOrOpen(file.c_str(), O_RDONLY | O_CLOEXEC); if (source_fd < 0) { PERROR << "open image file failed: " << file; return -1; } SparsePtr 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; } TemporaryFile tf; if (tf.fd < 0) { PERROR << "make temporary file failed"; return -1; } // We temporarily unsparse the file, rather than try to merge its chunks. int rv = sparse_file_write(source.get(), tf.fd, false, false, false); if (rv) { LERROR << "sparse_file_write failed with code: " << rv; return -1; } temp_fds_.push_back(android::base::unique_fd(tf.release())); return temp_fds_.back().get(); } bool WriteToImageFile(const char* file, const LpMetadata& metadata, uint32_t block_size, const std::map& images, bool sparsify) { ImageBuilder builder(metadata, block_size, images, sparsify); return builder.IsValid() && builder.Build() && builder.Export(file); } bool WriteSplitImageFiles(const std::string& output_dir, const LpMetadata& metadata, uint32_t block_size, const std::map& images, bool sparsify) { ImageBuilder builder(metadata, block_size, images, sparsify); return builder.IsValid() && builder.Build() && builder.ExportFiles(output_dir); } } // namespace fs_mgr } // namespace android