• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 /*
18  * GUID Partition Table and Composite Disk generation code.
19  */
20 
21 #include "host/libs/image_aggregator/image_aggregator.h"
22 
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <stdio.h>
27 
28 #include <fstream>
29 #include <random>
30 #include <string>
31 #include <vector>
32 
33 #include <android-base/file.h>
34 #include <android-base/logging.h>
35 #include <android-base/strings.h>
36 #include <cdisk_spec.pb.h>
37 #include <google/protobuf/text_format.h>
38 #include <sparse/sparse.h>
39 #include <zlib.h>
40 
41 #include "common/libs/fs/shared_buf.h"
42 #include "common/libs/fs/shared_fd.h"
43 #include "common/libs/utils/cf_endian.h"
44 #include "common/libs/utils/files.h"
45 #include "common/libs/utils/size_utils.h"
46 #include "common/libs/utils/subprocess.h"
47 #include "host/libs/config/mbr.h"
48 #include "host/libs/image_aggregator/sparse_image_utils.h"
49 
50 namespace cuttlefish {
51 namespace {
52 
53 constexpr int GPT_NUM_PARTITIONS = 128;
54 static const std::string CDISK_MAGIC = "composite_disk\x1d";
55 static const std::string QCOW2_MAGIC = "QFI\xfb";
56 
57 /**
58  * Creates a "Protective" MBR Partition Table header. The GUID
59  * Partition Table Specification recommends putting this on the first sector
60  * of the disk, to protect against old disk formatting tools from misidentifying
61  * the GUID Partition Table later and doing the wrong thing.
62  */
ProtectiveMbr(std::uint64_t size)63 MasterBootRecord ProtectiveMbr(std::uint64_t size) {
64   MasterBootRecord mbr = {
65       .partitions = {{
66           .partition_type = 0xEE,
67           .first_lba = 1,
68           .num_sectors = (std::uint32_t)size / kSectorSize,
69       }},
70       .boot_signature = {0x55, 0xAA},
71   };
72   return mbr;
73 }
74 
75 struct __attribute__((packed)) GptHeader {
76   std::uint8_t signature[8];
77   std::uint8_t revision[4];
78   std::uint32_t header_size;
79   std::uint32_t header_crc32;
80   std::uint32_t reserved;
81   std::uint64_t current_lba;
82   std::uint64_t backup_lba;
83   std::uint64_t first_usable_lba;
84   std::uint64_t last_usable_lba;
85   std::uint8_t disk_guid[16];
86   std::uint64_t partition_entries_lba;
87   std::uint32_t num_partition_entries;
88   std::uint32_t partition_entry_size;
89   std::uint32_t partition_entries_crc32;
90 };
91 
92 static_assert(sizeof(GptHeader) == 92);
93 
94 struct __attribute__((packed)) GptPartitionEntry {
95   std::uint8_t partition_type_guid[16];
96   std::uint8_t unique_partition_guid[16];
97   std::uint64_t first_lba;
98   std::uint64_t last_lba;
99   std::uint64_t attributes;
100   std::uint16_t partition_name[36]; // UTF-16LE
101 };
102 
103 static_assert(sizeof(GptPartitionEntry) == 128);
104 
105 struct __attribute__((packed)) GptBeginning {
106   MasterBootRecord protective_mbr;
107   GptHeader header;
108   std::uint8_t header_padding[kSectorSize - sizeof(GptHeader)];
109   GptPartitionEntry entries[GPT_NUM_PARTITIONS];
110   std::uint8_t partition_alignment[3072];
111 };
112 
113 static_assert(AlignToPowerOf2(sizeof(GptBeginning), PARTITION_SIZE_SHIFT) ==
114               sizeof(GptBeginning));
115 
116 struct __attribute__((packed)) GptEnd {
117   GptPartitionEntry entries[GPT_NUM_PARTITIONS];
118   GptHeader footer;
119   std::uint8_t footer_padding[kSectorSize - sizeof(GptHeader)];
120 };
121 
122 static_assert(sizeof(GptEnd) % kSectorSize == 0);
123 
124 struct PartitionInfo {
125   MultipleImagePartition source;
126   std::uint64_t size;
127   std::uint64_t offset;
128 
AlignedSizecuttlefish::__anonbd56fc7d0111::PartitionInfo129   std::uint64_t AlignedSize() const { return AlignToPartitionSize(size); }
130 };
131 
132 struct __attribute__((packed)) QCowHeader {
133   Be32 magic;
134   Be32 version;
135   Be64 backing_file_offset;
136   Be32 backing_file_size;
137   Be32 cluster_bits;
138   Be64 size;
139   Be32 crypt_method;
140   Be32 l1_size;
141   Be64 l1_table_offset;
142   Be64 refcount_table_offset;
143   Be32 refcount_table_clusters;
144   Be32 nb_snapshots;
145   Be64 snapshots_offset;
146 };
147 
148 static_assert(sizeof(QCowHeader) == 72);
149 
150 /*
151  * Returns the expanded file size of `file_path`. Note that the raw size of
152  * files doesn't match how large they may appear inside a VM.
153  *
154  * Supported types: Composite disk image, Qcows2, Android-Sparse, Raw
155  *
156  * Android-Sparse is a file format invented by Android that optimizes for
157  * chunks of zeroes or repeated data. The Android build system can produce
158  * sparse files to save on size of disk files after they are extracted from a
159  * disk file, as the imag eflashing process also can handle Android-Sparse
160  * images.
161  */
ExpandedStorageSize(const std::string & file_path)162 std::uint64_t ExpandedStorageSize(const std::string& file_path) {
163   android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY));
164   CHECK(fd.get() >= 0) << "Could not open \"" << file_path << "\""
165                        << strerror(errno);
166 
167   std::uint64_t file_size = FileSize(file_path);
168 
169   // Try to read the disk in a nicely-aligned block size unless the whole file
170   // is smaller.
171   constexpr uint64_t MAGIC_BLOCK_SIZE = 4096;
172   std::string magic(std::min(file_size, MAGIC_BLOCK_SIZE), '\0');
173   if (!android::base::ReadFully(fd, magic.data(), magic.size())) {
174     PLOG(FATAL) << "Fail to read: " << file_path;
175     return 0;
176   }
177   CHECK(lseek(fd, 0, SEEK_SET) != -1)
178       << "Fail to seek(\"" << file_path << "\")" << strerror(errno);
179 
180   // Composite disk image
181   if (android::base::StartsWith(magic, CDISK_MAGIC)) {
182     // seek to the beginning of proto message
183     CHECK(lseek(fd, CDISK_MAGIC.size(), SEEK_SET) != -1)
184         << "Fail to seek(\"" << file_path << "\")" << strerror(errno);
185     std::string message;
186     if (!android::base::ReadFdToString(fd, &message)) {
187       PLOG(FATAL) << "Fail to read(cdisk): " << file_path;
188       return 0;
189     }
190     CompositeDisk cdisk;
191     if (!cdisk.ParseFromString(message)) {
192       PLOG(FATAL) << "Fail to parse(cdisk): " << file_path;
193       return 0;
194     }
195     return cdisk.length();
196   }
197 
198   // Qcow2 image
199   if (android::base::StartsWith(magic, QCOW2_MAGIC)) {
200     QCowHeader header;
201     if (!android::base::ReadFully(fd, &header, sizeof(QCowHeader))) {
202       PLOG(FATAL) << "Fail to read(qcow2 header): " << file_path;
203       return 0;
204     }
205     return header.size.as_uint64_t();
206   }
207 
208   // Android-Sparse
209   if (auto sparse =
210           sparse_file_import(fd, /* verbose */ false, /* crc */ false);
211       sparse) {
212     auto size = sparse_file_len(sparse, false, true);
213     sparse_file_destroy(sparse);
214     return size;
215   }
216 
217   // raw image file
218   return file_size;
219 }
220 
221 /*
222  * strncpy equivalent for u16 data. GPT disks use UTF16-LE for disk labels.
223  */
u16cpy(std::uint16_t * dest,std::uint16_t * src,std::size_t size)224 void u16cpy(std::uint16_t* dest, std::uint16_t* src, std::size_t size) {
225   while (size > 0 && *src) {
226     *dest = *src;
227     dest++;
228     src++;
229     size--;
230   }
231   if (size > 0) {
232     *dest = 0;
233   }
234 }
235 
ToMultipleImagePartition(ImagePartition source)236 MultipleImagePartition ToMultipleImagePartition(ImagePartition source) {
237   return MultipleImagePartition{
238       .label = source.label,
239       .image_file_paths = std::vector{source.image_file_path},
240       .type = source.type,
241       .read_only = source.read_only,
242   };
243 }
244 
SetRandomUuid(std::uint8_t uuid[16])245 void SetRandomUuid(std::uint8_t uuid[16]) {
246   // https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)
247   std::random_device dev;
248   std::mt19937 rng(dev());
249   std::uniform_int_distribution<std::mt19937::result_type> dist(0, 0xff);
250 
251   for (int i = 0; i < 16; i++) {
252     uuid[i] = dist(rng);
253   }
254   // https://www.rfc-editor.org/rfc/rfc4122#section-4.4
255   uuid[7] = (uuid[7] & 0x0F) | 0x40;  // UUID v4
256   uuid[9] = (uuid[9] & 0x3F) | 0x80;
257 }
258 
259 /**
260  * Incremental builder class for producing partition tables. Add partitions
261  * one-by-one, then produce specification files
262  */
263 class CompositeDiskBuilder {
264 private:
265   std::vector<PartitionInfo> partitions_;
266   std::uint64_t next_disk_offset_;
267 
GetPartitionGUID(MultipleImagePartition source)268   static const std::uint8_t* GetPartitionGUID(MultipleImagePartition source) {
269     // Due to some endianness mismatch in e2fsprogs GUID vs GPT, the GUIDs are
270     // rearranged to make the right GUIDs appear in gdisk
271     switch (source.type) {
272       case kLinuxFilesystem: {
273         static constexpr std::uint8_t kLinuxFileSystemGuid[] = {
274             0xaf, 0x3d, 0xc6, 0xf,  0x83, 0x84, 0x72, 0x47,
275             0x8e, 0x79, 0x3d, 0x69, 0xd8, 0x47, 0x7d, 0xe4};
276         return kLinuxFileSystemGuid;
277       }
278       case kEfiSystemPartition:
279         static constexpr std::uint8_t kEfiSystemPartitionGuid[] = {
280             0x28, 0x73, 0x2a, 0xc1, 0x1f, 0xf8, 0xd2, 0x11,
281             0xba, 0x4b, 0x0,  0xa0, 0xc9, 0x3e, 0xc9, 0x3b};
282         return kEfiSystemPartitionGuid;
283       default:
284         LOG(FATAL) << "Unknown partition type: " << (int) source.type;
285     }
286   }
287 
288 public:
CompositeDiskBuilder()289   CompositeDiskBuilder() : next_disk_offset_(sizeof(GptBeginning)) {}
290 
AppendPartition(ImagePartition source)291   void AppendPartition(ImagePartition source) {
292     AppendPartition(ToMultipleImagePartition(source));
293   }
294 
AppendPartition(MultipleImagePartition source)295   void AppendPartition(MultipleImagePartition source) {
296     uint64_t size = 0;
297     for (const auto& path : source.image_file_paths) {
298       size += ExpandedStorageSize(path);
299     }
300     auto aligned_size = AlignToPartitionSize(size);
301     CHECK(size == aligned_size || source.read_only)
302         << "read-write partition " << source.label
303         << " is not aligned to the size of " << (1 << PARTITION_SIZE_SHIFT);
304     partitions_.push_back(PartitionInfo{
305         .source = source,
306         .size = size,
307         .offset = next_disk_offset_,
308     });
309     next_disk_offset_ = next_disk_offset_ + aligned_size;
310   }
311 
DiskSize() const312   std::uint64_t DiskSize() const {
313     return AlignToPowerOf2(next_disk_offset_ + sizeof(GptEnd), DISK_SIZE_SHIFT);
314   }
315 
316   /**
317    * Generates a composite disk specification file, assuming that `header_file`
318    * and `footer_file` will be populated with the contents of `Beginning()` and
319    * `End()`.
320    */
MakeCompositeDiskSpec(const std::string & header_file,const std::string & footer_file) const321   CompositeDisk MakeCompositeDiskSpec(const std::string& header_file,
322                                       const std::string& footer_file) const {
323     CompositeDisk disk;
324     disk.set_version(2);
325     disk.set_length(DiskSize());
326 
327     ComponentDisk* header = disk.add_component_disks();
328     header->set_file_path(header_file);
329     header->set_offset(0);
330 
331     for (auto& partition : partitions_) {
332       uint64_t size = 0;
333       for (const auto& path : partition.source.image_file_paths) {
334         ComponentDisk* component = disk.add_component_disks();
335         component->set_file_path(path);
336         component->set_offset(partition.offset + size);
337         component->set_read_write_capability(
338             partition.source.read_only ? ReadWriteCapability::READ_ONLY
339                                        : ReadWriteCapability::READ_WRITE);
340         size += ExpandedStorageSize(path);
341       }
342       CHECK(partition.size == size);
343       // When partition's aligned size differs from its (unaligned) size
344       // reading the disk within the guest os would fail due to the gap.
345       // Putting any disk bigger than 4K can fill this gap.
346       // Here we reuse the header which is always > 4K.
347       // We don't fill the "writable" disk's hole and it should be an error
348       // because writes in the guest of can't be reflected to the backing file.
349       if (partition.AlignedSize() != partition.size) {
350         ComponentDisk* component = disk.add_component_disks();
351         component->set_file_path(header_file);
352         component->set_offset(partition.offset + partition.size);
353         component->set_read_write_capability(ReadWriteCapability::READ_ONLY);
354       }
355     }
356 
357     ComponentDisk* footer = disk.add_component_disks();
358     footer->set_file_path(footer_file);
359     footer->set_offset(next_disk_offset_);
360 
361     return disk;
362   }
363 
364   /*
365    * Returns a GUID Partition Table header structure for all the disks that have
366    * been added with `AppendDisk`. Includes a protective MBR.
367    *
368    * This method is not deterministic: some data is generated such as the disk
369    * uuids.
370    */
Beginning() const371   GptBeginning Beginning() const {
372     if (partitions_.size() > GPT_NUM_PARTITIONS) {
373       LOG(FATAL) << "Too many partitions: " << partitions_.size();
374       return {};
375     }
376     GptBeginning gpt = {
377         .protective_mbr = ProtectiveMbr(DiskSize()),
378         .header =
379             {
380                 .signature = {'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T'},
381                 .revision = {0, 0, 1, 0},
382                 .header_size = sizeof(GptHeader),
383                 .current_lba = 1,
384                 .backup_lba = (DiskSize() / kSectorSize) - 1,
385                 .first_usable_lba = sizeof(GptBeginning) / kSectorSize,
386                 .last_usable_lba = (next_disk_offset_ / kSectorSize) - 1,
387                 .partition_entries_lba = 2,
388                 .num_partition_entries = GPT_NUM_PARTITIONS,
389                 .partition_entry_size = sizeof(GptPartitionEntry),
390             },
391     };
392     SetRandomUuid(gpt.header.disk_guid);
393     for (std::size_t i = 0; i < partitions_.size(); i++) {
394       const auto& partition = partitions_[i];
395       gpt.entries[i] = GptPartitionEntry{
396           .first_lba = partition.offset / kSectorSize,
397           .last_lba =
398               (partition.offset + partition.AlignedSize()) / kSectorSize - 1,
399       };
400       SetRandomUuid(gpt.entries[i].unique_partition_guid);
401       const std::uint8_t* const type_guid = GetPartitionGUID(partition.source);
402       if (type_guid == nullptr) {
403         LOG(FATAL) << "Could not recognize partition guid";
404       }
405       memcpy(gpt.entries[i].partition_type_guid, type_guid, 16);
406       std::u16string wide_name(partitions_[i].source.label.begin(),
407                               partitions_[i].source.label.end());
408       u16cpy((std::uint16_t*) gpt.entries[i].partition_name,
409             (std::uint16_t*) wide_name.c_str(), 36);
410     }
411     // Not sure these are right, but it works for bpttool
412     gpt.header.partition_entries_crc32 =
413         crc32(0, (std::uint8_t*) gpt.entries,
414               GPT_NUM_PARTITIONS * sizeof(GptPartitionEntry));
415     gpt.header.header_crc32 =
416         crc32(0, (std::uint8_t*) &gpt.header, sizeof(GptHeader));
417     return gpt;
418   }
419 
420   /**
421    * Generates a GUID Partition Table footer that matches the header in `head`.
422    */
End(const GptBeginning & head) const423   GptEnd End(const GptBeginning& head) const {
424     GptEnd gpt;
425     std::memcpy((void*)gpt.entries, (void*)head.entries, sizeof(gpt.entries));
426     gpt.footer = head.header;
427     gpt.footer.partition_entries_lba =
428         (DiskSize() - sizeof(gpt.entries)) / kSectorSize - 1;
429     std::swap(gpt.footer.current_lba, gpt.footer.backup_lba);
430     gpt.footer.header_crc32 = 0;
431     gpt.footer.header_crc32 =
432         crc32(0, (std::uint8_t*) &gpt.footer, sizeof(GptHeader));
433     return gpt;
434   }
435 };
436 
WriteBeginning(SharedFD out,const GptBeginning & beginning)437 bool WriteBeginning(SharedFD out, const GptBeginning& beginning) {
438   std::string begin_str((const char*) &beginning, sizeof(GptBeginning));
439   if (WriteAll(out, begin_str) != begin_str.size()) {
440     LOG(ERROR) << "Could not write GPT beginning: " << out->StrError();
441     return false;
442   }
443   return true;
444 }
445 
WriteEnd(SharedFD out,const GptEnd & end)446 bool WriteEnd(SharedFD out, const GptEnd& end) {
447   auto disk_size = (end.footer.current_lba + 1) * kSectorSize;
448   auto footer_start = (end.footer.last_usable_lba + 1) * kSectorSize;
449   auto padding = disk_size - footer_start - sizeof(GptEnd);
450   std::string padding_str(padding, '\0');
451   if (WriteAll(out, padding_str) != padding_str.size()) {
452     LOG(ERROR) << "Could not write GPT end padding: " << out->StrError();
453     return false;
454   }
455   if (WriteAllBinary(out, &end) != sizeof(end)) {
456     LOG(ERROR) << "Could not write GPT end contents: " << out->StrError();
457     return false;
458   }
459   return true;
460 }
461 
462 /**
463  * Converts any Android-Sparse image files in `partitions` to raw image files.
464  *
465  * Android-Sparse is a file format invented by Android that optimizes for
466  * chunks of zeroes or repeated data. The Android build system can produce
467  * sparse files to save on size of disk files after they are extracted from a
468  * disk file, as the imag eflashing process also can handle Android-Sparse
469  * images.
470  *
471  * crosvm has read-only support for Android-Sparse files, but QEMU does not
472  * support them.
473  */
DeAndroidSparse(const std::vector<ImagePartition> & partitions)474 void DeAndroidSparse(const std::vector<ImagePartition>& partitions) {
475   for (const auto& partition : partitions) {
476     Result<void> res = ForceRawImage(partition.image_file_path);
477     if (!res.ok()) {
478       LOG(FATAL) << "Desparse failed: " << res.error().FormatForEnv();
479     }
480   }
481 }
482 
483 } // namespace
484 
AlignToPartitionSize(uint64_t size)485 uint64_t AlignToPartitionSize(uint64_t size) {
486   return AlignToPowerOf2(size, PARTITION_SIZE_SHIFT);
487 }
488 
AggregateImage(const std::vector<ImagePartition> & partitions,const std::string & output_path)489 void AggregateImage(const std::vector<ImagePartition>& partitions,
490                     const std::string& output_path) {
491   DeAndroidSparse(partitions);
492   CompositeDiskBuilder builder;
493   for (auto& partition : partitions) {
494     builder.AppendPartition(partition);
495   }
496   auto output = SharedFD::Creat(output_path, 0600);
497   auto beginning = builder.Beginning();
498   if (!WriteBeginning(output, beginning)) {
499     LOG(FATAL) << "Could not write GPT beginning to \"" << output_path
500                << "\": " << output->StrError();
501   }
502   for (auto& disk : partitions) {
503     auto disk_fd = SharedFD::Open(disk.image_file_path, O_RDONLY);
504     auto file_size = FileSize(disk.image_file_path);
505     if (!output->CopyFrom(*disk_fd, file_size)) {
506       LOG(FATAL) << "Could not copy from \"" << disk.image_file_path
507                  << "\" to \"" << output_path << "\": " << output->StrError();
508     }
509     // Handle disk images that are not aligned to PARTITION_SIZE_SHIFT
510     std::uint64_t padding = AlignToPartitionSize(file_size) - file_size;
511     std::string padding_str;
512     padding_str.resize(padding, '\0');
513     if (WriteAll(output, padding_str) != padding_str.size()) {
514       LOG(FATAL) << "Could not write partition padding to \"" << output_path
515                  << "\": " << output->StrError();
516     }
517   }
518   if (!WriteEnd(output, builder.End(beginning))) {
519     LOG(FATAL) << "Could not write GPT end to \"" << output_path
520                << "\": " << output->StrError();
521   }
522 };
523 
CreateCompositeDisk(std::vector<ImagePartition> partitions,const std::string & header_file,const std::string & footer_file,const std::string & output_composite_path)524 void CreateCompositeDisk(std::vector<ImagePartition> partitions,
525                          const std::string& header_file,
526                          const std::string& footer_file,
527                          const std::string& output_composite_path) {
528   DeAndroidSparse(partitions);
529   std::vector<MultipleImagePartition> multiple_image_partitions;
530   for (const auto& partition : partitions) {
531     multiple_image_partitions.push_back(ToMultipleImagePartition(partition));
532   }
533   return CreateCompositeDisk(std::move(multiple_image_partitions), header_file,
534                              footer_file, output_composite_path);
535 }
536 
CreateCompositeDisk(std::vector<MultipleImagePartition> partitions,const std::string & header_file,const std::string & footer_file,const std::string & output_composite_path)537 void CreateCompositeDisk(std::vector<MultipleImagePartition> partitions,
538                          const std::string& header_file,
539                          const std::string& footer_file,
540                          const std::string& output_composite_path) {
541   CompositeDiskBuilder builder;
542   for (auto& partition : partitions) {
543     builder.AppendPartition(partition);
544   }
545   auto header = SharedFD::Creat(header_file, 0600);
546   auto beginning = builder.Beginning();
547   if (!WriteBeginning(header, beginning)) {
548     LOG(FATAL) << "Could not write GPT beginning to \"" << header_file
549                << "\": " << header->StrError();
550   }
551   auto footer = SharedFD::Creat(footer_file, 0600);
552   if (!WriteEnd(footer, builder.End(beginning))) {
553     LOG(FATAL) << "Could not write GPT end to \"" << footer_file
554                << "\": " << footer->StrError();
555   }
556   auto composite_proto = builder.MakeCompositeDiskSpec(header_file, footer_file);
557   std::ofstream composite(output_composite_path.c_str(),
558                           std::ios::binary | std::ios::trunc);
559   composite << CDISK_MAGIC;
560   composite_proto.SerializeToOstream(&composite);
561   composite.flush();
562 }
563 
CreateQcowOverlay(const std::string & crosvm_path,const std::string & backing_file,const std::string & output_overlay_path)564 void CreateQcowOverlay(const std::string& crosvm_path,
565                        const std::string& backing_file,
566                        const std::string& output_overlay_path) {
567   Command cmd(crosvm_path);
568   cmd.AddParameter("create_qcow2");
569   cmd.AddParameter("--backing-file");
570   cmd.AddParameter(backing_file);
571   cmd.AddParameter(output_overlay_path);
572 
573   std::string stdout_str;
574   std::string stderr_str;
575   int success =
576       RunWithManagedStdio(std::move(cmd), nullptr, &stdout_str, &stderr_str);
577 
578   if (success != 0) {
579     LOG(ERROR) << "Failed to run `" << crosvm_path
580                << " create_qcow2 --backing-file " << backing_file << " "
581                << output_overlay_path << "`";
582     LOG(ERROR) << "stdout:\n###\n" << stdout_str << "\n###";
583     LOG(ERROR) << "stderr:\n###\n" << stderr_str << "\n###";
584     LOG(FATAL) << "Return code: \"" << success << "\"";
585   }
586 }
587 
588 } // namespace cuttlefish
589