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