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/network.h"
26 #include "common/libs/utils/result.h"
27 #include "common/libs/utils/subprocess.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 const std::string kDataPolicyUseExisting = "use_existing";
37 const std::string kDataPolicyCreateIfMissing = "create_if_missing";
38 const std::string kDataPolicyAlwaysCreate = "always_create";
39 const std::string 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 bool ForceFsckImage(const std::string& data_image,
45 const CuttlefishConfig::InstanceSpecific& instance) {
46 std::string fsck_path;
47 if (instance.userdata_format() == "f2fs") {
48 fsck_path = HostBinaryPath("fsck.f2fs");
49 } else if (instance.userdata_format() == "ext4") {
50 fsck_path = "/sbin/e2fsck";
51 }
52 int fsck_status = execute({fsck_path, "-y", "-f", data_image});
53 if (fsck_status & ~(FSCK_ERROR_CORRECTED|FSCK_ERROR_CORRECTED_REQUIRES_REBOOT)) {
54 LOG(ERROR) << "`" << fsck_path << " -y -f " << data_image << "` failed with code "
55 << fsck_status;
56 return false;
57 }
58 return true;
59 }
60
ResizeImage(const std::string & data_image,int data_image_mb,const CuttlefishConfig::InstanceSpecific & instance)61 bool ResizeImage(const std::string& data_image, int data_image_mb,
62 const CuttlefishConfig::InstanceSpecific& instance) {
63 auto file_mb = FileSize(data_image) >> 20;
64 if (file_mb > data_image_mb) {
65 LOG(ERROR) << data_image << " is already " << file_mb << " MB, will not "
66 << "resize down.";
67 return false;
68 } else if (file_mb == data_image_mb) {
69 LOG(INFO) << data_image << " is already the right size";
70 return true;
71 } else {
72 off_t raw_target = static_cast<off_t>(data_image_mb) << 20;
73 auto fd = SharedFD::Open(data_image, O_RDWR);
74 if (fd->Truncate(raw_target) != 0) {
75 LOG(ERROR) << "`truncate --size=" << data_image_mb << "M "
76 << data_image << "` failed:" << fd->StrError();
77 return false;
78 }
79 bool fsck_success = ForceFsckImage(data_image, instance);
80 if (!fsck_success) {
81 return false;
82 }
83 std::string resize_path;
84 if (instance.userdata_format() == "f2fs") {
85 resize_path = HostBinaryPath("resize.f2fs");
86 } else if (instance.userdata_format() == "ext4") {
87 resize_path = "/sbin/resize2fs";
88 }
89 int resize_status = execute({resize_path, data_image});
90 if (resize_status != 0) {
91 LOG(ERROR) << "`" << resize_path << " " << data_image << "` failed with code "
92 << resize_status;
93 return false;
94 }
95 fsck_success = ForceFsckImage(data_image, instance);
96 if (!fsck_success) {
97 return false;
98 }
99 }
100 return true;
101 }
102 } // namespace
103
CreateBlankImage(const std::string & image,int num_mb,const std::string & image_fmt)104 bool CreateBlankImage(
105 const std::string& image, int num_mb, const std::string& image_fmt) {
106 LOG(DEBUG) << "Creating " << image;
107
108 off_t image_size_bytes = static_cast<off_t>(num_mb) << 20;
109 // The newfs_msdos tool with the mandatory -C option will do the same
110 // as below to zero the image file, so we don't need to do it here
111 if (image_fmt != "sdcard") {
112 auto fd = SharedFD::Open(image, O_CREAT | O_TRUNC | O_RDWR, 0666);
113 if (fd->Truncate(image_size_bytes) != 0) {
114 LOG(ERROR) << "`truncate --size=" << num_mb << "M " << image
115 << "` failed:" << fd->StrError();
116 return false;
117 }
118 }
119
120 if (image_fmt == "ext4") {
121 if (execute({"/sbin/mkfs.ext4", image}) != 0) {
122 return false;
123 }
124 } else if (image_fmt == "f2fs") {
125 auto make_f2fs_path = HostBinaryPath("make_f2fs");
126 if (execute({make_f2fs_path, "-l", "data", image, "-C", "utf8", "-O",
127 "compression,extra_attr,project_quota,casefold", "-g", "android"}) != 0) {
128 return false;
129 }
130 } else if (image_fmt == "sdcard") {
131 // Reserve 1MB in the image for the MBR and padding, to simulate what
132 // other OSes do by default when partitioning a drive
133 off_t offset_size_bytes = 1 << 20;
134 image_size_bytes -= offset_size_bytes;
135 if (!NewfsMsdos(image, num_mb, 1)) {
136 LOG(ERROR) << "Failed to create SD-Card filesystem";
137 return false;
138 }
139 // Write the MBR after the filesystem is formatted, as the formatting tools
140 // don't consistently preserve the image contents
141 MasterBootRecord mbr = {
142 .partitions = {{
143 .partition_type = 0xC,
144 .first_lba = (std::uint32_t) offset_size_bytes / SECTOR_SIZE,
145 .num_sectors = (std::uint32_t) image_size_bytes / SECTOR_SIZE,
146 }},
147 .boot_signature = {0x55, 0xAA},
148 };
149 auto fd = SharedFD::Open(image, O_RDWR);
150 if (WriteAllBinary(fd, &mbr) != sizeof(MasterBootRecord)) {
151 LOG(ERROR) << "Writing MBR to " << image << " failed:" << fd->StrError();
152 return false;
153 }
154 } else if (image_fmt != "none") {
155 LOG(WARNING) << "Unknown image format '" << image_fmt
156 << "' for " << image << ", treating as 'none'.";
157 }
158 return true;
159 }
160
GetFsType(const std::string & path)161 std::string GetFsType(const std::string& path) {
162 std::string fs_type;
163 blkid_cache cache;
164 if (blkid_get_cache(&cache, NULL) < 0) {
165 LOG(INFO) << "blkid_get_cache failed";
166 return fs_type;
167 }
168 blkid_dev dev = blkid_get_dev(cache, path.c_str(), BLKID_DEV_NORMAL);
169 if (!dev) {
170 LOG(INFO) << "blkid_get_dev failed";
171 blkid_put_cache(cache);
172 return fs_type;
173 }
174
175 const char *type, *value;
176 blkid_tag_iterate iter = blkid_tag_iterate_begin(dev);
177 while (blkid_tag_next(iter, &type, &value) == 0) {
178 if (!strcmp(type, "TYPE")) {
179 fs_type = value;
180 }
181 }
182 blkid_tag_iterate_end(iter);
183 blkid_put_cache(cache);
184 return fs_type;
185 }
186
187 class InitializeDataImageImpl : public InitializeDataImage {
188 public:
INJECT(InitializeDataImageImpl (const CuttlefishConfig::InstanceSpecific & instance))189 INJECT(InitializeDataImageImpl(
190 const CuttlefishConfig::InstanceSpecific& instance))
191 : instance_(instance) {}
192
193 // SetupFeature
Name() const194 std::string Name() const override { return "InitializeDataImageImpl"; }
Enabled() const195 bool Enabled() const override { return true; }
196
197 private:
Dependencies() const198 std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
Setup()199 bool Setup() override {
200 auto action = ChooseAction();
201 if (!action.ok()) {
202 LOG(ERROR) << "Failed to select a userdata processing action: "
203 << action.error().Message();
204 LOG(DEBUG) << "Failed to select a userdata processing action: "
205 << action.error().Trace();
206 return false;
207 }
208 auto result = EvaluateAction(*action);
209 if (!result.ok()) {
210 LOG(ERROR) << "Failed to evaluate userdata action: "
211 << result.error().Message();
212 LOG(DEBUG) << "Failed to evaluate userdata action: "
213 << result.error().Trace();
214 return false;
215 }
216 return true;
217 }
218
219 private:
220 enum class DataImageAction { kNoAction, kCreateImage, kResizeImage };
221
ChooseAction()222 Result<DataImageAction> ChooseAction() {
223 if (instance_.data_policy() == kDataPolicyAlwaysCreate) {
224 return DataImageAction::kCreateImage;
225 }
226 if (!FileHasContent(instance_.data_image())) {
227 if (instance_.data_policy() == kDataPolicyUseExisting) {
228 return CF_ERR("A data image must exist to use -data_policy="
229 << kDataPolicyUseExisting);
230 } else if (instance_.data_policy() == kDataPolicyResizeUpTo) {
231 return CF_ERR(instance_.data_image()
232 << " does not exist, but resizing was requested");
233 }
234 return DataImageAction::kCreateImage;
235 }
236 if (instance_.data_policy() == kDataPolicyUseExisting) {
237 return DataImageAction::kNoAction;
238 }
239 auto current_fs_type = GetFsType(instance_.data_image());
240 if (current_fs_type != instance_.userdata_format()) {
241 CF_EXPECT(instance_.data_policy() != kDataPolicyResizeUpTo,
242 "Changing the fs format is incompatible with -data_policy="
243 << kDataPolicyResizeUpTo << " (\"" << current_fs_type
244 << "\" != \"" << instance_.userdata_format() << "\")");
245 return DataImageAction::kCreateImage;
246 }
247 if (instance_.data_policy() == kDataPolicyResizeUpTo) {
248 return DataImageAction::kResizeImage;
249 }
250 return DataImageAction::kNoAction;
251 }
252
EvaluateAction(DataImageAction action)253 Result<void> EvaluateAction(DataImageAction action) {
254 switch (action) {
255 case DataImageAction::kNoAction:
256 LOG(DEBUG) << instance_.data_image() << " exists. Not creating it.";
257 return {};
258 case DataImageAction::kCreateImage: {
259 RemoveFile(instance_.data_image());
260 CF_EXPECT(instance_.blank_data_image_mb() != 0,
261 "Expected `-blank_data_image_mb` to be set for "
262 "image creation.");
263 CF_EXPECT(CreateBlankImage(instance_.data_image(),
264 instance_.blank_data_image_mb(),
265 instance_.userdata_format()),
266 "Failed to create a blank image at \""
267 << instance_.data_image() << "\" with size "
268 << instance_.blank_data_image_mb() << " and format \""
269 << instance_.userdata_format() << "\"");
270 return {};
271 }
272 case DataImageAction::kResizeImage: {
273 CF_EXPECT(instance_.blank_data_image_mb() != 0,
274 "Expected `-blank_data_image_mb` to be set for "
275 "image resizing.");
276 CF_EXPECT(ResizeImage(instance_.data_image(),
277 instance_.blank_data_image_mb(), instance_),
278 "Failed to resize \"" << instance_.data_image() << "\" to "
279 << instance_.blank_data_image_mb()
280 << " MB");
281 return {};
282 }
283 }
284 }
285
286 const CuttlefishConfig::InstanceSpecific& instance_;
287 };
288
289 fruit::Component<fruit::Required<const CuttlefishConfig::InstanceSpecific>,
290 InitializeDataImage>
InitializeDataImageComponent()291 InitializeDataImageComponent() {
292 return fruit::createComponent()
293 .addMultibinding<SetupFeature, InitializeDataImage>()
294 .bind<InitializeDataImage, InitializeDataImageImpl>();
295 }
296
297 class InitializeMiscImageImpl : public InitializeMiscImage {
298 public:
INJECT(InitializeMiscImageImpl (const CuttlefishConfig::InstanceSpecific & instance))299 INJECT(InitializeMiscImageImpl(
300 const CuttlefishConfig::InstanceSpecific& instance))
301 : instance_(instance) {}
302
303 // SetupFeature
Name() const304 std::string Name() const override { return "InitializeMiscImageImpl"; }
Enabled() const305 bool Enabled() const override { return true; }
306
307 private:
Dependencies() const308 std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
Setup()309 bool Setup() override {
310 bool misc_exists = FileHasContent(instance_.misc_image());
311
312 if (misc_exists) {
313 LOG(DEBUG) << "misc partition image: use existing at \""
314 << instance_.misc_image() << "\"";
315 return true;
316 }
317
318 LOG(DEBUG) << "misc partition image: creating empty at \""
319 << instance_.misc_image() << "\"";
320 if (!CreateBlankImage(instance_.new_misc_image(), 1 /* mb */, "none")) {
321 LOG(ERROR) << "Failed to create misc image";
322 return false;
323 }
324 return true;
325 }
326
327 private:
328 const CuttlefishConfig::InstanceSpecific& instance_;
329 };
330
331 fruit::Component<fruit::Required<const CuttlefishConfig::InstanceSpecific>,
332 InitializeMiscImage>
InitializeMiscImageComponent()333 InitializeMiscImageComponent() {
334 return fruit::createComponent()
335 .addMultibinding<SetupFeature, InitializeMiscImage>()
336 .bind<InitializeMiscImage, InitializeMiscImageImpl>();
337 }
338
339 class InitializeEspImageImpl : public InitializeEspImage {
340 public:
INJECT(InitializeEspImageImpl (const CuttlefishConfig & config,const CuttlefishConfig::InstanceSpecific & instance))341 INJECT(InitializeEspImageImpl(
342 const CuttlefishConfig& config,
343 const CuttlefishConfig::InstanceSpecific& instance))
344 : config_(config), instance_(instance) {}
345
346 // SetupFeature
Name() const347 std::string Name() const override { return "InitializeEspImageImpl"; }
Dependencies() const348 std::unordered_set<SetupFeature*> Dependencies() const override { return {}; }
349
Enabled() const350 bool Enabled() const override {
351 return EspRequiredForBootFlow() || EspRequiredForAPBootFlow();
352 }
353
354 protected:
Setup()355 bool Setup() override {
356 if (EspRequiredForAPBootFlow()) {
357 LOG(DEBUG) << "creating esp_image: " << instance_.ap_esp_image_path();
358 if (!BuildAPImage()) {
359 return false;
360 }
361 }
362 const auto is_not_gem5 = config_.vm_manager() != vm_manager::Gem5Manager::name();
363 const auto esp_required_for_boot_flow = EspRequiredForBootFlow();
364 if (is_not_gem5 && esp_required_for_boot_flow) {
365 LOG(DEBUG) << "creating esp_image: " << instance_.otheros_esp_image_path();
366 if (!BuildOSImage()) {
367 return false;
368 }
369 }
370
371 return true;
372 }
373
374 private:
375
EspRequiredForBootFlow() const376 bool EspRequiredForBootFlow() const {
377 const auto flow = instance_.boot_flow();
378 return flow == CuttlefishConfig::InstanceSpecific::BootFlow::Linux ||
379 flow == CuttlefishConfig::InstanceSpecific::BootFlow::Fuchsia;
380 }
381
EspRequiredForAPBootFlow() const382 bool EspRequiredForAPBootFlow() const {
383 return instance_.ap_boot_flow() == CuttlefishConfig::InstanceSpecific::APBootFlow::Grub;
384 }
385
BuildAPImage()386 bool BuildAPImage() {
387 auto linux = LinuxEspBuilder(instance_.ap_esp_image_path());
388 InitLinuxArgs(linux);
389
390 auto openwrt_args = OpenwrtArgsFromConfig(instance_);
391 for (auto& openwrt_arg : openwrt_args) {
392 linux.Argument(openwrt_arg.first, openwrt_arg.second);
393 }
394
395 linux.Root("/dev/vda2")
396 .Architecture(instance_.target_arch())
397 .Kernel(config_.ap_kernel_image());
398
399 return linux.Build();
400 }
401
BuildOSImage()402 bool BuildOSImage() {
403 switch (instance_.boot_flow()) {
404 case CuttlefishConfig::InstanceSpecific::BootFlow::Linux: {
405 auto linux = LinuxEspBuilder(instance_.otheros_esp_image_path());
406 InitLinuxArgs(linux);
407
408 linux.Root("/dev/vda2")
409 .Architecture(instance_.target_arch())
410 .Kernel(instance_.linux_kernel_path());
411
412 if (!instance_.linux_initramfs_path().empty()) {
413 linux.Initrd(instance_.linux_initramfs_path());
414 }
415
416 return linux.Build();
417 }
418 case CuttlefishConfig::InstanceSpecific::BootFlow::Fuchsia: {
419 auto fuchsia = FuchsiaEspBuilder(instance_.otheros_esp_image_path());
420 return fuchsia.Architecture(instance_.target_arch())
421 .Zedboot(instance_.fuchsia_zedboot_path())
422 .MultibootBinary(instance_.fuchsia_multiboot_bin_path())
423 .Build();
424 }
425 default:
426 break;
427 }
428
429 return true;
430 }
431
InitLinuxArgs(LinuxEspBuilder & linux)432 void InitLinuxArgs(LinuxEspBuilder& linux) {
433 linux.Root("/dev/vda2");
434
435 linux.Argument("console", "hvc0")
436 .Argument("panic", "-1")
437 .Argument("noefi");
438
439 switch (instance_.target_arch()) {
440 case Arch::Arm:
441 case Arch::Arm64:
442 linux.Argument("console", "ttyAMA0");
443 break;
444 case Arch::RiscV64:
445 linux.Argument("console", "ttyS0");
446 break;
447 case Arch::X86:
448 case Arch::X86_64:
449 linux.Argument("console", "ttyS0")
450 .Argument("pnpacpi", "off")
451 .Argument("acpi", "noirq")
452 .Argument("reboot", "k")
453 .Argument("noexec", "off");
454 break;
455 }
456 }
457
458 const CuttlefishConfig& config_;
459 const CuttlefishConfig::InstanceSpecific& instance_;
460 };
461
462 fruit::Component<fruit::Required<const CuttlefishConfig,
463 const CuttlefishConfig::InstanceSpecific>,
464 InitializeEspImage>
InitializeEspImageComponent()465 InitializeEspImageComponent() {
466 return fruit::createComponent()
467 .addMultibinding<SetupFeature, InitializeEspImage>()
468 .bind<InitializeEspImage, InitializeEspImageImpl>();
469 }
470
471 } // namespace cuttlefish
472