blob: a4e8fc8ab15075a74706df63e2d1ede9ee465626 [file] [log] [blame]
Daniel Rosenberg65f99c92018-08-28 01:58:49 -07001/*
2 * Copyright (C) 2018 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
17#define LOG_TAG "Checkpoint"
18#include "Checkpoint.h"
Daniel Rosenberg253b44e2019-02-01 19:25:47 -080019#include "VoldUtil.h"
Sandeep Patilf8da61f2019-04-15 08:45:27 -070020#include "VolumeManager.h"
Daniel Rosenberg65f99c92018-08-28 01:58:49 -070021
Paul Lawrence1abb2fe2018-09-21 10:49:57 -070022#include <fstream>
Daniel Rosenberg65f99c92018-08-28 01:58:49 -070023#include <list>
Paul Lawrence20400892018-10-03 14:14:52 -070024#include <memory>
Daniel Rosenberg65f99c92018-08-28 01:58:49 -070025#include <string>
Daniel Rosenberg8daeec02018-11-20 19:03:11 -080026#include <thread>
Paul Lawrence1abb2fe2018-09-21 10:49:57 -070027#include <vector>
28
29#include <android-base/file.h>
30#include <android-base/logging.h>
31#include <android-base/parseint.h>
Daniel Rosenbergffa1bb02018-12-14 00:20:03 -080032#include <android-base/properties.h>
Paul Lawrence1abb2fe2018-09-21 10:49:57 -070033#include <android-base/unique_fd.h>
Daniel Rosenbergd3992492018-10-02 17:40:44 -070034#include <android/hardware/boot/1.0/IBootControl.h>
Paul Lawrence1abb2fe2018-09-21 10:49:57 -070035#include <cutils/android_reboot.h>
36#include <fcntl.h>
37#include <fs_mgr.h>
38#include <linux/fs.h>
39#include <mntent.h>
40#include <sys/mount.h>
41#include <sys/stat.h>
Daniel Rosenberg8daeec02018-11-20 19:03:11 -080042#include <sys/statvfs.h>
43#include <unistd.h>
Daniel Rosenberg65f99c92018-08-28 01:58:49 -070044
Daniel Rosenberg8daeec02018-11-20 19:03:11 -080045using android::base::GetBoolProperty;
46using android::base::GetUintProperty;
Daniel Rosenbergffa1bb02018-12-14 00:20:03 -080047using android::base::SetProperty;
Daniel Rosenberg73680ec2018-10-10 18:52:04 -070048using android::binder::Status;
Tom Cherry4c5bde22019-01-29 14:34:01 -080049using android::fs_mgr::Fstab;
50using android::fs_mgr::ReadDefaultFstab;
51using android::fs_mgr::ReadFstabFromFile;
Daniel Rosenbergd3992492018-10-02 17:40:44 -070052using android::hardware::hidl_string;
53using android::hardware::boot::V1_0::BoolResult;
Daniel Rosenberg886915b2019-01-23 15:16:04 -080054using android::hardware::boot::V1_0::CommandResult;
Daniel Rosenbergd3992492018-10-02 17:40:44 -070055using android::hardware::boot::V1_0::IBootControl;
56using android::hardware::boot::V1_0::Slot;
57
Daniel Rosenberg65f99c92018-08-28 01:58:49 -070058namespace android {
59namespace vold {
60
Paul Lawrence1abb2fe2018-09-21 10:49:57 -070061namespace {
62const std::string kMetadataCPFile = "/metadata/vold/checkpoint";
63
Paul Lawrence82b35052019-04-19 14:26:39 -070064binder::Status error(const std::string& msg) {
65 PLOG(ERROR) << msg;
66 return binder::Status::fromServiceSpecificError(errno, String8(msg.c_str()));
67}
68
69binder::Status error(int error, const std::string& msg) {
70 LOG(ERROR) << msg;
71 return binder::Status::fromServiceSpecificError(error, String8(msg.c_str()));
72}
73
Paul Lawrence1abb2fe2018-09-21 10:49:57 -070074bool setBowState(std::string const& block_device, std::string const& state) {
Paul Lawrence236e5e82019-06-25 14:44:33 -070075 std::string bow_device = fs_mgr_find_bow_device(block_device);
76 if (bow_device.empty()) return false;
Paul Lawrence1abb2fe2018-09-21 10:49:57 -070077
Paul Lawrence236e5e82019-06-25 14:44:33 -070078 if (!android::base::WriteStringToFile(state, bow_device + "/bow/state")) {
79 PLOG(ERROR) << "Failed to write to file " << bow_device + "/bow/state";
Paul Lawrence1abb2fe2018-09-21 10:49:57 -070080 return false;
81 }
82
83 return true;
84}
85
86} // namespace
Daniel Rosenberg65f99c92018-08-28 01:58:49 -070087
Daniel Rosenberg9b667fb2019-01-22 17:27:25 -080088Status cp_supportsCheckpoint(bool& result) {
89 result = false;
Daniel Rosenberg9b667fb2019-01-22 17:27:25 -080090
Tom Cherry4c5bde22019-01-29 14:34:01 -080091 for (const auto& entry : fstab_default) {
92 if (entry.fs_mgr_flags.checkpoint_blk || entry.fs_mgr_flags.checkpoint_fs) {
Daniel Rosenberg9b667fb2019-01-22 17:27:25 -080093 result = true;
94 return Status::ok();
95 }
96 }
97 return Status::ok();
98}
99
Paul Lawrencec5c79c52019-03-18 13:36:40 -0700100Status cp_supportsBlockCheckpoint(bool& result) {
101 result = false;
102
103 for (const auto& entry : fstab_default) {
104 if (entry.fs_mgr_flags.checkpoint_blk) {
105 result = true;
106 return Status::ok();
107 }
108 }
109 return Status::ok();
110}
111
112Status cp_supportsFileCheckpoint(bool& result) {
113 result = false;
114
115 for (const auto& entry : fstab_default) {
116 if (entry.fs_mgr_flags.checkpoint_fs) {
117 result = true;
118 return Status::ok();
119 }
120 }
121 return Status::ok();
122}
123
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700124Status cp_startCheckpoint(int retry) {
Paul Lawrencec2a145f2019-05-15 09:42:04 -0700125 bool result;
126 if (!cp_supportsCheckpoint(result).isOk() || !result)
127 return error(ENOTSUP, "Checkpoints not supported");
128
Paul Lawrence82b35052019-04-19 14:26:39 -0700129 if (retry < -1) return error(EINVAL, "Retry count must be more than -1");
Daniel Rosenberg80d1ca52018-10-09 19:26:57 -0700130 std::string content = std::to_string(retry + 1);
Daniel Rosenbergd3992492018-10-02 17:40:44 -0700131 if (retry == -1) {
132 sp<IBootControl> module = IBootControl::getService();
133 if (module) {
134 std::string suffix;
135 auto cb = [&suffix](hidl_string s) { suffix = s; };
136 if (module->getSuffix(module->getCurrentSlot(), cb).isOk()) content += " " + suffix;
137 }
138 }
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700139 if (!android::base::WriteStringToFile(content, kMetadataCPFile))
Paul Lawrence82b35052019-04-19 14:26:39 -0700140 return error("Failed to write checkpoint file");
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700141 return Status::ok();
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700142}
143
Daniel Rosenbergffa1bb02018-12-14 00:20:03 -0800144namespace {
145
Daniel Rosenberg8daeec02018-11-20 19:03:11 -0800146volatile bool isCheckpointing = false;
Paul Lawrence1d57f682019-08-22 09:51:18 -0700147
Nikita Ioffea5798fc2019-10-11 16:38:21 +0100148volatile bool needsCheckpointWasCalled = false;
149
150// Protects isCheckpointing, needsCheckpointWasCalled and code that makes decisions based on status
151// of isCheckpointing
Paul Lawrence1d57f682019-08-22 09:51:18 -0700152std::mutex isCheckpointingLock;
Daniel Rosenbergffa1bb02018-12-14 00:20:03 -0800153}
154
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700155Status cp_commitChanges() {
Paul Lawrence1d57f682019-08-22 09:51:18 -0700156 std::lock_guard<std::mutex> lock(isCheckpointingLock);
157
Daniel Rosenbergffa1bb02018-12-14 00:20:03 -0800158 if (!isCheckpointing) {
159 return Status::ok();
160 }
Paul Lawrencea7972dc2019-06-12 12:03:01 -0700161 if (android::base::GetProperty("persist.vold.dont_commit_checkpoint", "0") == "1") {
162 LOG(WARNING)
163 << "NOT COMMITTING CHECKPOINT BECAUSE persist.vold.dont_commit_checkpoint IS 1";
164 return Status::ok();
165 }
Daniel Rosenberg886915b2019-01-23 15:16:04 -0800166 sp<IBootControl> module = IBootControl::getService();
167 if (module) {
168 CommandResult cr;
169 module->markBootSuccessful([&cr](CommandResult result) { cr = result; });
Paul Lawrence82b35052019-04-19 14:26:39 -0700170 if (!cr.success)
171 return error(EINVAL, "Error marking booted successfully: " + std::string(cr.errMsg));
Daniel Rosenberg886915b2019-01-23 15:16:04 -0800172 LOG(INFO) << "Marked slot as booted successfully.";
173 }
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700174 // Must take action for list of mounted checkpointed things here
175 // To do this, we walk the list of mounted file systems.
176 // But we also need to get the matching fstab entries to see
177 // the original flags
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700178 std::string err_str;
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700179
Tom Cherry4c5bde22019-01-29 14:34:01 -0800180 Fstab mounts;
181 if (!ReadFstabFromFile("/proc/mounts", &mounts)) {
Paul Lawrence82b35052019-04-19 14:26:39 -0700182 return error(EINVAL, "Failed to get /proc/mounts");
Tom Cherry4c5bde22019-01-29 14:34:01 -0800183 }
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700184
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700185 // Walk mounted file systems
Tom Cherry4c5bde22019-01-29 14:34:01 -0800186 for (const auto& mount_rec : mounts) {
187 const auto fstab_rec = GetEntryForMountPoint(&fstab_default, mount_rec.mount_point);
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700188 if (!fstab_rec) continue;
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700189
Tom Cherry4c5bde22019-01-29 14:34:01 -0800190 if (fstab_rec->fs_mgr_flags.checkpoint_fs) {
191 if (fstab_rec->fs_type == "f2fs") {
192 std::string options = mount_rec.fs_options + ",checkpoint=enable";
193 if (mount(mount_rec.blk_device.c_str(), mount_rec.mount_point.c_str(), "none",
Daniel Rosenberg14ca4ac2019-01-24 18:23:18 -0800194 MS_REMOUNT | fstab_rec->flags, options.c_str())) {
Paul Lawrence82b35052019-04-19 14:26:39 -0700195 return error(EINVAL, "Failed to remount");
Daniel Rosenberg4b86df12018-11-08 22:18:37 -0800196 }
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700197 }
Tom Cherry4c5bde22019-01-29 14:34:01 -0800198 } else if (fstab_rec->fs_mgr_flags.checkpoint_blk) {
199 if (!setBowState(mount_rec.blk_device, "2"))
Paul Lawrence82b35052019-04-19 14:26:39 -0700200 return error(EINVAL, "Failed to set bow state");
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700201 }
202 }
Daniel Rosenbergffa1bb02018-12-14 00:20:03 -0800203 SetProperty("vold.checkpoint_committed", "1");
Daniel Rosenberg886915b2019-01-23 15:16:04 -0800204 LOG(INFO) << "Checkpoint has been committed.";
Daniel Rosenbergffa1bb02018-12-14 00:20:03 -0800205 isCheckpointing = false;
Daniel Rosenberg4b86df12018-11-08 22:18:37 -0800206 if (!android::base::RemoveFileIfExists(kMetadataCPFile, &err_str))
Paul Lawrence82b35052019-04-19 14:26:39 -0700207 return error(err_str.c_str());
208
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700209 return Status::ok();
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700210}
211
Daniel Rosenberga59e4392019-03-20 17:02:47 -0700212namespace {
213void abort_metadata_file() {
214 std::string oldContent, newContent;
215 int retry = 0;
216 struct stat st;
217 int result = stat(kMetadataCPFile.c_str(), &st);
218
219 // If the file doesn't exist, we aren't managing a checkpoint retry counter
220 if (result != 0) return;
221 if (!android::base::ReadFileToString(kMetadataCPFile, &oldContent)) {
222 PLOG(ERROR) << "Failed to read checkpoint file";
223 return;
224 }
225 std::string retryContent = oldContent.substr(0, oldContent.find_first_of(" "));
226
227 if (!android::base::ParseInt(retryContent, &retry)) {
228 PLOG(ERROR) << "Could not parse retry count";
229 return;
230 }
231 if (retry > 0) {
232 newContent = "0";
233 if (!android::base::WriteStringToFile(newContent, kMetadataCPFile))
234 PLOG(ERROR) << "Could not write checkpoint file";
235 }
236}
237} // namespace
238
239void cp_abortChanges(const std::string& message, bool retry) {
240 if (!cp_needsCheckpoint()) return;
241 if (!retry) abort_metadata_file();
242 android_reboot(ANDROID_RB_RESTART2, 0, message.c_str());
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700243}
244
Daniel Rosenbergd3992492018-10-02 17:40:44 -0700245bool cp_needsRollback() {
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700246 std::string content;
247 bool ret;
248
249 ret = android::base::ReadFileToString(kMetadataCPFile, &content);
Daniel Rosenbergd3992492018-10-02 17:40:44 -0700250 if (ret) {
251 if (content == "0") return true;
252 if (content.substr(0, 3) == "-1 ") {
253 std::string oldSuffix = content.substr(3);
254 sp<IBootControl> module = IBootControl::getService();
255 std::string newSuffix;
256
257 if (module) {
258 auto cb = [&newSuffix](hidl_string s) { newSuffix = s; };
259 module->getSuffix(module->getCurrentSlot(), cb);
260 if (oldSuffix == newSuffix) return true;
261 }
262 }
263 }
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700264 return false;
265}
266
Daniel Rosenberg80d1ca52018-10-09 19:26:57 -0700267bool cp_needsCheckpoint() {
Nikita Ioffea5798fc2019-10-11 16:38:21 +0100268 std::lock_guard<std::mutex> lock(isCheckpointingLock);
269
Paul Lawrence9a6d1f72019-08-26 15:09:41 -0700270 // Make sure we only return true during boot. See b/138952436 for discussion
Nikita Ioffea5798fc2019-10-11 16:38:21 +0100271 if (needsCheckpointWasCalled) return isCheckpointing;
272 needsCheckpointWasCalled = true;
Paul Lawrence9a6d1f72019-08-26 15:09:41 -0700273
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700274 bool ret;
275 std::string content;
Daniel Rosenbergd3992492018-10-02 17:40:44 -0700276 sp<IBootControl> module = IBootControl::getService();
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700277
Daniel Rosenberg84203c12019-03-19 14:02:59 -0700278 if (isCheckpointing) return isCheckpointing;
279
Daniel Rosenbergffa1bb02018-12-14 00:20:03 -0800280 if (module && module->isSlotMarkedSuccessful(module->getCurrentSlot()) == BoolResult::FALSE) {
281 isCheckpointing = true;
Daniel Rosenbergd3992492018-10-02 17:40:44 -0700282 return true;
Daniel Rosenbergffa1bb02018-12-14 00:20:03 -0800283 }
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700284 ret = android::base::ReadFileToString(kMetadataCPFile, &content);
Daniel Rosenbergffa1bb02018-12-14 00:20:03 -0800285 if (ret) {
286 ret = content != "0";
287 isCheckpointing = ret;
288 return ret;
289 }
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700290 return false;
291}
292
Daniel Rosenberg8daeec02018-11-20 19:03:11 -0800293namespace {
Daniel Rosenbergb7dddd02019-03-26 14:42:14 -0700294const std::string kSleepTimeProp = "ro.sys.cp_msleeptime";
295const uint32_t msleeptime_default = 1000; // 1 s
296const uint32_t max_msleeptime = 3600000; // 1 h
Daniel Rosenberg8daeec02018-11-20 19:03:11 -0800297
298const std::string kMinFreeBytesProp = "ro.sys.cp_min_free_bytes";
299const uint64_t min_free_bytes_default = 100 * (1 << 20); // 100 MiB
300
301const std::string kCommitOnFullProp = "ro.sys.cp_commit_on_full";
302const bool commit_on_full_default = true;
303
304static void cp_healthDaemon(std::string mnt_pnt, std::string blk_device, bool is_fs_cp) {
305 struct statvfs data;
Daniel Rosenbergb7dddd02019-03-26 14:42:14 -0700306 uint32_t msleeptime = GetUintProperty(kSleepTimeProp, msleeptime_default, max_msleeptime);
Satoshi Futenma18d10d42019-03-25 23:13:36 +0900307 uint64_t min_free_bytes =
308 GetUintProperty(kMinFreeBytesProp, min_free_bytes_default, (uint64_t)-1);
Daniel Rosenberg8daeec02018-11-20 19:03:11 -0800309 bool commit_on_full = GetBoolProperty(kCommitOnFullProp, commit_on_full_default);
310
Daniel Rosenbergb7dddd02019-03-26 14:42:14 -0700311 struct timespec req;
312 req.tv_sec = msleeptime / 1000;
313 msleeptime %= 1000;
314 req.tv_nsec = msleeptime * 1000000;
Daniel Rosenberg8daeec02018-11-20 19:03:11 -0800315 while (isCheckpointing) {
Paul Lawrence236e5e82019-06-25 14:44:33 -0700316 uint64_t free_bytes = 0;
Daniel Rosenberg8daeec02018-11-20 19:03:11 -0800317 if (is_fs_cp) {
318 statvfs(mnt_pnt.c_str(), &data);
319 free_bytes = data.f_bavail * data.f_frsize;
320 } else {
Paul Lawrence236e5e82019-06-25 14:44:33 -0700321 std::string bow_device = fs_mgr_find_bow_device(blk_device);
322 if (!bow_device.empty()) {
323 std::string content;
324 if (android::base::ReadFileToString(bow_device + "/bow/free", &content)) {
325 free_bytes = std::strtoul(content.c_str(), NULL, 10);
326 }
Daniel Rosenberg8daeec02018-11-20 19:03:11 -0800327 }
328 }
329 if (free_bytes < min_free_bytes) {
330 if (commit_on_full) {
331 LOG(INFO) << "Low space for checkpointing. Commiting changes";
332 cp_commitChanges();
333 break;
334 } else {
335 LOG(INFO) << "Low space for checkpointing. Rebooting";
336 cp_abortChanges("checkpoint,low_space", false);
337 break;
338 }
339 }
Daniel Rosenbergb7dddd02019-03-26 14:42:14 -0700340 nanosleep(&req, NULL);
Daniel Rosenberg8daeec02018-11-20 19:03:11 -0800341 }
342}
343
344} // namespace
345
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700346Status cp_prepareCheckpoint() {
Paul Lawrence1d57f682019-08-22 09:51:18 -0700347 std::lock_guard<std::mutex> lock(isCheckpointingLock);
Paul Lawrencedb086942019-02-19 14:18:54 -0800348 if (!isCheckpointing) {
349 return Status::ok();
350 }
351
Tom Cherry4c5bde22019-01-29 14:34:01 -0800352 Fstab mounts;
353 if (!ReadFstabFromFile("/proc/mounts", &mounts)) {
Paul Lawrence82b35052019-04-19 14:26:39 -0700354 return error(EINVAL, "Failed to get /proc/mounts");
Tom Cherry4c5bde22019-01-29 14:34:01 -0800355 }
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700356
Tom Cherry4c5bde22019-01-29 14:34:01 -0800357 for (const auto& mount_rec : mounts) {
358 const auto fstab_rec = GetEntryForMountPoint(&fstab_default, mount_rec.mount_point);
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700359 if (!fstab_rec) continue;
360
Tom Cherry4c5bde22019-01-29 14:34:01 -0800361 if (fstab_rec->fs_mgr_flags.checkpoint_blk) {
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700362 android::base::unique_fd fd(
Tom Cherry4c5bde22019-01-29 14:34:01 -0800363 TEMP_FAILURE_RETRY(open(mount_rec.mount_point.c_str(), O_RDONLY | O_CLOEXEC)));
Bernie Innocentiebe293a2019-03-28 15:24:30 +0900364 if (fd == -1) {
Tom Cherry4c5bde22019-01-29 14:34:01 -0800365 PLOG(ERROR) << "Failed to open mount point" << mount_rec.mount_point;
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700366 continue;
367 }
368
369 struct fstrim_range range = {};
370 range.len = ULLONG_MAX;
Sandeep Patilf8da61f2019-04-15 08:45:27 -0700371 nsecs_t start = systemTime(SYSTEM_TIME_BOOTTIME);
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700372 if (ioctl(fd, FITRIM, &range)) {
Tom Cherry4c5bde22019-01-29 14:34:01 -0800373 PLOG(ERROR) << "Failed to trim " << mount_rec.mount_point;
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700374 continue;
375 }
Sandeep Patilf8da61f2019-04-15 08:45:27 -0700376 nsecs_t time = systemTime(SYSTEM_TIME_BOOTTIME) - start;
377 LOG(INFO) << "Trimmed " << range.len << " bytes on " << mount_rec.mount_point << " in "
378 << nanoseconds_to_milliseconds(time) << "ms for checkpoint";
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700379
Tom Cherry4c5bde22019-01-29 14:34:01 -0800380 setBowState(mount_rec.blk_device, "1");
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700381 }
Daniel Rosenberg8daeec02018-11-20 19:03:11 -0800382 if (fstab_rec->fs_mgr_flags.checkpoint_blk || fstab_rec->fs_mgr_flags.checkpoint_fs) {
383 std::thread(cp_healthDaemon, std::string(mount_rec.mount_point),
Paul Lawrencee81f4c12019-03-29 13:06:34 -0700384 std::string(mount_rec.blk_device),
Daniel Rosenberg8daeec02018-11-20 19:03:11 -0800385 fstab_rec->fs_mgr_flags.checkpoint_fs == 1)
386 .detach();
387 }
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700388 }
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700389 return Status::ok();
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700390}
391
392namespace {
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700393const int kSectorSize = 512;
394
395typedef uint64_t sector_t;
396
397struct log_entry {
Paul Lawrenced41a9392019-01-22 14:31:43 -0800398 sector_t source; // in sectors of size kSectorSize
399 sector_t dest; // in sectors of size kSectorSize
400 uint32_t size; // in bytes
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700401 uint32_t checksum;
402} __attribute__((packed));
403
Paul Lawrencef5077682019-01-18 10:28:34 -0800404struct log_sector_v1_0 {
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700405 uint32_t magic;
Paul Lawrencef5077682019-01-18 10:28:34 -0800406 uint16_t header_version;
407 uint16_t header_size;
Paul Lawrence4f13a902019-01-10 13:06:07 -0800408 uint32_t block_size;
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700409 uint32_t count;
410 uint32_t sequence;
Paul Lawrence27691c22018-11-20 14:07:59 -0800411 uint64_t sector0;
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700412} __attribute__((packed));
413
414// MAGIC is BOW in ascii
415const int kMagic = 0x00574f42;
Daniel Rosenberg52985932019-03-01 22:01:22 -0800416// Partially restored MAGIC is WOB in ascii
417const int kPartialRestoreMagic = 0x00424f57;
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700418
419void crc32(const void* data, size_t n_bytes, uint32_t* crc) {
420 static uint32_t table[0x100] = {
421 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535,
422 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD,
423 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D,
424 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
425 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
426 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
427 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC,
428 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
429 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB,
430 0xB6662D3D,
431
432 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5,
433 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
434 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED,
435 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
436 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074,
437 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC,
438 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C,
439 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
440 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B,
441 0xC0BA6CAD,
442
443 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,
444 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D,
445 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D,
446 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
447 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4,
448 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C,
449 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C,
450 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
451 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B,
452 0x5BDEAE1D,
453
454 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785,
455 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D,
456 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD,
457 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
458 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354,
459 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
460 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C,
461 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
462 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B,
463 0x2D02EF8D};
464
465 for (size_t i = 0; i < n_bytes; ++i) {
466 *crc ^= ((uint8_t*)data)[i];
467 *crc = table[(uint8_t)*crc] ^ *crc >> 8;
468 }
469}
470
Paul Lawrenced41a9392019-01-22 14:31:43 -0800471// A map of relocations.
472// The map must be initialized so that relocations[0] = 0
473// During restore, we replay the log records in reverse, copying from dest to
474// source
475// To validate, we must be able to read the 'dest' sectors as though they had
476// been copied but without actually copying. This map represents how the sectors
477// would have been moved. To read a sector s, find the index <= s and read
478// relocations[index] + s - index
479typedef std::map<sector_t, sector_t> Relocations;
Paul Lawrence27691c22018-11-20 14:07:59 -0800480
Paul Lawrenced41a9392019-01-22 14:31:43 -0800481void relocate(Relocations& relocations, sector_t dest, sector_t source, int count) {
482 // Find first one we're equal to or greater than
483 auto s = --relocations.upper_bound(source);
484
485 // Take slice
486 Relocations slice;
487 slice[dest] = source - s->first + s->second;
488 ++s;
489
490 // Add rest of elements
491 for (; s != relocations.end() && s->first < source + count; ++s)
492 slice[dest - source + s->first] = s->second;
493
494 // Split range at end of dest
495 auto dest_end = --relocations.upper_bound(dest + count);
496 relocations[dest + count] = dest + count - dest_end->first + dest_end->second;
497
498 // Remove all elements in [dest, dest + count)
499 relocations.erase(relocations.lower_bound(dest), relocations.lower_bound(dest + count));
500
501 // Add new elements
502 relocations.insert(slice.begin(), slice.end());
Paul Lawrence27691c22018-11-20 14:07:59 -0800503}
504
Daniel Rosenberg52985932019-03-01 22:01:22 -0800505// A map of sectors that have been written to.
506// The final entry must always be False.
507// When we restart the restore after an interruption, we must take care that
508// when we copy from dest to source, that the block we copy to was not
509// previously copied from.
510// i e. A->B C->A; If we replay this sequence, we end up copying C->B
511// We must save our partial result whenever we finish a page, or when we copy
512// to a location that was copied from earlier (our source is an earlier dest)
513typedef std::map<sector_t, bool> Used_Sectors;
514
515bool checkCollision(Used_Sectors& used_sectors, sector_t start, sector_t end) {
516 auto second_overlap = used_sectors.upper_bound(start);
517 auto first_overlap = --second_overlap;
518
519 if (first_overlap->second) {
520 return true;
521 } else if (second_overlap != used_sectors.end() && second_overlap->first < end) {
522 return true;
523 }
524 return false;
525}
526
527void markUsed(Used_Sectors& used_sectors, sector_t start, sector_t end) {
528 auto start_pos = used_sectors.insert_or_assign(start, true).first;
529 auto end_pos = used_sectors.insert_or_assign(end, false).first;
530
531 if (start_pos == used_sectors.begin() || !std::prev(start_pos)->second) {
532 start_pos++;
533 }
534 if (std::next(end_pos) != used_sectors.end() && !std::next(end_pos)->second) {
535 end_pos++;
536 }
537 if (start_pos->first < end_pos->first) {
538 used_sectors.erase(start_pos, end_pos);
539 }
540}
541
542// Restores the given log_entry's data from dest -> source
543// If that entry is a log sector, set the magic to kPartialRestoreMagic and flush.
544void restoreSector(int device_fd, Used_Sectors& used_sectors, std::vector<char>& ls_buffer,
545 log_entry* le, std::vector<char>& buffer) {
546 log_sector_v1_0& ls = *reinterpret_cast<log_sector_v1_0*>(&ls_buffer[0]);
547 uint32_t index = le - ((log_entry*)&ls_buffer[ls.header_size]);
548 int count = (le->size - 1) / kSectorSize + 1;
549
550 if (checkCollision(used_sectors, le->source, le->source + count)) {
551 fsync(device_fd);
552 lseek64(device_fd, 0, SEEK_SET);
553 ls.count = index + 1;
554 ls.magic = kPartialRestoreMagic;
555 write(device_fd, &ls_buffer[0], ls.block_size);
556 fsync(device_fd);
557 used_sectors.clear();
558 used_sectors[0] = false;
559 }
560
561 markUsed(used_sectors, le->dest, le->dest + count);
562
563 if (index == 0 && ls.sequence != 0) {
564 log_sector_v1_0* next = reinterpret_cast<log_sector_v1_0*>(&buffer[0]);
565 if (next->magic == kMagic) {
566 next->magic = kPartialRestoreMagic;
567 }
568 }
569
570 lseek64(device_fd, le->source * kSectorSize, SEEK_SET);
571 write(device_fd, &buffer[0], le->size);
572
573 if (index == 0) {
574 fsync(device_fd);
575 }
576}
577
Paul Lawrenced41a9392019-01-22 14:31:43 -0800578// Read from the device
579// If we are validating, the read occurs as though the relocations had happened
Daniel Rosenberg8271ae92019-03-04 21:46:31 -0800580std::vector<char> relocatedRead(int device_fd, Relocations const& relocations, bool validating,
581 sector_t sector, uint32_t size, uint32_t block_size) {
Paul Lawrence27691c22018-11-20 14:07:59 -0800582 if (!validating) {
583 std::vector<char> buffer(size);
Daniel Rosenberg8271ae92019-03-04 21:46:31 -0800584 lseek64(device_fd, sector * kSectorSize, SEEK_SET);
585 read(device_fd, &buffer[0], size);
Paul Lawrence27691c22018-11-20 14:07:59 -0800586 return buffer;
587 }
588
Paul Lawrence27691c22018-11-20 14:07:59 -0800589 std::vector<char> buffer(size);
Paul Lawrenced41a9392019-01-22 14:31:43 -0800590 for (uint32_t i = 0; i < size; i += block_size, sector += block_size / kSectorSize) {
591 auto relocation = --relocations.upper_bound(sector);
Daniel Rosenberg8271ae92019-03-04 21:46:31 -0800592 lseek64(device_fd, (sector + relocation->second - relocation->first) * kSectorSize,
593 SEEK_SET);
594 read(device_fd, &buffer[i], block_size);
Paul Lawrenced41a9392019-01-22 14:31:43 -0800595 }
Paul Lawrence27691c22018-11-20 14:07:59 -0800596
597 return buffer;
598}
599
Paul Lawrence4f13a902019-01-10 13:06:07 -0800600} // namespace
601
Daniel Rosenbergdda59812019-03-06 17:45:17 -0800602Status cp_restoreCheckpoint(const std::string& blockDevice, int restore_limit) {
Paul Lawrence27691c22018-11-20 14:07:59 -0800603 bool validating = true;
604 std::string action = "Validating";
Daniel Rosenbergdda59812019-03-06 17:45:17 -0800605 int restore_count = 0;
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700606
Paul Lawrence27691c22018-11-20 14:07:59 -0800607 for (;;) {
Paul Lawrenced41a9392019-01-22 14:31:43 -0800608 Relocations relocations;
609 relocations[0] = 0;
Paul Lawrence27691c22018-11-20 14:07:59 -0800610 Status status = Status::ok();
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700611
Paul Lawrence27691c22018-11-20 14:07:59 -0800612 LOG(INFO) << action << " checkpoint on " << blockDevice;
Nick Kraleviche7e89ac2019-03-29 16:03:51 -0700613 base::unique_fd device_fd(open(blockDevice.c_str(), O_RDWR | O_CLOEXEC));
Paul Lawrence82b35052019-04-19 14:26:39 -0700614 if (device_fd < 0) return error("Cannot open " + blockDevice);
Paul Lawrence4f13a902019-01-10 13:06:07 -0800615
Paul Lawrencef5077682019-01-18 10:28:34 -0800616 log_sector_v1_0 original_ls;
Daniel Rosenberg8271ae92019-03-04 21:46:31 -0800617 read(device_fd, reinterpret_cast<char*>(&original_ls), sizeof(original_ls));
Daniel Rosenberg52985932019-03-01 22:01:22 -0800618 if (original_ls.magic == kPartialRestoreMagic) {
619 validating = false;
620 action = "Restoring";
621 } else if (original_ls.magic != kMagic) {
Paul Lawrence82b35052019-04-19 14:26:39 -0700622 return error(EINVAL, "No magic");
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700623 }
624
Paul Lawrence4f13a902019-01-10 13:06:07 -0800625 LOG(INFO) << action << " " << original_ls.sequence << " log sectors";
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700626
Paul Lawrence4f13a902019-01-10 13:06:07 -0800627 for (int sequence = original_ls.sequence; sequence >= 0 && status.isOk(); sequence--) {
Daniel Rosenberg52985932019-03-01 22:01:22 -0800628 auto ls_buffer = relocatedRead(device_fd, relocations, validating, 0,
629 original_ls.block_size, original_ls.block_size);
630 log_sector_v1_0& ls = *reinterpret_cast<log_sector_v1_0*>(&ls_buffer[0]);
631
632 Used_Sectors used_sectors;
633 used_sectors[0] = false;
634
635 if (ls.magic != kMagic && (ls.magic != kPartialRestoreMagic || validating)) {
Paul Lawrence82b35052019-04-19 14:26:39 -0700636 status = error(EINVAL, "No magic");
Paul Lawrence27691c22018-11-20 14:07:59 -0800637 break;
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700638 }
639
Paul Lawrence4f13a902019-01-10 13:06:07 -0800640 if (ls.block_size != original_ls.block_size) {
Paul Lawrence82b35052019-04-19 14:26:39 -0700641 status = error(EINVAL, "Block size mismatch");
Paul Lawrence4f13a902019-01-10 13:06:07 -0800642 break;
643 }
644
Paul Lawrence27691c22018-11-20 14:07:59 -0800645 if ((int)ls.sequence != sequence) {
Paul Lawrence82b35052019-04-19 14:26:39 -0700646 status = error(EINVAL, "Expecting log sector " + std::to_string(sequence) +
647 " but got " + std::to_string(ls.sequence));
Paul Lawrence27691c22018-11-20 14:07:59 -0800648 break;
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700649 }
650
Paul Lawrence27691c22018-11-20 14:07:59 -0800651 LOG(INFO) << action << " from log sector " << ls.sequence;
Paul Lawrencef5077682019-01-18 10:28:34 -0800652 for (log_entry* le =
Daniel Rosenberg52985932019-03-01 22:01:22 -0800653 reinterpret_cast<log_entry*>(&ls_buffer[ls.header_size]) + ls.count - 1;
654 le >= reinterpret_cast<log_entry*>(&ls_buffer[ls.header_size]); --le) {
Paul Lawrencef5077682019-01-18 10:28:34 -0800655 // This is very noisy - limit to DEBUG only
Paul Lawrenced41a9392019-01-22 14:31:43 -0800656 LOG(VERBOSE) << action << " " << le->size << " bytes from sector " << le->dest
657 << " to " << le->source << " with checksum " << std::hex
658 << le->checksum;
Paul Lawrencef5077682019-01-18 10:28:34 -0800659
Daniel Rosenberg8271ae92019-03-04 21:46:31 -0800660 auto buffer = relocatedRead(device_fd, relocations, validating, le->dest, le->size,
Paul Lawrenced41a9392019-01-22 14:31:43 -0800661 ls.block_size);
Paul Lawrence4f13a902019-01-10 13:06:07 -0800662 uint32_t checksum = le->source / (ls.block_size / kSectorSize);
663 for (size_t i = 0; i < le->size; i += ls.block_size) {
664 crc32(&buffer[i], ls.block_size, &checksum);
Paul Lawrence27691c22018-11-20 14:07:59 -0800665 }
666
667 if (le->checksum && checksum != le->checksum) {
Paul Lawrence82b35052019-04-19 14:26:39 -0700668 status = error(EINVAL, "Checksums don't match");
Paul Lawrence27691c22018-11-20 14:07:59 -0800669 break;
670 }
671
Paul Lawrenced41a9392019-01-22 14:31:43 -0800672 if (validating) {
Daniel Rosenberg52985932019-03-01 22:01:22 -0800673 relocate(relocations, le->source, le->dest, (le->size - 1) / kSectorSize + 1);
Paul Lawrenced41a9392019-01-22 14:31:43 -0800674 } else {
Daniel Rosenberg52985932019-03-01 22:01:22 -0800675 restoreSector(device_fd, used_sectors, ls_buffer, le, buffer);
Daniel Rosenbergdda59812019-03-06 17:45:17 -0800676 restore_count++;
677 if (restore_limit && restore_count >= restore_limit) {
Paul Lawrence82b35052019-04-19 14:26:39 -0700678 status = error(EAGAIN, "Hit the test limit");
Daniel Rosenbergdda59812019-03-06 17:45:17 -0800679 break;
680 }
Paul Lawrence27691c22018-11-20 14:07:59 -0800681 }
682 }
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700683 }
Paul Lawrence27691c22018-11-20 14:07:59 -0800684
685 if (!status.isOk()) {
686 if (!validating) {
687 LOG(ERROR) << "Checkpoint restore failed even though checkpoint validation passed";
688 return status;
689 }
690
691 LOG(WARNING) << "Checkpoint validation failed - attempting to roll forward";
Daniel Rosenberg8271ae92019-03-04 21:46:31 -0800692 auto buffer = relocatedRead(device_fd, relocations, false, original_ls.sector0,
Paul Lawrenced41a9392019-01-22 14:31:43 -0800693 original_ls.block_size, original_ls.block_size);
Daniel Rosenberg8271ae92019-03-04 21:46:31 -0800694 lseek64(device_fd, 0, SEEK_SET);
695 write(device_fd, &buffer[0], original_ls.block_size);
Paul Lawrence27691c22018-11-20 14:07:59 -0800696 return Status::ok();
697 }
698
699 if (!validating) break;
700
701 validating = false;
702 action = "Restoring";
Paul Lawrence1abb2fe2018-09-21 10:49:57 -0700703 }
704
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700705 return Status::ok();
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700706}
707
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700708Status cp_markBootAttempt() {
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700709 std::string oldContent, newContent;
710 int retry = 0;
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700711 struct stat st;
712 int result = stat(kMetadataCPFile.c_str(), &st);
713
714 // If the file doesn't exist, we aren't managing a checkpoint retry counter
715 if (result != 0) return Status::ok();
Paul Lawrence82b35052019-04-19 14:26:39 -0700716 if (!android::base::ReadFileToString(kMetadataCPFile, &oldContent))
717 return error("Failed to read checkpoint file");
Daniel Rosenbergd3992492018-10-02 17:40:44 -0700718 std::string retryContent = oldContent.substr(0, oldContent.find_first_of(" "));
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700719
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700720 if (!android::base::ParseInt(retryContent, &retry))
Paul Lawrence82b35052019-04-19 14:26:39 -0700721 return error(EINVAL, "Could not parse retry count");
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700722 if (retry > 0) {
723 retry--;
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700724
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700725 newContent = std::to_string(retry);
726 if (!android::base::WriteStringToFile(newContent, kMetadataCPFile))
Paul Lawrence82b35052019-04-19 14:26:39 -0700727 return error("Could not write checkpoint file");
Daniel Rosenberg73680ec2018-10-10 18:52:04 -0700728 }
729 return Status::ok();
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700730}
731
Nikita Ioffea5798fc2019-10-11 16:38:21 +0100732void cp_resetCheckpoint() {
733 std::lock_guard<std::mutex> lock(isCheckpointingLock);
734 needsCheckpointWasCalled = false;
735}
736
Daniel Rosenberg65f99c92018-08-28 01:58:49 -0700737} // namespace vold
738} // namespace android