diff options
author | David Anderson <dvander@google.com> | 2019-04-24 18:16:27 -0700 |
---|---|---|
committer | David Anderson <dvander@google.com> | 2019-04-26 23:00:50 +0000 |
commit | 1f0277ae23c3f7a4c52629b666d020ebc4e9ac96 (patch) | |
tree | a37e9f9c421dcef83f14c0fa0901c0c8115a7013 | |
parent | 5e91e3fce339d94a0f8e90857c3f0aff07df27fa (diff) | |
download | extras-1f0277ae23c3f7a4c52629b666d020ebc4e9ac96.tar.gz |
Add an lpunpack tool for extracting partitions from super.img.
This tool performs the inverse of lpmake. It can extract partition
images out of a prebuilt super.img. There are a few caveats:
- lpunpack does not support retrofit/split images.
- lpunpack does not support sparse super images. They must be unsparsed
before using with lpunpack.
- Partition names will be suffixed on A/B devices, meaning,
vendor_a.img will contain vendor.img and vendor_b.img will be
0-length.
Note that lpunpack will store output files with holes for zero blocks,
as a space-saving measure.
Usage: lpunpack [options] SUPER_IMAGE [OUTPUT_DIR]
Bug: 131173010
Test: m superimage
mm lpunpack
lpunpack super.img
lpunpack super.img out/
lpunpack -p vendor_a super.img
lpunpack -p unknown super.img
sha1sum image with precursor image
Change-Id: Iad3f7362ae6ce767ddc7de2e8cfa7e4a47190e99
Merged-In: Iad3f7362ae6ce767ddc7de2e8cfa7e4a47190e99
-rw-r--r-- | partition_tools/Android.bp | 19 | ||||
-rw-r--r-- | partition_tools/lpunpack.cc | 326 |
2 files changed, 345 insertions, 0 deletions
diff --git a/partition_tools/Android.bp b/partition_tools/Android.bp index b68feba3..0f03c933 100644 --- a/partition_tools/Android.bp +++ b/partition_tools/Android.bp @@ -139,3 +139,22 @@ cc_binary { "lpdumpd.cc", ], } + +cc_binary { + name: "lpunpack", + defaults: ["lp_defaults"], + device_supported: false, + host_supported: true, + shared_libs: [ + "libbase", + "liblog", + "liblp", + "libsparse", + ], + srcs: [ + "lpunpack.cc", + ], + cppflags: [ + "-D_FILE_OFFSET_BITS=64", + ], +} diff --git a/partition_tools/lpunpack.cc b/partition_tools/lpunpack.cc new file mode 100644 index 00000000..1f870c5d --- /dev/null +++ b/partition_tools/lpunpack.cc @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2019 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 <fcntl.h> +#include <getopt.h> +#include <stdio.h> +#include <sysexits.h> +#include <sys/types.h> +#include <unistd.h> + +#include <iostream> +#include <limits> +#include <string> +#include <unordered_map> +#include <unordered_set> + +#include <android-base/file.h> +#include <android-base/parseint.h> +#include <liblp/liblp.h> +#include <sparse/sparse.h> + +using namespace android::fs_mgr; +using android::base::unique_fd; +using SparsePtr = std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)>; + +class ImageExtractor final { + public: + ImageExtractor(unique_fd&& image_fd, std::unique_ptr<LpMetadata>&& metadata, + std::unordered_set<std::string>&& partitions, const std::string& output_dir); + + bool Extract(); + + private: + bool BuildPartitionList(); + bool ExtractPartition(const LpMetadataPartition* partition); + bool ExtractExtent(const LpMetadataExtent& extent, int output_fd); + + unique_fd image_fd_; + std::unique_ptr<LpMetadata> metadata_; + std::unordered_set<std::string> partitions_; + std::string output_dir_; + std::unordered_map<std::string, const LpMetadataPartition*> partition_map_; +}; + +// Note that "sparse" here refers to filesystem sparse, not the Android sparse +// file format. +class SparseWriter final { + public: + SparseWriter(int output_fd, int image_fd, uint32_t block_size); + + bool WriteExtent(const LpMetadataExtent& extent); + bool Finish(); + + private: + bool WriteBlock(const uint8_t* data); + + int output_fd_; + int image_fd_; + uint32_t block_size_; + off_t hole_size_ = 0; +}; + +/* Prints program usage to |where|. */ +static int usage(int /* argc */, char* argv[]) { + fprintf(stderr, + "%s - command-line tool for extracting partition images from super\n" + "\n" + "Usage:\n" + " %s [options...] SUPER_IMAGE [OUTPUT_DIR]\n" + "\n" + "Options:\n" + " -p, --partition=NAME Extract the named partition. This can\n" + " be specified multiple times.\n" + " -S, --slot=NUM Slot number (default is 0).\n", + argv[0], argv[0]); + return EX_USAGE; +} + +int main(int argc, char* argv[]) { + // clang-format off + struct option options[] = { + { "partition", required_argument, nullptr, 'p' }, + { "slot", required_argument, nullptr, 'S' }, + { nullptr, 0, nullptr, 0 }, + }; + // clang-format on + + uint32_t slot_num = 0; + std::unordered_set<std::string> partitions; + + int rv, index; + while ((rv = getopt_long_only(argc, argv, "+p:sh", options, &index)) != -1) { + switch (rv) { + case 'h': + usage(argc, argv); + return EX_OK; + case '?': + std::cerr << "Unrecognized argument.\n"; + return usage(argc, argv); + case 'S': + if (!android::base::ParseUint(optarg, &slot_num)) { + std::cerr << "Slot must be a valid unsigned number.\n"; + return usage(argc, argv); + } + break; + case 'p': + partitions.emplace(optarg); + break; + } + } + + if (optind + 1 > argc) { + std::cerr << "Missing super image argument.\n"; + return usage(argc, argv); + } + std::string super_path = argv[optind++]; + + std::string output_dir = "."; + if (optind + 1 <= argc) { + output_dir = argv[optind++]; + } + + if (optind < argc) { + std::cerr << "Unrecognized command-line arguments.\n"; + return usage(argc, argv); + } + + // Done reading arguments; open super.img. PartitionOpener will decorate + // relative paths with /dev/block/by-name, so get an absolute path here. + std::string abs_super_path; + if (!android::base::Realpath(super_path, &abs_super_path)) { + std::cerr << "realpath failed: " << super_path << ": " << strerror(errno) << "\n"; + return EX_OSERR; + } + + unique_fd fd(open(super_path.c_str(), O_RDONLY | O_CLOEXEC)); + if (fd < 0) { + std::cerr << "open failed: " << abs_super_path << ": " << strerror(errno) << "\n"; + return EX_OSERR; + } + + auto metadata = ReadMetadata(abs_super_path, slot_num); + if (!metadata) { + SparsePtr ptr(sparse_file_import(fd, false, false), sparse_file_destroy); + if (ptr) { + std::cerr << "This image appears to be a sparse image. It must be " + "unsparsed to be" + << " unpacked.\n"; + return EX_USAGE; + } + std::cerr << "Image does not appear to be in super-partition format.\n"; + return EX_USAGE; + } + + ImageExtractor extractor(std::move(fd), std::move(metadata), std::move(partitions), output_dir); + if (!extractor.Extract()) { + return EX_SOFTWARE; + } + return EX_OK; +} + +ImageExtractor::ImageExtractor(unique_fd&& image_fd, std::unique_ptr<LpMetadata>&& metadata, + std::unordered_set<std::string>&& partitions, + const std::string& output_dir) + : image_fd_(std::move(image_fd)), + metadata_(std::move(metadata)), + partitions_(std::move(partitions)), + output_dir_(output_dir) {} + +bool ImageExtractor::Extract() { + if (!BuildPartitionList()) { + return false; + } + + for (const auto& [name, info] : partition_map_) { + if (!ExtractPartition(info)) { + return false; + } + } + return true; +} + +bool ImageExtractor::BuildPartitionList() { + bool extract_all = partitions_.empty(); + + for (const auto& partition : metadata_->partitions) { + auto name = GetPartitionName(partition); + if (extract_all || partitions_.count(name)) { + partition_map_[name] = &partition; + partitions_.erase(name); + } + } + + if (!extract_all && !partitions_.empty()) { + std::cerr << "Could not find partition: " << *partitions_.begin() << "\n"; + return false; + } + return true; +} + +bool ImageExtractor::ExtractPartition(const LpMetadataPartition* partition) { + // Validate the extents and find the total image size. + uint64_t total_size = 0; + for (uint32_t i = 0; i < partition->num_extents; i++) { + uint32_t index = partition->first_extent_index + i; + const LpMetadataExtent& extent = metadata_->extents[index]; + + if (extent.target_type != LP_TARGET_TYPE_LINEAR) { + std::cerr << "Unsupported target type in extent: " << extent.target_type << "\n"; + return false; + } + if (extent.target_source != 0) { + std::cerr << "Split super devices are not supported.\n"; + return false; + } + total_size += extent.num_sectors * LP_SECTOR_SIZE; + } + + // Make a temporary file so we can import it with sparse_file_read. + std::string output_path = output_dir_ + "/" + GetPartitionName(*partition) + ".img"; + unique_fd output_fd(open(output_path.c_str(), O_RDWR | O_CLOEXEC | O_CREAT | O_TRUNC, 0644)); + if (output_fd < 0) { + std::cerr << "open failed: " << output_path << ": " << strerror(errno) << "\n"; + return false; + } + + SparseWriter writer(output_fd, image_fd_, metadata_->geometry.logical_block_size); + + // Extract each extent into output_fd. + for (uint32_t i = 0; i < partition->num_extents; i++) { + uint32_t index = partition->first_extent_index + i; + const LpMetadataExtent& extent = metadata_->extents[index]; + + if (!writer.WriteExtent(extent)) { + return false; + } + } + return writer.Finish(); +} + +SparseWriter::SparseWriter(int output_fd, int image_fd, uint32_t block_size) + : output_fd_(output_fd), image_fd_(image_fd), block_size_(block_size) {} + +bool SparseWriter::WriteExtent(const LpMetadataExtent& extent) { + auto buffer = std::make_unique<uint8_t[]>(block_size_); + + off_t super_offset = extent.target_data * LP_SECTOR_SIZE; + if (lseek(image_fd_, super_offset, SEEK_SET) < 0) { + std::cerr << "image lseek failed: " << strerror(errno) << "\n"; + return false; + } + + uint64_t remaining_bytes = extent.num_sectors * LP_SECTOR_SIZE; + while (remaining_bytes) { + if (remaining_bytes < block_size_) { + std::cerr << "extent is not block-aligned\n"; + return false; + } + if (!android::base::ReadFully(image_fd_, buffer.get(), block_size_)) { + std::cerr << "read failed: " << strerror(errno) << "\n"; + return false; + } + if (!WriteBlock(buffer.get())) { + return false; + } + remaining_bytes -= block_size_; + } + return true; +} + +static bool ShouldSkipChunk(const uint8_t* data, size_t len) { + for (size_t i = 0; i < len; i++) { + if (data[i] != 0) { + return false; + } + } + return true; +} + +bool SparseWriter::WriteBlock(const uint8_t* data) { + if (ShouldSkipChunk(data, block_size_)) { + hole_size_ += block_size_; + return true; + } + + if (hole_size_) { + if (lseek(output_fd_, hole_size_, SEEK_CUR) < 0) { + std::cerr << "lseek failed: " << strerror(errno) << "\n"; + return false; + } + hole_size_ = 0; + } + if (!android::base::WriteFully(output_fd_, data, block_size_)) { + std::cerr << "write failed: " << strerror(errno) << "\n"; + return false; + } + return true; +} + +bool SparseWriter::Finish() { + if (hole_size_) { + off_t offset = lseek(output_fd_, 0, SEEK_CUR); + if (offset < 0) { + std::cerr << "lseek failed: " << strerror(errno) << "\n"; + return false; + } + if (ftruncate(output_fd_, offset + hole_size_) < 0) { + std::cerr << "ftruncate failed: " << strerror(errno) << "\n"; + return false; + } + } + return true; +} |