blob: b215c58e77d7ddaeb62ad2c014350b46fdf7739f [file] [log] [blame]
David Andersone2b1be12019-04-24 18:16:27 -07001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <fcntl.h>
18#include <getopt.h>
19#include <stdio.h>
20#include <sysexits.h>
21#include <sys/types.h>
22#include <unistd.h>
23
24#include <iostream>
25#include <limits>
26#include <string>
27#include <unordered_map>
28#include <unordered_set>
29
30#include <android-base/file.h>
31#include <android-base/parseint.h>
32#include <liblp/liblp.h>
33#include <sparse/sparse.h>
34
35using namespace android::fs_mgr;
36using android::base::unique_fd;
Daniel Köster9cd252e2023-03-24 11:20:44 +010037using android::base::borrowed_fd;
David Andersone2b1be12019-04-24 18:16:27 -070038using SparsePtr = std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)>;
39
40class ImageExtractor final {
41 public:
Daniel Köster9cd252e2023-03-24 11:20:44 +010042 ImageExtractor(std::vector<unique_fd>&& image_fds, std::unique_ptr<LpMetadata>&& metadata,
David Andersone2b1be12019-04-24 18:16:27 -070043 std::unordered_set<std::string>&& partitions, const std::string& output_dir);
44
45 bool Extract();
46
47 private:
48 bool BuildPartitionList();
49 bool ExtractPartition(const LpMetadataPartition* partition);
50 bool ExtractExtent(const LpMetadataExtent& extent, int output_fd);
51
Daniel Köster9cd252e2023-03-24 11:20:44 +010052 std::vector<unique_fd> image_fds_;
David Andersone2b1be12019-04-24 18:16:27 -070053 std::unique_ptr<LpMetadata> metadata_;
54 std::unordered_set<std::string> partitions_;
55 std::string output_dir_;
56 std::unordered_map<std::string, const LpMetadataPartition*> partition_map_;
57};
58
59// Note that "sparse" here refers to filesystem sparse, not the Android sparse
60// file format.
61class SparseWriter final {
62 public:
Daniel Köster9cd252e2023-03-24 11:20:44 +010063 SparseWriter(borrowed_fd output_fd, uint32_t block_size);
David Andersone2b1be12019-04-24 18:16:27 -070064
Daniel Köster9cd252e2023-03-24 11:20:44 +010065 bool WriteExtent(borrowed_fd image_fd, const LpMetadataExtent& extent);
David Andersone2b1be12019-04-24 18:16:27 -070066 bool Finish();
67
68 private:
69 bool WriteBlock(const uint8_t* data);
70
Daniel Köster9cd252e2023-03-24 11:20:44 +010071 borrowed_fd output_fd_;
David Andersone2b1be12019-04-24 18:16:27 -070072 uint32_t block_size_;
73 off_t hole_size_ = 0;
74};
75
76/* Prints program usage to |where|. */
77static int usage(int /* argc */, char* argv[]) {
78 fprintf(stderr,
79 "%s - command-line tool for extracting partition images from super\n"
80 "\n"
81 "Usage:\n"
82 " %s [options...] SUPER_IMAGE [OUTPUT_DIR]\n"
83 "\n"
Daniel Köster9cd252e2023-03-24 11:20:44 +010084 "The SUPER_IMAGE argument is mandatory and expected to contain\n"
85 "the metadata. Additional super images are referenced with '-i' as needed to extract\n"
86 "the desired partition[s].\n"
87 "Default OUTPUT_DIR is '.'.\n"
88 "\n"
David Andersone2b1be12019-04-24 18:16:27 -070089 "Options:\n"
Daniel Köster9cd252e2023-03-24 11:20:44 +010090 " -i, --image=IMAGE Use the given file as an additional super image.\n"
91 " This can be specified multiple times.\n"
David Andersone2b1be12019-04-24 18:16:27 -070092 " -p, --partition=NAME Extract the named partition. This can\n"
93 " be specified multiple times.\n"
94 " -S, --slot=NUM Slot number (default is 0).\n",
95 argv[0], argv[0]);
96 return EX_USAGE;
97}
98
99int main(int argc, char* argv[]) {
100 // clang-format off
101 struct option options[] = {
Daniel Köster9cd252e2023-03-24 11:20:44 +0100102 { "image", required_argument, nullptr, 'i' },
David Andersone2b1be12019-04-24 18:16:27 -0700103 { "partition", required_argument, nullptr, 'p' },
104 { "slot", required_argument, nullptr, 'S' },
105 { nullptr, 0, nullptr, 0 },
106 };
107 // clang-format on
108
109 uint32_t slot_num = 0;
110 std::unordered_set<std::string> partitions;
Daniel Köster9cd252e2023-03-24 11:20:44 +0100111 std::vector<std::string> image_files;
David Andersone2b1be12019-04-24 18:16:27 -0700112
113 int rv, index;
114 while ((rv = getopt_long_only(argc, argv, "+p:sh", options, &index)) != -1) {
115 switch (rv) {
116 case 'h':
117 usage(argc, argv);
118 return EX_OK;
119 case '?':
120 std::cerr << "Unrecognized argument.\n";
121 return usage(argc, argv);
122 case 'S':
123 if (!android::base::ParseUint(optarg, &slot_num)) {
124 std::cerr << "Slot must be a valid unsigned number.\n";
125 return usage(argc, argv);
126 }
127 break;
Daniel Köster9cd252e2023-03-24 11:20:44 +0100128 case 'i':
129 image_files.push_back(optarg);
130 break;
David Andersone2b1be12019-04-24 18:16:27 -0700131 case 'p':
132 partitions.emplace(optarg);
133 break;
134 }
135 }
136
137 if (optind + 1 > argc) {
138 std::cerr << "Missing super image argument.\n";
139 return usage(argc, argv);
140 }
Daniel Köster9cd252e2023-03-24 11:20:44 +0100141 image_files.emplace(image_files.begin(), argv[optind++]);
David Andersone2b1be12019-04-24 18:16:27 -0700142
143 std::string output_dir = ".";
144 if (optind + 1 <= argc) {
145 output_dir = argv[optind++];
146 }
147
Daniel Köster9cd252e2023-03-24 11:20:44 +0100148 std::unique_ptr<LpMetadata> metadata;
149 std::vector<unique_fd> fds;
David Andersone2b1be12019-04-24 18:16:27 -0700150
Daniel Köster9cd252e2023-03-24 11:20:44 +0100151 for (std::size_t index = 0; index < image_files.size(); ++index) {
152 std::string super_path = image_files[index];
David Andersone2b1be12019-04-24 18:16:27 -0700153
Daniel Köster9cd252e2023-03-24 11:20:44 +0100154 // Done reading arguments; open super.img. PartitionOpener will decorate
155 // relative paths with /dev/block/by-name, so get an absolute path here.
156 std::string abs_super_path;
157 if (!android::base::Realpath(super_path, &abs_super_path)) {
158 std::cerr << "realpath failed: " << super_path << ": " << strerror(errno) << "\n";
159 return EX_OSERR;
160 }
David Andersone2b1be12019-04-24 18:16:27 -0700161
Daniel Köster9cd252e2023-03-24 11:20:44 +0100162 unique_fd fd(open(super_path.c_str(), O_RDONLY | O_CLOEXEC));
163 if (fd < 0) {
164 std::cerr << "open failed: " << abs_super_path << ": " << strerror(errno) << "\n";
165 return EX_OSERR;
166 }
167
David Andersone2b1be12019-04-24 18:16:27 -0700168 SparsePtr ptr(sparse_file_import(fd, false, false), sparse_file_destroy);
169 if (ptr) {
Daniel Köster9cd252e2023-03-24 11:20:44 +0100170 std::cerr << "The image file '"
171 << super_path
172 << "' appears to be a sparse image. It must be unsparsed to be unpacked.\n";
David Andersone2b1be12019-04-24 18:16:27 -0700173 return EX_USAGE;
174 }
Daniel Köster9cd252e2023-03-24 11:20:44 +0100175
176 if (!metadata) {
177 metadata = ReadMetadata(abs_super_path, slot_num);
178 if (!metadata) {
179 std::cerr << "Could not read metadata from the super image file '"
180 << super_path
181 << "'.\n";
182 return EX_USAGE;
183 }
184 }
185
186 fds.emplace_back(std::move(fd));
David Andersone2b1be12019-04-24 18:16:27 -0700187 }
188
Daniel Köster9cd252e2023-03-24 11:20:44 +0100189 // Now do actual extraction.
190 ImageExtractor extractor(std::move(fds), std::move(metadata), std::move(partitions), output_dir);
David Andersone2b1be12019-04-24 18:16:27 -0700191 if (!extractor.Extract()) {
192 return EX_SOFTWARE;
193 }
194 return EX_OK;
195}
196
Daniel Köster9cd252e2023-03-24 11:20:44 +0100197ImageExtractor::ImageExtractor(std::vector<unique_fd>&& image_fds, std::unique_ptr<LpMetadata>&& metadata,
David Andersone2b1be12019-04-24 18:16:27 -0700198 std::unordered_set<std::string>&& partitions,
199 const std::string& output_dir)
Daniel Köster9cd252e2023-03-24 11:20:44 +0100200 : image_fds_(std::move(image_fds)),
David Andersone2b1be12019-04-24 18:16:27 -0700201 metadata_(std::move(metadata)),
202 partitions_(std::move(partitions)),
203 output_dir_(output_dir) {}
204
205bool ImageExtractor::Extract() {
206 if (!BuildPartitionList()) {
207 return false;
208 }
209
210 for (const auto& [name, info] : partition_map_) {
Daniel Köster9cd252e2023-03-24 11:20:44 +0100211 std::cout << "Attempting to extract partition '" << name << "'...\n";
David Andersone2b1be12019-04-24 18:16:27 -0700212 if (!ExtractPartition(info)) {
213 return false;
214 }
215 }
216 return true;
217}
218
219bool ImageExtractor::BuildPartitionList() {
220 bool extract_all = partitions_.empty();
221
222 for (const auto& partition : metadata_->partitions) {
223 auto name = GetPartitionName(partition);
224 if (extract_all || partitions_.count(name)) {
225 partition_map_[name] = &partition;
226 partitions_.erase(name);
227 }
228 }
229
230 if (!extract_all && !partitions_.empty()) {
231 std::cerr << "Could not find partition: " << *partitions_.begin() << "\n";
232 return false;
233 }
234 return true;
235}
236
237bool ImageExtractor::ExtractPartition(const LpMetadataPartition* partition) {
238 // Validate the extents and find the total image size.
239 uint64_t total_size = 0;
240 for (uint32_t i = 0; i < partition->num_extents; i++) {
241 uint32_t index = partition->first_extent_index + i;
242 const LpMetadataExtent& extent = metadata_->extents[index];
Daniel Köster9cd252e2023-03-24 11:20:44 +0100243 std::cout << " Dealing with extent " << i << " from target source " << extent.target_source << "...\n";
David Andersone2b1be12019-04-24 18:16:27 -0700244
245 if (extent.target_type != LP_TARGET_TYPE_LINEAR) {
246 std::cerr << "Unsupported target type in extent: " << extent.target_type << "\n";
247 return false;
248 }
Daniel Köster9cd252e2023-03-24 11:20:44 +0100249 if (extent.target_source >= image_fds_.size()) {
250 std::cerr << "Insufficient number of super images passed, need at least " << extent.target_source + 1 << ".\n";
David Andersone2b1be12019-04-24 18:16:27 -0700251 return false;
252 }
253 total_size += extent.num_sectors * LP_SECTOR_SIZE;
254 }
255
256 // Make a temporary file so we can import it with sparse_file_read.
257 std::string output_path = output_dir_ + "/" + GetPartitionName(*partition) + ".img";
258 unique_fd output_fd(open(output_path.c_str(), O_RDWR | O_CLOEXEC | O_CREAT | O_TRUNC, 0644));
259 if (output_fd < 0) {
260 std::cerr << "open failed: " << output_path << ": " << strerror(errno) << "\n";
261 return false;
262 }
263
Daniel Köster9cd252e2023-03-24 11:20:44 +0100264 SparseWriter writer(output_fd, metadata_->geometry.logical_block_size);
David Andersone2b1be12019-04-24 18:16:27 -0700265
266 // Extract each extent into output_fd.
267 for (uint32_t i = 0; i < partition->num_extents; i++) {
268 uint32_t index = partition->first_extent_index + i;
269 const LpMetadataExtent& extent = metadata_->extents[index];
270
Daniel Köster9cd252e2023-03-24 11:20:44 +0100271 if (!writer.WriteExtent(image_fds_[extent.target_source], extent)) {
David Andersone2b1be12019-04-24 18:16:27 -0700272 return false;
273 }
274 }
275 return writer.Finish();
276}
277
Daniel Köster9cd252e2023-03-24 11:20:44 +0100278SparseWriter::SparseWriter(borrowed_fd output_fd, uint32_t block_size)
279 : output_fd_(output_fd), block_size_(block_size) {}
David Andersone2b1be12019-04-24 18:16:27 -0700280
Daniel Köster9cd252e2023-03-24 11:20:44 +0100281bool SparseWriter::WriteExtent(borrowed_fd image_fd, const LpMetadataExtent& extent) {
David Andersone2b1be12019-04-24 18:16:27 -0700282 auto buffer = std::make_unique<uint8_t[]>(block_size_);
283
284 off_t super_offset = extent.target_data * LP_SECTOR_SIZE;
Daniel Köster9cd252e2023-03-24 11:20:44 +0100285 if (lseek(image_fd.get(), super_offset, SEEK_SET) < 0) {
David Andersone2b1be12019-04-24 18:16:27 -0700286 std::cerr << "image lseek failed: " << strerror(errno) << "\n";
287 return false;
288 }
289
290 uint64_t remaining_bytes = extent.num_sectors * LP_SECTOR_SIZE;
291 while (remaining_bytes) {
292 if (remaining_bytes < block_size_) {
293 std::cerr << "extent is not block-aligned\n";
294 return false;
295 }
Daniel Köster9cd252e2023-03-24 11:20:44 +0100296 if (!android::base::ReadFully(image_fd, buffer.get(), block_size_)) {
David Andersone2b1be12019-04-24 18:16:27 -0700297 std::cerr << "read failed: " << strerror(errno) << "\n";
298 return false;
299 }
300 if (!WriteBlock(buffer.get())) {
301 return false;
302 }
303 remaining_bytes -= block_size_;
304 }
305 return true;
306}
307
308static bool ShouldSkipChunk(const uint8_t* data, size_t len) {
309 for (size_t i = 0; i < len; i++) {
310 if (data[i] != 0) {
311 return false;
312 }
313 }
314 return true;
315}
316
317bool SparseWriter::WriteBlock(const uint8_t* data) {
318 if (ShouldSkipChunk(data, block_size_)) {
319 hole_size_ += block_size_;
320 return true;
321 }
322
323 if (hole_size_) {
Daniel Köster9cd252e2023-03-24 11:20:44 +0100324 if (lseek(output_fd_.get(), hole_size_, SEEK_CUR) < 0) {
David Andersone2b1be12019-04-24 18:16:27 -0700325 std::cerr << "lseek failed: " << strerror(errno) << "\n";
326 return false;
327 }
328 hole_size_ = 0;
329 }
330 if (!android::base::WriteFully(output_fd_, data, block_size_)) {
331 std::cerr << "write failed: " << strerror(errno) << "\n";
332 return false;
333 }
334 return true;
335}
336
337bool SparseWriter::Finish() {
338 if (hole_size_) {
Daniel Köster9cd252e2023-03-24 11:20:44 +0100339 off_t offset = lseek(output_fd_.get(), 0, SEEK_CUR);
David Andersone2b1be12019-04-24 18:16:27 -0700340 if (offset < 0) {
341 std::cerr << "lseek failed: " << strerror(errno) << "\n";
342 return false;
343 }
Daniel Köster9cd252e2023-03-24 11:20:44 +0100344 if (ftruncate(output_fd_.get(), offset + hole_size_) < 0) {
David Andersone2b1be12019-04-24 18:16:27 -0700345 std::cerr << "ftruncate failed: " << strerror(errno) << "\n";
346 return false;
347 }
348 }
349 return true;
350}