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/commands/assemble_cvd/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/logging.h>
33 #include <json/json.h>
34 #include <google/protobuf/text_format.h>
35 #include <sparse/sparse.h>
36 #include <uuid.h>
37 #include <zlib.h>
38 
39 #include "common/libs/fs/shared_buf.h"
40 #include "common/libs/fs/shared_fd.h"
41 #include "common/libs/utils/files.h"
42 #include "common/libs/utils/subprocess.h"
43 #include "host/libs/config/cuttlefish_config.h"
44 #include "host/libs/config/mbr.h"
45 #include "device/google/cuttlefish/host/commands/assemble_cvd/cdisk_spec.pb.h"
46 
47 namespace {
48 
49 constexpr int GPT_NUM_PARTITIONS = 128;
50 
51 /**
52  * Creates a "Protective" MBR Partition Table header. The GUID
53  * Partition Table Specification recommends putting this on the first sector
54  * of the disk, to protect against old disk formatting tools from misidentifying
55  * the GUID Partition Table later and doing the wrong thing.
56  */
ProtectiveMbr(std::uint64_t size)57 MasterBootRecord ProtectiveMbr(std::uint64_t size) {
58   MasterBootRecord mbr = {
59     .partitions =  {{
60       .partition_type = 0xEE,
61       .first_lba = 1,
62       .num_sectors = (std::uint32_t) size / SECTOR_SIZE,
63     }},
64     .boot_signature = { 0x55, 0xAA },
65   };
66   return mbr;
67 }
68 
69 struct __attribute__((packed)) GptHeader {
70   std::uint8_t signature[8];
71   std::uint8_t revision[4];
72   std::uint32_t header_size;
73   std::uint32_t header_crc32;
74   std::uint32_t reserved;
75   std::uint64_t current_lba;
76   std::uint64_t backup_lba;
77   std::uint64_t first_usable_lba;
78   std::uint64_t last_usable_lba;
79   std::uint8_t disk_guid[16];
80   std::uint64_t partition_entries_lba;
81   std::uint32_t num_partition_entries;
82   std::uint32_t partition_entry_size;
83   std::uint32_t partition_entries_crc32;
84 };
85 
86 static_assert(sizeof(GptHeader) == 92);
87 
88 struct __attribute__((packed)) GptPartitionEntry {
89   std::uint8_t partition_type_guid[16];
90   std::uint8_t unique_partition_guid[16];
91   std::uint64_t first_lba;
92   std::uint64_t last_lba;
93   std::uint64_t attributes;
94   std::uint16_t partition_name[36]; // UTF-16LE
95 };
96 
97 static_assert(sizeof(GptPartitionEntry) == 128);
98 
99 struct __attribute__((packed)) GptBeginning {
100   MasterBootRecord protective_mbr;
101   GptHeader header;
102   std::uint8_t header_padding[420];
103   GptPartitionEntry entries[GPT_NUM_PARTITIONS];
104   std::uint8_t partition_alignment[3072];
105 };
106 
107 static_assert(sizeof(GptBeginning) == SECTOR_SIZE * 40);
108 
109 struct __attribute__((packed)) GptEnd {
110   GptPartitionEntry entries[GPT_NUM_PARTITIONS];
111   GptHeader footer;
112   std::uint8_t footer_padding[420];
113 };
114 
115 static_assert(sizeof(GptEnd) == SECTOR_SIZE * 33);
116 
117 struct PartitionInfo {
118   ImagePartition source;
119   std::uint64_t size;
120   std::uint64_t offset;
121 };
122 
123 /*
124  * Returns the file size of `file_path`. If `file_path` is an Android-Sparse
125  * file, returns the file size it would have after being converted to a raw
126  * file.
127  *
128  * Android-Sparse is a file format invented by Android that optimizes for
129  * chunks of zeroes or repeated data. The Android build system can produce
130  * sparse files to save on size of disk files after they are extracted from a
131  * disk file, as the imag eflashing process also can handle Android-Sparse
132  * images.
133  */
UnsparsedSize(const std::string & file_path)134 std::uint64_t UnsparsedSize(const std::string& file_path) {
135   auto fd = open(file_path.c_str(), O_RDONLY);
136   CHECK(fd >= 0) << "Could not open \"" << file_path << "\""
137                  << strerror(errno);
138   auto sparse = sparse_file_import(fd, /* verbose */ false, /* crc */ false);
139   auto size =
140       sparse ? sparse_file_len(sparse, false, true) : cuttlefish::FileSize(file_path);
141   close(fd);
142   return size;
143 }
144 
145 /*
146  * strncpy equivalent for u16 data. GPT disks use UTF16-LE for disk labels.
147  */
u16cpy(std::uint16_t * dest,std::uint16_t * src,std::size_t size)148 void u16cpy(std::uint16_t* dest, std::uint16_t* src, std::size_t size) {
149   while (size > 0 && *src) {
150     *dest = *src;
151     dest++;
152     src++;
153     size--;
154   }
155   if (size > 0) {
156     *dest = 0;
157   }
158 }
159 
160 /**
161  * Incremental builder class for producing partition tables. Add partitions
162  * one-by-one, then produce specification files
163  */
164 class CompositeDiskBuilder {
165 private:
166   std::vector<PartitionInfo> partitions_;
167   std::uint64_t next_disk_offset_;
168 public:
CompositeDiskBuilder()169   CompositeDiskBuilder() : next_disk_offset_(sizeof(GptBeginning)) {}
170 
AppendDisk(ImagePartition source)171   void AppendDisk(ImagePartition source) {
172     auto size = UnsparsedSize(source.image_file_path);
173     partitions_.push_back(PartitionInfo {
174       .source = source,
175       .size = size,
176       .offset = next_disk_offset_,
177     });
178     next_disk_offset_ += size;
179   }
180 
DiskSize() const181   std::uint64_t DiskSize() const {
182     std::uint64_t align = 1 << 16; // 64k alignment
183     std::uint64_t val = next_disk_offset_ + sizeof(GptEnd);
184     return ((val + (align - 1)) / align) * align;
185   }
186 
187   /**
188    * Generates a composite disk specification file, assuming that `header_file`
189    * and `footer_file` will be populated with the contents of `Beginning()` and
190    * `End()`.
191    */
MakeCompositeDiskSpec(const std::string & header_file,const std::string & footer_file) const192   CompositeDisk MakeCompositeDiskSpec(const std::string& header_file,
193                                       const std::string& footer_file) const {
194     CompositeDisk disk;
195     disk.set_version(1);
196     disk.set_length(DiskSize());
197 
198     ComponentDisk* header = disk.add_component_disks();
199     header->set_file_path(header_file);
200     header->set_offset(0);
201 
202     for (auto& partition : partitions_) {
203       ComponentDisk* component = disk.add_component_disks();
204       component->set_file_path(partition.source.image_file_path);
205       component->set_offset(partition.offset);
206       component->set_read_write_capability(ReadWriteCapability::READ_WRITE);
207     }
208 
209     ComponentDisk* footer = disk.add_component_disks();
210     footer->set_file_path(footer_file);
211     footer->set_offset(next_disk_offset_);
212 
213     return disk;
214   }
215 
216   /*
217    * Returns a GUID Partition Table header structure for all the disks that have
218    * been added with `AppendDisk`. Includes a protective MBR.
219    *
220    * This method is not deterministic: some data is generated such as the disk
221    * uuids.
222    */
Beginning() const223   GptBeginning Beginning() const {
224     if (partitions_.size() > GPT_NUM_PARTITIONS) {
225       LOG(FATAL) << "Too many partitions: " << partitions_.size();
226       return {};
227     }
228     GptBeginning gpt = {
229       .protective_mbr = ProtectiveMbr(DiskSize()),
230       .header = {
231         .signature = {'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T'},
232         .revision = {0, 0, 1, 0},
233         .header_size = sizeof(GptHeader),
234         .current_lba = 1,
235         .backup_lba = (next_disk_offset_ + sizeof(GptEnd)) / SECTOR_SIZE - 1,
236         .first_usable_lba = sizeof(GptBeginning) / SECTOR_SIZE,
237         .last_usable_lba = (next_disk_offset_ - SECTOR_SIZE) / SECTOR_SIZE,
238         .partition_entries_lba = 2,
239         .num_partition_entries = GPT_NUM_PARTITIONS,
240         .partition_entry_size = sizeof(GptPartitionEntry),
241       },
242     };
243     uuid_generate(gpt.header.disk_guid);
244     for (std::size_t i = 0; i < partitions_.size(); i++) {
245       const auto& partition = partitions_[i];
246       gpt.entries[i] = GptPartitionEntry {
247         .first_lba = partition.offset / SECTOR_SIZE,
248         .last_lba = (partition.offset + partition.size - SECTOR_SIZE)
249                     / SECTOR_SIZE,
250       };
251       uuid_generate(gpt.entries[i].unique_partition_guid);
252       // The right uuid is technically 0FC63DAF-8483-4772-8E79-3D69D8477DE4.
253       // Due to some endianness mismatch in e2fsprogs uuid vs GPT, this rearranged
254       // one makes the right uuid type appear in gdisk.
255       if (uuid_parse("AF3DC60F-8384-7247-8E79-3D69D8477DE4", // linux_fs
256                     gpt.entries[i].partition_type_guid)) {
257         LOG(FATAL) << "Could not parse linux_fs uuid";
258       }
259       std::u16string wide_name(partitions_[i].source.label.begin(),
260                               partitions_[i].source.label.end());
261       u16cpy((std::uint16_t*) gpt.entries[i].partition_name,
262             (std::uint16_t*) wide_name.c_str(), 36);
263     }
264     // Not sure these are right, but it works for bpttool
265     gpt.header.partition_entries_crc32 =
266         crc32(0, (std::uint8_t*) gpt.entries,
267               GPT_NUM_PARTITIONS * sizeof(GptPartitionEntry));
268     gpt.header.header_crc32 =
269         crc32(0, (std::uint8_t*) &gpt.header, sizeof(GptHeader));
270     return gpt;
271   }
272 
273   /**
274    * Generates a GUID Partition Table footer that matches the header in `head`.
275    */
End(const GptBeginning & head) const276   GptEnd End(const GptBeginning& head) const {
277     GptEnd gpt;
278     std::memcpy((void*) gpt.entries, (void*) head.entries, 128 * 128);
279     gpt.footer = head.header;
280     gpt.footer.partition_entries_lba = next_disk_offset_ / SECTOR_SIZE;
281     std::swap(gpt.footer.current_lba, gpt.footer.backup_lba);
282     gpt.footer.header_crc32 = 0;
283     gpt.footer.header_crc32 =
284         crc32(0, (std::uint8_t*) &gpt.footer, sizeof(GptHeader));
285     return gpt;
286   }
287 };
288 
WriteBeginning(cuttlefish::SharedFD out,const GptBeginning & beginning)289 bool WriteBeginning(cuttlefish::SharedFD out, const GptBeginning& beginning) {
290   std::string begin_str((const char*) &beginning, sizeof(GptBeginning));
291   if (cuttlefish::WriteAll(out, begin_str) != begin_str.size()) {
292     LOG(ERROR) << "Could not write GPT beginning: " << out->StrError();
293     return false;
294   }
295   return true;
296 }
297 
WriteEnd(cuttlefish::SharedFD out,const GptEnd & end,std::int64_t padding)298 bool WriteEnd(cuttlefish::SharedFD out, const GptEnd& end, std::int64_t padding) {
299   std::string end_str((const char*) &end, sizeof(GptEnd));
300   end_str.resize(end_str.size() + padding, '\0');
301   if (cuttlefish::WriteAll(out, end_str) != end_str.size()) {
302     LOG(ERROR) << "Could not write GPT end: " << out->StrError();
303     return false;
304   }
305   return true;
306 }
307 
308 /**
309  * Converts any Android-Sparse image files in `partitions` to raw image files.
310  *
311  * Android-Sparse is a file format invented by Android that optimizes for
312  * chunks of zeroes or repeated data. The Android build system can produce
313  * sparse files to save on size of disk files after they are extracted from a
314  * disk file, as the imag eflashing process also can handle Android-Sparse
315  * images.
316  *
317  * crosvm has read-only support for Android-Sparse files, but QEMU does not
318  * support them.
319  */
DeAndroidSparse(const std::vector<ImagePartition> & partitions)320 void DeAndroidSparse(const std::vector<ImagePartition>& partitions) {
321   for (const auto& partition : partitions) {
322     auto fd = open(partition.image_file_path.c_str(), O_RDONLY);
323     if (fd < 0) {
324       PLOG(FATAL) << "Could not open \"" << partition.image_file_path;
325       break;
326     }
327     auto sparse = sparse_file_import(fd, /* verbose */ false, /* crc */ false);
328     if (!sparse) {
329       close(fd);
330       continue;
331     }
332     LOG(INFO) << "Desparsing " << partition.image_file_path;
333     std::string out_file_name = partition.image_file_path + ".desparse";
334     auto write_fd = open(out_file_name.c_str(), O_RDWR | O_CREAT | O_TRUNC,
335                          S_IRUSR | S_IWUSR | S_IRGRP);
336     if (write_fd < 0) {
337       PLOG(FATAL) << "Could not open " << out_file_name;
338     }
339     int write_status = sparse_file_write(sparse, write_fd, /* gz */ false,
340                                          /* sparse */ false, /* crc */ false);
341     if (write_status < 0) {
342       LOG(FATAL) << "Failed to desparse \"" << partition.image_file_path
343                  << "\": " << write_status;
344     }
345     close(write_fd);
346     if (rename(out_file_name.c_str(), partition.image_file_path.c_str()) < 0) {
347       int error_num = errno;
348       LOG(FATAL) << "Could not move \"" << out_file_name << "\" to \""
349                  << partition.image_file_path << "\": " << strerror(error_num);
350     }
351     sparse_file_destroy(sparse);
352     close(fd);
353   }
354 }
355 
356 } // namespace
357 
AggregateImage(const std::vector<ImagePartition> & partitions,const std::string & output_path)358 void AggregateImage(const std::vector<ImagePartition>& partitions,
359                     const std::string& output_path) {
360   DeAndroidSparse(partitions);
361   CompositeDiskBuilder builder;
362   for (auto& disk : partitions) {
363     builder.AppendDisk(disk);
364   }
365   auto output = cuttlefish::SharedFD::Creat(output_path, 0600);
366   auto beginning = builder.Beginning();
367   if (!WriteBeginning(output, beginning)) {
368     LOG(FATAL) << "Could not write GPT beginning to \"" << output_path
369                << "\": " << output->StrError();
370   }
371   for (auto& disk : partitions) {
372     auto disk_fd = cuttlefish::SharedFD::Open(disk.image_file_path, O_RDONLY);
373     auto file_size = cuttlefish::FileSize(disk.image_file_path);
374     if (!output->CopyFrom(*disk_fd, file_size)) {
375       LOG(FATAL) << "Could not copy from \"" << disk.image_file_path
376                  << "\" to \"" << output_path << "\": " << output->StrError();
377     }
378   }
379   std::uint64_t padding =
380       builder.DiskSize() - ((beginning.header.backup_lba + 1) * SECTOR_SIZE);
381   if (!WriteEnd(output, builder.End(beginning), padding)) {
382     LOG(FATAL) << "Could not write GPT end to \"" << output_path
383                << "\": " << output->StrError();
384   }
385 };
386 
CreateCompositeDisk(std::vector<ImagePartition> partitions,const std::string & header_file,const std::string & footer_file,const std::string & output_composite_path)387 void CreateCompositeDisk(std::vector<ImagePartition> partitions,
388                          const std::string& header_file,
389                          const std::string& footer_file,
390                          const std::string& output_composite_path) {
391   CompositeDiskBuilder builder;
392   for (auto& disk : partitions) {
393     builder.AppendDisk(disk);
394   }
395   auto header = cuttlefish::SharedFD::Creat(header_file, 0600);
396   auto beginning = builder.Beginning();
397   if (!WriteBeginning(header, beginning)) {
398     LOG(FATAL) << "Could not write GPT beginning to \"" << header_file
399                << "\": " << header->StrError();
400   }
401   auto footer = cuttlefish::SharedFD::Creat(footer_file, 0600);
402   std::uint64_t padding =
403       builder.DiskSize() - ((beginning.header.backup_lba + 1) * SECTOR_SIZE);
404   if (!WriteEnd(footer, builder.End(beginning), padding)) {
405     LOG(FATAL) << "Could not write GPT end to \"" << footer_file
406                << "\": " << footer->StrError();
407   }
408   auto composite_proto = builder.MakeCompositeDiskSpec(header_file, footer_file);
409   std::ofstream composite(output_composite_path.c_str(),
410                           std::ios::binary | std::ios::trunc);
411   composite << "composite_disk\x1d";
412   composite_proto.SerializeToOstream(&composite);
413   composite.flush();
414 }
415 
CreateQcowOverlay(const std::string & crosvm_path,const std::string & backing_file,const std::string & output_overlay_path)416 void CreateQcowOverlay(const std::string& crosvm_path,
417                        const std::string& backing_file,
418                        const std::string& output_overlay_path) {
419   cuttlefish::Command crosvm_qcow2_cmd(crosvm_path);
420   crosvm_qcow2_cmd.AddParameter("create_qcow2");
421   crosvm_qcow2_cmd.AddParameter("--backing_file=", backing_file);
422   crosvm_qcow2_cmd.AddParameter(output_overlay_path);
423   int success = crosvm_qcow2_cmd.Start().Wait();
424   if (success != 0) {
425     LOG(FATAL) << "Unable to run crosvm create_qcow2. Exited with status " << success;
426   }
427 }
428