245 lines
9.7 KiB
C++
245 lines
9.7 KiB
C++
/*
|
|
* Copyright (C) 2007 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 <inttypes.h>
|
|
#include <unistd.h>
|
|
|
|
#include <string>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/unique_fd.h>
|
|
|
|
#include "liblp/reader.h"
|
|
#include "liblp/writer.h"
|
|
#include "utility.h"
|
|
|
|
namespace android {
|
|
namespace fs_mgr {
|
|
|
|
static std::string SerializeGeometry(const LpMetadataGeometry& input) {
|
|
LpMetadataGeometry geometry = input;
|
|
memset(geometry.checksum, 0, sizeof(geometry.checksum));
|
|
SHA256(&geometry, sizeof(geometry), geometry.checksum);
|
|
return std::string(reinterpret_cast<const char*>(&geometry), sizeof(geometry));
|
|
}
|
|
|
|
static bool CompareGeometry(const LpMetadataGeometry& g1, const LpMetadataGeometry& g2) {
|
|
return g1.metadata_max_size == g2.metadata_max_size &&
|
|
g1.metadata_slot_count == g2.metadata_slot_count &&
|
|
g1.first_logical_sector == g2.first_logical_sector &&
|
|
g1.last_logical_sector == g2.last_logical_sector;
|
|
}
|
|
|
|
static std::string SerializeMetadata(const LpMetadata& input) {
|
|
LpMetadata metadata = input;
|
|
LpMetadataHeader& header = metadata.header;
|
|
|
|
// Serialize individual tables.
|
|
std::string partitions(reinterpret_cast<const char*>(metadata.partitions.data()),
|
|
metadata.partitions.size() * sizeof(LpMetadataPartition));
|
|
std::string extents(reinterpret_cast<const char*>(metadata.extents.data()),
|
|
metadata.extents.size() * sizeof(LpMetadataExtent));
|
|
|
|
// Compute positions of tables.
|
|
header.partitions.offset = 0;
|
|
header.extents.offset = header.partitions.offset + partitions.size();
|
|
header.tables_size = header.extents.offset + extents.size();
|
|
|
|
// Compute payload checksum.
|
|
std::string tables = partitions + extents;
|
|
SHA256(tables.data(), tables.size(), header.tables_checksum);
|
|
|
|
// Compute header checksum.
|
|
memset(header.header_checksum, 0, sizeof(header.header_checksum));
|
|
SHA256(&header, sizeof(header), header.header_checksum);
|
|
|
|
std::string header_blob =
|
|
std::string(reinterpret_cast<const char*>(&metadata.header), sizeof(metadata.header));
|
|
return header_blob + tables;
|
|
}
|
|
|
|
// Perform sanity checks so we don't accidentally overwrite valid metadata
|
|
// with potentially invalid metadata, or random partition data with metadata.
|
|
static bool ValidateGeometryAndMetadata(const LpMetadata& metadata, uint64_t blockdevice_size,
|
|
uint64_t metadata_size) {
|
|
const LpMetadataHeader& header = metadata.header;
|
|
const LpMetadataGeometry& geometry = metadata.geometry;
|
|
// Validate the usable sector range.
|
|
if (geometry.first_logical_sector > geometry.last_logical_sector) {
|
|
LERROR << "Logical partition metadata has invalid sector range.";
|
|
return false;
|
|
}
|
|
// Make sure we're writing within the space reserved.
|
|
if (metadata_size > geometry.metadata_max_size) {
|
|
LERROR << "Logical partition metadata is too large.";
|
|
return false;
|
|
}
|
|
|
|
// Make sure the device has enough space to store two backup copies of the
|
|
// metadata.
|
|
uint64_t reserved_size = LP_METADATA_GEOMETRY_SIZE +
|
|
uint64_t(geometry.metadata_max_size) * geometry.metadata_slot_count;
|
|
if (reserved_size > blockdevice_size ||
|
|
reserved_size > geometry.first_logical_sector * LP_SECTOR_SIZE) {
|
|
LERROR << "Not enough space to store all logical partition metadata slots.";
|
|
return false;
|
|
}
|
|
if (blockdevice_size - reserved_size < (geometry.last_logical_sector + 1) * LP_SECTOR_SIZE) {
|
|
LERROR << "Not enough space to backup all logical partition metadata slots.";
|
|
return false;
|
|
}
|
|
|
|
// Make sure all partition entries reference valid extents.
|
|
for (const auto& partition : metadata.partitions) {
|
|
if (partition.first_extent_index + partition.num_extents > metadata.extents.size()) {
|
|
LERROR << "Partition references invalid extent.";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Make sure all linear extents have a valid range.
|
|
for (const auto& extent : metadata.extents) {
|
|
if (extent.target_type == LP_TARGET_TYPE_LINEAR) {
|
|
uint64_t physical_sector = extent.target_data;
|
|
if (physical_sector < geometry.first_logical_sector ||
|
|
physical_sector + extent.num_sectors > geometry.last_logical_sector) {
|
|
LERROR << "Extent table entry is out of bounds.";
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WritePartitionTable(const char* block_device, const LpMetadata& metadata, SyncMode sync_mode,
|
|
uint32_t slot_number) {
|
|
android::base::unique_fd fd(open(block_device, O_RDWR | O_SYNC));
|
|
if (fd < 0) {
|
|
PERROR << __PRETTY_FUNCTION__ << "open failed: " << block_device;
|
|
return false;
|
|
}
|
|
|
|
uint64_t size;
|
|
if (!GetDescriptorSize(fd, &size)) {
|
|
return false;
|
|
}
|
|
|
|
const LpMetadataGeometry& geometry = metadata.geometry;
|
|
if (sync_mode != SyncMode::Flash) {
|
|
// Verify that the old geometry is identical. If it's not, then we've
|
|
// based this new metadata on invalid assumptions.
|
|
LpMetadataGeometry old_geometry;
|
|
if (!ReadLogicalPartitionGeometry(block_device, &old_geometry)) {
|
|
return false;
|
|
}
|
|
if (!CompareGeometry(geometry, old_geometry)) {
|
|
LERROR << "Incompatible geometry in new logical partition metadata";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Make sure we're writing to a valid metadata slot.
|
|
if (slot_number >= geometry.metadata_slot_count) {
|
|
LERROR << "Invalid logical partition metadata slot number.";
|
|
return false;
|
|
}
|
|
|
|
// Before writing geometry and/or logical partition tables, perform some
|
|
// basic checks that the geometry and tables are coherent, and will fit
|
|
// on the given block device.
|
|
std::string blob = SerializeMetadata(metadata);
|
|
if (!ValidateGeometryAndMetadata(metadata, size, blob.size())) {
|
|
return false;
|
|
}
|
|
|
|
// First write geometry if this is a flash operation. It gets written to
|
|
// the first and last 4096-byte regions of the device.
|
|
if (sync_mode == SyncMode::Flash) {
|
|
std::string blob = SerializeGeometry(metadata.geometry);
|
|
if (SeekFile64(fd, 0, SEEK_SET) < 0) {
|
|
PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset 0";
|
|
return false;
|
|
}
|
|
if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
|
|
PERROR << __PRETTY_FUNCTION__ << "write " << blob.size()
|
|
<< " bytes failed: " << block_device;
|
|
return false;
|
|
}
|
|
if (SeekFile64(fd, -LP_METADATA_GEOMETRY_SIZE, SEEK_END) < 0) {
|
|
PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << -LP_METADATA_GEOMETRY_SIZE;
|
|
return false;
|
|
}
|
|
if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
|
|
PERROR << __PRETTY_FUNCTION__ << "backup write " << blob.size()
|
|
<< " bytes failed: " << block_device;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Write the primary copy of the metadata.
|
|
int64_t primary_offset = GetPrimaryMetadataOffset(geometry, slot_number);
|
|
if (SeekFile64(fd, primary_offset, SEEK_SET) < 0) {
|
|
PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << primary_offset;
|
|
return false;
|
|
}
|
|
if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
|
|
PERROR << __PRETTY_FUNCTION__ << "write " << blob.size()
|
|
<< " bytes failed: " << block_device;
|
|
return false;
|
|
}
|
|
|
|
// Write the backup copy of the metadata.
|
|
int64_t backup_offset = GetBackupMetadataOffset(geometry, slot_number);
|
|
int64_t abs_offset = SeekFile64(fd, backup_offset, SEEK_END);
|
|
if (abs_offset == (int64_t)-1) {
|
|
PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << backup_offset;
|
|
return false;
|
|
}
|
|
if (abs_offset < int64_t((geometry.last_logical_sector + 1) * LP_SECTOR_SIZE)) {
|
|
PERROR << __PRETTY_FUNCTION__ << "backup offset " << abs_offset
|
|
<< " is within logical partition bounds, sector " << geometry.last_logical_sector;
|
|
return false;
|
|
}
|
|
if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
|
|
PERROR << __PRETTY_FUNCTION__ << "backup write " << blob.size()
|
|
<< " bytes failed: " << block_device;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WriteToImageFile(const char* file, const LpMetadata& input) {
|
|
std::string geometry = SerializeGeometry(input.geometry);
|
|
std::string padding(LP_METADATA_GEOMETRY_SIZE - geometry.size(), '\0');
|
|
std::string metadata = SerializeMetadata(input);
|
|
|
|
std::string everything = geometry + padding + metadata;
|
|
|
|
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;
|
|
}
|
|
if (!android::base::WriteFully(fd, everything.data(), everything.size())) {
|
|
PERROR << __PRETTY_FUNCTION__ << "write " << everything.size() << " bytes failed: " << file;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace fs_mgr
|
|
} // namespace android
|