1 // Copyright (C) 2024 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <dirent.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <linux/fs.h>
19 #include <selinux/selinux.h>
20 #include <stdlib.h>
21 #include <sys/mount.h>
22 #include <sys/stat.h>
23 #include <sys/statvfs.h>
24 #include <sys/types.h>
25 #include <sys/vfs.h>
26 #include <unistd.h>
27
28 #include <android-base/file.h>
29 #include <android-base/logging.h>
30 #include <android-base/macros.h>
31 #include <android-base/properties.h>
32 #include <android-base/scopeguard.h>
33 #include <android-base/strings.h>
34 #include <android-base/unique_fd.h>
35 #include <ext4_utils/ext4_utils.h>
36
37 #include <libsnapshot/snapshot.h>
38
39 #include <fs_mgr.h>
40 #include <fs_mgr_dm_linear.h>
41 #include <fstab/fstab.h>
42 #include <liblp/builder.h>
43 #include <storage_literals/storage_literals.h>
44 #include <algorithm>
45 #include <filesystem>
46 #include <memory>
47 #include <optional>
48 #include <string>
49 #include <vector>
50
51 #include "device_info.h"
52 #include "scratch_super.h"
53
54 using namespace std::literals;
55 using namespace android::dm;
56 using namespace android::fs_mgr;
57 using namespace android::storage_literals;
58
59 namespace android {
60 namespace snapshot {
61
UmountScratch()62 static bool UmountScratch() {
63 auto ota_dir = std::string(kOtaMetadataMount) + "/" + "ota";
64 std::error_code ec;
65
66 if (std::filesystem::remove_all(ota_dir, ec) == static_cast<std::uintmax_t>(-1)) {
67 LOG(ERROR) << "Failed to remove OTA directory: " << ec.message();
68 return false;
69 }
70
71 if (umount(kOtaMetadataMount) != 0) {
72 PLOG(ERROR) << "UmountScratch failed";
73 return false;
74 }
75
76 LOG(INFO) << "umount scratch_super success";
77 return true;
78 }
79
CleanupScratchOtaMetadataIfPresent(const ISnapshotManager::IDeviceInfo * info)80 bool CleanupScratchOtaMetadataIfPresent(const ISnapshotManager::IDeviceInfo* info) {
81 if (!UmountScratch()) {
82 return false;
83 }
84
85 std::unique_ptr<MetadataBuilder> builder;
86 const auto partition_name = android::base::Basename(kOtaMetadataMount);
87 const std::vector<int> slots = {0, 1};
88
89 if (info == nullptr) {
90 info = new android::snapshot::DeviceInfo();
91 }
92
93 std::string super_device;
94 if (info->IsTestDevice()) {
95 super_device = "super";
96 } else {
97 super_device = kPhysicalDevice + fs_mgr_get_super_partition_name();
98 }
99 const auto& opener = info->GetPartitionOpener();
100 std::string slot_suffix = info->GetSlotSuffix();
101 // Walk both the slots and clean up metadata related to scratch space from
102 // both the slots.
103 for (auto slot : slots) {
104 std::unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(opener, super_device, slot);
105 if (!builder) {
106 return false;
107 }
108
109 if (builder->FindPartition(partition_name) != nullptr) {
110 builder->RemovePartition(partition_name);
111 auto metadata = builder->Export();
112 if (!metadata) {
113 return false;
114 }
115 if (!UpdatePartitionTable(info->GetPartitionOpener(), super_device, *metadata.get(),
116 slot)) {
117 LOG(ERROR) << "UpdatePartitionTable failed for slot: " << slot;
118 return false;
119 }
120 if (DestroyLogicalPartition(partition_name)) {
121 LOG(INFO) << "CleanupScratchOtaMetadata success for slot: " << slot;
122 }
123 }
124 }
125
126 return true;
127 }
128
SetupOTADirs()129 static bool SetupOTADirs() {
130 if (setfscreatecon(android::snapshot::kOtaMetadataFileContext)) {
131 PLOG(ERROR) << "setfscreatecon failed: " << android::snapshot::kOtaMetadataFileContext;
132 return false;
133 }
134 const auto ota_dir = std::string(kOtaMetadataMount) + "/" + "ota";
135 if (mkdir(ota_dir.c_str(), 0755) != 0 && errno != EEXIST) {
136 PLOG(ERROR) << "mkdir " << ota_dir;
137 return false;
138 }
139
140 const auto snapshot_dir = ota_dir + "/" + "snapshots";
141 if (mkdir(snapshot_dir.c_str(), 0755) != 0 && errno != EEXIST) {
142 PLOG(ERROR) << "mkdir " << snapshot_dir;
143 return false;
144 }
145 if (setfscreatecon(nullptr)) {
146 PLOG(ERROR) << "setfscreatecon null";
147 return false;
148 }
149 return true;
150 }
151
MountScratch(const std::string & device_path)152 static bool MountScratch(const std::string& device_path) {
153 if (access(device_path.c_str(), R_OK | W_OK)) {
154 LOG(ERROR) << "Path does not exist or is not readwrite: " << device_path;
155 return false;
156 }
157
158 std::string filesystem_candidate;
159 if (fs_mgr_is_ext4(device_path)) {
160 filesystem_candidate = "ext4";
161 } else {
162 LOG(ERROR) << "Scratch partition is not ext4";
163 return false;
164 }
165 if (setfscreatecon(android::snapshot::kOtaMetadataFileContext)) {
166 PLOG(ERROR) << "setfscreatecon failed: " << android::snapshot::kOtaMetadataFileContext;
167 return false;
168 }
169 if (mkdir(kOtaMetadataMount, 0755) && (errno != EEXIST)) {
170 PLOG(ERROR) << "create " << kOtaMetadataMount;
171 return false;
172 }
173
174 android::fs_mgr::FstabEntry entry;
175 entry.blk_device = device_path;
176 entry.mount_point = kOtaMetadataMount;
177 entry.flags = MS_NOATIME;
178 entry.flags |= MS_SYNCHRONOUS;
179 entry.fs_options = "nodiscard";
180 fs_mgr_set_blk_ro(device_path, false);
181 entry.fs_mgr_flags.check = true;
182
183 bool mounted = false;
184 entry.fs_type = filesystem_candidate.c_str();
185 if (fs_mgr_do_mount_one(entry) == 0) {
186 mounted = true;
187 }
188
189 if (setfscreatecon(nullptr)) {
190 PLOG(ERROR) << "setfscreatecon null";
191 return false;
192 }
193 if (!mounted) {
194 rmdir(kOtaMetadataMount);
195 return false;
196 }
197
198 return true;
199 }
200
MakeScratchFilesystem(const std::string & scratch_device)201 static bool MakeScratchFilesystem(const std::string& scratch_device) {
202 std::string fs_type;
203 std::string command;
204 if (!access(kMkExt4, X_OK)) {
205 fs_type = "ext4";
206 command = kMkExt4 + " -F -b 4096 -t ext4 -m 0 -O has_journal -M "s + kOtaMetadataMount;
207 } else {
208 LOG(ERROR) << "No supported mkfs command or filesystem driver available, supported "
209 "filesystems "
210 "are: f2fs, ext4";
211 return false;
212 }
213 command += " " + scratch_device + " >/dev/null 2>/dev/null </dev/null";
214 fs_mgr_set_blk_ro(scratch_device, false);
215 auto ret = system(command.c_str());
216 if (ret) {
217 LOG(ERROR) << "make " << fs_type << " filesystem on " << scratch_device
218 << " return=" << ret;
219 return false;
220 }
221 return true;
222 }
223
CreateDynamicScratch(const ISnapshotManager::IDeviceInfo * info,std::string * scratch_device)224 static bool CreateDynamicScratch(const ISnapshotManager::IDeviceInfo* info,
225 std::string* scratch_device) {
226 const auto partition_name = android::base::Basename(kOtaMetadataMount);
227 auto& dm = DeviceMapper::Instance();
228 if (info == nullptr) {
229 info = new android::snapshot::DeviceInfo();
230 }
231
232 std::string super_device;
233 if (info->IsTestDevice()) {
234 super_device = "super";
235 } else {
236 super_device = kPhysicalDevice + fs_mgr_get_super_partition_name();
237 }
238
239 bool partition_exists = dm.GetState(partition_name) != DmDeviceState::INVALID;
240 if (partition_exists) {
241 LOG(ERROR) << "Partition already exists: " << partition_name;
242 return false;
243 }
244
245 const auto& opener = info->GetPartitionOpener();
246 std::string slot_suffix = info->GetSlotSuffix();
247 int slot = SlotNumberForSlotSuffix(slot_suffix);
248 std::unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(opener, super_device, slot);
249
250 if (!builder) {
251 LOG(ERROR) << "open " << super_device << " failed";
252 return false;
253 }
254
255 auto partition = builder->FindPartition(partition_name);
256 partition_exists = partition != nullptr;
257 if (partition_exists) {
258 LOG(ERROR) << "Partition exists in super metadata";
259 return false;
260 }
261
262 partition = builder->AddPartition(partition_name, LP_PARTITION_ATTR_NONE);
263 if (!partition) {
264 LOG(ERROR) << "AddPartition failed " << partition_name;
265 return false;
266 }
267
268 auto free_space = builder->AllocatableSpace() - builder->UsedSpace();
269 if (free_space < kOtaMetadataPartitionSize) {
270 LOG(ERROR) << "No space in super partition. Free space: " << free_space
271 << " Requested space: " << kOtaMetadataPartitionSize;
272 return false;
273 }
274
275 LOG(INFO) << "CreateDynamicScratch: free_space: " << free_space
276 << " scratch_size: " << kOtaMetadataPartitionSize << " slot_number: " << slot;
277
278 if (!builder->ResizePartition(partition, kOtaMetadataPartitionSize)) {
279 LOG(ERROR) << "ResizePartition failed: " << partition_name << " free_space: " << free_space
280 << " scratch_size: " << kOtaMetadataPartitionSize;
281 return false;
282 }
283
284 auto metadata = builder->Export();
285 CreateLogicalPartitionParams params;
286
287 if (!metadata ||
288 !UpdatePartitionTable(info->GetPartitionOpener(), super_device, *metadata.get(), slot)) {
289 LOG(ERROR) << "UpdatePartitionTable failed: " << partition_name;
290 return false;
291 }
292 params = {
293 .block_device = super_device,
294 .metadata_slot = slot,
295 .partition_name = partition_name,
296 .force_writable = true,
297 .timeout_ms = 10s,
298 .partition_opener = &info->GetPartitionOpener(),
299 };
300
301 if (!CreateLogicalPartition(params, scratch_device)) {
302 LOG(ERROR) << "CreateLogicalPartition failed";
303 return false;
304 }
305
306 LOG(INFO) << "Scratch device created successfully: " << *scratch_device << " slot: " << slot;
307 return true;
308 }
309
IsScratchOtaMetadataOnSuper()310 bool IsScratchOtaMetadataOnSuper() {
311 auto partition_name = android::base::Basename(kOtaMetadataMount);
312 auto source_slot = fs_mgr_get_slot_suffix();
313 auto source_slot_number = SlotNumberForSlotSuffix(source_slot);
314
315 const auto super_device =
316 kPhysicalDevice + fs_mgr_get_super_partition_name(!source_slot_number);
317
318 auto metadata = android::fs_mgr::ReadMetadata(super_device, !source_slot_number);
319 if (!metadata) {
320 return false;
321 }
322 auto partition = android::fs_mgr::FindPartition(*metadata.get(), partition_name);
323 if (!partition) {
324 return false;
325 }
326
327 auto& dm = DeviceMapper::Instance();
328 if (dm.GetState(partition_name) == DmDeviceState::ACTIVE) {
329 LOG(INFO) << "Partition: " << partition_name << " is active";
330 return true;
331 }
332
333 CreateLogicalPartitionParams params = {
334 .block_device = super_device,
335 .metadata = metadata.get(),
336 .partition = partition,
337 };
338
339 std::string scratch_path;
340 if (!CreateLogicalPartition(params, &scratch_path)) {
341 LOG(ERROR) << "Could not create logical partition: " << partition_name;
342 return false;
343 }
344 LOG(INFO) << "Scratch device: " << scratch_path << " created successfully";
345
346 return true;
347 }
348
GetScratchOtaMetadataPartition()349 std::string GetScratchOtaMetadataPartition() {
350 std::string device;
351 auto& dm = DeviceMapper::Instance();
352 auto partition_name = android::base::Basename(kOtaMetadataMount);
353
354 bool invalid_partition = (dm.GetState(partition_name) == DmDeviceState::INVALID);
355 if (!invalid_partition && dm.GetDmDevicePathByName(partition_name, &device)) {
356 return device;
357 }
358 return "";
359 }
360
ScratchAlreadyMounted(const std::string & mount_point)361 static bool ScratchAlreadyMounted(const std::string& mount_point) {
362 android::fs_mgr::Fstab fstab;
363 if (!ReadFstabFromProcMounts(&fstab)) {
364 return false;
365 }
366 for (const auto& entry : GetEntriesForMountPoint(&fstab, mount_point)) {
367 if (entry->fs_type == "ext4") {
368 return true;
369 }
370 }
371 return false;
372 }
373
MapScratchOtaMetadataPartition(const std::string & scratch_device)374 std::string MapScratchOtaMetadataPartition(const std::string& scratch_device) {
375 if (!ScratchAlreadyMounted(kOtaMetadataMount)) {
376 if (!MountScratch(scratch_device)) {
377 return "";
378 }
379 }
380
381 auto ota_dir = std::string(kOtaMetadataMount) + "/" + "ota";
382 if (access(ota_dir.c_str(), F_OK) != 0) {
383 return "";
384 }
385 return ota_dir;
386 }
387
388 // Entry point to create a scratch device on super partition
389 // This will create a 1MB space in super. The space will be
390 // from the current active slot. Ext4 filesystem will be created
391 // on this scratch device and all the OTA related directories
392 // will be created.
CreateScratchOtaMetadataOnSuper(const ISnapshotManager::IDeviceInfo * info)393 bool CreateScratchOtaMetadataOnSuper(const ISnapshotManager::IDeviceInfo* info) {
394 std::string scratch_device;
395
396 if (!CreateDynamicScratch(info, &scratch_device)) {
397 LOG(ERROR) << "CreateDynamicScratch failed";
398 return false;
399 }
400 if (!MakeScratchFilesystem(scratch_device)) {
401 LOG(ERROR) << "MakeScratchFilesystem failed";
402 return false;
403 }
404 if (!MountScratch(scratch_device)) {
405 LOG(ERROR) << "MountScratch failed";
406 return false;
407 }
408 if (!SetupOTADirs()) {
409 LOG(ERROR) << "SetupOTADirs failed";
410 return false;
411 }
412 return true;
413 }
414
415 } // namespace snapshot
416 } // namespace android
417