dd20dc3128
When userdata partition is the type of ext4, checkpoint and metadata encryption are enabled, boot up the images, if the slot currently in use is not marked as "successful" and userdata is wiped, a reboot will be triggered because of "Checkpoint commit failed" In this patch, do not create checkpoint when the preceding condition occurs, otherwise "dm-default-key" sits below "dm-bow". Because cp_needsCheckpoint(), cp_prepareCheckpoint() and cp_commitChanges() are always executed when the system boots up, and now there is a condition that BOW device is not created while the slot in use is not marked as "successful", set "isBow" to be "false" if BOW state fails to be set to 1, if "isBow" is false, then there is no need to commit the checkpoint, and the system won't automatically reboot. Bug: 193457319 Test: i.MX 8M Plus EVK, images flashed to board, misc and userdata partitions are wiped, checkpoint and metadata encryption are enabled for ext4 userdata partition. Signed-off-by: faqiang.zhu <faqiang.zhu@nxp.com> Change-Id: I4ad47a9504f1be0407f0fd196001a0e96919be33
743 lines
27 KiB
C++
743 lines
27 KiB
C++
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define LOG_TAG "Checkpoint"
|
|
#include "Checkpoint.h"
|
|
#include "VoldUtil.h"
|
|
#include "VolumeManager.h"
|
|
|
|
#include <fstream>
|
|
#include <list>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include <BootControlClient.h>
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/parseint.h>
|
|
#include <android-base/properties.h>
|
|
#include <android-base/unique_fd.h>
|
|
#include <cutils/android_reboot.h>
|
|
#include <fcntl.h>
|
|
#include <fs_mgr.h>
|
|
#include <linux/fs.h>
|
|
#include <mntent.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/statvfs.h>
|
|
#include <unistd.h>
|
|
|
|
using android::base::GetBoolProperty;
|
|
using android::base::GetUintProperty;
|
|
using android::base::SetProperty;
|
|
using android::binder::Status;
|
|
using android::fs_mgr::Fstab;
|
|
using android::fs_mgr::ReadFstabFromFile;
|
|
using android::hal::BootControlClient;
|
|
|
|
namespace android {
|
|
namespace vold {
|
|
|
|
namespace {
|
|
const std::string kMetadataCPFile = "/metadata/vold/checkpoint";
|
|
|
|
binder::Status error(const std::string& msg) {
|
|
PLOG(ERROR) << msg;
|
|
return binder::Status::fromServiceSpecificError(errno, String8(msg.c_str()));
|
|
}
|
|
|
|
binder::Status error(int error, const std::string& msg) {
|
|
LOG(ERROR) << msg;
|
|
return binder::Status::fromServiceSpecificError(error, String8(msg.c_str()));
|
|
}
|
|
|
|
bool setBowState(std::string const& block_device, std::string const& state) {
|
|
std::string bow_device = fs_mgr_find_bow_device(block_device);
|
|
if (bow_device.empty()) return false;
|
|
|
|
if (!android::base::WriteStringToFile(state, bow_device + "/bow/state")) {
|
|
PLOG(ERROR) << "Failed to write to file " << bow_device + "/bow/state";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Status cp_supportsCheckpoint(bool& result) {
|
|
result = false;
|
|
|
|
for (const auto& entry : fstab_default) {
|
|
if (entry.fs_mgr_flags.checkpoint_blk || entry.fs_mgr_flags.checkpoint_fs) {
|
|
result = true;
|
|
return Status::ok();
|
|
}
|
|
}
|
|
return Status::ok();
|
|
}
|
|
|
|
Status cp_supportsBlockCheckpoint(bool& result) {
|
|
result = false;
|
|
|
|
for (const auto& entry : fstab_default) {
|
|
if (entry.fs_mgr_flags.checkpoint_blk) {
|
|
result = true;
|
|
return Status::ok();
|
|
}
|
|
}
|
|
return Status::ok();
|
|
}
|
|
|
|
Status cp_supportsFileCheckpoint(bool& result) {
|
|
result = false;
|
|
|
|
for (const auto& entry : fstab_default) {
|
|
if (entry.fs_mgr_flags.checkpoint_fs) {
|
|
result = true;
|
|
return Status::ok();
|
|
}
|
|
}
|
|
return Status::ok();
|
|
}
|
|
|
|
Status cp_startCheckpoint(int retry) {
|
|
bool result;
|
|
if (!cp_supportsCheckpoint(result).isOk() || !result)
|
|
return error(ENOTSUP, "Checkpoints not supported");
|
|
|
|
if (retry < -1) return error(EINVAL, "Retry count must be more than -1");
|
|
std::string content = std::to_string(retry + 1);
|
|
if (retry == -1) {
|
|
auto module = BootControlClient::WaitForService();
|
|
if (module) {
|
|
std::string suffix = module->GetSuffix(module->GetCurrentSlot());
|
|
if (!suffix.empty()) content += " " + suffix;
|
|
}
|
|
}
|
|
if (!android::base::WriteStringToFile(content, kMetadataCPFile))
|
|
return error("Failed to write checkpoint file");
|
|
return Status::ok();
|
|
}
|
|
|
|
namespace {
|
|
|
|
volatile bool isCheckpointing = false;
|
|
volatile bool isBow = true;
|
|
|
|
volatile bool needsCheckpointWasCalled = false;
|
|
|
|
// Protects isCheckpointing, needsCheckpointWasCalled and code that makes decisions based on status
|
|
// of isCheckpointing
|
|
std::mutex isCheckpointingLock;
|
|
}
|
|
|
|
Status cp_commitChanges() {
|
|
std::lock_guard<std::mutex> lock(isCheckpointingLock);
|
|
|
|
if (!isCheckpointing) {
|
|
return Status::ok();
|
|
}
|
|
if (android::base::GetProperty("persist.vold.dont_commit_checkpoint", "0") == "1") {
|
|
LOG(WARNING)
|
|
<< "NOT COMMITTING CHECKPOINT BECAUSE persist.vold.dont_commit_checkpoint IS 1";
|
|
return Status::ok();
|
|
}
|
|
auto module = BootControlClient::WaitForService();
|
|
if (module) {
|
|
auto cr = module->MarkBootSuccessful();
|
|
if (!cr.success)
|
|
return error(EINVAL, "Error marking booted successfully: " + std::string(cr.errMsg));
|
|
LOG(INFO) << "Marked slot as booted successfully.";
|
|
// Clears the warm reset flag for next reboot.
|
|
if (!SetProperty("ota.warm_reset", "0")) {
|
|
LOG(WARNING) << "Failed to reset the warm reset flag";
|
|
}
|
|
} else {
|
|
LOG(ERROR) << "Failed to get BootControl HAL, not marking slot as successful.";
|
|
}
|
|
// Must take action for list of mounted checkpointed things here
|
|
// To do this, we walk the list of mounted file systems.
|
|
// But we also need to get the matching fstab entries to see
|
|
// the original flags
|
|
std::string err_str;
|
|
|
|
Fstab mounts;
|
|
if (!ReadFstabFromFile("/proc/mounts", &mounts)) {
|
|
return error(EINVAL, "Failed to get /proc/mounts");
|
|
}
|
|
|
|
// Walk mounted file systems
|
|
for (const auto& mount_rec : mounts) {
|
|
const auto fstab_rec = GetEntryForMountPoint(&fstab_default, mount_rec.mount_point);
|
|
if (!fstab_rec) continue;
|
|
|
|
if (fstab_rec->fs_mgr_flags.checkpoint_fs) {
|
|
if (fstab_rec->fs_type == "f2fs") {
|
|
std::string options = mount_rec.fs_options + ",checkpoint=enable";
|
|
if (mount(mount_rec.blk_device.c_str(), mount_rec.mount_point.c_str(), "none",
|
|
MS_REMOUNT | fstab_rec->flags, options.c_str())) {
|
|
return error(EINVAL, "Failed to remount");
|
|
}
|
|
}
|
|
} else if (fstab_rec->fs_mgr_flags.checkpoint_blk && isBow) {
|
|
if (!setBowState(mount_rec.blk_device, "2"))
|
|
return error(EINVAL, "Failed to set bow state");
|
|
}
|
|
}
|
|
SetProperty("vold.checkpoint_committed", "1");
|
|
LOG(INFO) << "Checkpoint has been committed.";
|
|
isCheckpointing = false;
|
|
if (!android::base::RemoveFileIfExists(kMetadataCPFile, &err_str))
|
|
return error(err_str.c_str());
|
|
|
|
return Status::ok();
|
|
}
|
|
|
|
namespace {
|
|
void abort_metadata_file() {
|
|
std::string oldContent, newContent;
|
|
int retry = 0;
|
|
struct stat st;
|
|
int result = stat(kMetadataCPFile.c_str(), &st);
|
|
|
|
// If the file doesn't exist, we aren't managing a checkpoint retry counter
|
|
if (result != 0) return;
|
|
if (!android::base::ReadFileToString(kMetadataCPFile, &oldContent)) {
|
|
PLOG(ERROR) << "Failed to read checkpoint file";
|
|
return;
|
|
}
|
|
std::string retryContent = oldContent.substr(0, oldContent.find_first_of(" "));
|
|
|
|
if (!android::base::ParseInt(retryContent, &retry)) {
|
|
PLOG(ERROR) << "Could not parse retry count";
|
|
return;
|
|
}
|
|
if (retry > 0) {
|
|
newContent = "0";
|
|
if (!android::base::WriteStringToFile(newContent, kMetadataCPFile))
|
|
PLOG(ERROR) << "Could not write checkpoint file";
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
void cp_abortChanges(const std::string& message, bool retry) {
|
|
if (!cp_needsCheckpoint()) return;
|
|
if (!retry) abort_metadata_file();
|
|
android_reboot(ANDROID_RB_RESTART2, 0, message.c_str());
|
|
}
|
|
|
|
bool cp_needsRollback() {
|
|
std::string content;
|
|
bool ret;
|
|
|
|
ret = android::base::ReadFileToString(kMetadataCPFile, &content);
|
|
if (ret) {
|
|
if (content == "0") return true;
|
|
if (content.substr(0, 3) == "-1 ") {
|
|
std::string oldSuffix = content.substr(3);
|
|
auto module = BootControlClient::WaitForService();
|
|
std::string newSuffix;
|
|
|
|
if (module) {
|
|
newSuffix = module->GetSuffix(module->GetCurrentSlot());
|
|
if (oldSuffix == newSuffix) return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool cp_needsCheckpoint() {
|
|
std::lock_guard<std::mutex> lock(isCheckpointingLock);
|
|
|
|
// Make sure we only return true during boot. See b/138952436 for discussion
|
|
if (needsCheckpointWasCalled) return isCheckpointing;
|
|
needsCheckpointWasCalled = true;
|
|
|
|
bool ret;
|
|
std::string content;
|
|
auto module = BootControlClient::WaitForService();
|
|
|
|
if (isCheckpointing) return isCheckpointing;
|
|
// In case of INVALID slot or other failures, we do not perform checkpoint.
|
|
if (module && !module->IsSlotMarkedSuccessful(module->GetCurrentSlot()).value_or(true)) {
|
|
isCheckpointing = true;
|
|
return true;
|
|
}
|
|
ret = android::base::ReadFileToString(kMetadataCPFile, &content);
|
|
if (ret) {
|
|
ret = content != "0";
|
|
isCheckpointing = ret;
|
|
return ret;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool cp_isCheckpointing() {
|
|
return isCheckpointing;
|
|
}
|
|
|
|
namespace {
|
|
const std::string kSleepTimeProp = "ro.sys.cp_msleeptime";
|
|
const uint32_t msleeptime_default = 1000; // 1 s
|
|
const uint32_t max_msleeptime = 3600000; // 1 h
|
|
|
|
const std::string kMinFreeBytesProp = "ro.sys.cp_min_free_bytes";
|
|
const uint64_t min_free_bytes_default = 100 * (1 << 20); // 100 MiB
|
|
|
|
const std::string kCommitOnFullProp = "ro.sys.cp_commit_on_full";
|
|
const bool commit_on_full_default = true;
|
|
|
|
static void cp_healthDaemon(std::string mnt_pnt, std::string blk_device, bool is_fs_cp) {
|
|
struct statvfs data;
|
|
uint32_t msleeptime = GetUintProperty(kSleepTimeProp, msleeptime_default, max_msleeptime);
|
|
uint64_t min_free_bytes =
|
|
GetUintProperty(kMinFreeBytesProp, min_free_bytes_default, (uint64_t)-1);
|
|
bool commit_on_full = GetBoolProperty(kCommitOnFullProp, commit_on_full_default);
|
|
|
|
struct timespec req;
|
|
req.tv_sec = msleeptime / 1000;
|
|
msleeptime %= 1000;
|
|
req.tv_nsec = msleeptime * 1000000;
|
|
while (isCheckpointing) {
|
|
uint64_t free_bytes = 0;
|
|
if (is_fs_cp) {
|
|
statvfs(mnt_pnt.c_str(), &data);
|
|
free_bytes = ((uint64_t) data.f_bavail) * data.f_frsize;
|
|
} else {
|
|
std::string bow_device = fs_mgr_find_bow_device(blk_device);
|
|
if (!bow_device.empty()) {
|
|
std::string content;
|
|
if (android::base::ReadFileToString(bow_device + "/bow/free", &content)) {
|
|
free_bytes = std::strtoull(content.c_str(), NULL, 10);
|
|
}
|
|
}
|
|
}
|
|
if (free_bytes < min_free_bytes) {
|
|
if (commit_on_full) {
|
|
LOG(INFO) << "Low space for checkpointing. Commiting changes";
|
|
cp_commitChanges();
|
|
break;
|
|
} else {
|
|
LOG(INFO) << "Low space for checkpointing. Rebooting";
|
|
cp_abortChanges("checkpoint,low_space", false);
|
|
break;
|
|
}
|
|
}
|
|
nanosleep(&req, NULL);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Status cp_prepareCheckpoint() {
|
|
// Log to notify CTS - see b/137924328 for context
|
|
LOG(INFO) << "cp_prepareCheckpoint called";
|
|
std::lock_guard<std::mutex> lock(isCheckpointingLock);
|
|
if (!isCheckpointing) {
|
|
return Status::ok();
|
|
}
|
|
|
|
Fstab mounts;
|
|
if (!ReadFstabFromFile("/proc/mounts", &mounts)) {
|
|
return error(EINVAL, "Failed to get /proc/mounts");
|
|
}
|
|
|
|
for (const auto& mount_rec : mounts) {
|
|
const auto fstab_rec = GetEntryForMountPoint(&fstab_default, mount_rec.mount_point);
|
|
if (!fstab_rec) continue;
|
|
|
|
if (fstab_rec->fs_mgr_flags.checkpoint_blk) {
|
|
android::base::unique_fd fd(
|
|
TEMP_FAILURE_RETRY(open(mount_rec.mount_point.c_str(), O_RDONLY | O_CLOEXEC)));
|
|
if (fd == -1) {
|
|
PLOG(ERROR) << "Failed to open mount point" << mount_rec.mount_point;
|
|
continue;
|
|
}
|
|
|
|
struct fstrim_range range = {};
|
|
range.len = ULLONG_MAX;
|
|
nsecs_t start = systemTime(SYSTEM_TIME_BOOTTIME);
|
|
if (ioctl(fd, FITRIM, &range)) {
|
|
PLOG(ERROR) << "Failed to trim " << mount_rec.mount_point;
|
|
continue;
|
|
}
|
|
nsecs_t time = systemTime(SYSTEM_TIME_BOOTTIME) - start;
|
|
LOG(INFO) << "Trimmed " << range.len << " bytes on " << mount_rec.mount_point << " in "
|
|
<< nanoseconds_to_milliseconds(time) << "ms for checkpoint";
|
|
|
|
isBow &= setBowState(mount_rec.blk_device, "1");
|
|
}
|
|
if (fstab_rec->fs_mgr_flags.checkpoint_blk || fstab_rec->fs_mgr_flags.checkpoint_fs) {
|
|
std::thread(cp_healthDaemon, std::string(mount_rec.mount_point),
|
|
std::string(mount_rec.blk_device),
|
|
fstab_rec->fs_mgr_flags.checkpoint_fs == 1)
|
|
.detach();
|
|
}
|
|
}
|
|
return Status::ok();
|
|
}
|
|
|
|
namespace {
|
|
const int kSectorSize = 512;
|
|
|
|
typedef uint64_t sector_t;
|
|
|
|
struct log_entry {
|
|
sector_t source; // in sectors of size kSectorSize
|
|
sector_t dest; // in sectors of size kSectorSize
|
|
uint32_t size; // in bytes
|
|
uint32_t checksum;
|
|
} __attribute__((packed));
|
|
|
|
struct log_sector_v1_0 {
|
|
uint32_t magic;
|
|
uint16_t header_version;
|
|
uint16_t header_size;
|
|
uint32_t block_size;
|
|
uint32_t count;
|
|
uint32_t sequence;
|
|
uint64_t sector0;
|
|
} __attribute__((packed));
|
|
|
|
// MAGIC is BOW in ascii
|
|
const int kMagic = 0x00574f42;
|
|
// Partially restored MAGIC is WOB in ascii
|
|
const int kPartialRestoreMagic = 0x00424f57;
|
|
|
|
void crc32(const void* data, size_t n_bytes, uint32_t* crc) {
|
|
static uint32_t table[0x100] = {
|
|
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535,
|
|
0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD,
|
|
0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D,
|
|
0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
|
|
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
|
|
0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
|
|
0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC,
|
|
0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
|
|
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB,
|
|
0xB6662D3D,
|
|
|
|
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5,
|
|
0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
|
|
0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED,
|
|
0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
|
|
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074,
|
|
0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC,
|
|
0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C,
|
|
0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
|
|
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B,
|
|
0xC0BA6CAD,
|
|
|
|
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,
|
|
0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D,
|
|
0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D,
|
|
0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
|
|
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4,
|
|
0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C,
|
|
0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C,
|
|
0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
|
|
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B,
|
|
0x5BDEAE1D,
|
|
|
|
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785,
|
|
0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D,
|
|
0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD,
|
|
0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
|
|
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354,
|
|
0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
|
|
0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C,
|
|
0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
|
|
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B,
|
|
0x2D02EF8D};
|
|
|
|
for (size_t i = 0; i < n_bytes; ++i) {
|
|
*crc ^= ((uint8_t*)data)[i];
|
|
*crc = table[(uint8_t)*crc] ^ *crc >> 8;
|
|
}
|
|
}
|
|
|
|
// A map of relocations.
|
|
// The map must be initialized so that relocations[0] = 0
|
|
// During restore, we replay the log records in reverse, copying from dest to
|
|
// source
|
|
// To validate, we must be able to read the 'dest' sectors as though they had
|
|
// been copied but without actually copying. This map represents how the sectors
|
|
// would have been moved. To read a sector s, find the index <= s and read
|
|
// relocations[index] + s - index
|
|
typedef std::map<sector_t, sector_t> Relocations;
|
|
|
|
void relocate(Relocations& relocations, sector_t dest, sector_t source, int count) {
|
|
// Find first one we're equal to or greater than
|
|
auto s = --relocations.upper_bound(source);
|
|
|
|
// Take slice
|
|
Relocations slice;
|
|
slice[dest] = source - s->first + s->second;
|
|
++s;
|
|
|
|
// Add rest of elements
|
|
for (; s != relocations.end() && s->first < source + count; ++s)
|
|
slice[dest - source + s->first] = s->second;
|
|
|
|
// Split range at end of dest
|
|
auto dest_end = --relocations.upper_bound(dest + count);
|
|
relocations[dest + count] = dest + count - dest_end->first + dest_end->second;
|
|
|
|
// Remove all elements in [dest, dest + count)
|
|
relocations.erase(relocations.lower_bound(dest), relocations.lower_bound(dest + count));
|
|
|
|
// Add new elements
|
|
relocations.insert(slice.begin(), slice.end());
|
|
}
|
|
|
|
// A map of sectors that have been written to.
|
|
// The final entry must always be False.
|
|
// When we restart the restore after an interruption, we must take care that
|
|
// when we copy from dest to source, that the block we copy to was not
|
|
// previously copied from.
|
|
// i e. A->B C->A; If we replay this sequence, we end up copying C->B
|
|
// We must save our partial result whenever we finish a page, or when we copy
|
|
// to a location that was copied from earlier (our source is an earlier dest)
|
|
typedef std::map<sector_t, bool> Used_Sectors;
|
|
|
|
bool checkCollision(Used_Sectors& used_sectors, sector_t start, sector_t end) {
|
|
auto second_overlap = used_sectors.upper_bound(start);
|
|
auto first_overlap = --second_overlap;
|
|
|
|
if (first_overlap->second) {
|
|
return true;
|
|
} else if (second_overlap != used_sectors.end() && second_overlap->first < end) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void markUsed(Used_Sectors& used_sectors, sector_t start, sector_t end) {
|
|
auto start_pos = used_sectors.insert_or_assign(start, true).first;
|
|
auto end_pos = used_sectors.insert_or_assign(end, false).first;
|
|
|
|
if (start_pos == used_sectors.begin() || !std::prev(start_pos)->second) {
|
|
start_pos++;
|
|
}
|
|
if (std::next(end_pos) != used_sectors.end() && !std::next(end_pos)->second) {
|
|
end_pos++;
|
|
}
|
|
if (start_pos->first < end_pos->first) {
|
|
used_sectors.erase(start_pos, end_pos);
|
|
}
|
|
}
|
|
|
|
// Restores the given log_entry's data from dest -> source
|
|
// If that entry is a log sector, set the magic to kPartialRestoreMagic and flush.
|
|
void restoreSector(int device_fd, Used_Sectors& used_sectors, std::vector<char>& ls_buffer,
|
|
log_entry* le, std::vector<char>& buffer) {
|
|
log_sector_v1_0& ls = *reinterpret_cast<log_sector_v1_0*>(&ls_buffer[0]);
|
|
uint32_t index = le - ((log_entry*)&ls_buffer[ls.header_size]);
|
|
int count = (le->size - 1) / kSectorSize + 1;
|
|
|
|
if (checkCollision(used_sectors, le->source, le->source + count)) {
|
|
fsync(device_fd);
|
|
lseek64(device_fd, 0, SEEK_SET);
|
|
ls.count = index + 1;
|
|
ls.magic = kPartialRestoreMagic;
|
|
write(device_fd, &ls_buffer[0], ls.block_size);
|
|
fsync(device_fd);
|
|
used_sectors.clear();
|
|
used_sectors[0] = false;
|
|
}
|
|
|
|
markUsed(used_sectors, le->dest, le->dest + count);
|
|
|
|
if (index == 0 && ls.sequence != 0) {
|
|
log_sector_v1_0* next = reinterpret_cast<log_sector_v1_0*>(&buffer[0]);
|
|
if (next->magic == kMagic) {
|
|
next->magic = kPartialRestoreMagic;
|
|
}
|
|
}
|
|
|
|
lseek64(device_fd, le->source * kSectorSize, SEEK_SET);
|
|
write(device_fd, &buffer[0], le->size);
|
|
|
|
if (index == 0) {
|
|
fsync(device_fd);
|
|
}
|
|
}
|
|
|
|
// Read from the device
|
|
// If we are validating, the read occurs as though the relocations had happened
|
|
std::vector<char> relocatedRead(int device_fd, Relocations const& relocations, bool validating,
|
|
sector_t sector, uint32_t size, uint32_t block_size) {
|
|
if (!validating) {
|
|
std::vector<char> buffer(size);
|
|
lseek64(device_fd, sector * kSectorSize, SEEK_SET);
|
|
read(device_fd, &buffer[0], size);
|
|
return buffer;
|
|
}
|
|
|
|
std::vector<char> buffer(size);
|
|
for (uint32_t i = 0; i < size; i += block_size, sector += block_size / kSectorSize) {
|
|
auto relocation = --relocations.upper_bound(sector);
|
|
lseek64(device_fd, (sector + relocation->second - relocation->first) * kSectorSize,
|
|
SEEK_SET);
|
|
read(device_fd, &buffer[i], block_size);
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Status cp_restoreCheckpoint(const std::string& blockDevice, int restore_limit) {
|
|
bool validating = true;
|
|
std::string action = "Validating";
|
|
int restore_count = 0;
|
|
|
|
for (;;) {
|
|
Relocations relocations;
|
|
relocations[0] = 0;
|
|
Status status = Status::ok();
|
|
|
|
LOG(INFO) << action << " checkpoint on " << blockDevice;
|
|
base::unique_fd device_fd(open(blockDevice.c_str(), O_RDWR | O_CLOEXEC));
|
|
if (device_fd < 0) return error("Cannot open " + blockDevice);
|
|
|
|
log_sector_v1_0 original_ls;
|
|
read(device_fd, reinterpret_cast<char*>(&original_ls), sizeof(original_ls));
|
|
if (original_ls.magic == kPartialRestoreMagic) {
|
|
validating = false;
|
|
action = "Restoring";
|
|
} else if (original_ls.magic != kMagic) {
|
|
return error(EINVAL, "No magic");
|
|
}
|
|
|
|
LOG(INFO) << action << " " << original_ls.sequence << " log sectors";
|
|
|
|
for (int sequence = original_ls.sequence; sequence >= 0 && status.isOk(); sequence--) {
|
|
auto ls_buffer = relocatedRead(device_fd, relocations, validating, 0,
|
|
original_ls.block_size, original_ls.block_size);
|
|
log_sector_v1_0& ls = *reinterpret_cast<log_sector_v1_0*>(&ls_buffer[0]);
|
|
|
|
Used_Sectors used_sectors;
|
|
used_sectors[0] = false;
|
|
|
|
if (ls.magic != kMagic && (ls.magic != kPartialRestoreMagic || validating)) {
|
|
status = error(EINVAL, "No magic");
|
|
break;
|
|
}
|
|
|
|
if (ls.block_size != original_ls.block_size) {
|
|
status = error(EINVAL, "Block size mismatch");
|
|
break;
|
|
}
|
|
|
|
if ((int)ls.sequence != sequence) {
|
|
status = error(EINVAL, "Expecting log sector " + std::to_string(sequence) +
|
|
" but got " + std::to_string(ls.sequence));
|
|
break;
|
|
}
|
|
|
|
LOG(INFO) << action << " from log sector " << ls.sequence;
|
|
for (log_entry* le =
|
|
reinterpret_cast<log_entry*>(&ls_buffer[ls.header_size]) + ls.count - 1;
|
|
le >= reinterpret_cast<log_entry*>(&ls_buffer[ls.header_size]); --le) {
|
|
// This is very noisy - limit to DEBUG only
|
|
LOG(VERBOSE) << action << " " << le->size << " bytes from sector " << le->dest
|
|
<< " to " << le->source << " with checksum " << std::hex
|
|
<< le->checksum;
|
|
|
|
auto buffer = relocatedRead(device_fd, relocations, validating, le->dest, le->size,
|
|
ls.block_size);
|
|
uint32_t checksum = le->source / (ls.block_size / kSectorSize);
|
|
for (size_t i = 0; i < le->size; i += ls.block_size) {
|
|
crc32(&buffer[i], ls.block_size, &checksum);
|
|
}
|
|
|
|
if (le->checksum && checksum != le->checksum) {
|
|
status = error(EINVAL, "Checksums don't match");
|
|
break;
|
|
}
|
|
|
|
if (validating) {
|
|
relocate(relocations, le->source, le->dest, (le->size - 1) / kSectorSize + 1);
|
|
} else {
|
|
restoreSector(device_fd, used_sectors, ls_buffer, le, buffer);
|
|
restore_count++;
|
|
if (restore_limit && restore_count >= restore_limit) {
|
|
status = error(EAGAIN, "Hit the test limit");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!status.isOk()) {
|
|
if (!validating) {
|
|
LOG(ERROR) << "Checkpoint restore failed even though checkpoint validation passed";
|
|
return status;
|
|
}
|
|
|
|
LOG(WARNING) << "Checkpoint validation failed - attempting to roll forward";
|
|
auto buffer = relocatedRead(device_fd, relocations, false, original_ls.sector0,
|
|
original_ls.block_size, original_ls.block_size);
|
|
lseek64(device_fd, 0, SEEK_SET);
|
|
write(device_fd, &buffer[0], original_ls.block_size);
|
|
return Status::ok();
|
|
}
|
|
|
|
if (!validating) break;
|
|
|
|
validating = false;
|
|
action = "Restoring";
|
|
}
|
|
|
|
return Status::ok();
|
|
}
|
|
|
|
Status cp_markBootAttempt() {
|
|
std::string oldContent, newContent;
|
|
int retry = 0;
|
|
struct stat st;
|
|
int result = stat(kMetadataCPFile.c_str(), &st);
|
|
|
|
// If the file doesn't exist, we aren't managing a checkpoint retry counter
|
|
if (result != 0) return Status::ok();
|
|
if (!android::base::ReadFileToString(kMetadataCPFile, &oldContent))
|
|
return error("Failed to read checkpoint file");
|
|
std::string retryContent = oldContent.substr(0, oldContent.find_first_of(" "));
|
|
|
|
if (!android::base::ParseInt(retryContent, &retry))
|
|
return error(EINVAL, "Could not parse retry count");
|
|
if (retry > 0) {
|
|
retry--;
|
|
|
|
newContent = std::to_string(retry);
|
|
if (!android::base::WriteStringToFile(newContent, kMetadataCPFile))
|
|
return error("Could not write checkpoint file");
|
|
}
|
|
return Status::ok();
|
|
}
|
|
|
|
void cp_resetCheckpoint() {
|
|
std::lock_guard<std::mutex> lock(isCheckpointingLock);
|
|
needsCheckpointWasCalled = false;
|
|
}
|
|
|
|
} // namespace vold
|
|
} // namespace android
|