1 /*
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 <filesystem>
25 #include <iostream>
26 #include <limits>
27 #include <string>
28 #include <unordered_map>
29 #include <unordered_set>
30
31 #include <android-base/file.h>
32 #include <android-base/parseint.h>
33 #include <liblp/liblp.h>
34 #include <sparse/sparse.h>
35
36 using namespace android::fs_mgr;
37 using android::base::unique_fd;
38 using android::base::borrowed_fd;
39 using SparsePtr = std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)>;
40
41 class ImageExtractor final {
42 public:
43 ImageExtractor(std::vector<unique_fd>&& image_fds, std::unique_ptr<LpMetadata>&& metadata,
44 std::unordered_set<std::string>&& partitions, const std::string& output_dir);
45
46 bool Extract();
47
48 private:
49 bool BuildPartitionList();
50 bool ExtractPartition(const LpMetadataPartition* partition);
51 bool ExtractExtent(const LpMetadataExtent& extent, int output_fd);
52
53 std::vector<unique_fd> image_fds_;
54 std::unique_ptr<LpMetadata> metadata_;
55 std::unordered_set<std::string> partitions_;
56 std::string output_dir_;
57 std::unordered_map<std::string, const LpMetadataPartition*> partition_map_;
58 };
59
60 // Note that "sparse" here refers to filesystem sparse, not the Android sparse
61 // file format.
62 class SparseWriter final {
63 public:
64 SparseWriter(borrowed_fd output_fd, uint32_t block_size);
65
66 bool WriteExtent(borrowed_fd image_fd, const LpMetadataExtent& extent);
67 bool Finish();
68
69 private:
70 bool WriteBlock(const uint8_t* data);
71
72 borrowed_fd output_fd_;
73 uint32_t block_size_;
74 off_t hole_size_ = 0;
75 };
76
77 /* Prints program usage to |where|. */
usage(int,char * argv[])78 static int usage(int /* argc */, char* argv[]) {
79 fprintf(stderr,
80 "%s - command-line tool for extracting partition images from super\n"
81 "\n"
82 "Usage:\n"
83 " %s [options...] SUPER_IMAGE [OUTPUT_DIR]\n"
84 "\n"
85 "The SUPER_IMAGE argument is mandatory and expected to contain\n"
86 "the metadata. Additional super images are referenced with '-i' as needed to extract\n"
87 "the desired partition[s].\n"
88 "Default OUTPUT_DIR is '.'.\n"
89 "\n"
90 "Options:\n"
91 " -i, --image=IMAGE Use the given file as an additional super image.\n"
92 " This can be specified multiple times.\n"
93 " -p, --partition=NAME Extract the named partition. This can\n"
94 " be specified multiple times.\n"
95 " -S, --slot=NUM Slot number (default is 0).\n",
96 argv[0], argv[0]);
97 return EX_USAGE;
98 }
99
main(int argc,char * argv[])100 int main(int argc, char* argv[]) {
101 // clang-format off
102 struct option options[] = {
103 { "image", required_argument, nullptr, 'i' },
104 { "partition", required_argument, nullptr, 'p' },
105 { "slot", required_argument, nullptr, 'S' },
106 { nullptr, 0, nullptr, 0 },
107 };
108 // clang-format on
109
110 uint32_t slot_num = 0;
111 std::unordered_set<std::string> partitions;
112 std::vector<std::string> image_files;
113
114 int rv, index;
115 while ((rv = getopt_long_only(argc, argv, "+p:sh", options, &index)) != -1) {
116 switch (rv) {
117 case 'h':
118 usage(argc, argv);
119 return EX_OK;
120 case '?':
121 std::cerr << "Unrecognized argument.\n";
122 return usage(argc, argv);
123 case 'S':
124 if (!android::base::ParseUint(optarg, &slot_num)) {
125 std::cerr << "Slot must be a valid unsigned number.\n";
126 return usage(argc, argv);
127 }
128 break;
129 case 'i':
130 image_files.push_back(optarg);
131 break;
132 case 'p':
133 partitions.emplace(optarg);
134 break;
135 }
136 }
137
138 if (optind + 1 > argc) {
139 std::cerr << "Missing super image argument.\n";
140 return usage(argc, argv);
141 }
142 image_files.emplace(image_files.begin(), argv[optind++]);
143
144 std::string output_dir = ".";
145 if (optind + 1 <= argc) {
146 output_dir = argv[optind++];
147 }
148
149 std::unique_ptr<LpMetadata> metadata;
150 std::vector<unique_fd> fds;
151
152 for (std::size_t index = 0; index < image_files.size(); ++index) {
153 std::string super_path = image_files[index];
154
155 // Done reading arguments; open super.img. PartitionOpener will decorate
156 // relative paths with /dev/block/by-name, so get an absolute path here.
157 std::string abs_super_path;
158 if (!android::base::Realpath(super_path, &abs_super_path)) {
159 std::cerr << "realpath failed: " << super_path << ": " << strerror(errno) << "\n";
160 return EX_OSERR;
161 }
162
163 unique_fd fd(open(super_path.c_str(), O_RDONLY | O_CLOEXEC));
164 if (fd < 0) {
165 std::cerr << "open failed: " << abs_super_path << ": " << strerror(errno) << "\n";
166 return EX_OSERR;
167 }
168
169 SparsePtr ptr(sparse_file_import(fd, false, false), sparse_file_destroy);
170 if (ptr) {
171 std::cerr << "The image file '"
172 << super_path
173 << "' appears to be a sparse image. It must be unsparsed to be unpacked.\n";
174 return EX_USAGE;
175 }
176
177 if (!metadata) {
178 metadata = ReadMetadata(abs_super_path, slot_num);
179 if (!metadata) {
180 std::cerr << "Could not read metadata from the super image file '"
181 << super_path
182 << "'.\n";
183 return EX_USAGE;
184 }
185 }
186
187 fds.emplace_back(std::move(fd));
188 }
189
190 // Now do actual extraction.
191 ImageExtractor extractor(std::move(fds), std::move(metadata), std::move(partitions), output_dir);
192 if (!extractor.Extract()) {
193 return EX_SOFTWARE;
194 }
195 return EX_OK;
196 }
197
ImageExtractor(std::vector<unique_fd> && image_fds,std::unique_ptr<LpMetadata> && metadata,std::unordered_set<std::string> && partitions,const std::string & output_dir)198 ImageExtractor::ImageExtractor(std::vector<unique_fd>&& image_fds, std::unique_ptr<LpMetadata>&& metadata,
199 std::unordered_set<std::string>&& partitions,
200 const std::string& output_dir)
201 : image_fds_(std::move(image_fds)),
202 metadata_(std::move(metadata)),
203 partitions_(std::move(partitions)),
204 output_dir_(output_dir) {}
205
Extract()206 bool ImageExtractor::Extract() {
207 std::filesystem::create_directories(output_dir_);
208 if (!BuildPartitionList()) {
209 return false;
210 }
211
212 for (const auto& [name, info] : partition_map_) {
213 std::cout << "Attempting to extract partition '" << name << "'...\n";
214 if (!ExtractPartition(info)) {
215 return false;
216 }
217 }
218 return true;
219 }
220
BuildPartitionList()221 bool ImageExtractor::BuildPartitionList() {
222 bool extract_all = partitions_.empty();
223
224 for (const auto& partition : metadata_->partitions) {
225 auto name = GetPartitionName(partition);
226 if (extract_all || partitions_.count(name)) {
227 partition_map_[name] = &partition;
228 partitions_.erase(name);
229 }
230 }
231
232 if (!extract_all && !partitions_.empty()) {
233 std::cerr << "Could not find partition: " << *partitions_.begin() << "\n";
234 return false;
235 }
236 return true;
237 }
238
ExtractPartition(const LpMetadataPartition * partition)239 bool ImageExtractor::ExtractPartition(const LpMetadataPartition* partition) {
240 // Validate the extents and find the total image size.
241 uint64_t total_size = 0;
242 for (uint32_t i = 0; i < partition->num_extents; i++) {
243 uint32_t index = partition->first_extent_index + i;
244 const LpMetadataExtent& extent = metadata_->extents[index];
245 std::cout << " Dealing with extent " << i << " from target source " << extent.target_source << "...\n";
246
247 if (extent.target_type != LP_TARGET_TYPE_LINEAR) {
248 std::cerr << "Unsupported target type in extent: " << extent.target_type << "\n";
249 return false;
250 }
251 if (extent.target_source >= image_fds_.size()) {
252 std::cerr << "Insufficient number of super images passed, need at least " << extent.target_source + 1 << ".\n";
253 return false;
254 }
255 total_size += extent.num_sectors * LP_SECTOR_SIZE;
256 }
257
258 // Make a temporary file so we can import it with sparse_file_read.
259 std::string output_path = output_dir_ + "/" + GetPartitionName(*partition) + ".img";
260 unique_fd output_fd(open(output_path.c_str(), O_RDWR | O_CLOEXEC | O_CREAT | O_TRUNC, 0644));
261 if (output_fd < 0) {
262 std::cerr << "open failed: " << output_path << ": " << strerror(errno) << "\n";
263 return false;
264 }
265
266 SparseWriter writer(output_fd, metadata_->geometry.logical_block_size);
267
268 // Extract each extent into output_fd.
269 for (uint32_t i = 0; i < partition->num_extents; i++) {
270 uint32_t index = partition->first_extent_index + i;
271 const LpMetadataExtent& extent = metadata_->extents[index];
272
273 if (!writer.WriteExtent(image_fds_[extent.target_source], extent)) {
274 return false;
275 }
276 }
277 return writer.Finish();
278 }
279
SparseWriter(borrowed_fd output_fd,uint32_t block_size)280 SparseWriter::SparseWriter(borrowed_fd output_fd, uint32_t block_size)
281 : output_fd_(output_fd), block_size_(block_size) {}
282
WriteExtent(borrowed_fd image_fd,const LpMetadataExtent & extent)283 bool SparseWriter::WriteExtent(borrowed_fd image_fd, const LpMetadataExtent& extent) {
284 auto buffer = std::make_unique<uint8_t[]>(block_size_);
285
286 off_t super_offset = extent.target_data * LP_SECTOR_SIZE;
287 if (lseek(image_fd.get(), super_offset, SEEK_SET) < 0) {
288 std::cerr << "image lseek failed: " << strerror(errno) << "\n";
289 return false;
290 }
291
292 uint64_t remaining_bytes = extent.num_sectors * LP_SECTOR_SIZE;
293 while (remaining_bytes) {
294 if (remaining_bytes < block_size_) {
295 std::cerr << "extent is not block-aligned\n";
296 return false;
297 }
298 if (!android::base::ReadFully(image_fd, buffer.get(), block_size_)) {
299 std::cerr << "read failed: " << strerror(errno) << "\n";
300 return false;
301 }
302 if (!WriteBlock(buffer.get())) {
303 return false;
304 }
305 remaining_bytes -= block_size_;
306 }
307 return true;
308 }
309
ShouldSkipChunk(const uint8_t * data,size_t len)310 static bool ShouldSkipChunk(const uint8_t* data, size_t len) {
311 for (size_t i = 0; i < len; i++) {
312 if (data[i] != 0) {
313 return false;
314 }
315 }
316 return true;
317 }
318
WriteBlock(const uint8_t * data)319 bool SparseWriter::WriteBlock(const uint8_t* data) {
320 if (ShouldSkipChunk(data, block_size_)) {
321 hole_size_ += block_size_;
322 return true;
323 }
324
325 if (hole_size_) {
326 if (lseek(output_fd_.get(), hole_size_, SEEK_CUR) < 0) {
327 std::cerr << "lseek failed: " << strerror(errno) << "\n";
328 return false;
329 }
330 hole_size_ = 0;
331 }
332 if (!android::base::WriteFully(output_fd_, data, block_size_)) {
333 std::cerr << "write failed: " << strerror(errno) << "\n";
334 return false;
335 }
336 return true;
337 }
338
Finish()339 bool SparseWriter::Finish() {
340 if (hole_size_) {
341 off_t offset = lseek(output_fd_.get(), 0, SEEK_CUR);
342 if (offset < 0) {
343 std::cerr << "lseek failed: " << strerror(errno) << "\n";
344 return false;
345 }
346 if (ftruncate(output_fd_.get(), offset + hole_size_) < 0) {
347 std::cerr << "ftruncate failed: " << strerror(errno) << "\n";
348 return false;
349 }
350 }
351 return true;
352 }
353