• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright (C) 2020 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 <arpa/inet.h>
18 #include <errno.h>
19 #include <string.h>
20 #include <sys/stat.h>
21 #include <sys/types.h>
22 #include <unistd.h>
23 
24 #include <iostream>
25 #include <limits>
26 #include <string>
27 #include <unordered_set>
28 
29 #include <android-base/file.h>
30 #include <android-base/logging.h>
31 #include <android-base/unique_fd.h>
32 #include <bsdiff/bspatch.h>
33 #include <bzlib.h>
34 #include <gflags/gflags.h>
35 #include <libsnapshot/cow_writer.h>
36 #include <puffin/puffpatch.h>
37 #include <sparse/sparse.h>
38 #include <update_engine/update_metadata.pb.h>
39 #include <xz.h>
40 #include <ziparchive/zip_archive.h>
41 
42 namespace android {
43 namespace snapshot {
44 
45 using android::base::borrowed_fd;
46 using android::base::unique_fd;
47 using chromeos_update_engine::DeltaArchiveManifest;
48 using chromeos_update_engine::Extent;
49 using chromeos_update_engine::InstallOperation;
50 using chromeos_update_engine::PartitionUpdate;
51 
52 static constexpr uint64_t kBlockSize = 4096;
53 
54 DEFINE_string(source_tf, "", "Source target files (dir or zip file) for incremental payloads");
55 DEFINE_string(compression, "gz", "Compression type to use (none or gz)");
56 DEFINE_uint32(cluster_ops, 0, "Number of Cow Ops per cluster (0 or >1)");
57 
MyLogger(android::base::LogId,android::base::LogSeverity severity,const char *,const char *,unsigned int,const char * message)58 void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
59               unsigned int, const char* message) {
60     if (severity == android::base::ERROR) {
61         fprintf(stderr, "%s\n", message);
62     } else {
63         fprintf(stdout, "%s\n", message);
64     }
65 }
66 
ToLittleEndian(uint64_t value)67 uint64_t ToLittleEndian(uint64_t value) {
68     union {
69         uint64_t u64;
70         char bytes[8];
71     } packed;
72     packed.u64 = value;
73     std::swap(packed.bytes[0], packed.bytes[7]);
74     std::swap(packed.bytes[1], packed.bytes[6]);
75     std::swap(packed.bytes[2], packed.bytes[5]);
76     std::swap(packed.bytes[3], packed.bytes[4]);
77     return packed.u64;
78 }
79 
80 class PayloadConverter final {
81   public:
PayloadConverter(const std::string & in_file,const std::string & out_dir)82     PayloadConverter(const std::string& in_file, const std::string& out_dir)
83         : in_file_(in_file), out_dir_(out_dir), source_tf_zip_(nullptr, &CloseArchive) {}
84 
85     bool Run();
86 
87   private:
88     bool OpenPayload();
89     bool OpenSourceTargetFiles();
90     bool ProcessPartition(const PartitionUpdate& update);
91     bool ProcessOperation(const InstallOperation& op);
92     bool ProcessZero(const InstallOperation& op);
93     bool ProcessCopy(const InstallOperation& op);
94     bool ProcessReplace(const InstallOperation& op);
95     bool ProcessDiff(const InstallOperation& op);
96     borrowed_fd OpenSourceImage();
97 
98     std::string in_file_;
99     std::string out_dir_;
100     unique_fd in_fd_;
101     uint64_t payload_offset_ = 0;
102     DeltaArchiveManifest manifest_;
103     std::unordered_set<std::string> dap_;
104     unique_fd source_tf_fd_;
105     std::unique_ptr<ZipArchive, decltype(&CloseArchive)> source_tf_zip_;
106 
107     // Updated during ProcessPartition().
108     std::string partition_name_;
109     std::unique_ptr<CowWriter> writer_;
110     unique_fd source_image_;
111 };
112 
Run()113 bool PayloadConverter::Run() {
114     if (!OpenPayload()) {
115         return false;
116     }
117 
118     if (manifest_.has_dynamic_partition_metadata()) {
119         const auto& dpm = manifest_.dynamic_partition_metadata();
120         for (const auto& group : dpm.groups()) {
121             for (const auto& partition : group.partition_names()) {
122                 dap_.emplace(partition);
123             }
124         }
125     }
126 
127     if (dap_.empty()) {
128         LOG(ERROR) << "No dynamic partitions found.";
129         return false;
130     }
131 
132     if (!OpenSourceTargetFiles()) {
133         return false;
134     }
135 
136     for (const auto& update : manifest_.partitions()) {
137         if (!ProcessPartition(update)) {
138             return false;
139         }
140         writer_ = nullptr;
141         source_image_.reset();
142     }
143     return true;
144 }
145 
OpenSourceTargetFiles()146 bool PayloadConverter::OpenSourceTargetFiles() {
147     if (FLAGS_source_tf.empty()) {
148         return true;
149     }
150 
151     source_tf_fd_.reset(open(FLAGS_source_tf.c_str(), O_RDONLY));
152     if (source_tf_fd_ < 0) {
153         LOG(ERROR) << "open failed: " << FLAGS_source_tf;
154         return false;
155     }
156 
157     struct stat s;
158     if (fstat(source_tf_fd_.get(), &s) < 0) {
159         LOG(ERROR) << "fstat failed: " << FLAGS_source_tf;
160         return false;
161     }
162     if (S_ISDIR(s.st_mode)) {
163         return true;
164     }
165 
166     // Otherwise, assume it's a zip file.
167     ZipArchiveHandle handle;
168     if (OpenArchiveFd(source_tf_fd_.get(), FLAGS_source_tf.c_str(), &handle, false)) {
169         LOG(ERROR) << "Could not open " << FLAGS_source_tf << " as a zip archive.";
170         return false;
171     }
172     source_tf_zip_.reset(handle);
173     return true;
174 }
175 
ProcessPartition(const PartitionUpdate & update)176 bool PayloadConverter::ProcessPartition(const PartitionUpdate& update) {
177     auto partition_name = update.partition_name();
178     if (dap_.find(partition_name) == dap_.end()) {
179         // Skip non-DAP partitions.
180         return true;
181     }
182 
183     auto path = out_dir_ + "/" + partition_name + ".cow";
184     unique_fd fd(open(path.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0644));
185     if (fd < 0) {
186         PLOG(ERROR) << "open failed: " << path;
187         return false;
188     }
189 
190     CowOptions options;
191     options.block_size = kBlockSize;
192     options.compression = FLAGS_compression;
193     options.cluster_ops = FLAGS_cluster_ops;
194 
195     writer_ = std::make_unique<CowWriter>(options);
196     if (!writer_->Initialize(std::move(fd))) {
197         LOG(ERROR) << "Unable to initialize COW writer";
198         return false;
199     }
200 
201     partition_name_ = partition_name;
202 
203     for (const auto& op : update.operations()) {
204         if (!ProcessOperation(op)) {
205             return false;
206         }
207     }
208 
209     if (!writer_->Finalize()) {
210         LOG(ERROR) << "Unable to finalize COW for " << partition_name;
211         return false;
212     }
213     return true;
214 }
215 
ProcessOperation(const InstallOperation & op)216 bool PayloadConverter::ProcessOperation(const InstallOperation& op) {
217     switch (op.type()) {
218         case InstallOperation::SOURCE_COPY:
219             return ProcessCopy(op);
220         case InstallOperation::BROTLI_BSDIFF:
221         case InstallOperation::PUFFDIFF:
222             return ProcessDiff(op);
223         case InstallOperation::REPLACE:
224         case InstallOperation::REPLACE_XZ:
225         case InstallOperation::REPLACE_BZ:
226             return ProcessReplace(op);
227         case InstallOperation::ZERO:
228             return ProcessZero(op);
229         default:
230             LOG(ERROR) << "Unsupported op: " << (int)op.type();
231             return false;
232     }
233     return true;
234 }
235 
ProcessZero(const InstallOperation & op)236 bool PayloadConverter::ProcessZero(const InstallOperation& op) {
237     for (const auto& extent : op.dst_extents()) {
238         if (!writer_->AddZeroBlocks(extent.start_block(), extent.num_blocks())) {
239             LOG(ERROR) << "Could not add zero operation";
240             return false;
241         }
242     }
243     return true;
244 }
245 
246 template <typename T>
SizeOfAllExtents(const T & extents)247 static uint64_t SizeOfAllExtents(const T& extents) {
248     uint64_t total = 0;
249     for (const auto& extent : extents) {
250         total += extent.num_blocks() * kBlockSize;
251     }
252     return total;
253 }
254 
255 class PuffInputStream final : public puffin::StreamInterface {
256   public:
PuffInputStream(uint8_t * buffer,size_t length)257     PuffInputStream(uint8_t* buffer, size_t length) : buffer_(buffer), length_(length), pos_(0) {}
258 
GetSize(uint64_t * size) const259     bool GetSize(uint64_t* size) const override {
260         *size = length_;
261         return true;
262     }
GetOffset(uint64_t * offset) const263     bool GetOffset(uint64_t* offset) const override {
264         *offset = pos_;
265         return true;
266     }
Seek(uint64_t offset)267     bool Seek(uint64_t offset) override {
268         if (offset > length_) return false;
269         pos_ = offset;
270         return true;
271     }
Read(void * buffer,size_t length)272     bool Read(void* buffer, size_t length) override {
273         if (length_ - pos_ < length) return false;
274         memcpy(buffer, buffer_ + pos_, length);
275         pos_ += length;
276         return true;
277     }
Write(const void *,size_t)278     bool Write(const void*, size_t) override { return false; }
Close()279     bool Close() override { return true; }
280 
281   private:
282     uint8_t* buffer_;
283     size_t length_;
284     size_t pos_;
285 };
286 
287 class PuffOutputStream final : public puffin::StreamInterface {
288   public:
PuffOutputStream(std::vector<uint8_t> & stream)289     PuffOutputStream(std::vector<uint8_t>& stream) : stream_(stream), pos_(0) {}
290 
GetSize(uint64_t * size) const291     bool GetSize(uint64_t* size) const override {
292         *size = stream_.size();
293         return true;
294     }
GetOffset(uint64_t * offset) const295     bool GetOffset(uint64_t* offset) const override {
296         *offset = pos_;
297         return true;
298     }
Seek(uint64_t offset)299     bool Seek(uint64_t offset) override {
300         if (offset > stream_.size()) {
301             return false;
302         }
303         pos_ = offset;
304         return true;
305     }
Read(void * buffer,size_t length)306     bool Read(void* buffer, size_t length) override {
307         if (stream_.size() - pos_ < length) {
308             return false;
309         }
310         memcpy(buffer, &stream_[0] + pos_, length);
311         pos_ += length;
312         return true;
313     }
Write(const void * buffer,size_t length)314     bool Write(const void* buffer, size_t length) override {
315         auto remaining = stream_.size() - pos_;
316         if (remaining < length) {
317             stream_.resize(stream_.size() + (length - remaining));
318         }
319         memcpy(&stream_[0] + pos_, buffer, length);
320         pos_ += length;
321         return true;
322     }
Close()323     bool Close() override { return true; }
324 
325   private:
326     std::vector<uint8_t>& stream_;
327     size_t pos_;
328 };
329 
ProcessDiff(const InstallOperation & op)330 bool PayloadConverter::ProcessDiff(const InstallOperation& op) {
331     auto source_image = OpenSourceImage();
332     if (source_image < 0) {
333         return false;
334     }
335 
336     uint64_t src_length = SizeOfAllExtents(op.src_extents());
337     auto src = std::make_unique<uint8_t[]>(src_length);
338     size_t src_pos = 0;
339 
340     // Read source bytes.
341     for (const auto& extent : op.src_extents()) {
342         uint64_t offset = extent.start_block() * kBlockSize;
343         if (lseek(source_image.get(), offset, SEEK_SET) < 0) {
344             PLOG(ERROR) << "lseek source image failed";
345             return false;
346         }
347 
348         uint64_t size = extent.num_blocks() * kBlockSize;
349         CHECK(src_length - src_pos >= size);
350         if (!android::base::ReadFully(source_image, src.get() + src_pos, size)) {
351             PLOG(ERROR) << "read source image failed";
352             return false;
353         }
354         src_pos += size;
355     }
356     CHECK(src_pos == src_length);
357 
358     // Read patch bytes.
359     auto patch = std::make_unique<uint8_t[]>(op.data_length());
360     if (lseek(in_fd_.get(), payload_offset_ + op.data_offset(), SEEK_SET) < 0) {
361         PLOG(ERROR) << "lseek payload failed";
362         return false;
363     }
364     if (!android::base::ReadFully(in_fd_, patch.get(), op.data_length())) {
365         PLOG(ERROR) << "read payload failed";
366         return false;
367     }
368 
369     std::vector<uint8_t> dest(SizeOfAllExtents(op.dst_extents()));
370 
371     // Apply the diff.
372     if (op.type() == InstallOperation::BROTLI_BSDIFF) {
373         size_t dest_pos = 0;
374         auto sink = [&](const uint8_t* data, size_t length) -> size_t {
375             CHECK(dest.size() - dest_pos >= length);
376             memcpy(&dest[dest_pos], data, length);
377             dest_pos += length;
378             return length;
379         };
380         if (int rv = bsdiff::bspatch(src.get(), src_pos, patch.get(), op.data_length(), sink)) {
381             LOG(ERROR) << "bspatch failed, error code " << rv;
382             return false;
383         }
384     } else if (op.type() == InstallOperation::PUFFDIFF) {
385         auto src_stream = std::make_unique<PuffInputStream>(src.get(), src_length);
386         auto dest_stream = std::make_unique<PuffOutputStream>(dest);
387         bool ok = PuffPatch(std::move(src_stream), std::move(dest_stream), patch.get(),
388                             op.data_length());
389         if (!ok) {
390             LOG(ERROR) << "puffdiff operation failed to apply";
391             return false;
392         }
393     } else {
394         LOG(ERROR) << "unsupported diff operation: " << op.type();
395         return false;
396     }
397 
398     // Write the final blocks to the COW.
399     size_t dest_pos = 0;
400     for (const auto& extent : op.dst_extents()) {
401         uint64_t size = extent.num_blocks() * kBlockSize;
402         CHECK(dest.size() - dest_pos >= size);
403 
404         if (!writer_->AddRawBlocks(extent.start_block(), &dest[dest_pos], size)) {
405             return false;
406         }
407         dest_pos += size;
408     }
409     return true;
410 }
411 
OpenSourceImage()412 borrowed_fd PayloadConverter::OpenSourceImage() {
413     if (source_image_ >= 0) {
414         return source_image_;
415     }
416 
417     unique_fd unzip_fd;
418 
419     auto local_path = "IMAGES/" + partition_name_ + ".img";
420     if (source_tf_zip_) {
421         {
422             TemporaryFile tmp;
423             if (tmp.fd < 0) {
424                 PLOG(ERROR) << "mkstemp failed";
425                 return -1;
426             }
427             unzip_fd.reset(tmp.release());
428         }
429 
430         ZipEntry64 entry;
431         if (FindEntry(source_tf_zip_.get(), local_path, &entry)) {
432             LOG(ERROR) << "not found in archive: " << local_path;
433             return -1;
434         }
435         if (ExtractEntryToFile(source_tf_zip_.get(), &entry, unzip_fd.get())) {
436             LOG(ERROR) << "could not extract " << local_path;
437             return -1;
438         }
439         if (lseek(unzip_fd.get(), 0, SEEK_SET) < 0) {
440             PLOG(ERROR) << "lseek failed";
441             return -1;
442         }
443     } else if (source_tf_fd_ >= 0) {
444         unzip_fd.reset(openat(source_tf_fd_.get(), local_path.c_str(), O_RDONLY));
445         if (unzip_fd < 0) {
446             PLOG(ERROR) << "open failed: " << FLAGS_source_tf << "/" << local_path;
447             return -1;
448         }
449     } else {
450         LOG(ERROR) << "No source target files package was specified; need -source_tf";
451         return -1;
452     }
453 
454     std::unique_ptr<struct sparse_file, decltype(&sparse_file_destroy)> s(
455             sparse_file_import(unzip_fd.get(), false, false), &sparse_file_destroy);
456     if (s) {
457         TemporaryFile tmp;
458         if (tmp.fd < 0) {
459             PLOG(ERROR) << "mkstemp failed";
460             return -1;
461         }
462         if (sparse_file_write(s.get(), tmp.fd, false, false, false) < 0) {
463             LOG(ERROR) << "sparse_file_write failed";
464             return -1;
465         }
466         source_image_.reset(tmp.release());
467     } else {
468         source_image_ = std::move(unzip_fd);
469     }
470     return source_image_;
471 }
472 
473 template <typename ContainerType>
474 class ExtentIter final {
475   public:
ExtentIter(const ContainerType & container)476     ExtentIter(const ContainerType& container)
477         : iter_(container.cbegin()), end_(container.cend()), dst_index_(0) {}
478 
GetNext(uint64_t * block)479     bool GetNext(uint64_t* block) {
480         while (iter_ != end_) {
481             if (dst_index_ < iter_->num_blocks()) {
482                 break;
483             }
484             iter_++;
485             dst_index_ = 0;
486         }
487         if (iter_ == end_) {
488             return false;
489         }
490         *block = iter_->start_block() + dst_index_;
491         dst_index_++;
492         return true;
493     }
494 
495   private:
496     typename ContainerType::const_iterator iter_;
497     typename ContainerType::const_iterator end_;
498     uint64_t dst_index_;
499 };
500 
ProcessCopy(const InstallOperation & op)501 bool PayloadConverter::ProcessCopy(const InstallOperation& op) {
502     ExtentIter dst_blocks(op.dst_extents());
503 
504     for (const auto& extent : op.src_extents()) {
505         for (uint64_t i = 0; i < extent.num_blocks(); i++) {
506             uint64_t src_block = extent.start_block() + i;
507             uint64_t dst_block;
508             if (!dst_blocks.GetNext(&dst_block)) {
509                 LOG(ERROR) << "SOURCE_COPY contained mismatching extents";
510                 return false;
511             }
512             if (src_block == dst_block) continue;
513             if (!writer_->AddCopy(dst_block, src_block)) {
514                 LOG(ERROR) << "Could not add copy operation";
515                 return false;
516             }
517         }
518     }
519     return true;
520 }
521 
ProcessReplace(const InstallOperation & op)522 bool PayloadConverter::ProcessReplace(const InstallOperation& op) {
523     auto buffer_size = op.data_length();
524     auto buffer = std::make_unique<char[]>(buffer_size);
525     uint64_t offs = payload_offset_ + op.data_offset();
526     if (lseek(in_fd_.get(), offs, SEEK_SET) < 0) {
527         PLOG(ERROR) << "lseek " << offs << " failed";
528         return false;
529     }
530     if (!android::base::ReadFully(in_fd_, buffer.get(), buffer_size)) {
531         PLOG(ERROR) << "read " << buffer_size << " bytes from offset " << offs << "failed";
532         return false;
533     }
534 
535     uint64_t dst_size = 0;
536     for (const auto& extent : op.dst_extents()) {
537         dst_size += extent.num_blocks() * kBlockSize;
538     }
539 
540     if (op.type() == InstallOperation::REPLACE_BZ) {
541         auto tmp = std::make_unique<char[]>(dst_size);
542 
543         uint32_t actual_size;
544         if (dst_size > std::numeric_limits<typeof(actual_size)>::max()) {
545             LOG(ERROR) << "too many bytes to decompress: " << dst_size;
546             return false;
547         }
548         actual_size = static_cast<uint32_t>(dst_size);
549 
550         auto rv = BZ2_bzBuffToBuffDecompress(tmp.get(), &actual_size, buffer.get(), buffer_size, 0,
551                                              0);
552         if (rv) {
553             LOG(ERROR) << "bz2 decompress failed: " << rv;
554             return false;
555         }
556         if (actual_size != dst_size) {
557             LOG(ERROR) << "bz2 returned " << actual_size << " bytes, expected " << dst_size;
558             return false;
559         }
560         buffer = std::move(tmp);
561         buffer_size = dst_size;
562     } else if (op.type() == InstallOperation::REPLACE_XZ) {
563         constexpr uint32_t kXzMaxDictSize = 64 * 1024 * 1024;
564 
565         if (dst_size > std::numeric_limits<size_t>::max()) {
566             LOG(ERROR) << "too many bytes to decompress: " << dst_size;
567             return false;
568         }
569 
570         std::unique_ptr<struct xz_dec, decltype(&xz_dec_end)> s(
571                 xz_dec_init(XZ_DYNALLOC, kXzMaxDictSize), xz_dec_end);
572         if (!s) {
573             LOG(ERROR) << "xz_dec_init failed";
574             return false;
575         }
576 
577         auto tmp = std::make_unique<char[]>(dst_size);
578 
579         struct xz_buf args;
580         args.in = reinterpret_cast<const uint8_t*>(buffer.get());
581         args.in_pos = 0;
582         args.in_size = buffer_size;
583         args.out = reinterpret_cast<uint8_t*>(tmp.get());
584         args.out_pos = 0;
585         args.out_size = dst_size;
586 
587         auto rv = xz_dec_run(s.get(), &args);
588         if (rv != XZ_STREAM_END) {
589             LOG(ERROR) << "xz decompress failed: " << (int)rv;
590             return false;
591         }
592         buffer = std::move(tmp);
593         buffer_size = dst_size;
594     }
595 
596     uint64_t buffer_pos = 0;
597     for (const auto& extent : op.dst_extents()) {
598         uint64_t extent_size = extent.num_blocks() * kBlockSize;
599         if (buffer_size - buffer_pos < extent_size) {
600             LOG(ERROR) << "replace op ran out of input buffer";
601             return false;
602         }
603         if (!writer_->AddRawBlocks(extent.start_block(), buffer.get() + buffer_pos, extent_size)) {
604             LOG(ERROR) << "failed to add raw blocks from replace op";
605             return false;
606         }
607         buffer_pos += extent_size;
608     }
609     return true;
610 }
611 
OpenPayload()612 bool PayloadConverter::OpenPayload() {
613     in_fd_.reset(open(in_file_.c_str(), O_RDONLY));
614     if (in_fd_ < 0) {
615         PLOG(ERROR) << "open " << in_file_;
616         return false;
617     }
618 
619     char magic[4];
620     if (!android::base::ReadFully(in_fd_, magic, sizeof(magic))) {
621         PLOG(ERROR) << "read magic";
622         return false;
623     }
624     if (std::string(magic, sizeof(magic)) != "CrAU") {
625         LOG(ERROR) << "Invalid magic in " << in_file_;
626         return false;
627     }
628 
629     uint64_t version;
630     uint64_t manifest_size;
631     uint32_t manifest_signature_size = 0;
632     if (!android::base::ReadFully(in_fd_, &version, sizeof(version))) {
633         PLOG(ERROR) << "read version";
634         return false;
635     }
636     version = ToLittleEndian(version);
637     if (version < 2) {
638         LOG(ERROR) << "Only payload version 2 or higher is supported.";
639         return false;
640     }
641 
642     if (!android::base::ReadFully(in_fd_, &manifest_size, sizeof(manifest_size))) {
643         PLOG(ERROR) << "read manifest_size";
644         return false;
645     }
646     manifest_size = ToLittleEndian(manifest_size);
647     if (!android::base::ReadFully(in_fd_, &manifest_signature_size,
648                                   sizeof(manifest_signature_size))) {
649         PLOG(ERROR) << "read manifest_signature_size";
650         return false;
651     }
652     manifest_signature_size = ntohl(manifest_signature_size);
653 
654     auto manifest = std::make_unique<uint8_t[]>(manifest_size);
655     if (!android::base::ReadFully(in_fd_, manifest.get(), manifest_size)) {
656         PLOG(ERROR) << "read manifest";
657         return false;
658     }
659 
660     // Skip past manifest signature.
661     auto offs = lseek(in_fd_, manifest_signature_size, SEEK_CUR);
662     if (offs < 0) {
663         PLOG(ERROR) << "lseek failed";
664         return false;
665     }
666     payload_offset_ = offs;
667 
668     if (!manifest_.ParseFromArray(manifest.get(), manifest_size)) {
669         LOG(ERROR) << "could not parse manifest";
670         return false;
671     }
672     return true;
673 }
674 
675 }  // namespace snapshot
676 }  // namespace android
677 
main(int argc,char ** argv)678 int main(int argc, char** argv) {
679     android::base::InitLogging(argv, android::snapshot::MyLogger);
680     gflags::SetUsageMessage("Convert OTA payload to a Virtual A/B COW");
681     int arg_start = gflags::ParseCommandLineFlags(&argc, &argv, false);
682 
683     xz_crc32_init();
684 
685     if (argc - arg_start != 2) {
686         std::cerr << "Usage: [options] <payload.bin> <out-dir>\n";
687         return 1;
688     }
689 
690     android::snapshot::PayloadConverter pc(argv[arg_start], argv[arg_start + 1]);
691     return pc.Run() ? 0 : 1;
692 }
693