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