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 #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::__anon79229f870111::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
245 /**
246 * Incremental builder class for producing partition tables. Add partitions
247 * one-by-one, then produce specification files
248 */
249 class CompositeDiskBuilder {
250 private:
251 std::vector<PartitionInfo> partitions_;
252 std::uint64_t next_disk_offset_;
253
GetPartitionGUID(MultipleImagePartition source)254 static const char* GetPartitionGUID(MultipleImagePartition source) {
255 // Due to some endianness mismatch in e2fsprogs GUID vs GPT, the GUIDs are
256 // rearranged to make the right GUIDs appear in gdisk
257 switch (source.type) {
258 case kLinuxFilesystem:
259 // Technically 0FC63DAF-8483-4772-8E79-3D69D8477DE4
260 return "AF3DC60F-8384-7247-8E79-3D69D8477DE4";
261 case kEfiSystemPartition:
262 // Technically C12A7328-F81F-11D2-BA4B-00A0C93EC93B
263 return "28732AC1-1FF8-D211-BA4B-00A0C93EC93B";
264 default:
265 LOG(FATAL) << "Unknown partition type: " << (int) source.type;
266 }
267 }
268
269 public:
CompositeDiskBuilder()270 CompositeDiskBuilder() : next_disk_offset_(sizeof(GptBeginning)) {}
271
AppendPartition(ImagePartition source)272 void AppendPartition(ImagePartition source) {
273 AppendPartition(ToMultipleImagePartition(source));
274 }
275
AppendPartition(MultipleImagePartition source)276 void AppendPartition(MultipleImagePartition source) {
277 uint64_t size = 0;
278 for (const auto& path : source.image_file_paths) {
279 size += ExpandedStorageSize(path);
280 }
281 auto aligned_size = AlignToPartitionSize(size);
282 CHECK(size == aligned_size || source.read_only)
283 << "read-write partition " << source.label
284 << " is not aligned to the size of " << (1 << PARTITION_SIZE_SHIFT);
285 partitions_.push_back(PartitionInfo{
286 .source = source,
287 .size = size,
288 .offset = next_disk_offset_,
289 });
290 next_disk_offset_ = next_disk_offset_ + aligned_size;
291 }
292
DiskSize() const293 std::uint64_t DiskSize() const {
294 return AlignToPowerOf2(next_disk_offset_ + sizeof(GptEnd), DISK_SIZE_SHIFT);
295 }
296
297 /**
298 * Generates a composite disk specification file, assuming that `header_file`
299 * and `footer_file` will be populated with the contents of `Beginning()` and
300 * `End()`.
301 */
MakeCompositeDiskSpec(const std::string & header_file,const std::string & footer_file) const302 CompositeDisk MakeCompositeDiskSpec(const std::string& header_file,
303 const std::string& footer_file) const {
304 CompositeDisk disk;
305 disk.set_version(2);
306 disk.set_length(DiskSize());
307
308 ComponentDisk* header = disk.add_component_disks();
309 header->set_file_path(header_file);
310 header->set_offset(0);
311
312 for (auto& partition : partitions_) {
313 uint64_t size = 0;
314 for (const auto& path : partition.source.image_file_paths) {
315 ComponentDisk* component = disk.add_component_disks();
316 component->set_file_path(path);
317 component->set_offset(partition.offset + size);
318 component->set_read_write_capability(
319 partition.source.read_only ? ReadWriteCapability::READ_ONLY
320 : ReadWriteCapability::READ_WRITE);
321 size += ExpandedStorageSize(path);
322 }
323 CHECK(partition.size == size);
324 // When partition's aligned size differs from its (unaligned) size
325 // reading the disk within the guest os would fail due to the gap.
326 // Putting any disk bigger than 4K can fill this gap.
327 // Here we reuse the header which is always > 4K.
328 // We don't fill the "writable" disk's hole and it should be an error
329 // because writes in the guest of can't be reflected to the backing file.
330 if (partition.AlignedSize() != partition.size) {
331 ComponentDisk* component = disk.add_component_disks();
332 component->set_file_path(header_file);
333 component->set_offset(partition.offset + partition.size);
334 component->set_read_write_capability(ReadWriteCapability::READ_ONLY);
335 }
336 }
337
338 ComponentDisk* footer = disk.add_component_disks();
339 footer->set_file_path(footer_file);
340 footer->set_offset(next_disk_offset_);
341
342 return disk;
343 }
344
345 /*
346 * Returns a GUID Partition Table header structure for all the disks that have
347 * been added with `AppendDisk`. Includes a protective MBR.
348 *
349 * This method is not deterministic: some data is generated such as the disk
350 * uuids.
351 */
Beginning() const352 GptBeginning Beginning() const {
353 if (partitions_.size() > GPT_NUM_PARTITIONS) {
354 LOG(FATAL) << "Too many partitions: " << partitions_.size();
355 return {};
356 }
357 GptBeginning gpt = {
358 .protective_mbr = ProtectiveMbr(DiskSize()),
359 .header =
360 {
361 .signature = {'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T'},
362 .revision = {0, 0, 1, 0},
363 .header_size = sizeof(GptHeader),
364 .current_lba = 1,
365 .backup_lba = (DiskSize() / kSectorSize) - 1,
366 .first_usable_lba = sizeof(GptBeginning) / kSectorSize,
367 .last_usable_lba = (next_disk_offset_ / kSectorSize) - 1,
368 .partition_entries_lba = 2,
369 .num_partition_entries = GPT_NUM_PARTITIONS,
370 .partition_entry_size = sizeof(GptPartitionEntry),
371 },
372 };
373 uuid_generate(gpt.header.disk_guid);
374 for (std::size_t i = 0; i < partitions_.size(); i++) {
375 const auto& partition = partitions_[i];
376 gpt.entries[i] = GptPartitionEntry{
377 .first_lba = partition.offset / kSectorSize,
378 .last_lba =
379 (partition.offset + partition.AlignedSize()) / kSectorSize - 1,
380 };
381 uuid_generate(gpt.entries[i].unique_partition_guid);
382 if (uuid_parse(GetPartitionGUID(partition.source),
383 gpt.entries[i].partition_type_guid)) {
384 LOG(FATAL) << "Could not parse partition guid";
385 }
386 std::u16string wide_name(partitions_[i].source.label.begin(),
387 partitions_[i].source.label.end());
388 u16cpy((std::uint16_t*) gpt.entries[i].partition_name,
389 (std::uint16_t*) wide_name.c_str(), 36);
390 }
391 // Not sure these are right, but it works for bpttool
392 gpt.header.partition_entries_crc32 =
393 crc32(0, (std::uint8_t*) gpt.entries,
394 GPT_NUM_PARTITIONS * sizeof(GptPartitionEntry));
395 gpt.header.header_crc32 =
396 crc32(0, (std::uint8_t*) &gpt.header, sizeof(GptHeader));
397 return gpt;
398 }
399
400 /**
401 * Generates a GUID Partition Table footer that matches the header in `head`.
402 */
End(const GptBeginning & head) const403 GptEnd End(const GptBeginning& head) const {
404 GptEnd gpt;
405 std::memcpy((void*)gpt.entries, (void*)head.entries, sizeof(gpt.entries));
406 gpt.footer = head.header;
407 gpt.footer.partition_entries_lba =
408 (DiskSize() - sizeof(gpt.entries)) / kSectorSize - 1;
409 std::swap(gpt.footer.current_lba, gpt.footer.backup_lba);
410 gpt.footer.header_crc32 = 0;
411 gpt.footer.header_crc32 =
412 crc32(0, (std::uint8_t*) &gpt.footer, sizeof(GptHeader));
413 return gpt;
414 }
415 };
416
WriteBeginning(SharedFD out,const GptBeginning & beginning)417 bool WriteBeginning(SharedFD out, const GptBeginning& beginning) {
418 std::string begin_str((const char*) &beginning, sizeof(GptBeginning));
419 if (WriteAll(out, begin_str) != begin_str.size()) {
420 LOG(ERROR) << "Could not write GPT beginning: " << out->StrError();
421 return false;
422 }
423 return true;
424 }
425
WriteEnd(SharedFD out,const GptEnd & end)426 bool WriteEnd(SharedFD out, const GptEnd& end) {
427 auto disk_size = (end.footer.current_lba + 1) * kSectorSize;
428 auto footer_start = (end.footer.last_usable_lba + 1) * kSectorSize;
429 auto padding = disk_size - footer_start - sizeof(GptEnd);
430 std::string padding_str(padding, '\0');
431 if (WriteAll(out, padding_str) != padding_str.size()) {
432 LOG(ERROR) << "Could not write GPT end padding: " << out->StrError();
433 return false;
434 }
435 if (WriteAllBinary(out, &end) != sizeof(end)) {
436 LOG(ERROR) << "Could not write GPT end contents: " << out->StrError();
437 return false;
438 }
439 return true;
440 }
441
442 /**
443 * Converts any Android-Sparse image files in `partitions` to raw image files.
444 *
445 * Android-Sparse is a file format invented by Android that optimizes for
446 * chunks of zeroes or repeated data. The Android build system can produce
447 * sparse files to save on size of disk files after they are extracted from a
448 * disk file, as the imag eflashing process also can handle Android-Sparse
449 * images.
450 *
451 * crosvm has read-only support for Android-Sparse files, but QEMU does not
452 * support them.
453 */
DeAndroidSparse(const std::vector<ImagePartition> & partitions)454 void DeAndroidSparse(const std::vector<ImagePartition>& partitions) {
455 for (const auto& partition : partitions) {
456 if (!ConvertToRawImage(partition.image_file_path)) {
457 LOG(DEBUG) << "Failed to desparse " << partition.image_file_path;
458 }
459 }
460 }
461
462 } // namespace
463
AlignToPartitionSize(uint64_t size)464 uint64_t AlignToPartitionSize(uint64_t size) {
465 return AlignToPowerOf2(size, PARTITION_SIZE_SHIFT);
466 }
467
AggregateImage(const std::vector<ImagePartition> & partitions,const std::string & output_path)468 void AggregateImage(const std::vector<ImagePartition>& partitions,
469 const std::string& output_path) {
470 DeAndroidSparse(partitions);
471 CompositeDiskBuilder builder;
472 for (auto& partition : partitions) {
473 builder.AppendPartition(partition);
474 }
475 auto output = SharedFD::Creat(output_path, 0600);
476 auto beginning = builder.Beginning();
477 if (!WriteBeginning(output, beginning)) {
478 LOG(FATAL) << "Could not write GPT beginning to \"" << output_path
479 << "\": " << output->StrError();
480 }
481 for (auto& disk : partitions) {
482 auto disk_fd = SharedFD::Open(disk.image_file_path, O_RDONLY);
483 auto file_size = FileSize(disk.image_file_path);
484 if (!output->CopyFrom(*disk_fd, file_size)) {
485 LOG(FATAL) << "Could not copy from \"" << disk.image_file_path
486 << "\" to \"" << output_path << "\": " << output->StrError();
487 }
488 // Handle disk images that are not aligned to PARTITION_SIZE_SHIFT
489 std::uint64_t padding = AlignToPartitionSize(file_size) - file_size;
490 std::string padding_str;
491 padding_str.resize(padding, '\0');
492 if (WriteAll(output, padding_str) != padding_str.size()) {
493 LOG(FATAL) << "Could not write partition padding to \"" << output_path
494 << "\": " << output->StrError();
495 }
496 }
497 if (!WriteEnd(output, builder.End(beginning))) {
498 LOG(FATAL) << "Could not write GPT end to \"" << output_path
499 << "\": " << output->StrError();
500 }
501 };
502
CreateCompositeDisk(std::vector<ImagePartition> partitions,const std::string & header_file,const std::string & footer_file,const std::string & output_composite_path)503 void CreateCompositeDisk(std::vector<ImagePartition> partitions,
504 const std::string& header_file,
505 const std::string& footer_file,
506 const std::string& output_composite_path) {
507 DeAndroidSparse(partitions);
508 std::vector<MultipleImagePartition> multiple_image_partitions;
509 for (const auto& partition : partitions) {
510 multiple_image_partitions.push_back(ToMultipleImagePartition(partition));
511 }
512 return CreateCompositeDisk(std::move(multiple_image_partitions), header_file,
513 footer_file, output_composite_path);
514 }
515
CreateCompositeDisk(std::vector<MultipleImagePartition> partitions,const std::string & header_file,const std::string & footer_file,const std::string & output_composite_path)516 void CreateCompositeDisk(std::vector<MultipleImagePartition> partitions,
517 const std::string& header_file,
518 const std::string& footer_file,
519 const std::string& output_composite_path) {
520 CompositeDiskBuilder builder;
521 for (auto& partition : partitions) {
522 builder.AppendPartition(partition);
523 }
524 auto header = SharedFD::Creat(header_file, 0600);
525 auto beginning = builder.Beginning();
526 if (!WriteBeginning(header, beginning)) {
527 LOG(FATAL) << "Could not write GPT beginning to \"" << header_file
528 << "\": " << header->StrError();
529 }
530 auto footer = SharedFD::Creat(footer_file, 0600);
531 if (!WriteEnd(footer, builder.End(beginning))) {
532 LOG(FATAL) << "Could not write GPT end to \"" << footer_file
533 << "\": " << footer->StrError();
534 }
535 auto composite_proto = builder.MakeCompositeDiskSpec(header_file, footer_file);
536 std::ofstream composite(output_composite_path.c_str(),
537 std::ios::binary | std::ios::trunc);
538 composite << CDISK_MAGIC;
539 composite_proto.SerializeToOstream(&composite);
540 composite.flush();
541 }
542
CreateQcowOverlay(const std::string & crosvm_path,const std::string & backing_file,const std::string & output_overlay_path)543 void CreateQcowOverlay(const std::string& crosvm_path,
544 const std::string& backing_file,
545 const std::string& output_overlay_path) {
546 Command cmd(crosvm_path);
547 cmd.AddParameter("create_qcow2");
548 cmd.AddParameter("--backing-file");
549 cmd.AddParameter(backing_file);
550 cmd.AddParameter(output_overlay_path);
551
552 std::string stdout_str;
553 std::string stderr_str;
554 int success =
555 RunWithManagedStdio(std::move(cmd), nullptr, &stdout_str, &stderr_str);
556
557 if (success != 0) {
558 LOG(ERROR) << "Failed to run `" << crosvm_path
559 << " create_qcow2 --backing-file " << backing_file << " "
560 << output_overlay_path << "`";
561 LOG(ERROR) << "stdout:\n###\n" << stdout_str << "\n###";
562 LOG(ERROR) << "stderr:\n###\n" << stderr_str << "\n###";
563 LOG(FATAL) << "Return code: \"" << success << "\"";
564 }
565 }
566
567 } // namespace cuttlefish
568