1 #include "host/libs/config/data_image.h"
2
3 #include <android-base/logging.h>
4
5 #include "common/libs/fs/shared_buf.h"
6
7 #include "common/libs/utils/files.h"
8 #include "common/libs/utils/subprocess.h"
9
10 #include "host/libs/config/mbr.h"
11
12 namespace cuttlefish {
13
14 namespace {
15 const std::string kDataPolicyUseExisting = "use_existing";
16 const std::string kDataPolicyCreateIfMissing = "create_if_missing";
17 const std::string kDataPolicyAlwaysCreate = "always_create";
18 const std::string kDataPolicyResizeUpTo= "resize_up_to";
19
20 const int FSCK_ERROR_CORRECTED = 1;
21 const int FSCK_ERROR_CORRECTED_REQUIRES_REBOOT = 2;
22
ForceFsckImage(const char * data_image)23 bool ForceFsckImage(const char* data_image) {
24 auto fsck_path = cuttlefish::DefaultHostArtifactsPath("bin/fsck.f2fs");
25 int fsck_status = cuttlefish::execute({fsck_path, "-y", "-f", data_image});
26 if (fsck_status & ~(FSCK_ERROR_CORRECTED|FSCK_ERROR_CORRECTED_REQUIRES_REBOOT)) {
27 LOG(ERROR) << "`fsck.f2fs -y -f " << data_image << "` failed with code "
28 << fsck_status;
29 return false;
30 }
31 return true;
32 }
33
ResizeImage(const char * data_image,int data_image_mb)34 bool ResizeImage(const char* data_image, int data_image_mb) {
35 auto file_mb = cuttlefish::FileSize(data_image) >> 20;
36 if (file_mb > data_image_mb) {
37 LOG(ERROR) << data_image << " is already " << file_mb << " MB, will not "
38 << "resize down.";
39 return false;
40 } else if (file_mb == data_image_mb) {
41 LOG(INFO) << data_image << " is already the right size";
42 return true;
43 } else {
44 off_t raw_target = static_cast<off_t>(data_image_mb) << 20;
45 auto fd = cuttlefish::SharedFD::Open(data_image, O_RDWR);
46 if (fd->Truncate(raw_target) != 0) {
47 LOG(ERROR) << "`truncate --size=" << data_image_mb << "M "
48 << data_image << "` failed:" << fd->StrError();
49 return false;
50 }
51 bool fsck_success = ForceFsckImage(data_image);
52 if (!fsck_success) {
53 return false;
54 }
55 auto resize_path = cuttlefish::DefaultHostArtifactsPath("bin/resize.f2fs");
56 int resize_status = cuttlefish::execute({resize_path, data_image});
57 if (resize_status != 0) {
58 LOG(ERROR) << "`resize.f2fs " << data_image << "` failed with code "
59 << resize_status;
60 return false;
61 }
62 fsck_success = ForceFsckImage(data_image);
63 if (!fsck_success) {
64 return false;
65 }
66 }
67 return true;
68 }
69 } // namespace
70
CreateBlankImage(const std::string & image,int num_mb,const std::string & image_fmt)71 void CreateBlankImage(
72 const std::string& image, int num_mb, const std::string& image_fmt) {
73 LOG(DEBUG) << "Creating " << image;
74
75 off_t image_size_bytes = static_cast<off_t>(num_mb) << 20;
76 // The newfs_msdos tool with the mandatory -C option will do the same
77 // as below to zero the image file, so we don't need to do it here
78 if (image_fmt != "sdcard") {
79 auto fd = cuttlefish::SharedFD::Open(image, O_CREAT | O_TRUNC | O_RDWR, 0666);
80 if (fd->Truncate(image_size_bytes) != 0) {
81 LOG(ERROR) << "`truncate --size=" << num_mb << "M " << image
82 << "` failed:" << fd->StrError();
83 return;
84 }
85 }
86
87 if (image_fmt == "ext4") {
88 cuttlefish::execute({"/sbin/mkfs.ext4", image});
89 } else if (image_fmt == "f2fs") {
90 auto make_f2fs_path = cuttlefish::DefaultHostArtifactsPath("bin/make_f2fs");
91 cuttlefish::execute({make_f2fs_path, "-t", image_fmt, image, "-g", "android"});
92 } else if (image_fmt == "sdcard") {
93 // Reserve 1MB in the image for the MBR and padding, to simulate what
94 // other OSes do by default when partitioning a drive
95 off_t offset_size_bytes = 1 << 20;
96 image_size_bytes -= offset_size_bytes;
97 off_t image_size_sectors = image_size_bytes / 512;
98 auto newfs_msdos_path = cuttlefish::DefaultHostArtifactsPath("bin/newfs_msdos");
99 cuttlefish::execute({newfs_msdos_path, "-F", "32", "-m", "0xf8", "-a", "4088",
100 "-o", "0", "-c", "8", "-h", "255",
101 "-u", "63", "-S", "512",
102 "-s", std::to_string(image_size_sectors),
103 "-C", std::to_string(num_mb) + "M",
104 "-@", std::to_string(offset_size_bytes),
105 image});
106 // Write the MBR after the filesystem is formatted, as the formatting tools
107 // don't consistently preserve the image contents
108 MasterBootRecord mbr = {
109 .partitions = {{
110 .partition_type = 0xC,
111 .first_lba = (std::uint32_t) offset_size_bytes / SECTOR_SIZE,
112 .num_sectors = (std::uint32_t) image_size_bytes / SECTOR_SIZE,
113 }},
114 .boot_signature = { 0x55, 0xAA },
115 };
116 auto fd = cuttlefish::SharedFD::Open(image, O_RDWR);
117 if (cuttlefish::WriteAllBinary(fd, &mbr) != sizeof(MasterBootRecord)) {
118 LOG(ERROR) << "Writing MBR to " << image << " failed:" << fd->StrError();
119 return;
120 }
121 } else if (image_fmt != "none") {
122 LOG(WARNING) << "Unknown image format '" << image_fmt
123 << "' for " << image << ", treating as 'none'.";
124 }
125 }
126
ApplyDataImagePolicy(const cuttlefish::CuttlefishConfig & config,const std::string & data_image)127 DataImageResult ApplyDataImagePolicy(const cuttlefish::CuttlefishConfig& config,
128 const std::string& data_image) {
129 bool data_exists = cuttlefish::FileHasContent(data_image.c_str());
130 bool remove{};
131 bool create{};
132 bool resize{};
133
134 if (config.data_policy() == kDataPolicyUseExisting) {
135 if (!data_exists) {
136 LOG(ERROR) << "Specified data image file does not exists: " << data_image;
137 return DataImageResult::Error;
138 }
139 if (config.blank_data_image_mb() > 0) {
140 LOG(ERROR) << "You should NOT use -blank_data_image_mb with -data_policy="
141 << kDataPolicyUseExisting;
142 return DataImageResult::Error;
143 }
144 create = false;
145 remove = false;
146 resize = false;
147 } else if (config.data_policy() == kDataPolicyAlwaysCreate) {
148 remove = data_exists;
149 create = true;
150 resize = false;
151 } else if (config.data_policy() == kDataPolicyCreateIfMissing) {
152 create = !data_exists;
153 remove = false;
154 resize = false;
155 } else if (config.data_policy() == kDataPolicyResizeUpTo) {
156 create = false;
157 remove = false;
158 resize = true;
159 } else {
160 LOG(ERROR) << "Invalid data_policy: " << config.data_policy();
161 return DataImageResult::Error;
162 }
163
164 if (remove) {
165 cuttlefish::RemoveFile(data_image.c_str());
166 }
167
168 if (create) {
169 if (config.blank_data_image_mb() <= 0) {
170 LOG(ERROR) << "-blank_data_image_mb is required to create data image";
171 return DataImageResult::Error;
172 }
173 CreateBlankImage(data_image.c_str(), config.blank_data_image_mb(),
174 config.blank_data_image_fmt());
175 return DataImageResult::FileUpdated;
176 } else if (resize) {
177 if (!data_exists) {
178 LOG(ERROR) << data_image << " does not exist, but resizing was requested";
179 return DataImageResult::Error;
180 }
181 bool success = ResizeImage(data_image.c_str(), config.blank_data_image_mb());
182 return success ? DataImageResult::FileUpdated : DataImageResult::Error;
183 } else {
184 LOG(DEBUG) << data_image << " exists. Not creating it.";
185 return DataImageResult::NoChange;
186 }
187 }
188
InitializeMiscImage(const std::string & misc_image)189 bool InitializeMiscImage(const std::string& misc_image) {
190 bool misc_exists = cuttlefish::FileHasContent(misc_image.c_str());
191
192 if (misc_exists) {
193 LOG(DEBUG) << "misc partition image: use existing";
194 return true;
195 }
196
197 LOG(DEBUG) << "misc partition image: creating empty";
198 CreateBlankImage(misc_image, 1 /* mb */, "none");
199 return true;
200 }
201
202 } // namespace cuttlefish
203