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 #include "host/commands/assemble_cvd/disk_flags.h"
18
19 #include <sys/statvfs.h>
20
21 #include <fstream>
22
23 #include <android-base/logging.h>
24 #include <gflags/gflags.h>
25
26 #include "common/libs/utils/files.h"
27 #include "common/libs/utils/subprocess.h"
28 #include "host/libs/config/cuttlefish_config.h"
29 #include "host/libs/config/data_image.h"
30 #include "host/libs/vm_manager/crosvm_manager.h"
31 #include "host/commands/assemble_cvd/assembler_defs.h"
32 #include "host/commands/assemble_cvd/boot_config.h"
33 #include "host/commands/assemble_cvd/boot_image_unpacker.h"
34 #include "host/commands/assemble_cvd/image_aggregator.h"
35 #include "host/commands/assemble_cvd/super_image_mixer.h"
36
37 // Taken from external/avb/libavb/avb_slot_verify.c; this define is not in the headers
38 #define VBMETA_MAX_SIZE 65536ul
39
40 using cuttlefish::AssemblerExitCodes;
41 using cuttlefish::CreateBlankImage;
42 using cuttlefish::DataImageResult;
43 using cuttlefish::InitializeMiscImage;
44 using cuttlefish::vm_manager::CrosvmManager;
45
46 DEFINE_string(system_image_dir, cuttlefish::DefaultGuestImagePath(""),
47 "Location of the system partition images.");
48
49 DEFINE_string(boot_env_image, "",
50 "Location of the boot environment image. If the image does not "
51 "exist, a default boot environment image is created.");
52 DEFINE_string(boot_image, "",
53 "Location of cuttlefish boot image. If empty it is assumed to be "
54 "boot.img in the directory specified by -system_image_dir.");
55 DEFINE_string(cache_image, "", "Location of the cache partition image.");
56 DEFINE_string(data_image, "", "Location of the data partition image.");
57 DEFINE_string(super_image, "", "Location of the super partition image.");
58 DEFINE_string(misc_image, "",
59 "Location of the misc partition image. If the image does not "
60 "exist, a blank new misc partition image is created.");
61 DEFINE_string(metadata_image, "", "Location of the metadata partition image "
62 "to be generated.");
63 DEFINE_string(vendor_boot_image, "",
64 "Location of cuttlefish vendor boot image. If empty it is assumed to "
65 "be vendor_boot.img in the directory specified by -system_image_dir.");
66 DEFINE_string(vbmeta_image, "",
67 "Location of cuttlefish vbmeta image. If empty it is assumed to "
68 "be vbmeta.img in the directory specified by -system_image_dir.");
69 DEFINE_string(vbmeta_system_image, "",
70 "Location of cuttlefish vbmeta_system image. If empty it is assumed to "
71 "be vbmeta_system.img in the directory specified by -system_image_dir.");
72
73 DEFINE_int32(blank_metadata_image_mb, 16,
74 "The size of the blank metadata image to generate, MB.");
75 DEFINE_int32(blank_sdcard_image_mb, 2048,
76 "The size of the blank sdcard image to generate, MB.");
77
78 DECLARE_string(initramfs_path);
79 DECLARE_string(kernel_path);
80 DECLARE_bool(resume);
81
ResolveInstanceFiles()82 bool ResolveInstanceFiles() {
83 if (FLAGS_system_image_dir.empty()) {
84 LOG(ERROR) << "--system_image_dir must be specified.";
85 return false;
86 }
87
88 // If user did not specify location of either of these files, expect them to
89 // be placed in --system_image_dir location.
90 std::string default_boot_image = FLAGS_system_image_dir + "/boot.img";
91 SetCommandLineOptionWithMode("boot_image", default_boot_image.c_str(),
92 google::FlagSettingMode::SET_FLAGS_DEFAULT);
93 std::string default_cache_image = FLAGS_system_image_dir + "/cache.img";
94 SetCommandLineOptionWithMode("cache_image", default_cache_image.c_str(),
95 google::FlagSettingMode::SET_FLAGS_DEFAULT);
96 std::string default_data_image = FLAGS_system_image_dir + "/userdata.img";
97 SetCommandLineOptionWithMode("data_image", default_data_image.c_str(),
98 google::FlagSettingMode::SET_FLAGS_DEFAULT);
99 std::string default_metadata_image = FLAGS_system_image_dir + "/metadata.img";
100 SetCommandLineOptionWithMode("metadata_image", default_metadata_image.c_str(),
101 google::FlagSettingMode::SET_FLAGS_DEFAULT);
102 std::string default_super_image = FLAGS_system_image_dir + "/super.img";
103 SetCommandLineOptionWithMode("super_image", default_super_image.c_str(),
104 google::FlagSettingMode::SET_FLAGS_DEFAULT);
105 std::string default_misc_image = FLAGS_system_image_dir + "/misc.img";
106 SetCommandLineOptionWithMode("misc_image", default_misc_image.c_str(),
107 google::FlagSettingMode::SET_FLAGS_DEFAULT);
108 std::string default_vendor_boot_image = FLAGS_system_image_dir
109 + "/vendor_boot.img";
110 SetCommandLineOptionWithMode("vendor_boot_image",
111 default_vendor_boot_image.c_str(),
112 google::FlagSettingMode::SET_FLAGS_DEFAULT);
113 std::string default_boot_env_image = FLAGS_system_image_dir + "/env.img";
114 SetCommandLineOptionWithMode("boot_env_image", default_boot_env_image.c_str(),
115 google::FlagSettingMode::SET_FLAGS_DEFAULT);
116 std::string default_vbmeta_image = FLAGS_system_image_dir + "/vbmeta.img";
117 SetCommandLineOptionWithMode("vbmeta_image", default_vbmeta_image.c_str(),
118 google::FlagSettingMode::SET_FLAGS_DEFAULT);
119 std::string default_vbmeta_system_image = FLAGS_system_image_dir
120 + "/vbmeta_system.img";
121 SetCommandLineOptionWithMode("vbmeta_system_image",
122 default_vbmeta_system_image.c_str(),
123 google::FlagSettingMode::SET_FLAGS_DEFAULT);
124
125 return true;
126 }
127
CreateBootImageUnpacker()128 std::unique_ptr<cuttlefish::BootImageUnpacker> CreateBootImageUnpacker() {
129 return cuttlefish::BootImageUnpacker::FromImages(
130 FLAGS_boot_image, FLAGS_vendor_boot_image);
131 }
132
DecompressKernel(const std::string & src,const std::string & dst)133 static bool DecompressKernel(const std::string& src, const std::string& dst) {
134 cuttlefish::Command decomp_cmd(cuttlefish::DefaultHostArtifactsPath("bin/extract-vmlinux"));
135 decomp_cmd.AddParameter(src);
136 std::string current_path = getenv("PATH") == nullptr ? "" : getenv("PATH");
137 std::string bin_folder = cuttlefish::DefaultHostArtifactsPath("bin");
138 decomp_cmd.SetEnvironment({"PATH=" + current_path + ":" + bin_folder});
139 auto output_file = cuttlefish::SharedFD::Creat(dst.c_str(), 0666);
140 if (!output_file->IsOpen()) {
141 LOG(ERROR) << "Unable to create decompressed image file: "
142 << output_file->StrError();
143 return false;
144 }
145 decomp_cmd.RedirectStdIO(cuttlefish::Subprocess::StdIOChannel::kStdOut, output_file);
146 auto decomp_proc = decomp_cmd.Start();
147 return decomp_proc.Started() && decomp_proc.Wait() == 0;
148 }
149
disk_config()150 static std::vector<ImagePartition> disk_config() {
151 std::vector<ImagePartition> partitions;
152
153 // Note that if the positions of env or misc change, the environment for
154 // u-boot must be updated as well (see boot_config.cc and
155 // configs/cf-x86_defconfig in external/u-boot).
156 partitions.push_back(ImagePartition {
157 .label = "env",
158 .image_file_path = FLAGS_boot_env_image,
159 });
160 partitions.push_back(ImagePartition {
161 .label = "misc",
162 .image_file_path = FLAGS_misc_image,
163 });
164 partitions.push_back(ImagePartition {
165 .label = "boot_a",
166 .image_file_path = FLAGS_boot_image,
167 });
168 partitions.push_back(ImagePartition {
169 .label = "boot_b",
170 .image_file_path = FLAGS_boot_image,
171 });
172 partitions.push_back(ImagePartition {
173 .label = "vendor_boot_a",
174 .image_file_path = FLAGS_vendor_boot_image,
175 });
176 partitions.push_back(ImagePartition {
177 .label = "vendor_boot_b",
178 .image_file_path = FLAGS_vendor_boot_image,
179 });
180 partitions.push_back(ImagePartition {
181 .label = "vbmeta_a",
182 .image_file_path = FLAGS_vbmeta_image,
183 });
184 partitions.push_back(ImagePartition {
185 .label = "vbmeta_b",
186 .image_file_path = FLAGS_vbmeta_image,
187 });
188 partitions.push_back(ImagePartition {
189 .label = "vbmeta_system_a",
190 .image_file_path = FLAGS_vbmeta_system_image,
191 });
192 partitions.push_back(ImagePartition {
193 .label = "vbmeta_system_b",
194 .image_file_path = FLAGS_vbmeta_system_image,
195 });
196 partitions.push_back(ImagePartition {
197 .label = "super",
198 .image_file_path = FLAGS_super_image,
199 });
200 partitions.push_back(ImagePartition {
201 .label = "userdata",
202 .image_file_path = FLAGS_data_image,
203 });
204 partitions.push_back(ImagePartition {
205 .label = "cache",
206 .image_file_path = FLAGS_cache_image,
207 });
208 partitions.push_back(ImagePartition {
209 .label = "metadata",
210 .image_file_path = FLAGS_metadata_image,
211 });
212 return partitions;
213 }
214
LastUpdatedInputDisk()215 static std::chrono::system_clock::time_point LastUpdatedInputDisk() {
216 std::chrono::system_clock::time_point ret;
217 for (auto& partition : disk_config()) {
218 auto partition_mod_time = cuttlefish::FileModificationTime(partition.image_file_path);
219 if (partition_mod_time > ret) {
220 ret = partition_mod_time;
221 }
222 }
223 return ret;
224 }
225
ShouldCreateCompositeDisk(const cuttlefish::CuttlefishConfig & config)226 bool ShouldCreateCompositeDisk(const cuttlefish::CuttlefishConfig& config) {
227 if (!cuttlefish::FileExists(config.composite_disk_path())) {
228 return true;
229 }
230 auto composite_age = cuttlefish::FileModificationTime(config.composite_disk_path());
231 return composite_age < LastUpdatedInputDisk();
232 }
233
ConcatRamdisks(const std::string & new_ramdisk_path,const std::string & ramdisk_a_path,const std::string & ramdisk_b_path)234 static bool ConcatRamdisks(
235 const std::string& new_ramdisk_path,
236 const std::string& ramdisk_a_path,
237 const std::string& ramdisk_b_path) {
238 // clear out file of any pre-existing content
239 std::ofstream new_ramdisk(new_ramdisk_path, std::ios_base::binary | std::ios_base::trunc);
240 std::ifstream ramdisk_a(ramdisk_a_path, std::ios_base::binary);
241 std::ifstream ramdisk_b(ramdisk_b_path, std::ios_base::binary);
242
243 if(!new_ramdisk.is_open() || !ramdisk_a.is_open() || !ramdisk_b.is_open()) {
244 return false;
245 }
246
247 new_ramdisk << ramdisk_a.rdbuf() << ramdisk_b.rdbuf();
248 return true;
249 }
250
AvailableSpaceAtPath(const std::string & path)251 static off_t AvailableSpaceAtPath(const std::string& path) {
252 struct statvfs vfs;
253 if (statvfs(path.c_str(), &vfs) != 0) {
254 int error_num = errno;
255 LOG(ERROR) << "Could not find space available at " << path << ", error was "
256 << strerror(error_num);
257 return 0;
258 }
259 return vfs.f_bsize * vfs.f_bavail; // block size * free blocks for unprivileged users
260 }
261
CreateCompositeDisk(const cuttlefish::CuttlefishConfig & config)262 bool CreateCompositeDisk(const cuttlefish::CuttlefishConfig& config) {
263 if (!cuttlefish::SharedFD::Open(config.composite_disk_path().c_str(), O_WRONLY | O_CREAT, 0644)->IsOpen()) {
264 LOG(ERROR) << "Could not ensure " << config.composite_disk_path() << " exists";
265 return false;
266 }
267 if (config.vm_manager() == CrosvmManager::name()) {
268 // Check if filling in the sparse image would run out of disk space.
269 auto existing_sizes = cuttlefish::SparseFileSizes(FLAGS_data_image);
270 if (existing_sizes.sparse_size == 0 && existing_sizes.disk_size == 0) {
271 LOG(ERROR) << "Unable to determine size of \"" << FLAGS_data_image
272 << "\". Does this file exist?";
273 }
274 auto available_space = AvailableSpaceAtPath(FLAGS_data_image);
275 if (available_space < existing_sizes.sparse_size - existing_sizes.disk_size) {
276 // TODO(schuffelen): Duplicate this check in run_cvd when it can run on a separate machine
277 LOG(ERROR) << "Not enough space remaining in fs containing " << FLAGS_data_image;
278 LOG(ERROR) << "Wanted " << (existing_sizes.sparse_size - existing_sizes.disk_size);
279 LOG(ERROR) << "Got " << available_space;
280 return false;
281 } else {
282 LOG(DEBUG) << "Available space: " << available_space;
283 LOG(DEBUG) << "Sparse size of \"" << FLAGS_data_image << "\": "
284 << existing_sizes.sparse_size;
285 LOG(DEBUG) << "Disk size of \"" << FLAGS_data_image << "\": "
286 << existing_sizes.disk_size;
287 }
288 std::string header_path = config.AssemblyPath("gpt_header.img");
289 std::string footer_path = config.AssemblyPath("gpt_footer.img");
290 CreateCompositeDisk(disk_config(), header_path, footer_path,
291 config.composite_disk_path());
292 } else {
293 // If this doesn't fit into the disk, it will fail while aggregating. The
294 // aggregator doesn't maintain any sparse attributes.
295 AggregateImage(disk_config(), config.composite_disk_path());
296 }
297 return true;
298 }
299
300 const std::string kKernelDefaultPath = "kernel";
301 const std::string kInitramfsImg = "initramfs.img";
302 const std::string kRamdiskConcatExt = ".concat";
303
CreateDynamicDiskFiles(const cuttlefish::FetcherConfig & fetcher_config,const cuttlefish::CuttlefishConfig * config,cuttlefish::BootImageUnpacker * boot_img_unpacker)304 void CreateDynamicDiskFiles(const cuttlefish::FetcherConfig& fetcher_config,
305 const cuttlefish::CuttlefishConfig* config,
306 cuttlefish::BootImageUnpacker* boot_img_unpacker) {
307
308 if (!cuttlefish::FileHasContent(FLAGS_boot_image)) {
309 LOG(ERROR) << "File not found: " << FLAGS_boot_image;
310 exit(cuttlefish::kCuttlefishConfigurationInitError);
311 }
312
313 if (!cuttlefish::FileHasContent(FLAGS_vendor_boot_image)) {
314 LOG(ERROR) << "File not found: " << FLAGS_vendor_boot_image;
315 exit(cuttlefish::kCuttlefishConfigurationInitError);
316 }
317
318 if (!boot_img_unpacker->Unpack(config->ramdisk_image_path(),
319 config->vendor_ramdisk_image_path(),
320 config->use_unpacked_kernel()
321 ? config->kernel_image_path()
322 : "")) {
323 LOG(ERROR) << "Failed to unpack boot image";
324 exit(AssemblerExitCodes::kBootImageUnpackError);
325 }
326
327 // TODO(134522463) as part of the bootloader refactor, repack the vendor boot
328 // image and use the bootloader to load both the boot and vendor ramdisk.
329 // Until then, this hack to get gki modules into cuttlefish will suffice.
330
331 // If a vendor ramdisk comes in via this mechanism, let it supersede the one
332 // in the vendor boot image. This flag is what kernel presubmit testing uses
333 // to pass in the kernel ramdisk.
334
335 // If no kernel is passed in or an initramfs is made available, the default
336 // vendor boot ramdisk or the initramfs provided should be appended to the
337 // boot ramdisk. If a kernel IS provided with no initramfs, it is safe to
338 // safe to assume that the kernel was built with no modules and expects no
339 // modules for cf to run properly.
340 std::string discovered_kernel = fetcher_config.FindCvdFileWithSuffix(kKernelDefaultPath);
341 std::string foreign_kernel = FLAGS_kernel_path.size() ? FLAGS_kernel_path : discovered_kernel;
342 std::string discovered_ramdisk = fetcher_config.FindCvdFileWithSuffix(kInitramfsImg);
343 std::string foreign_ramdisk = FLAGS_initramfs_path.size () ? FLAGS_initramfs_path : discovered_ramdisk;
344 if(!foreign_kernel.size() || foreign_ramdisk.size()) {
345 const std::string& vendor_ramdisk_path =
346 config->initramfs_path().size() ? config->initramfs_path()
347 : config->vendor_ramdisk_image_path();
348 if(!ConcatRamdisks(config->final_ramdisk_path(),
349 config->ramdisk_image_path(), vendor_ramdisk_path)) {
350 LOG(ERROR) << "Failed to concatenate ramdisk and vendor ramdisk";
351 exit(AssemblerExitCodes::kInitRamFsConcatError);
352 }
353 }
354
355 if (config->decompress_kernel()) {
356 if (!DecompressKernel(config->kernel_image_path(),
357 config->decompressed_kernel_image_path())) {
358 LOG(ERROR) << "Failed to decompress kernel";
359 exit(AssemblerExitCodes::kKernelDecompressError);
360 }
361 }
362
363 // Create misc if necessary
364 if (!InitializeMiscImage(FLAGS_misc_image)) {
365 exit(cuttlefish::kCuttlefishConfigurationInitError);
366 }
367
368 // Create data if necessary
369 DataImageResult dataImageResult = ApplyDataImagePolicy(*config, FLAGS_data_image);
370 if (dataImageResult == DataImageResult::Error) {
371 exit(cuttlefish::kCuttlefishConfigurationInitError);
372 }
373
374 // Create boot_config if necessary
375 if (!InitBootloaderEnvPartition(*config, FLAGS_boot_env_image)) {
376 exit(cuttlefish::kCuttlefishConfigurationInitError);
377 }
378
379 if (!cuttlefish::FileExists(FLAGS_metadata_image)) {
380 CreateBlankImage(FLAGS_metadata_image, FLAGS_blank_metadata_image_mb, "none");
381 }
382
383 for (const auto& instance : config->Instances()) {
384 if (!cuttlefish::FileExists(instance.access_kregistry_path())) {
385 CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */, "none");
386 }
387
388 if (!cuttlefish::FileExists(instance.pstore_path())) {
389 CreateBlankImage(instance.pstore_path(), 2 /* mb */, "none");
390 }
391
392 if (!cuttlefish::FileExists(instance.sdcard_path())) {
393 CreateBlankImage(instance.sdcard_path(),
394 FLAGS_blank_sdcard_image_mb, "sdcard");
395 }
396 }
397
398 // libavb expects to be able to read the maximum vbmeta size, so we must
399 // provide a partition which matches this or the read will fail
400 for (const auto& vbmeta_image : { FLAGS_vbmeta_image, FLAGS_vbmeta_system_image }) {
401 if (cuttlefish::FileSize(vbmeta_image) != VBMETA_MAX_SIZE) {
402 auto fd = cuttlefish::SharedFD::Open(vbmeta_image, O_RDWR);
403 if (fd->Truncate(VBMETA_MAX_SIZE) != 0) {
404 LOG(ERROR) << "`truncate --size=" << VBMETA_MAX_SIZE << " "
405 << vbmeta_image << "` failed: " << fd->StrError();
406 exit(cuttlefish::kCuttlefishConfigurationInitError);
407 }
408 }
409 }
410
411 if (SuperImageNeedsRebuilding(fetcher_config, *config)) {
412 if (!RebuildSuperImage(fetcher_config, *config, FLAGS_super_image)) {
413 LOG(ERROR) << "Super image rebuilding requested but could not be completed.";
414 exit(cuttlefish::kCuttlefishConfigurationInitError);
415 }
416 }
417
418 bool oldCompositeDisk = ShouldCreateCompositeDisk(*config);
419 bool newDataImage = dataImageResult == DataImageResult::FileUpdated;
420 if (oldCompositeDisk || newDataImage) {
421 if (!CreateCompositeDisk(*config)) {
422 exit(cuttlefish::kDiskSpaceError);
423 }
424 }
425
426 for (auto instance : config->Instances()) {
427 auto overlay_path = instance.PerInstancePath("overlay.img");
428 bool missingOverlay = !cuttlefish::FileExists(overlay_path);
429 bool newOverlay = cuttlefish::FileModificationTime(overlay_path)
430 < cuttlefish::FileModificationTime(config->composite_disk_path());
431 if (missingOverlay || oldCompositeDisk || !FLAGS_resume || newDataImage || newOverlay) {
432 if (FLAGS_resume) {
433 LOG(INFO) << "Requested to continue an existing session, (the default) "
434 << "but the disk files have become out of date. Wiping the "
435 << "old session files and starting a new session.";
436 }
437 CreateQcowOverlay(config->crosvm_binary(), config->composite_disk_path(), overlay_path);
438 CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */, "none");
439 CreateBlankImage(instance.pstore_path(), 2 /* mb */, "none");
440 }
441 }
442
443 for (auto instance : config->Instances()) {
444 // Check that the files exist
445 for (const auto& file : instance.virtual_disk_paths()) {
446 if (!file.empty() && !cuttlefish::FileHasContent(file.c_str())) {
447 LOG(ERROR) << "File not found: " << file;
448 exit(cuttlefish::kCuttlefishConfigurationInitError);
449 }
450 }
451 }
452 }
453