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