system/extras
Révision | 1f0277ae23c3f7a4c52629b666d020ebc4e9ac96 (tree) |
---|---|
l'heure | 2019-04-27 08:00:50 |
Auteur | David Anderson <dvander@goog...> |
Commiter | David Anderson |
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:
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
Change-Id: Iad3f7362ae6ce767ddc7de2e8cfa7e4a47190e99
Merged-In: Iad3f7362ae6ce767ddc7de2e8cfa7e4a47190e99
@@ -139,3 +139,22 @@ cc_binary { | ||
139 | 139 | "lpdumpd.cc", |
140 | 140 | ], |
141 | 141 | } |
142 | + | |
143 | +cc_binary { | |
144 | + name: "lpunpack", | |
145 | + defaults: ["lp_defaults"], | |
146 | + device_supported: false, | |
147 | + host_supported: true, | |
148 | + shared_libs: [ | |
149 | + "libbase", | |
150 | + "liblog", | |
151 | + "liblp", | |
152 | + "libsparse", | |
153 | + ], | |
154 | + srcs: [ | |
155 | + "lpunpack.cc", | |
156 | + ], | |
157 | + cppflags: [ | |
158 | + "-D_FILE_OFFSET_BITS=64", | |
159 | + ], | |
160 | +} |
@@ -0,0 +1,326 @@ | ||
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 <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 | + | |
35 | +using namespace android::fs_mgr; | |
36 | +using android::base::unique_fd; | |
37 | +using SparsePtr = std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)>; | |
38 | + | |
39 | +class ImageExtractor final { | |
40 | + public: | |
41 | + ImageExtractor(unique_fd&& image_fd, std::unique_ptr<LpMetadata>&& metadata, | |
42 | + std::unordered_set<std::string>&& partitions, const std::string& output_dir); | |
43 | + | |
44 | + bool Extract(); | |
45 | + | |
46 | + private: | |
47 | + bool BuildPartitionList(); | |
48 | + bool ExtractPartition(const LpMetadataPartition* partition); | |
49 | + bool ExtractExtent(const LpMetadataExtent& extent, int output_fd); | |
50 | + | |
51 | + unique_fd image_fd_; | |
52 | + std::unique_ptr<LpMetadata> metadata_; | |
53 | + std::unordered_set<std::string> partitions_; | |
54 | + std::string output_dir_; | |
55 | + std::unordered_map<std::string, const LpMetadataPartition*> partition_map_; | |
56 | +}; | |
57 | + | |
58 | +// Note that "sparse" here refers to filesystem sparse, not the Android sparse | |
59 | +// file format. | |
60 | +class SparseWriter final { | |
61 | + public: | |
62 | + SparseWriter(int output_fd, int image_fd, uint32_t block_size); | |
63 | + | |
64 | + bool WriteExtent(const LpMetadataExtent& extent); | |
65 | + bool Finish(); | |
66 | + | |
67 | + private: | |
68 | + bool WriteBlock(const uint8_t* data); | |
69 | + | |
70 | + int output_fd_; | |
71 | + int image_fd_; | |
72 | + uint32_t block_size_; | |
73 | + off_t hole_size_ = 0; | |
74 | +}; | |
75 | + | |
76 | +/* Prints program usage to |where|. */ | |
77 | +static 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" | |
84 | + "Options:\n" | |
85 | + " -p, --partition=NAME Extract the named partition. This can\n" | |
86 | + " be specified multiple times.\n" | |
87 | + " -S, --slot=NUM Slot number (default is 0).\n", | |
88 | + argv[0], argv[0]); | |
89 | + return EX_USAGE; | |
90 | +} | |
91 | + | |
92 | +int main(int argc, char* argv[]) { | |
93 | + // clang-format off | |
94 | + struct option options[] = { | |
95 | + { "partition", required_argument, nullptr, 'p' }, | |
96 | + { "slot", required_argument, nullptr, 'S' }, | |
97 | + { nullptr, 0, nullptr, 0 }, | |
98 | + }; | |
99 | + // clang-format on | |
100 | + | |
101 | + uint32_t slot_num = 0; | |
102 | + std::unordered_set<std::string> partitions; | |
103 | + | |
104 | + int rv, index; | |
105 | + while ((rv = getopt_long_only(argc, argv, "+p:sh", options, &index)) != -1) { | |
106 | + switch (rv) { | |
107 | + case 'h': | |
108 | + usage(argc, argv); | |
109 | + return EX_OK; | |
110 | + case '?': | |
111 | + std::cerr << "Unrecognized argument.\n"; | |
112 | + return usage(argc, argv); | |
113 | + case 'S': | |
114 | + if (!android::base::ParseUint(optarg, &slot_num)) { | |
115 | + std::cerr << "Slot must be a valid unsigned number.\n"; | |
116 | + return usage(argc, argv); | |
117 | + } | |
118 | + break; | |
119 | + case 'p': | |
120 | + partitions.emplace(optarg); | |
121 | + break; | |
122 | + } | |
123 | + } | |
124 | + | |
125 | + if (optind + 1 > argc) { | |
126 | + std::cerr << "Missing super image argument.\n"; | |
127 | + return usage(argc, argv); | |
128 | + } | |
129 | + std::string super_path = argv[optind++]; | |
130 | + | |
131 | + std::string output_dir = "."; | |
132 | + if (optind + 1 <= argc) { | |
133 | + output_dir = argv[optind++]; | |
134 | + } | |
135 | + | |
136 | + if (optind < argc) { | |
137 | + std::cerr << "Unrecognized command-line arguments.\n"; | |
138 | + return usage(argc, argv); | |
139 | + } | |
140 | + | |
141 | + // Done reading arguments; open super.img. PartitionOpener will decorate | |
142 | + // relative paths with /dev/block/by-name, so get an absolute path here. | |
143 | + std::string abs_super_path; | |
144 | + if (!android::base::Realpath(super_path, &abs_super_path)) { | |
145 | + std::cerr << "realpath failed: " << super_path << ": " << strerror(errno) << "\n"; | |
146 | + return EX_OSERR; | |
147 | + } | |
148 | + | |
149 | + unique_fd fd(open(super_path.c_str(), O_RDONLY | O_CLOEXEC)); | |
150 | + if (fd < 0) { | |
151 | + std::cerr << "open failed: " << abs_super_path << ": " << strerror(errno) << "\n"; | |
152 | + return EX_OSERR; | |
153 | + } | |
154 | + | |
155 | + auto metadata = ReadMetadata(abs_super_path, slot_num); | |
156 | + if (!metadata) { | |
157 | + SparsePtr ptr(sparse_file_import(fd, false, false), sparse_file_destroy); | |
158 | + if (ptr) { | |
159 | + std::cerr << "This image appears to be a sparse image. It must be " | |
160 | + "unsparsed to be" | |
161 | + << " unpacked.\n"; | |
162 | + return EX_USAGE; | |
163 | + } | |
164 | + std::cerr << "Image does not appear to be in super-partition format.\n"; | |
165 | + return EX_USAGE; | |
166 | + } | |
167 | + | |
168 | + ImageExtractor extractor(std::move(fd), std::move(metadata), std::move(partitions), output_dir); | |
169 | + if (!extractor.Extract()) { | |
170 | + return EX_SOFTWARE; | |
171 | + } | |
172 | + return EX_OK; | |
173 | +} | |
174 | + | |
175 | +ImageExtractor::ImageExtractor(unique_fd&& image_fd, std::unique_ptr<LpMetadata>&& metadata, | |
176 | + std::unordered_set<std::string>&& partitions, | |
177 | + const std::string& output_dir) | |
178 | + : image_fd_(std::move(image_fd)), | |
179 | + metadata_(std::move(metadata)), | |
180 | + partitions_(std::move(partitions)), | |
181 | + output_dir_(output_dir) {} | |
182 | + | |
183 | +bool ImageExtractor::Extract() { | |
184 | + if (!BuildPartitionList()) { | |
185 | + return false; | |
186 | + } | |
187 | + | |
188 | + for (const auto& [name, info] : partition_map_) { | |
189 | + if (!ExtractPartition(info)) { | |
190 | + return false; | |
191 | + } | |
192 | + } | |
193 | + return true; | |
194 | +} | |
195 | + | |
196 | +bool ImageExtractor::BuildPartitionList() { | |
197 | + bool extract_all = partitions_.empty(); | |
198 | + | |
199 | + for (const auto& partition : metadata_->partitions) { | |
200 | + auto name = GetPartitionName(partition); | |
201 | + if (extract_all || partitions_.count(name)) { | |
202 | + partition_map_[name] = &partition; | |
203 | + partitions_.erase(name); | |
204 | + } | |
205 | + } | |
206 | + | |
207 | + if (!extract_all && !partitions_.empty()) { | |
208 | + std::cerr << "Could not find partition: " << *partitions_.begin() << "\n"; | |
209 | + return false; | |
210 | + } | |
211 | + return true; | |
212 | +} | |
213 | + | |
214 | +bool ImageExtractor::ExtractPartition(const LpMetadataPartition* partition) { | |
215 | + // Validate the extents and find the total image size. | |
216 | + uint64_t total_size = 0; | |
217 | + for (uint32_t i = 0; i < partition->num_extents; i++) { | |
218 | + uint32_t index = partition->first_extent_index + i; | |
219 | + const LpMetadataExtent& extent = metadata_->extents[index]; | |
220 | + | |
221 | + if (extent.target_type != LP_TARGET_TYPE_LINEAR) { | |
222 | + std::cerr << "Unsupported target type in extent: " << extent.target_type << "\n"; | |
223 | + return false; | |
224 | + } | |
225 | + if (extent.target_source != 0) { | |
226 | + std::cerr << "Split super devices are not supported.\n"; | |
227 | + return false; | |
228 | + } | |
229 | + total_size += extent.num_sectors * LP_SECTOR_SIZE; | |
230 | + } | |
231 | + | |
232 | + // Make a temporary file so we can import it with sparse_file_read. | |
233 | + std::string output_path = output_dir_ + "/" + GetPartitionName(*partition) + ".img"; | |
234 | + unique_fd output_fd(open(output_path.c_str(), O_RDWR | O_CLOEXEC | O_CREAT | O_TRUNC, 0644)); | |
235 | + if (output_fd < 0) { | |
236 | + std::cerr << "open failed: " << output_path << ": " << strerror(errno) << "\n"; | |
237 | + return false; | |
238 | + } | |
239 | + | |
240 | + SparseWriter writer(output_fd, image_fd_, metadata_->geometry.logical_block_size); | |
241 | + | |
242 | + // Extract each extent into output_fd. | |
243 | + for (uint32_t i = 0; i < partition->num_extents; i++) { | |
244 | + uint32_t index = partition->first_extent_index + i; | |
245 | + const LpMetadataExtent& extent = metadata_->extents[index]; | |
246 | + | |
247 | + if (!writer.WriteExtent(extent)) { | |
248 | + return false; | |
249 | + } | |
250 | + } | |
251 | + return writer.Finish(); | |
252 | +} | |
253 | + | |
254 | +SparseWriter::SparseWriter(int output_fd, int image_fd, uint32_t block_size) | |
255 | + : output_fd_(output_fd), image_fd_(image_fd), block_size_(block_size) {} | |
256 | + | |
257 | +bool SparseWriter::WriteExtent(const LpMetadataExtent& extent) { | |
258 | + auto buffer = std::make_unique<uint8_t[]>(block_size_); | |
259 | + | |
260 | + off_t super_offset = extent.target_data * LP_SECTOR_SIZE; | |
261 | + if (lseek(image_fd_, super_offset, SEEK_SET) < 0) { | |
262 | + std::cerr << "image lseek failed: " << strerror(errno) << "\n"; | |
263 | + return false; | |
264 | + } | |
265 | + | |
266 | + uint64_t remaining_bytes = extent.num_sectors * LP_SECTOR_SIZE; | |
267 | + while (remaining_bytes) { | |
268 | + if (remaining_bytes < block_size_) { | |
269 | + std::cerr << "extent is not block-aligned\n"; | |
270 | + return false; | |
271 | + } | |
272 | + if (!android::base::ReadFully(image_fd_, buffer.get(), block_size_)) { | |
273 | + std::cerr << "read failed: " << strerror(errno) << "\n"; | |
274 | + return false; | |
275 | + } | |
276 | + if (!WriteBlock(buffer.get())) { | |
277 | + return false; | |
278 | + } | |
279 | + remaining_bytes -= block_size_; | |
280 | + } | |
281 | + return true; | |
282 | +} | |
283 | + | |
284 | +static bool ShouldSkipChunk(const uint8_t* data, size_t len) { | |
285 | + for (size_t i = 0; i < len; i++) { | |
286 | + if (data[i] != 0) { | |
287 | + return false; | |
288 | + } | |
289 | + } | |
290 | + return true; | |
291 | +} | |
292 | + | |
293 | +bool SparseWriter::WriteBlock(const uint8_t* data) { | |
294 | + if (ShouldSkipChunk(data, block_size_)) { | |
295 | + hole_size_ += block_size_; | |
296 | + return true; | |
297 | + } | |
298 | + | |
299 | + if (hole_size_) { | |
300 | + if (lseek(output_fd_, hole_size_, SEEK_CUR) < 0) { | |
301 | + std::cerr << "lseek failed: " << strerror(errno) << "\n"; | |
302 | + return false; | |
303 | + } | |
304 | + hole_size_ = 0; | |
305 | + } | |
306 | + if (!android::base::WriteFully(output_fd_, data, block_size_)) { | |
307 | + std::cerr << "write failed: " << strerror(errno) << "\n"; | |
308 | + return false; | |
309 | + } | |
310 | + return true; | |
311 | +} | |
312 | + | |
313 | +bool SparseWriter::Finish() { | |
314 | + if (hole_size_) { | |
315 | + off_t offset = lseek(output_fd_, 0, SEEK_CUR); | |
316 | + if (offset < 0) { | |
317 | + std::cerr << "lseek failed: " << strerror(errno) << "\n"; | |
318 | + return false; | |
319 | + } | |
320 | + if (ftruncate(output_fd_, offset + hole_size_) < 0) { | |
321 | + std::cerr << "ftruncate failed: " << strerror(errno) << "\n"; | |
322 | + return false; | |
323 | + } | |
324 | + } | |
325 | + return true; | |
326 | +} |