libsnapshot: Integrate userspace snapshots APIs

dm-user block device will be the snapshot device; thus, no
more explicit call to MapSnapshot(). Additionally, block device
name for dm-user will be the snapshot name so that mount works
seamlessly.

API's to query the snapshot status, merge progress has been
integrated. Since daemon requires base device for merge, we pass
additional parameter during initialization.

Add a new virtual a/b property flag to enable/disable
user-snapshots feature. Propagate this flag to init layer
for first stage mount during boot process.

Some minor cleanup and renaming of variables.

Bug: 193863443
Test: 1: Full OTA on CF and pixel and verify the merge completion.
Tested merge-resume path by rebooting device during merge.
2: Incremental OTA on CF and pixel

Signed-off-by: Akilesh Kailash <akailash@google.com>
Change-Id: I5088f40a55807946cd044b3987678ead3696d996
This commit is contained in:
Akilesh Kailash 2021-10-03 09:41:13 +00:00
parent adae766986
commit 3b874456fc
24 changed files with 764 additions and 229 deletions

View file

@ -194,6 +194,9 @@ message SnapshotUpdateStatus {
// Source build fingerprint.
string source_build_fingerprint = 8;
// user-space snapshots
bool userspace_snapshots = 9;
}
// Next: 10

View file

@ -35,6 +35,7 @@ class MockSnapshotManager : public ISnapshotManager {
(override));
MOCK_METHOD(UpdateState, GetUpdateState, (double* progress), (override));
MOCK_METHOD(bool, UpdateUsesCompression, (), (override));
MOCK_METHOD(bool, UpdateUsesUserSnapshots, (), (override));
MOCK_METHOD(Return, CreateUpdateSnapshots,
(const chromeos_update_engine::DeltaArchiveManifest& manifest), (override));
MOCK_METHOD(bool, MapUpdateSnapshot,

View file

@ -193,6 +193,9 @@ class ISnapshotManager {
// UpdateState is None, or no snapshots have been created.
virtual bool UpdateUsesCompression() = 0;
// Returns true if userspace snapshots is enabled for the current update.
virtual bool UpdateUsesUserSnapshots() = 0;
// Create necessary COW device / files for OTA clients. New logical partitions will be added to
// group "cow" in target_metadata. Regions of partitions of current_metadata will be
// "write-protected" and snapshotted.
@ -352,6 +355,7 @@ class SnapshotManager final : public ISnapshotManager {
const std::function<bool()>& before_cancel = {}) override;
UpdateState GetUpdateState(double* progress = nullptr) override;
bool UpdateUsesCompression() override;
bool UpdateUsesUserSnapshots() override;
Return CreateUpdateSnapshots(const DeltaArchiveManifest& manifest) override;
bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params,
std::string* snapshot_path) override;
@ -387,6 +391,11 @@ class SnapshotManager final : public ISnapshotManager {
// first-stage to decide whether to launch snapuserd.
bool IsSnapuserdRequired();
enum class SnapshotDriver {
DM_SNAPSHOT,
DM_USER,
};
private:
FRIEND_TEST(SnapshotTest, CleanFirstStageMount);
FRIEND_TEST(SnapshotTest, CreateSnapshot);
@ -456,6 +465,8 @@ class SnapshotManager final : public ISnapshotManager {
};
static std::unique_ptr<LockedFile> OpenFile(const std::string& file, int lock_flags);
SnapshotDriver GetSnapshotDriver(LockedFile* lock);
// Create a new snapshot record. This creates the backing COW store and
// persists information needed to map the device. The device can be mapped
// with MapSnapshot().
@ -491,8 +502,8 @@ class SnapshotManager final : public ISnapshotManager {
// Create a dm-user device for a given snapshot.
bool MapDmUserCow(LockedFile* lock, const std::string& name, const std::string& cow_file,
const std::string& base_device, const std::chrono::milliseconds& timeout_ms,
std::string* path);
const std::string& base_device, const std::string& base_path_merge,
const std::chrono::milliseconds& timeout_ms, std::string* path);
// Map the source device used for dm-user.
bool MapSourceDevice(LockedFile* lock, const std::string& name,
@ -591,7 +602,8 @@ class SnapshotManager final : public ISnapshotManager {
// Internal callback for when merging is complete.
bool OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
const SnapshotStatus& status);
bool CollapseSnapshotDevice(const std::string& name, const SnapshotStatus& status);
bool CollapseSnapshotDevice(LockedFile* lock, const std::string& name,
const SnapshotStatus& status);
struct MergeResult {
explicit MergeResult(UpdateState state,
@ -689,7 +701,10 @@ class SnapshotManager final : public ISnapshotManager {
bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name);
// Unmap a dm-user device through snapuserd.
bool UnmapDmUserDevice(const std::string& snapshot_name);
bool UnmapDmUserDevice(const std::string& dm_user_name);
// Unmap a dm-user device for user space snapshots
bool UnmapUserspaceSnapshotDevice(LockedFile* lock, const std::string& snapshot_name);
// If there isn't a previous update, return true. |needs_merge| is set to false.
// If there is a previous update but the device has not boot into it, tries to cancel the
@ -778,6 +793,8 @@ class SnapshotManager final : public ISnapshotManager {
// Helper of UpdateUsesCompression
bool UpdateUsesCompression(LockedFile* lock);
// Helper of UpdateUsesUsersnapshots
bool UpdateUsesUserSnapshots(LockedFile* lock);
// Wrapper around libdm, with diagnostics.
bool DeleteDeviceIfExists(const std::string& name,
@ -792,6 +809,7 @@ class SnapshotManager final : public ISnapshotManager {
std::function<bool(const std::string&)> uevent_regen_callback_;
std::unique_ptr<SnapuserdClient> snapuserd_client_;
std::unique_ptr<LpMetadata> old_partition_metadata_;
std::optional<bool> is_snapshot_userspace_;
};
} // namespace snapshot

View file

@ -35,6 +35,7 @@ class SnapshotManagerStub : public ISnapshotManager {
const std::function<bool()>& before_cancel = {}) override;
UpdateState GetUpdateState(double* progress = nullptr) override;
bool UpdateUsesCompression() override;
bool UpdateUsesUserSnapshots() override;
Return CreateUpdateSnapshots(
const chromeos_update_engine::DeltaArchiveManifest& manifest) override;
bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params,

View file

@ -95,6 +95,7 @@ std::unique_ptr<SnapshotManager> SnapshotManager::New(IDeviceInfo* info) {
if (!info) {
info = new DeviceInfo();
}
return std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
}
@ -121,8 +122,34 @@ static std::string GetCowName(const std::string& snapshot_name) {
return snapshot_name + "-cow";
}
static std::string GetDmUserCowName(const std::string& snapshot_name) {
return snapshot_name + "-user-cow";
SnapshotManager::SnapshotDriver SnapshotManager::GetSnapshotDriver(LockedFile* lock) {
if (UpdateUsesUserSnapshots(lock)) {
return SnapshotManager::SnapshotDriver::DM_USER;
} else {
return SnapshotManager::SnapshotDriver::DM_SNAPSHOT;
}
}
static std::string GetDmUserCowName(const std::string& snapshot_name,
SnapshotManager::SnapshotDriver driver) {
// dm-user block device will act as a snapshot device. We identify it with
// the same partition name so that when partitions can be mounted off
// dm-user.
switch (driver) {
case SnapshotManager::SnapshotDriver::DM_USER: {
return snapshot_name;
}
case SnapshotManager::SnapshotDriver::DM_SNAPSHOT: {
return snapshot_name + "-user-cow";
}
default: {
LOG(ERROR) << "Invalid snapshot driver";
return "";
}
}
}
static std::string GetCowImageDeviceName(const std::string& snapshot_name) {
@ -398,9 +425,33 @@ Return SnapshotManager::CreateCowImage(LockedFile* lock, const std::string& name
bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name,
const std::string& cow_file, const std::string& base_device,
const std::string& base_path_merge,
const std::chrono::milliseconds& timeout_ms, std::string* path) {
CHECK(lock);
if (UpdateUsesUserSnapshots(lock)) {
SnapshotStatus status;
if (!ReadSnapshotStatus(lock, name, &status)) {
LOG(ERROR) << "MapDmUserCow: ReadSnapshotStatus failed...";
return false;
}
if (status.state() == SnapshotState::NONE ||
status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Should not create a snapshot device for " << name
<< " after merging has completed.";
return false;
}
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
if (update_status.state() == UpdateState::MergeCompleted ||
update_status.state() == UpdateState::MergeNeedsReboot) {
LOG(ERROR) << "Should not create a snapshot device for " << name
<< " after global merging has completed.";
return false;
}
}
// Use an extra decoration for first-stage init, so we can transition
// to a new table entry in second-stage.
std::string misc_name = name;
@ -412,18 +463,41 @@ bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name,
return false;
}
uint64_t base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device);
if (base_sectors == 0) {
LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd";
return false;
uint64_t base_sectors = 0;
if (!UpdateUsesUserSnapshots(lock)) {
base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device);
if (base_sectors == 0) {
LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd";
return false;
}
} else {
// For userspace snapshots, the size of the base device is taken as the
// size of the dm-user block device. Since there is no pseudo mapping
// created in the daemon, we no longer need to rely on the daemon for
// sizing the dm-user block device.
unique_fd fd(TEMP_FAILURE_RETRY(open(base_path_merge.c_str(), O_RDONLY | O_CLOEXEC)));
if (fd < 0) {
LOG(ERROR) << "Cannot open block device: " << base_path_merge;
return false;
}
uint64_t dev_sz = get_block_device_size(fd.get());
if (!dev_sz) {
LOG(ERROR) << "Failed to find block device size: " << base_path_merge;
return false;
}
base_sectors = dev_sz >> 9;
}
DmTable table;
table.Emplace<DmTargetUser>(0, base_sectors, misc_name);
if (!dm_.CreateDevice(name, table, path, timeout_ms)) {
LOG(ERROR) << " dm-user: CreateDevice failed... ";
return false;
}
if (!WaitForDevice(*path, timeout_ms)) {
LOG(ERROR) << " dm-user: timeout: Failed to create block device for: " << name;
return false;
}
@ -432,6 +506,15 @@ bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name,
return false;
}
if (UpdateUsesUserSnapshots(lock)) {
// Now that the dm-user device is created, initialize the daemon and
// spin up the worker threads.
if (!snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device, base_path_merge)) {
LOG(ERROR) << "InitDmUserCow failed";
return false;
}
}
return snapuserd_client_->AttachDmUser(misc_name);
}
@ -698,13 +781,15 @@ bool SnapshotManager::InitiateMerge() {
DmTargetSnapshot::Status initial_target_values = {};
for (const auto& snapshot : snapshots) {
DmTargetSnapshot::Status current_status;
if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) {
return false;
if (!UpdateUsesUserSnapshots(lock.get())) {
DmTargetSnapshot::Status current_status;
if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) {
return false;
}
initial_target_values.sectors_allocated += current_status.sectors_allocated;
initial_target_values.total_sectors += current_status.total_sectors;
initial_target_values.metadata_sectors += current_status.metadata_sectors;
}
initial_target_values.sectors_allocated += current_status.sectors_allocated;
initial_target_values.total_sectors += current_status.total_sectors;
initial_target_values.metadata_sectors += current_status.metadata_sectors;
SnapshotStatus snapshot_status;
if (!ReadSnapshotStatus(lock.get(), snapshot, &snapshot_status)) {
@ -719,11 +804,14 @@ bool SnapshotManager::InitiateMerge() {
SnapshotUpdateStatus initial_status = ReadSnapshotUpdateStatus(lock.get());
initial_status.set_state(UpdateState::Merging);
initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
initial_status.set_total_sectors(initial_target_values.total_sectors);
initial_status.set_metadata_sectors(initial_target_values.metadata_sectors);
initial_status.set_compression_enabled(compression_enabled);
if (!UpdateUsesUserSnapshots(lock.get())) {
initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
initial_status.set_total_sectors(initial_target_values.total_sectors);
initial_status.set_metadata_sectors(initial_target_values.metadata_sectors);
}
// If any partitions shrunk, we need to merge them before we merge any other
// partitions (see b/177935716). Otherwise, a merge from another partition
// may overwrite the source block of a copy operation.
@ -777,20 +865,36 @@ MergeFailureCode SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const
<< " has unexpected state: " << SnapshotState_Name(status.state());
}
// After this, we return true because we technically did switch to a merge
// target. Everything else we do here is just informational.
if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
return code;
if (UpdateUsesUserSnapshots(lock)) {
if (EnsureSnapuserdConnected()) {
// This is the point where we inform the daemon to initiate/resume
// the merge
if (!snapuserd_client_->InitiateMerge(name)) {
return MergeFailureCode::UnknownTable;
}
} else {
LOG(ERROR) << "Failed to connect to snapuserd daemon to initiate merge";
return MergeFailureCode::UnknownTable;
}
} else {
// After this, we return true because we technically did switch to a merge
// target. Everything else we do here is just informational.
if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
return code;
}
}
status.set_state(SnapshotState::MERGING);
DmTargetSnapshot::Status dm_status;
if (!QuerySnapshotStatus(name, nullptr, &dm_status)) {
LOG(ERROR) << "Could not query merge status for snapshot: " << name;
if (!UpdateUsesUserSnapshots(lock)) {
DmTargetSnapshot::Status dm_status;
if (!QuerySnapshotStatus(name, nullptr, &dm_status)) {
LOG(ERROR) << "Could not query merge status for snapshot: " << name;
}
status.set_sectors_allocated(dm_status.sectors_allocated);
status.set_metadata_sectors(dm_status.metadata_sectors);
}
status.set_sectors_allocated(dm_status.sectors_allocated);
status.set_metadata_sectors(dm_status.metadata_sectors);
if (!WriteSnapshotStatus(lock, status)) {
LOG(ERROR) << "Could not update status file for snapshot: " << name;
}
@ -856,9 +960,15 @@ bool SnapshotManager::IsSnapshotDevice(const std::string& dm_name, TargetInfo* t
return false;
}
auto type = DeviceMapper::GetTargetType(snap_target.spec);
if (type != "snapshot" && type != "snapshot-merge") {
return false;
// If this is not a user-snapshot device then it should either
// be a dm-snapshot or dm-snapshot-merge target
if (type != "user") {
if (type != "snapshot" && type != "snapshot-merge") {
return false;
}
}
if (target) {
*target = std::move(snap_target);
}
@ -1094,34 +1204,86 @@ auto SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string&
DCHECK((current_metadata = ReadCurrentMetadata()) &&
GetMetadataPartitionState(*current_metadata, name) == MetadataPartitionState::Updated);
std::string target_type;
DmTargetSnapshot::Status status;
if (!QuerySnapshotStatus(name, &target_type, &status)) {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
}
if (target_type == "snapshot" &&
DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
update_status.merge_phase() == MergePhase::FIRST_PHASE) {
// The snapshot is not being merged because it's in the wrong phase.
return MergeResult(UpdateState::None);
}
if (target_type != "snapshot-merge") {
// We can get here if we failed to rewrite the target type in
// InitiateMerge(). If we failed to create the target in first-stage
// init, boot would not succeed.
LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
if (UpdateUsesUserSnapshots(lock)) {
std::string merge_status;
if (EnsureSnapuserdConnected()) {
// Query the snapshot status from the daemon
merge_status = snapuserd_client_->QuerySnapshotStatus(name);
} else {
MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
}
if (merge_status == "snapshot-merge-failed") {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
}
// This is the case when device reboots during merge. Once the device boots,
// snapuserd daemon will not resume merge immediately in first stage init.
// This is slightly different as compared to dm-snapshot-merge; In this
// case, metadata file will have "MERGING" state whereas the daemon will be
// waiting to resume the merge. Thus, we resume the merge at this point.
if (merge_status == "snapshot" && snapshot_status.state() == SnapshotState::MERGING) {
if (!snapuserd_client_->InitiateMerge(name)) {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
}
return MergeResult(UpdateState::Merging);
}
if (merge_status == "snapshot" &&
DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
update_status.merge_phase() == MergePhase::FIRST_PHASE) {
// The snapshot is not being merged because it's in the wrong phase.
return MergeResult(UpdateState::None);
}
if (merge_status == "snapshot-merge") {
if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Snapshot " << name
<< " is merging after being marked merge-complete.";
return MergeResult(UpdateState::MergeFailed,
MergeFailureCode::UnmergedSectorsAfterCompletion);
}
return MergeResult(UpdateState::Merging);
}
if (merge_status != "snapshot-merge-complete") {
LOG(ERROR) << "Snapshot " << name << " has incorrect status: " << merge_status;
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
}
} else {
// dm-snapshot in the kernel
std::string target_type;
DmTargetSnapshot::Status status;
if (!QuerySnapshotStatus(name, &target_type, &status)) {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
}
if (target_type == "snapshot" &&
DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
update_status.merge_phase() == MergePhase::FIRST_PHASE) {
// The snapshot is not being merged because it's in the wrong phase.
return MergeResult(UpdateState::None);
}
if (target_type != "snapshot-merge") {
// We can get here if we failed to rewrite the target type in
// InitiateMerge(). If we failed to create the target in first-stage
// init, boot would not succeed.
LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
}
// These two values are equal when merging is complete.
if (status.sectors_allocated != status.metadata_sectors) {
if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Snapshot " << name
<< " is merging after being marked merge-complete.";
return MergeResult(UpdateState::MergeFailed,
MergeFailureCode::UnmergedSectorsAfterCompletion);
}
return MergeResult(UpdateState::Merging);
}
}
// These two values are equal when merging is complete.
if (status.sectors_allocated != status.metadata_sectors) {
if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete.";
return MergeResult(UpdateState::MergeFailed,
MergeFailureCode::UnmergedSectorsAfterCompletion);
}
return MergeResult(UpdateState::Merging);
}
// Merge is complete at this point
auto code = CheckMergeConsistency(lock, name, snapshot_status);
if (code != MergeFailureCode::Ok) {
@ -1311,30 +1473,40 @@ void SnapshotManager::AcknowledgeMergeFailure(MergeFailureCode failure_code) {
bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
const SnapshotStatus& status) {
if (IsSnapshotDevice(name)) {
// We are extra-cautious here, to avoid deleting the wrong table.
std::string target_type;
DmTargetSnapshot::Status dm_status;
if (!QuerySnapshotStatus(name, &target_type, &dm_status)) {
return false;
if (!UpdateUsesUserSnapshots(lock)) {
if (IsSnapshotDevice(name)) {
// We are extra-cautious here, to avoid deleting the wrong table.
std::string target_type;
DmTargetSnapshot::Status dm_status;
if (!QuerySnapshotStatus(name, &target_type, &dm_status)) {
return false;
}
if (target_type != "snapshot-merge") {
LOG(ERROR) << "Unexpected target type " << target_type
<< " for snapshot device: " << name;
return false;
}
if (dm_status.sectors_allocated != dm_status.metadata_sectors) {
LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name;
return false;
}
if (!CollapseSnapshotDevice(lock, name, status)) {
LOG(ERROR) << "Unable to collapse snapshot: " << name;
return false;
}
}
if (target_type != "snapshot-merge") {
LOG(ERROR) << "Unexpected target type " << target_type
<< " for snapshot device: " << name;
return false;
}
if (dm_status.sectors_allocated != dm_status.metadata_sectors) {
LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name;
return false;
}
if (!CollapseSnapshotDevice(name, status)) {
} else {
// Just collapse the device - no need to query again as we just did
// prior to calling this function
if (!CollapseSnapshotDevice(lock, name, status)) {
LOG(ERROR) << "Unable to collapse snapshot: " << name;
return false;
}
// Note that collapsing is implicitly an Unmap, so we don't need to
// unmap the snapshot.
}
// Note that collapsing is implicitly an Unmap, so we don't need to
// unmap the snapshot.
if (!DeleteSnapshot(lock, name)) {
LOG(ERROR) << "Could not delete snapshot: " << name;
return false;
@ -1342,23 +1514,26 @@ bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::strin
return true;
}
bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
bool SnapshotManager::CollapseSnapshotDevice(LockedFile* lock, const std::string& name,
const SnapshotStatus& status) {
// Verify we have a snapshot-merge device.
DeviceMapper::TargetInfo target;
if (!GetSingleTarget(name, TableQuery::Table, &target)) {
return false;
}
if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") {
// This should be impossible, it was checked earlier.
LOG(ERROR) << "Snapshot device has invalid target type: " << name;
return false;
}
if (!UpdateUsesUserSnapshots(lock)) {
// Verify we have a snapshot-merge device.
DeviceMapper::TargetInfo target;
if (!GetSingleTarget(name, TableQuery::Table, &target)) {
return false;
}
if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") {
// This should be impossible, it was checked earlier.
LOG(ERROR) << "Snapshot device has invalid target type: " << name;
return false;
}
std::string base_device, cow_device;
if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) {
LOG(ERROR) << "Could not parse snapshot device " << name << " parameters: " << target.data;
return false;
std::string base_device, cow_device;
if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) {
LOG(ERROR) << "Could not parse snapshot device " << name
<< " parameters: " << target.data;
return false;
}
}
uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize;
@ -1386,14 +1561,32 @@ bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
return false;
}
// Attempt to delete the snapshot device if one still exists. Nothing
// should be depending on the device, and device-mapper should have
// flushed remaining I/O. We could in theory replace with dm-zero (or
// re-use the table above), but for now it's better to know why this
// would fail.
if (status.compression_enabled()) {
UnmapDmUserDevice(name);
if (!UpdateUsesUserSnapshots(lock)) {
// Attempt to delete the snapshot device if one still exists. Nothing
// should be depending on the device, and device-mapper should have
// flushed remaining I/O. We could in theory replace with dm-zero (or
// re-use the table above), but for now it's better to know why this
// would fail.
//
// Furthermore, we should not be trying to unmap for userspace snapshot
// as unmap will fail since dm-user itself was a snapshot device prior
// to switching of tables. Unmap will fail as the device will be mounted
// by system partitions
if (status.compression_enabled()) {
auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
UnmapDmUserDevice(dm_user_name);
}
}
// We can't delete base device immediately as daemon holds a reference.
// Make sure we wait for all the worker threads to terminate and release
// the reference
if (UpdateUsesUserSnapshots(lock) && EnsureSnapuserdConnected()) {
if (!snapuserd_client_->WaitForDeviceDelete(name)) {
LOG(ERROR) << "Failed to wait for " << name << " control device to delete";
}
}
auto base_name = GetBaseDeviceName(name);
if (!DeleteDeviceIfExists(base_name)) {
LOG(ERROR) << "Unable to delete base device for snapshot: " << base_name;
@ -1464,10 +1657,15 @@ bool SnapshotManager::PerformInitTransition(InitTransition transition,
return false;
}
if (UpdateUsesUserSnapshots(lock.get()) && transition == InitTransition::SELINUX_DETACH) {
snapuserd_argv->emplace_back("-user_snapshot");
}
size_t num_cows = 0;
size_t ok_cows = 0;
for (const auto& snapshot : snapshots) {
std::string user_cow_name = GetDmUserCowName(snapshot);
std::string user_cow_name = GetDmUserCowName(snapshot, GetSnapshotDriver(lock.get()));
if (dm_.GetState(user_cow_name) == DmDeviceState::INVALID) {
continue;
}
@ -1513,6 +1711,12 @@ bool SnapshotManager::PerformInitTransition(InitTransition transition,
continue;
}
std::string base_path_merge;
if (!dm_.GetDmDevicePathByName(GetBaseDeviceName(snapshot), &base_path_merge)) {
LOG(ERROR) << "Could not get device path for " << GetSourceDeviceName(snapshot);
continue;
}
std::string cow_image_name = GetMappedCowDeviceName(snapshot, snapshot_status);
std::string cow_image_device;
@ -1529,8 +1733,14 @@ bool SnapshotManager::PerformInitTransition(InitTransition transition,
}
if (transition == InitTransition::SELINUX_DETACH) {
auto message = misc_name + "," + cow_image_device + "," + source_device;
snapuserd_argv->emplace_back(std::move(message));
if (!UpdateUsesUserSnapshots(lock.get())) {
auto message = misc_name + "," + cow_image_device + "," + source_device;
snapuserd_argv->emplace_back(std::move(message));
} else {
auto message = misc_name + "," + cow_image_device + "," + source_device + "," +
base_path_merge;
snapuserd_argv->emplace_back(std::move(message));
}
// Do not attempt to connect to the new snapuserd yet, it hasn't
// been started. We do however want to wait for the misc device
@ -1539,8 +1749,15 @@ bool SnapshotManager::PerformInitTransition(InitTransition transition,
continue;
}
uint64_t base_sectors =
snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device);
uint64_t base_sectors;
if (!UpdateUsesUserSnapshots(lock.get())) {
base_sectors =
snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device);
} else {
base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_image_device,
source_device, base_path_merge);
}
if (base_sectors == 0) {
// Unrecoverable as metadata reads from cow device failed
LOG(FATAL) << "Failed to retrieve base_sectors from Snapuserd";
@ -1775,30 +1992,36 @@ UpdateState SnapshotManager::GetUpdateState(double* progress) {
return state;
}
// Sum all the snapshot states as if the system consists of a single huge
// snapshots device, then compute the merge completion percentage of that
// device.
std::vector<std::string> snapshots;
if (!ListSnapshots(lock.get(), &snapshots)) {
LOG(ERROR) << "Could not list snapshots";
return state;
if (!UpdateUsesUserSnapshots(lock.get())) {
// Sum all the snapshot states as if the system consists of a single huge
// snapshots device, then compute the merge completion percentage of that
// device.
std::vector<std::string> snapshots;
if (!ListSnapshots(lock.get(), &snapshots)) {
LOG(ERROR) << "Could not list snapshots";
return state;
}
DmTargetSnapshot::Status fake_snapshots_status = {};
for (const auto& snapshot : snapshots) {
DmTargetSnapshot::Status current_status;
if (!IsSnapshotDevice(snapshot)) continue;
if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) continue;
fake_snapshots_status.sectors_allocated += current_status.sectors_allocated;
fake_snapshots_status.total_sectors += current_status.total_sectors;
fake_snapshots_status.metadata_sectors += current_status.metadata_sectors;
}
*progress = DmTargetSnapshot::MergePercent(fake_snapshots_status,
update_status.sectors_allocated());
} else {
if (EnsureSnapuserdConnected()) {
*progress = snapuserd_client_->GetMergePercent();
}
}
DmTargetSnapshot::Status fake_snapshots_status = {};
for (const auto& snapshot : snapshots) {
DmTargetSnapshot::Status current_status;
if (!IsSnapshotDevice(snapshot)) continue;
if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) continue;
fake_snapshots_status.sectors_allocated += current_status.sectors_allocated;
fake_snapshots_status.total_sectors += current_status.total_sectors;
fake_snapshots_status.metadata_sectors += current_status.metadata_sectors;
}
*progress = DmTargetSnapshot::MergePercent(fake_snapshots_status,
update_status.sectors_allocated());
return state;
}
@ -1813,6 +2036,38 @@ bool SnapshotManager::UpdateUsesCompression(LockedFile* lock) {
return update_status.compression_enabled();
}
bool SnapshotManager::UpdateUsesUserSnapshots() {
// This and the following function is constantly
// invoked during snapshot merge. We want to avoid
// constantly reading from disk. Hence, store this
// value in memory.
//
// Furthermore, this value in the disk is set
// only when OTA is applied and doesn't change
// during merge phase. Hence, once we know that
// the value is read from disk the very first time,
// it is safe to read successive checks from memory.
if (is_snapshot_userspace_.has_value()) {
return is_snapshot_userspace_.value();
}
auto lock = LockShared();
if (!lock) return false;
return UpdateUsesUserSnapshots(lock.get());
}
bool SnapshotManager::UpdateUsesUserSnapshots(LockedFile* lock) {
// See UpdateUsesUserSnapshots()
if (is_snapshot_userspace_.has_value()) {
return is_snapshot_userspace_.value();
}
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
is_snapshot_userspace_ = update_status.userspace_snapshots();
return is_snapshot_userspace_.value();
}
bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots,
const std::string& suffix) {
CHECK(lock);
@ -2040,6 +2295,16 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
paths->target_device = base_path;
}
auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
if (remaining_time.count() < 0) {
return false;
}
// Wait for the base device to appear
if (!WaitForDevice(base_path, remaining_time)) {
return false;
}
if (!live_snapshot_status.has_value()) {
created_devices.Release();
return true;
@ -2053,7 +2318,7 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
return false;
}
auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
remaining_time = GetRemainingTime(params.timeout_ms, begin);
if (remaining_time.count() < 0) return false;
std::string cow_name;
@ -2109,10 +2374,10 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
return false;
}
auto name = GetDmUserCowName(params.GetPartitionName());
auto name = GetDmUserCowName(params.GetPartitionName(), GetSnapshotDriver(lock));
std::string new_cow_device;
if (!MapDmUserCow(lock, name, cow_path, source_device_path, remaining_time,
if (!MapDmUserCow(lock, name, cow_path, source_device_path, base_path, remaining_time,
&new_cow_device)) {
LOG(ERROR) << "Could not map dm-user device for partition "
<< params.GetPartitionName();
@ -2126,21 +2391,37 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
cow_device = new_cow_device;
}
std::string path;
if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time,
&path)) {
LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName();
return false;
}
// No need to add params.GetPartitionName() to created_devices since it is immediately released.
// For userspace snapshots, dm-user block device itself will act as a
// snapshot device. There is one subtle difference - MapSnapshot will create
// either snapshot target or snapshot-merge target based on the underlying
// state of the snapshot device. If snapshot-merge target is created, merge
// will immediately start in the kernel.
//
// This is no longer true with respect to userspace snapshots. When dm-user
// block device is created, we just have the snapshots ready but daemon in
// the user-space will not start the merge. We have to explicitly inform the
// daemon to resume the merge. Check ProcessUpdateState() call stack.
if (!UpdateUsesUserSnapshots(lock)) {
std::string path;
if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time,
&path)) {
LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName();
return false;
}
// No need to add params.GetPartitionName() to created_devices since it is immediately
// released.
if (paths) {
paths->snapshot_device = path;
if (paths) {
paths->snapshot_device = path;
}
LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path;
} else {
LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at "
<< cow_device;
}
created_devices.Release();
LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path;
return true;
}
@ -2148,8 +2429,10 @@ bool SnapshotManager::UnmapPartitionWithSnapshot(LockedFile* lock,
const std::string& target_partition_name) {
CHECK(lock);
if (!UnmapSnapshot(lock, target_partition_name)) {
return false;
if (!UpdateUsesUserSnapshots(lock)) {
if (!UnmapSnapshot(lock, target_partition_name)) {
return false;
}
}
if (!UnmapCowDevices(lock, target_partition_name)) {
@ -2247,8 +2530,17 @@ bool SnapshotManager::UnmapCowDevices(LockedFile* lock, const std::string& name)
CHECK(lock);
if (!EnsureImageManager()) return false;
if (UpdateUsesCompression(lock) && !UnmapDmUserDevice(name)) {
return false;
if (UpdateUsesCompression(lock)) {
if (UpdateUsesUserSnapshots(lock)) {
if (!UnmapUserspaceSnapshotDevice(lock, name)) {
return false;
}
} else {
auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
if (!UnmapDmUserDevice(dm_user_name)) {
return false;
}
}
}
if (!DeleteDeviceIfExists(GetCowName(name), 4000ms)) {
@ -2264,8 +2556,7 @@ bool SnapshotManager::UnmapCowDevices(LockedFile* lock, const std::string& name)
return true;
}
bool SnapshotManager::UnmapDmUserDevice(const std::string& snapshot_name) {
auto dm_user_name = GetDmUserCowName(snapshot_name);
bool SnapshotManager::UnmapDmUserDevice(const std::string& dm_user_name) {
if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) {
return true;
}
@ -2291,6 +2582,46 @@ bool SnapshotManager::UnmapDmUserDevice(const std::string& snapshot_name) {
return true;
}
bool SnapshotManager::UnmapUserspaceSnapshotDevice(LockedFile* lock,
const std::string& snapshot_name) {
auto dm_user_name = GetDmUserCowName(snapshot_name, GetSnapshotDriver(lock));
if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) {
return true;
}
CHECK(lock);
SnapshotStatus snapshot_status;
if (!ReadSnapshotStatus(lock, snapshot_name, &snapshot_status)) {
return false;
}
// If the merge is complete, then we switch dm tables which is equivalent
// to unmap; hence, we can't be deleting the device
// as the table would be mounted off partitions and will fail.
if (snapshot_status.state() != SnapshotState::MERGE_COMPLETED) {
if (!DeleteDeviceIfExists(dm_user_name)) {
LOG(ERROR) << "Cannot unmap " << dm_user_name;
return false;
}
}
if (EnsureSnapuserdConnected()) {
if (!snapuserd_client_->WaitForDeviceDelete(dm_user_name)) {
LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete";
return false;
}
}
// Ensure the control device is gone so we don't run into ABA problems.
auto control_device = "/dev/dm-user/" + dm_user_name;
if (!android::fs_mgr::WaitForFileDeleted(control_device, 10s)) {
LOG(ERROR) << "Timed out waiting for " << control_device << " to unlink";
return false;
}
return true;
}
bool SnapshotManager::MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) {
auto lock = LockExclusive();
if (!lock) return false;
@ -2527,6 +2858,7 @@ bool SnapshotManager::WriteUpdateState(LockedFile* lock, UpdateState state,
status.set_compression_enabled(old_status.compression_enabled());
status.set_source_build_fingerprint(old_status.source_build_fingerprint());
status.set_merge_phase(old_status.merge_phase());
status.set_userspace_snapshots(old_status.userspace_snapshots());
}
return WriteSnapshotUpdateStatus(lock, status);
}
@ -2844,6 +3176,43 @@ Return SnapshotManager::CreateUpdateSnapshots(const DeltaArchiveManifest& manife
SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get());
status.set_state(update_state);
status.set_compression_enabled(cow_creator.compression_enabled);
if (cow_creator.compression_enabled) {
if (!device()->IsTestDevice()) {
// Userspace snapshots is enabled only if compression is enabled
status.set_userspace_snapshots(IsUserspaceSnapshotsEnabled());
if (IsUserspaceSnapshotsEnabled()) {
is_snapshot_userspace_ = true;
LOG(INFO) << "User-space snapshots enabled";
} else {
is_snapshot_userspace_ = false;
LOG(INFO) << "User-space snapshots disabled";
}
// Terminate stale daemon if any
std::unique_ptr<SnapuserdClient> snapuserd_client =
SnapuserdClient::Connect(kSnapuserdSocket, 10s);
if (snapuserd_client) {
snapuserd_client->DetachSnapuserd();
snapuserd_client->CloseConnection();
snapuserd_client = nullptr;
}
// Clear the cached client if any
if (snapuserd_client_) {
snapuserd_client_->CloseConnection();
snapuserd_client_ = nullptr;
}
} else {
status.set_userspace_snapshots(!IsDmSnapshotTestingEnabled());
if (IsDmSnapshotTestingEnabled()) {
is_snapshot_userspace_ = false;
LOG(INFO) << "User-space snapshots disabled for testing";
} else {
is_snapshot_userspace_ = true;
LOG(INFO) << "User-space snapshots enabled for testing";
}
}
}
if (!WriteSnapshotUpdateStatus(lock.get(), status)) {
LOG(ERROR) << "Unable to write new update state";
return Return::Error();

View file

@ -121,6 +121,11 @@ bool SnapshotManagerStub::UpdateUsesCompression() {
return false;
}
bool SnapshotManagerStub::UpdateUsesUserSnapshots() {
LOG(ERROR) << __FUNCTION__ << " should never be called.";
return false;
}
class SnapshotMergeStatsStub : public ISnapshotMergeStats {
bool Start() override { return false; }
void set_state(android::snapshot::UpdateState, bool) override {}

View file

@ -45,6 +45,8 @@
#include "partition_cow_creator.h"
#include "utility.h"
#include <android-base/properties.h>
// Mock classes are not used. Header included to ensure mocked class definition aligns with the
// class itself.
#include <libsnapshot/mock_device_info.h>
@ -272,7 +274,7 @@ class SnapshotTest : public ::testing::Test {
AssertionResult DeleteSnapshotDevice(const std::string& snapshot) {
AssertionResult res = AssertionSuccess();
if (!(res = DeleteDevice(snapshot))) return res;
if (!sm->UnmapDmUserDevice(snapshot)) {
if (!sm->UnmapDmUserDevice(snapshot + "-user-cow")) {
return AssertionFailure() << "Cannot delete dm-user device for " << snapshot;
}
if (!(res = DeleteDevice(snapshot + "-inner"))) return res;
@ -2559,5 +2561,15 @@ void SnapshotTestEnvironment::TearDown() {
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
::testing::AddGlobalTestEnvironment(new ::android::snapshot::SnapshotTestEnvironment());
return RUN_ALL_TESTS();
android::base::SetProperty("ctl.stop", "snapuserd");
if (!android::base::SetProperty("snapuserd.test.dm.snapshots", "1")) {
return testing::AssertionFailure()
<< "Failed to disable property: virtual_ab.userspace.snapshots.enabled";
}
int ret = RUN_ALL_TESTS();
android::base::SetProperty("snapuserd.test.dm.snapshots", "0");
return ret;
}

View file

@ -63,7 +63,8 @@ class SnapuserdClient {
// The misc_name must be the "misc_name" given to dm-user in step 2.
//
uint64_t InitDmUserCow(const std::string& misc_name, const std::string& cow_device,
const std::string& backing_device);
const std::string& backing_device,
const std::string& base_path_merge = "");
bool AttachDmUser(const std::string& misc_name);
// Wait for snapuserd to disassociate with a dm-user control device. This

View file

@ -195,8 +195,16 @@ bool SnapuserdClient::AttachDmUser(const std::string& misc_name) {
}
uint64_t SnapuserdClient::InitDmUserCow(const std::string& misc_name, const std::string& cow_device,
const std::string& backing_device) {
std::vector<std::string> parts = {"init", misc_name, cow_device, backing_device};
const std::string& backing_device,
const std::string& base_path_merge) {
std::vector<std::string> parts;
if (base_path_merge.empty()) {
parts = {"init", misc_name, cow_device, backing_device};
} else {
// For userspace snapshots
parts = {"init", misc_name, cow_device, backing_device, base_path_merge};
}
std::string msg = android::base::Join(parts, ",");
if (!Sendmsg(msg)) {
LOG(ERROR) << "Failed to send message " << msg << " to snapuserd daemon";

View file

@ -14,25 +14,95 @@
* limitations under the License.
*/
#include "snapuserd_daemon.h"
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <gflags/gflags.h>
#include <snapuserd/snapuserd_client.h>
#include "snapuserd_daemon.h"
DEFINE_string(socket, android::snapshot::kSnapuserdSocket, "Named socket or socket path.");
DEFINE_bool(no_socket, false,
"If true, no socket is used. Each additional argument is an INIT message.");
DEFINE_bool(socket_handoff, false,
"If true, perform a socket hand-off with an existing snapuserd instance, then exit.");
DEFINE_bool(user_snapshot, false, "If true, user-space snapshots are used");
namespace android {
namespace snapshot {
bool Daemon::StartServer(int argc, char** argv) {
bool Daemon::IsUserspaceSnapshotsEnabled() {
return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
}
bool Daemon::IsDmSnapshotTestingEnabled() {
return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false);
}
bool Daemon::StartDaemon(int argc, char** argv) {
int arg_start = gflags::ParseCommandLineFlags(&argc, &argv, true);
// Daemon launched from first stage init and during selinux transition
// will have the command line "-user_snapshot" flag set if the user-space
// snapshots are enabled.
//
// Daemon launched as a init service during "socket-handoff" and when OTA
// is applied will check for the property. This is ok as the system
// properties are valid at this point. We can't do this during first
// stage init and hence use the command line flags to get the information.
if (!IsDmSnapshotTestingEnabled() && (FLAGS_user_snapshot || IsUserspaceSnapshotsEnabled())) {
LOG(INFO) << "Starting daemon for user-space snapshots.....";
return StartServerForUserspaceSnapshots(arg_start, argc, argv);
} else {
LOG(INFO) << "Starting daemon for dm-snapshots.....";
return StartServerForDmSnapshot(arg_start, argc, argv);
}
}
bool Daemon::StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv) {
sigfillset(&signal_mask_);
sigdelset(&signal_mask_, SIGINT);
sigdelset(&signal_mask_, SIGTERM);
sigdelset(&signal_mask_, SIGUSR1);
// Masking signals here ensure that after this point, we won't handle INT/TERM
// until after we call into ppoll()
signal(SIGINT, Daemon::SignalHandler);
signal(SIGTERM, Daemon::SignalHandler);
signal(SIGPIPE, Daemon::SignalHandler);
signal(SIGUSR1, Daemon::SignalHandler);
MaskAllSignalsExceptIntAndTerm();
if (FLAGS_socket_handoff) {
return user_server_.RunForSocketHandoff();
}
if (!FLAGS_no_socket) {
if (!user_server_.Start(FLAGS_socket)) {
return false;
}
return user_server_.Run();
}
for (int i = arg_start; i < argc; i++) {
auto parts = android::base::Split(argv[i], ",");
if (parts.size() != 4) {
LOG(ERROR) << "Malformed message, expected three sub-arguments.";
return false;
}
auto handler = user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3]);
if (!handler || !user_server_.StartHandler(handler)) {
return false;
}
}
// Skip the accept() call to avoid spurious log spam. The server will still
// run until all handlers have completed.
return user_server_.WaitForSocket();
}
bool Daemon::StartServerForDmSnapshot(int arg_start, int argc, char** argv) {
sigfillset(&signal_mask_);
sigdelset(&signal_mask_, SIGINT);
sigdelset(&signal_mask_, SIGTERM);
@ -95,11 +165,19 @@ void Daemon::MaskAllSignals() {
}
void Daemon::Interrupt() {
server_.Interrupt();
if (IsUserspaceSnapshotsEnabled()) {
user_server_.Interrupt();
} else {
server_.Interrupt();
}
}
void Daemon::ReceivedSocketSignal() {
server_.ReceivedSocketSignal();
if (IsUserspaceSnapshotsEnabled()) {
user_server_.ReceivedSocketSignal();
} else {
server_.ReceivedSocketSignal();
}
}
void Daemon::SignalHandler(int signal) {
@ -133,9 +211,10 @@ int main(int argc, char** argv) {
android::snapshot::Daemon& daemon = android::snapshot::Daemon::Instance();
if (!daemon.StartServer(argc, argv)) {
LOG(ERROR) << "Snapuserd daemon failed to start.";
if (!daemon.StartDaemon(argc, argv)) {
LOG(ERROR) << "Snapuserd daemon failed to start";
exit(EXIT_FAILURE);
}
return 0;
}

View file

@ -20,6 +20,7 @@
#include <vector>
#include "dm-snapshot-merge/snapuserd_server.h"
#include "user-space-merge/snapuserd_server.h"
namespace android {
namespace snapshot {
@ -35,9 +36,13 @@ class Daemon {
return instance;
}
bool StartServer(int argc, char** argv);
bool StartServerForDmSnapshot(int arg_start, int argc, char** argv);
bool StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv);
void Interrupt();
void ReceivedSocketSignal();
bool IsUserspaceSnapshotsEnabled();
bool IsDmSnapshotTestingEnabled();
bool StartDaemon(int argc, char** argv);
private:
// Signal mask used with ppoll()
@ -47,6 +52,7 @@ class Daemon {
void operator=(Daemon const&) = delete;
SnapuserdServer server_;
UserSnapshotServer user_server_;
void MaskAllSignalsExceptIntAndTerm();
void MaskAllSignals();
static void SignalHandler(int signal);

View file

@ -35,7 +35,7 @@ SnapshotHandler::SnapshotHandler(std::string misc_name, std::string cow_device,
}
bool SnapshotHandler::InitializeWorkers() {
for (int i = 0; i < NUM_THREADS_PER_PARTITION; i++) {
for (int i = 0; i < kNumWorkerThreads; i++) {
std::unique_ptr<Worker> wt =
std::make_unique<Worker>(cow_device_, backing_store_device_, control_device_,
misc_name_, base_path_merge_, GetSharedPtr());

View file

@ -48,10 +48,10 @@ namespace snapshot {
using android::base::unique_fd;
using namespace std::chrono_literals;
static constexpr size_t PAYLOAD_SIZE = (1UL << 20);
static_assert(PAYLOAD_SIZE >= BLOCK_SZ);
static constexpr size_t PAYLOAD_BUFFER_SZ = (1UL << 20);
static_assert(PAYLOAD_BUFFER_SZ >= BLOCK_SZ);
static constexpr int NUM_THREADS_PER_PARTITION = 1;
static constexpr int kNumWorkerThreads = 4;
#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "

View file

@ -231,8 +231,8 @@ void Worker::InitializeBufsink() {
// Allocate the buffer which is used to communicate between
// daemon and dm-user. The buffer comprises of header and a fixed payload.
// If the dm-user requests a big IO, the IO will be broken into chunks
// of PAYLOAD_SIZE.
size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
// of PAYLOAD_BUFFER_SZ.
size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_BUFFER_SZ;
bufsink_.Initialize(buf_size);
}
@ -326,7 +326,7 @@ bool Worker::ReadAlignedSector(sector_t sector, size_t sz, bool header_response)
do {
// Process 1MB payload at a time
size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
size_t read_size = std::min(PAYLOAD_BUFFER_SZ, remaining_size);
header->type = DM_USER_RESP_SUCCESS;
size_t total_bytes_read = 0;

View file

@ -81,11 +81,11 @@ bool Worker::MergeReplaceZeroOps(const std::unique_ptr<ICowOpIter>& cowop_iter)
// Why 2048 ops ? We can probably increase this to bigger value but just
// need to ensure that merge makes forward progress if there are
// crashes repeatedly which is highly unlikely.
int total_ops_merged_per_commit = (PAYLOAD_SIZE / BLOCK_SZ) * 8;
int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 8;
int num_ops_merged = 0;
while (!cowop_iter->Done()) {
int num_ops = PAYLOAD_SIZE / BLOCK_SZ;
int num_ops = PAYLOAD_BUFFER_SZ / BLOCK_SZ;
std::vector<const CowOperation*> replace_zero_vec;
uint64_t source_offset;
@ -292,6 +292,7 @@ bool Worker::RunMergeThread() {
if (!Init()) {
SNAP_LOG(ERROR) << "Merge thread initialization failed...";
snapuserd_->MergeFailed();
return false;
}

View file

@ -429,7 +429,7 @@ void ReadAhead::InitializeBuffer() {
static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferMetadataOffset());
read_ahead_buffer_ = static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
// For xor ops
bufsink_.Initialize(PAYLOAD_SIZE);
bufsink_.Initialize(PAYLOAD_BUFFER_SZ);
}
} // namespace snapshot

View file

@ -44,7 +44,7 @@ using namespace std::string_literals;
using android::base::borrowed_fd;
using android::base::unique_fd;
DaemonOps SnapuserServer::Resolveop(std::string& input) {
DaemonOps UserSnapshotServer::Resolveop(std::string& input) {
if (input == "init") return DaemonOps::INIT;
if (input == "start") return DaemonOps::START;
if (input == "stop") return DaemonOps::STOP;
@ -59,14 +59,14 @@ DaemonOps SnapuserServer::Resolveop(std::string& input) {
return DaemonOps::INVALID;
}
SnapuserServer::~SnapuserServer() {
UserSnapshotServer::~UserSnapshotServer() {
// Close any client sockets that were added via AcceptClient().
for (size_t i = 1; i < watched_fds_.size(); i++) {
close(watched_fds_[i].fd);
}
}
std::string SnapuserServer::GetDaemonStatus() {
std::string UserSnapshotServer::GetDaemonStatus() {
std::string msg = "";
if (IsTerminating())
@ -77,8 +77,8 @@ std::string SnapuserServer::GetDaemonStatus() {
return msg;
}
void SnapuserServer::Parsemsg(std::string const& msg, const char delim,
std::vector<std::string>& out) {
void UserSnapshotServer::Parsemsg(std::string const& msg, const char delim,
std::vector<std::string>& out) {
std::stringstream ss(msg);
std::string s;
@ -87,15 +87,15 @@ void SnapuserServer::Parsemsg(std::string const& msg, const char delim,
}
}
void SnapuserServer::ShutdownThreads() {
void UserSnapshotServer::ShutdownThreads() {
terminating_ = true;
JoinAllThreads();
}
DmUserHandler::DmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd)
UserSnapshotDmUserHandler::UserSnapshotDmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd)
: snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
bool SnapuserServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
bool UserSnapshotServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
if (ret < 0) {
PLOG(ERROR) << "Snapuserd:server: send() failed";
@ -109,8 +109,8 @@ bool SnapuserServer::Sendmsg(android::base::borrowed_fd fd, const std::string& m
return true;
}
bool SnapuserServer::Recv(android::base::borrowed_fd fd, std::string* data) {
char msg[MAX_PACKET_SIZE];
bool UserSnapshotServer::Recv(android::base::borrowed_fd fd, std::string* data) {
char msg[kMaxPacketSize];
ssize_t rv = TEMP_FAILURE_RETRY(recv(fd.get(), msg, sizeof(msg), 0));
if (rv < 0) {
PLOG(ERROR) << "recv failed";
@ -120,7 +120,7 @@ bool SnapuserServer::Recv(android::base::borrowed_fd fd, std::string* data) {
return true;
}
bool SnapuserServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
bool UserSnapshotServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
const char delim = ',';
std::vector<std::string> out;
@ -290,7 +290,7 @@ bool SnapuserServer::Receivemsg(android::base::borrowed_fd fd, const std::string
}
}
void SnapuserServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
void UserSnapshotServer::RunThread(std::shared_ptr<UserSnapshotDmUserHandler> handler) {
LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
handler->snapuserd()->SetSocketPresent(is_socket_present_);
@ -337,7 +337,7 @@ void SnapuserServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
}
}
bool SnapuserServer::Start(const std::string& socketname) {
bool UserSnapshotServer::Start(const std::string& socketname) {
bool start_listening = true;
sockfd_.reset(android_get_control_socket(socketname.c_str()));
@ -353,7 +353,7 @@ bool SnapuserServer::Start(const std::string& socketname) {
return StartWithSocket(start_listening);
}
bool SnapuserServer::StartWithSocket(bool start_listening) {
bool UserSnapshotServer::StartWithSocket(bool start_listening) {
if (start_listening && listen(sockfd_.get(), 4) < 0) {
PLOG(ERROR) << "listen socket failed";
return false;
@ -374,7 +374,7 @@ bool SnapuserServer::StartWithSocket(bool start_listening) {
return true;
}
bool SnapuserServer::Run() {
bool UserSnapshotServer::Run() {
LOG(INFO) << "Now listening on snapuserd socket";
while (!IsTerminating()) {
@ -406,9 +406,9 @@ bool SnapuserServer::Run() {
return true;
}
void SnapuserServer::JoinAllThreads() {
void UserSnapshotServer::JoinAllThreads() {
// Acquire the thread list within the lock.
std::vector<std::shared_ptr<DmUserHandler>> dm_users;
std::vector<std::shared_ptr<UserSnapshotDmUserHandler>> dm_users;
{
std::lock_guard<std::mutex> guard(lock_);
dm_users = std::move(dm_users_);
@ -421,14 +421,14 @@ void SnapuserServer::JoinAllThreads() {
}
}
void SnapuserServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
void UserSnapshotServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
struct pollfd p = {};
p.fd = fd.get();
p.events = events;
watched_fds_.emplace_back(std::move(p));
}
void SnapuserServer::AcceptClient() {
void UserSnapshotServer::AcceptClient() {
int fd = TEMP_FAILURE_RETRY(accept4(sockfd_.get(), nullptr, nullptr, SOCK_CLOEXEC));
if (fd < 0) {
PLOG(ERROR) << "accept4 failed";
@ -438,7 +438,7 @@ void SnapuserServer::AcceptClient() {
AddWatchedFd(fd, POLLIN);
}
bool SnapuserServer::HandleClient(android::base::borrowed_fd fd, int revents) {
bool UserSnapshotServer::HandleClient(android::base::borrowed_fd fd, int revents) {
if (revents & POLLHUP) {
LOG(DEBUG) << "Snapuserd client disconnected";
return false;
@ -455,16 +455,15 @@ bool SnapuserServer::HandleClient(android::base::borrowed_fd fd, int revents) {
return true;
}
void SnapuserServer::Interrupt() {
void UserSnapshotServer::Interrupt() {
// Force close the socket so poll() fails.
sockfd_ = {};
SetTerminating();
}
std::shared_ptr<DmUserHandler> SnapuserServer::AddHandler(const std::string& misc_name,
const std::string& cow_device_path,
const std::string& backing_device,
const std::string& base_path_merge) {
std::shared_ptr<UserSnapshotDmUserHandler> UserSnapshotServer::AddHandler(
const std::string& misc_name, const std::string& cow_device_path,
const std::string& backing_device, const std::string& base_path_merge) {
auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
base_path_merge);
if (!snapuserd->InitCowDevice()) {
@ -477,7 +476,7 @@ std::shared_ptr<DmUserHandler> SnapuserServer::AddHandler(const std::string& mis
return nullptr;
}
auto handler = std::make_shared<DmUserHandler>(snapuserd);
auto handler = std::make_shared<UserSnapshotDmUserHandler>(snapuserd);
{
std::lock_guard<std::mutex> lock(lock_);
if (FindHandler(&lock, misc_name) != dm_users_.end()) {
@ -489,7 +488,7 @@ std::shared_ptr<DmUserHandler> SnapuserServer::AddHandler(const std::string& mis
return handler;
}
bool SnapuserServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
bool UserSnapshotServer::StartHandler(const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
if (handler->snapuserd()->IsAttached()) {
LOG(ERROR) << "Handler already attached";
return false;
@ -497,11 +496,11 @@ bool SnapuserServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler)
handler->snapuserd()->AttachControlDevice();
handler->thread() = std::thread(std::bind(&SnapuserServer::RunThread, this, handler));
handler->thread() = std::thread(std::bind(&UserSnapshotServer::RunThread, this, handler));
return true;
}
bool SnapuserServer::StartMerge(const std::shared_ptr<DmUserHandler>& handler) {
bool UserSnapshotServer::StartMerge(const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
if (!handler->snapuserd()->IsAttached()) {
LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
return false;
@ -511,8 +510,8 @@ bool SnapuserServer::StartMerge(const std::shared_ptr<DmUserHandler>& handler) {
return true;
}
auto SnapuserServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
const std::string& misc_name) -> HandlerList::iterator {
auto UserSnapshotServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
const std::string& misc_name) -> HandlerList::iterator {
CHECK(proof_of_lock);
for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
@ -523,7 +522,7 @@ auto SnapuserServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
return dm_users_.end();
}
void SnapuserServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
void UserSnapshotServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
CHECK(proof_of_lock);
for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
@ -533,11 +532,12 @@ void SnapuserServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of
}
}
std::string SnapuserServer::GetMergeStatus(const std::shared_ptr<DmUserHandler>& handler) {
std::string UserSnapshotServer::GetMergeStatus(
const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
return handler->snapuserd()->GetMergeStatus();
}
double SnapuserServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock) {
double UserSnapshotServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock) {
CHECK(proof_of_lock);
double percentage = 0.0;
int n = 0;
@ -567,8 +567,8 @@ double SnapuserServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_
return percentage;
}
bool SnapuserServer::RemoveAndJoinHandler(const std::string& misc_name) {
std::shared_ptr<DmUserHandler> handler;
bool UserSnapshotServer::RemoveAndJoinHandler(const std::string& misc_name) {
std::shared_ptr<UserSnapshotDmUserHandler> handler;
{
std::lock_guard<std::mutex> lock(lock_);
@ -588,7 +588,7 @@ bool SnapuserServer::RemoveAndJoinHandler(const std::string& misc_name) {
return true;
}
bool SnapuserServer::WaitForSocket() {
bool UserSnapshotServer::WaitForSocket() {
auto scope_guard = android::base::make_scope_guard([this]() -> void { JoinAllThreads(); });
auto socket_path = ANDROID_SOCKET_DIR "/"s + kSnapuserdSocketProxy;
@ -642,7 +642,7 @@ bool SnapuserServer::WaitForSocket() {
return Run();
}
bool SnapuserServer::RunForSocketHandoff() {
bool UserSnapshotServer::RunForSocketHandoff() {
unique_fd proxy_fd(android_get_control_socket(kSnapuserdSocketProxy));
if (proxy_fd < 0) {
PLOG(FATAL) << "Proxy could not get android control socket " << kSnapuserdSocketProxy;

View file

@ -33,7 +33,7 @@
namespace android {
namespace snapshot {
static constexpr uint32_t MAX_PACKET_SIZE = 512;
static constexpr uint32_t kMaxPacketSize = 512;
enum class DaemonOps {
INIT,
@ -49,9 +49,9 @@ enum class DaemonOps {
INVALID,
};
class DmUserHandler {
class UserSnapshotDmUserHandler {
public:
explicit DmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd);
explicit UserSnapshotDmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd);
void FreeResources() {
// Each worker thread holds a reference to snapuserd.
@ -76,7 +76,7 @@ class DmUserHandler {
bool thread_terminated_ = false;
};
class SnapuserServer {
class UserSnapshotServer {
private:
android::base::unique_fd sockfd_;
bool terminating_;
@ -87,7 +87,7 @@ class SnapuserServer {
std::mutex lock_;
using HandlerList = std::vector<std::shared_ptr<DmUserHandler>>;
using HandlerList = std::vector<std::shared_ptr<UserSnapshotDmUserHandler>>;
HandlerList dm_users_;
void AddWatchedFd(android::base::borrowed_fd fd, int events);
@ -105,11 +105,11 @@ class SnapuserServer {
bool IsTerminating() { return terminating_; }
void RunThread(std::shared_ptr<DmUserHandler> handler);
void RunThread(std::shared_ptr<UserSnapshotDmUserHandler> handler);
void JoinAllThreads();
bool StartWithSocket(bool start_listening);
// Find a DmUserHandler within a lock.
// Find a UserSnapshotDmUserHandler within a lock.
HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
const std::string& misc_name);
@ -117,8 +117,8 @@ class SnapuserServer {
void TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock);
public:
SnapuserServer() { terminating_ = false; }
~SnapuserServer();
UserSnapshotServer() { terminating_ = false; }
~UserSnapshotServer();
bool Start(const std::string& socketname);
bool Run();
@ -126,13 +126,13 @@ class SnapuserServer {
bool RunForSocketHandoff();
bool WaitForSocket();
std::shared_ptr<DmUserHandler> AddHandler(const std::string& misc_name,
const std::string& cow_device_path,
const std::string& backing_device,
const std::string& base_path_merge);
bool StartHandler(const std::shared_ptr<DmUserHandler>& handler);
bool StartMerge(const std::shared_ptr<DmUserHandler>& handler);
std::string GetMergeStatus(const std::shared_ptr<DmUserHandler>& handler);
std::shared_ptr<UserSnapshotDmUserHandler> AddHandler(const std::string& misc_name,
const std::string& cow_device_path,
const std::string& backing_device,
const std::string& base_path_merge);
bool StartHandler(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
bool StartMerge(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
std::string GetMergeStatus(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
void SetTerminating() { terminating_ = true; }
void ReceivedSocketSignal() { received_socket_signal_ = true; }

View file

@ -570,7 +570,6 @@ void SnapshotHandler::NotifyIOCompletion(uint64_t new_block) {
{
std::unique_lock<std::mutex> lock(blk_state->m_lock);
CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_PENDING);
blk_state->num_ios_in_progress -= 1;
if (blk_state->num_ios_in_progress == 0) {
pending_ios = false;

View file

@ -22,6 +22,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <fs_mgr/roots.h>
@ -187,6 +188,10 @@ bool IsCompressionEnabled() {
return android::base::GetBoolProperty("ro.virtual_ab.compression.enabled", false);
}
bool IsUserspaceSnapshotsEnabled() {
return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
}
std::string GetOtherPartitionName(const std::string& name) {
auto suffix = android::fs_mgr::GetPartitionSlotSuffix(name);
CHECK(suffix == "_a" || suffix == "_b");
@ -195,5 +200,9 @@ std::string GetOtherPartitionName(const std::string& name) {
return name.substr(0, name.size() - suffix.size()) + other_suffix;
}
bool IsDmSnapshotTestingEnabled() {
return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false);
}
} // namespace snapshot
} // namespace android

View file

@ -131,8 +131,11 @@ void AppendExtent(google::protobuf::RepeatedPtrField<chromeos_update_engine::Ext
bool IsCompressionEnabled();
bool IsUserspaceSnapshotsEnabled();
bool IsDmSnapshotTestingEnabled();
// Swap the suffix of a partition name.
std::string GetOtherPartitionName(const std::string& name);
} // namespace snapshot
} // namespace android

View file

@ -391,7 +391,11 @@ bool FirstStageMount::CreateSnapshotPartitions(SnapshotManager* sm) {
use_snapuserd_ = sm->IsSnapuserdRequired();
if (use_snapuserd_) {
LaunchFirstStageSnapuserd();
if (sm->UpdateUsesUserSnapshots()) {
LaunchFirstStageSnapuserd(SnapshotDriver::DM_USER);
} else {
LaunchFirstStageSnapuserd(SnapshotDriver::DM_SNAPSHOT);
}
}
sm->SetUeventRegenCallback([this](const std::string& device) -> bool {

View file

@ -58,7 +58,7 @@ static constexpr char kSnapuserdFirstStageInfoVar[] = "FIRST_STAGE_SNAPUSERD_INF
static constexpr char kSnapuserdLabel[] = "u:object_r:snapuserd_exec:s0";
static constexpr char kSnapuserdSocketLabel[] = "u:object_r:snapuserd_socket:s0";
void LaunchFirstStageSnapuserd() {
void LaunchFirstStageSnapuserd(SnapshotDriver driver) {
SocketDescriptor socket_desc;
socket_desc.name = android::snapshot::kSnapuserdSocket;
socket_desc.type = SOCK_STREAM;
@ -80,12 +80,23 @@ void LaunchFirstStageSnapuserd() {
}
if (pid == 0) {
socket->Publish();
char arg0[] = "/system/bin/snapuserd";
char* const argv[] = {arg0, nullptr};
if (execv(arg0, argv) < 0) {
PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
if (driver == SnapshotDriver::DM_USER) {
char arg0[] = "/system/bin/snapuserd";
char arg1[] = "-user_snapshot";
char* const argv[] = {arg0, arg1, nullptr};
if (execv(arg0, argv) < 0) {
PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
}
_exit(127);
} else {
char arg0[] = "/system/bin/snapuserd";
char* const argv[] = {arg0, nullptr};
if (execv(arg0, argv) < 0) {
PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
}
_exit(127);
}
_exit(127);
}
auto client = SnapuserdClient::Connect(android::snapshot::kSnapuserdSocket, 10s);

View file

@ -29,8 +29,13 @@
namespace android {
namespace init {
enum class SnapshotDriver {
DM_SNAPSHOT,
DM_USER,
};
// Fork and exec a new copy of snapuserd.
void LaunchFirstStageSnapuserd();
void LaunchFirstStageSnapuserd(SnapshotDriver driver);
class SnapuserdSelinuxHelper final {
using SnapshotManager = android::snapshot::SnapshotManager;