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 #include "host/libs/config/data_image.h"
17
18 #include <android-base/logging.h>
19 #include <android-base/result.h>
20
21 #include "blkid.h"
22
23 #include "common/libs/fs/shared_buf.h"
24 #include "common/libs/utils/files.h"
25 #include "common/libs/utils/result.h"
26 #include "common/libs/utils/subprocess.h"
27 #include "host/libs/config/cuttlefish_config.h"
28 #include "host/libs/config/esp.h"
29 #include "host/libs/config/mbr.h"
30 #include "host/libs/config/openwrt_args.h"
31 #include "host/libs/vm_manager/gem5_manager.h"
32
33 namespace cuttlefish {
34
35 namespace {
36
37 static constexpr std::string_view kDataPolicyUseExisting = "use_existing";
38 static constexpr std::string_view kDataPolicyAlwaysCreate = "always_create";
39 static constexpr std::string_view kDataPolicyResizeUpTo = "resize_up_to";
40
41 const int FSCK_ERROR_CORRECTED = 1;
42 const int FSCK_ERROR_CORRECTED_REQUIRES_REBOOT = 2;
43
ForceFsckImage(const std::string & data_image,const CuttlefishConfig::InstanceSpecific & instance)44 Result<void> ForceFsckImage(
45 const std::string& data_image,
46 const CuttlefishConfig::InstanceSpecific& instance) {
47 std::string fsck_path;
48 if (instance.userdata_format() == "f2fs") {
49 fsck_path = HostBinaryPath("fsck.f2fs");
50 } else if (instance.userdata_format() == "ext4") {
51 fsck_path = "/sbin/e2fsck";
52 }
53 int fsck_status = Execute({fsck_path, "-y", "-f", data_image});
54 CF_EXPECTF(!(fsck_status &
55 ~(FSCK_ERROR_CORRECTED | FSCK_ERROR_CORRECTED_REQUIRES_REBOOT)),
56 "`{} -y -f {}` failed with code {}", fsck_path, data_image,
57 fsck_status);
58 return {};
59 }
60
ResizeImage(const std::string & data_image,int data_image_mb,const CuttlefishConfig::InstanceSpecific & instance)61 Result<void> ResizeImage(const std::string& data_image, int data_image_mb,
62 const CuttlefishConfig::InstanceSpecific& instance) {
63 auto file_mb = FileSize(data_image) >> 20;
64 CF_EXPECTF(data_image_mb <= file_mb, "'{}' is already {} MB, won't downsize",
65 data_image, file_mb);
66 if (file_mb == data_image_mb) {
67 LOG(INFO) << data_image << " is already the right size";
68 return {};
69 }
70 off_t raw_target = static_cast<off_t>(data_image_mb) << 20;
71 auto fd = SharedFD::Open(data_image, O_RDWR);
72 CF_EXPECTF(fd->IsOpen(), "Can't open '{}': '{}'", data_image, fd->StrError());
73 CF_EXPECTF(fd->Truncate(raw_target) == 0, "`truncate --size={}M {} fail: {}",
74 data_image_mb, data_image, fd->StrError());
75 CF_EXPECT(ForceFsckImage(data_image, instance));
76 std::string resize_path;
77 if (instance.userdata_format() == "f2fs") {
78 resize_path = HostBinaryPath("resize.f2fs");
79 } else if (instance.userdata_format() == "ext4") {
80 resize_path = "/sbin/resize2fs";
81 }
82 if (resize_path != "") {
83 CF_EXPECT_EQ(Execute({resize_path, data_image}), 0,
84 "`" << resize_path << " " << data_image << "` failed");
85 CF_EXPECT(ForceFsckImage(data_image, instance));
86 }
87
88 return {};
89 }
90
GetFsType(const std::string & path)91 std::string GetFsType(const std::string& path) {
92 std::string fs_type;
93 blkid_cache cache;
94 if (blkid_get_cache(&cache, NULL) < 0) {
95 LOG(INFO) << "blkid_get_cache failed";
96 return fs_type;
97 }
98 blkid_dev dev = blkid_get_dev(cache, path.c_str(), BLKID_DEV_NORMAL);
99 if (!dev) {
100 LOG(INFO) << "blkid_get_dev failed";
101 blkid_put_cache(cache);
102 return fs_type;
103 }
104
105 const char *type, *value;
106 blkid_tag_iterate iter = blkid_tag_iterate_begin(dev);
107 while (blkid_tag_next(iter, &type, &value) == 0) {
108 if (!strcmp(type, "TYPE")) {
109 fs_type = value;
110 }
111 }
112 blkid_tag_iterate_end(iter);
113 blkid_put_cache(cache);
114 return fs_type;
115 }
116
117 enum class DataImageAction { kNoAction, kCreateImage, kResizeImage };
118
ChooseDataImageAction(const CuttlefishConfig::InstanceSpecific & instance)119 static Result<DataImageAction> ChooseDataImageAction(
120 const CuttlefishConfig::InstanceSpecific& instance) {
121 if (instance.data_policy() == kDataPolicyAlwaysCreate) {
122 return DataImageAction::kCreateImage;
123 }
124 if (!FileHasContent(instance.data_image())) {
125 if (instance.data_policy() == kDataPolicyUseExisting) {
126 return CF_ERR("A data image must exist to use -data_policy="
127 << kDataPolicyUseExisting);
128 } else if (instance.data_policy() == kDataPolicyResizeUpTo) {
129 return CF_ERR(instance.data_image()
130 << " does not exist, but resizing was requested");
131 }
132 return DataImageAction::kCreateImage;
133 }
134 if (instance.data_policy() == kDataPolicyUseExisting) {
135 return DataImageAction::kNoAction;
136 }
137 auto current_fs_type = GetFsType(instance.data_image());
138 if (current_fs_type != instance.userdata_format()) {
139 CF_EXPECT(instance.data_policy() != kDataPolicyResizeUpTo,
140 "Changing the fs format is incompatible with -data_policy="
141 << kDataPolicyResizeUpTo << " (\"" << current_fs_type
142 << "\" != \"" << instance.userdata_format() << "\")");
143 return DataImageAction::kCreateImage;
144 }
145 if (instance.data_policy() == kDataPolicyResizeUpTo) {
146 return DataImageAction::kResizeImage;
147 }
148 return DataImageAction::kNoAction;
149 }
150
151 } // namespace
152
CreateBlankImage(const std::string & image,int num_mb,const std::string & image_fmt)153 Result<void> CreateBlankImage(const std::string& image, int num_mb,
154 const std::string& image_fmt) {
155 LOG(DEBUG) << "Creating " << image;
156
157 off_t image_size_bytes = static_cast<off_t>(num_mb) << 20;
158 // The newfs_msdos tool with the mandatory -C option will do the same
159 // as below to zero the image file, so we don't need to do it here
160 if (image_fmt != "sdcard") {
161 auto fd = SharedFD::Open(image, O_CREAT | O_TRUNC | O_RDWR, 0666);
162 CF_EXPECTF(fd->Truncate(image_size_bytes) == 0,
163 "`truncate --size={}M '{}'` failed: {}", num_mb, image,
164 fd->StrError());
165 }
166
167 if (image_fmt == "ext4") {
168 CF_EXPECT(Execute({"/sbin/mkfs.ext4", image}) == 0);
169 } else if (image_fmt == "f2fs") {
170 auto make_f2fs_path = HostBinaryPath("make_f2fs");
171 CF_EXPECT(
172 Execute({make_f2fs_path, "-l", "data", image, "-C", "utf8", "-O",
173 "compression,extra_attr,project_quota,casefold", "-g",
174 "android", "-b", F2FS_BLOCKSIZE, "-w", F2FS_BLOCKSIZE}) == 0);
175 } else if (image_fmt == "sdcard") {
176 // Reserve 1MB in the image for the MBR and padding, to simulate what
177 // other OSes do by default when partitioning a drive
178 off_t offset_size_bytes = 1 << 20;
179 image_size_bytes -= offset_size_bytes;
180 CF_EXPECT(NewfsMsdos(image, num_mb, 1), "Failed to create SD-Card fs");
181 // Write the MBR after the filesystem is formatted, as the formatting tools
182 // don't consistently preserve the image contents
183 MasterBootRecord mbr = {
184 .partitions = {{
185 .partition_type = 0xC,
186 .first_lba = (std::uint32_t)offset_size_bytes / kSectorSize,
187 .num_sectors = (std::uint32_t)image_size_bytes / kSectorSize,
188 }},
189 .boot_signature = {0x55, 0xAA},
190 };
191 auto fd = SharedFD::Open(image, O_RDWR);
192 CF_EXPECTF(WriteAllBinary(fd, &mbr) == sizeof(MasterBootRecord),
193 "Writing MBR to '{}' failed: '{}'", image, fd->StrError());
194 } else if (image_fmt != "none") {
195 LOG(WARNING) << "Unknown image format '" << image_fmt
196 << "' for " << image << ", treating as 'none'.";
197 }
198 return {};
199 }
200
InitializeDataImage(const CuttlefishConfig::InstanceSpecific & instance)201 Result<void> InitializeDataImage(
202 const CuttlefishConfig::InstanceSpecific& instance) {
203 auto action = CF_EXPECT(ChooseDataImageAction(instance));
204 switch (action) {
205 case DataImageAction::kNoAction:
206 LOG(DEBUG) << instance.data_image() << " exists. Not creating it.";
207 return {};
208 case DataImageAction::kCreateImage: {
209 RemoveFile(instance.new_data_image());
210 CF_EXPECT(instance.blank_data_image_mb() != 0,
211 "Expected `-blank_data_image_mb` to be set for "
212 "image creation.");
213 CF_EXPECT(CreateBlankImage(instance.new_data_image(),
214 instance.blank_data_image_mb(),
215 instance.userdata_format()),
216 "Failed to create a blank image at \""
217 << instance.new_data_image() << "\" with size "
218 << instance.blank_data_image_mb() << " and format \""
219 << instance.userdata_format() << "\"");
220 return {};
221 }
222 case DataImageAction::kResizeImage: {
223 CF_EXPECT(instance.blank_data_image_mb() != 0,
224 "Expected `-blank_data_image_mb` to be set for "
225 "image resizing.");
226 CF_EXPECTF(Copy(instance.data_image(), instance.new_data_image()),
227 "Failed to `cp {} {}`", instance.data_image(),
228 instance.new_data_image());
229 CF_EXPECT(ResizeImage(instance.new_data_image(),
230 instance.blank_data_image_mb(), instance),
231 "Failed to resize \"" << instance.new_data_image() << "\" to "
232 << instance.blank_data_image_mb()
233 << " MB");
234 return {};
235 }
236 }
237 }
238
InitializeMiscImage(const CuttlefishConfig::InstanceSpecific & instance)239 Result<void> InitializeMiscImage(
240 const CuttlefishConfig::InstanceSpecific& instance) {
241 if (FileHasContent(instance.misc_image())) {
242 LOG(DEBUG) << "misc partition image already exists";
243 return {};
244 }
245
246 LOG(DEBUG) << "misc partition image: creating empty at \""
247 << instance.misc_image() << "\"";
248 CF_EXPECT(CreateBlankImage(instance.misc_image(), 1 /* mb */, "none"),
249 "Failed to create misc image");
250 return {};
251 }
252
253 class InitializeEspImageImpl : public InitializeEspImage {
254 public:
INJECT(InitializeEspImageImpl (const CuttlefishConfig & config,const CuttlefishConfig::InstanceSpecific & instance))255 INJECT(InitializeEspImageImpl(
256 const CuttlefishConfig& config,
257 const CuttlefishConfig::InstanceSpecific& instance))
258 : config_(config), instance_(instance) {}
259
260 // SetupFeature
Name() const261 std::string Name() const override { return "InitializeEspImageImpl"; }
Dependencies() const262 std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
263
Enabled() const264 bool Enabled() const override {
265 return EspRequiredForBootFlow() || EspRequiredForAPBootFlow();
266 }
267
268 protected:
ResultSetup()269 Result<void> ResultSetup() override {
270 if (EspRequiredForAPBootFlow()) {
271 LOG(DEBUG) << "creating esp_image: " << instance_.ap_esp_image_path();
272 CF_EXPECT(BuildAPImage());
273 }
274 const auto is_not_gem5 = config_.vm_manager() != VmmMode::kGem5;
275 const auto esp_required_for_boot_flow = EspRequiredForBootFlow();
276 if (is_not_gem5 && esp_required_for_boot_flow) {
277 LOG(DEBUG) << "creating esp_image: " << instance_.esp_image_path();
278 CF_EXPECT(BuildOSImage());
279 }
280 return {};
281 }
282
283 private:
284
EspRequiredForBootFlow() const285 bool EspRequiredForBootFlow() const {
286 const auto flow = instance_.boot_flow();
287 using BootFlow = CuttlefishConfig::InstanceSpecific::BootFlow;
288 return flow == BootFlow::AndroidEfiLoader || flow == BootFlow::ChromeOs ||
289 flow == BootFlow::Linux || flow == BootFlow::Fuchsia;
290 }
291
EspRequiredForAPBootFlow() const292 bool EspRequiredForAPBootFlow() const {
293 return instance_.ap_boot_flow() == CuttlefishConfig::InstanceSpecific::APBootFlow::Grub;
294 }
295
BuildAPImage()296 bool BuildAPImage() {
297 auto linux = LinuxEspBuilder(instance_.ap_esp_image_path());
298 InitLinuxArgs(linux);
299
300 auto openwrt_args = OpenwrtArgsFromConfig(instance_);
301 for (auto& openwrt_arg : openwrt_args) {
302 linux.Argument(openwrt_arg.first, openwrt_arg.second);
303 }
304
305 linux.Root("/dev/vda2")
306 .Architecture(instance_.target_arch())
307 .Kernel(config_.ap_kernel_image());
308
309 return linux.Build();
310 }
311
BuildOSImage()312 bool BuildOSImage() {
313 switch (instance_.boot_flow()) {
314 case CuttlefishConfig::InstanceSpecific::BootFlow::AndroidEfiLoader: {
315 auto android_efi_loader =
316 AndroidEfiLoaderEspBuilder(instance_.esp_image_path());
317 android_efi_loader.EfiLoaderPath(instance_.android_efi_loader())
318 .Architecture(instance_.target_arch());
319 return android_efi_loader.Build();
320 }
321 case CuttlefishConfig::InstanceSpecific::BootFlow::ChromeOs: {
322 auto linux = LinuxEspBuilder(instance_.esp_image_path());
323 InitChromeOsArgs(linux);
324
325 linux.Root("/dev/vda3")
326 .Architecture(instance_.target_arch())
327 .Kernel(instance_.chromeos_kernel_path());
328
329 return linux.Build();
330 }
331 case CuttlefishConfig::InstanceSpecific::BootFlow::Linux: {
332 auto linux = LinuxEspBuilder(instance_.esp_image_path());
333 InitLinuxArgs(linux);
334
335 linux.Root("/dev/vda2")
336 .Architecture(instance_.target_arch())
337 .Kernel(instance_.linux_kernel_path());
338
339 if (!instance_.linux_initramfs_path().empty()) {
340 linux.Initrd(instance_.linux_initramfs_path());
341 }
342
343 return linux.Build();
344 }
345 case CuttlefishConfig::InstanceSpecific::BootFlow::Fuchsia: {
346 auto fuchsia = FuchsiaEspBuilder(instance_.esp_image_path());
347 return fuchsia.Architecture(instance_.target_arch())
348 .Zedboot(instance_.fuchsia_zedboot_path())
349 .MultibootBinary(instance_.fuchsia_multiboot_bin_path())
350 .Build();
351 }
352 default:
353 break;
354 }
355
356 return true;
357 }
358
InitLinuxArgs(LinuxEspBuilder & linux)359 void InitLinuxArgs(LinuxEspBuilder& linux) {
360 linux.Root("/dev/vda2");
361
362 linux.Argument("console", "hvc0")
363 .Argument("panic", "-1")
364 .Argument("noefi");
365
366 switch (instance_.target_arch()) {
367 case Arch::Arm:
368 case Arch::Arm64:
369 linux.Argument("console", "ttyAMA0");
370 break;
371 case Arch::RiscV64:
372 linux.Argument("console", "ttyS0");
373 break;
374 case Arch::X86:
375 case Arch::X86_64:
376 linux.Argument("console", "ttyS0")
377 .Argument("pnpacpi", "off")
378 .Argument("acpi", "noirq")
379 .Argument("reboot", "k")
380 .Argument("noexec", "off");
381 break;
382 }
383 }
384
InitChromeOsArgs(LinuxEspBuilder & linux)385 void InitChromeOsArgs(LinuxEspBuilder& linux) {
386 linux.Root("/dev/vda2")
387 .Argument("console", "ttyS0")
388 .Argument("panic", "-1")
389 .Argument("noefi")
390 .Argument("init=/sbin/init")
391 .Argument("boot=local")
392 .Argument("rootwait")
393 .Argument("noresume")
394 .Argument("noswap")
395 .Argument("loglevel=7")
396 .Argument("noinitrd")
397 .Argument("cros_efi")
398 .Argument("cros_debug")
399 .Argument("earlyprintk=serial,ttyS0,115200")
400 .Argument("earlycon=uart8250,io,0x3f8")
401 .Argument("pnpacpi", "off")
402 .Argument("acpi", "noirq")
403 .Argument("reboot", "k")
404 .Argument("noexec", "off");
405 }
406
407 const CuttlefishConfig& config_;
408 const CuttlefishConfig::InstanceSpecific& instance_;
409 };
410
411 fruit::Component<fruit::Required<const CuttlefishConfig,
412 const CuttlefishConfig::InstanceSpecific>,
413 InitializeEspImage>
InitializeEspImageComponent()414 InitializeEspImageComponent() {
415 return fruit::createComponent()
416 .addMultibinding<SetupFeature, InitializeEspImage>()
417 .bind<InitializeEspImage, InitializeEspImageImpl>();
418 }
419
420 } // namespace cuttlefish
421