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