From 456e50193bf5989c787d1f33b9b411f4fd462093 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 1 Aug 2019 14:37:35 -0700 Subject: [PATCH] Implement basic libsnapshot functionality. This CL implements some of the libsnapshot internals necessary to work with update_engine. In particular it implements snapshot and update state, as well as creating and mapping snapshot devices. It does not implement anything related to merging, nor does it implement the full update_engine flow. Update state is stored in /metadata/ota/state. To synchronize callers of libsnapshot, we always flock() this file at the top of public functions in SnapshotManager. Internal functions are only called while the lock is held, and a "LockedFile" guard object is always passed through to indicate proof-of-lock. Low-level functions, such as snapshot management, have been moved to private methods. Higher-level methods designed for update_engine will ultimately call into these. This CL also adds some functional tests for SnapshotManager. Test state is stored in /metadata/ota/test to avoid conflicts with the rest of the system. Bug: 136678799 Test: libsnapshot_test gtest Change-Id: I78c769ed33b307d5214ee386bb13648e35db6cc6 --- fs_mgr/libdm/dm_target.cpp | 12 + fs_mgr/libdm/include/libdm/dm.h | 2 + fs_mgr/libdm/include/libdm/dm_target.h | 2 + fs_mgr/libsnapshot/Android.bp | 58 +- .../include/libsnapshot/snapshot.h | 188 +++++- fs_mgr/libsnapshot/snapshot.cpp | 559 +++++++++++++++++- fs_mgr/libsnapshot/snapshot_test.cpp | 189 ++++++ rootdir/init.rc | 1 + 8 files changed, 946 insertions(+), 65 deletions(-) create mode 100644 fs_mgr/libsnapshot/snapshot_test.cpp diff --git a/fs_mgr/libdm/dm_target.cpp b/fs_mgr/libdm/dm_target.cpp index 1a483ecaf..be88eae6f 100644 --- a/fs_mgr/libdm/dm_target.cpp +++ b/fs_mgr/libdm/dm_target.cpp @@ -191,6 +191,18 @@ bool DmTargetSnapshot::ParseStatusText(const std::string& text, Status* status) return false; } +bool DmTargetSnapshot::GetDevicesFromParams(const std::string& params, std::string* base_device, + std::string* cow_device) { + auto pieces = android::base::Split(params, " "); + if (pieces.size() < 2) { + LOG(ERROR) << "Parameter string is invalid: " << params; + return false; + } + *base_device = pieces[0]; + *cow_device = pieces[1]; + return true; +} + std::string DmTargetCrypt::GetParameterString() const { std::vector argv = { cipher_, diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h index c6b37cfd6..f5783cb4e 100644 --- a/fs_mgr/libdm/include/libdm/dm.h +++ b/fs_mgr/libdm/include/libdm/dm.h @@ -47,6 +47,8 @@ namespace dm { enum class DmDeviceState { INVALID, SUSPENDED, ACTIVE }; +static constexpr uint64_t kSectorSize = 512; + class DeviceMapper final { public: class DmBlockDevice final { diff --git a/fs_mgr/libdm/include/libdm/dm_target.h b/fs_mgr/libdm/include/libdm/dm_target.h index 722922d67..6754920f8 100644 --- a/fs_mgr/libdm/include/libdm/dm_target.h +++ b/fs_mgr/libdm/include/libdm/dm_target.h @@ -218,6 +218,8 @@ class DmTargetSnapshot final : public DmTarget { static bool ParseStatusText(const std::string& text, Status* status); static bool ReportsOverflow(const std::string& target_type); + static bool GetDevicesFromParams(const std::string& params, std::string* base_device, + std::string* cow_device); private: std::string base_device_; diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp index 3a08049b9..52aad1207 100644 --- a/fs_mgr/libsnapshot/Android.bp +++ b/fs_mgr/libsnapshot/Android.bp @@ -14,15 +14,13 @@ // limitations under the License. // -cc_library { - name: "libsnapshot", - recovery_available: true, +cc_defaults { + name: "libsnapshot_defaults", defaults: ["fs_mgr_defaults"], - cppflags: [ + cflags: [ "-D_FILE_OFFSET_BITS=64", - ], - srcs: [ - "snapshot.cpp", + "-Wall", + "-Werror", ], shared_libs: [ "libbase", @@ -30,7 +28,53 @@ cc_library { ], static_libs: [ "libdm", + ], + whole_static_libs: [ "libext2_uuid", + "libext4_utils", + "libfiemap", ], export_include_dirs: ["include"], } + +filegroup { + name: "libsnapshot_sources", + srcs: [ + "snapshot.cpp", + ], +} + +cc_library_static { + name: "libsnapshot", + defaults: ["libsnapshot_defaults"], + srcs: [":libsnapshot_sources"], + static_libs: [ + "libfiemap_binder", + ], +} + +cc_library_static { + name: "libsnapshot_nobinder", + defaults: ["libsnapshot_defaults"], + srcs: [":libsnapshot_sources"], + recovery_available: true, +} + +cc_test { + name: "libsnapshot_test", + defaults: ["libsnapshot_defaults"], + srcs: [ + "snapshot_test.cpp", + ], + shared_libs: [ + "libbinder", + "libutils", + ], + static_libs: [ + "libcutils", + "libcrypto", + "libfs_mgr", + "liblp", + "libsnapshot", + ], +} diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h index 5cfd7fa5e..03e9eef6b 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h @@ -19,14 +19,28 @@ #include #include #include +#include + +#include +#include +#include + +#ifndef FRIEND_TEST +#define FRIEND_TEST(test_set_name, individual_test) \ + friend class test_set_name##_##individual_test##_Test +#define DEFINED_FRIEND_TEST +#endif namespace android { namespace snapshot { -enum class UpdateStatus { +enum class UpdateState { // No update or merge is in progress. None, + // An update is applying; snapshots may already exist. + Initiated, + // An update is pending, but has not been successfully booted yet. Unverified, @@ -34,36 +48,39 @@ enum class UpdateStatus { Merging, // Merging is complete, and needs to be acknowledged. - MergeCompleted + MergeCompleted, + + // Merging failed due to an unrecoverable error. + MergeFailed }; class SnapshotManager final { public: - // Return a new SnapshotManager instance, or null on error. - static std::unique_ptr New(); + // Dependency injection for testing. + class IDeviceInfo { + public: + virtual ~IDeviceInfo() {} + virtual std::string GetGsidDir() const = 0; + virtual std::string GetMetadataDir() const = 0; - // Create a new snapshot device with the given name, base device, and COW device - // size. The new device path will be returned in |dev_path|. If timeout_ms is - // greater than zero, this function will wait the given amount of time for - // |dev_path| to become available, and fail otherwise. If timeout_ms is 0, then - // no wait will occur and |dev_path| may not yet exist on return. - bool CreateSnapshot(const std::string& name, const std::string& base_device, uint64_t cow_size, - std::string* dev_path, const std::chrono::milliseconds& timeout_ms); + // Return true if the device is currently running off snapshot devices, + // indicating that we have booted after applying (but not merging) an + // OTA. + virtual bool IsRunningSnapshot() const = 0; + }; - // Map a snapshot device that was previously created with CreateSnapshot. - // If a merge was previously initiated, the device-mapper table will have a - // snapshot-merge target instead of a snapshot target. The timeout parameter - // is the same as in CreateSnapshotDevice. - bool MapSnapshotDevice(const std::string& name, const std::string& base_device, - const std::chrono::milliseconds& timeout_ms, std::string* dev_path); + // Return a new SnapshotManager instance, or null on error. The device + // pointer is owned for the lifetime of SnapshotManager. If null, a default + // instance will be created. + static std::unique_ptr New(IDeviceInfo* device = nullptr); - // Unmap a snapshot device previously mapped with MapSnapshotDevice(). - bool UnmapSnapshotDevice(const std::string& name); + // Begin an update. This must be called before creating any snapshots. It + // will fail if GetUpdateState() != None. + bool BeginUpdate(); - // Remove the backing copy-on-write image for the named snapshot. If the - // device is still mapped, this will attempt an Unmap, and fail if the - // unmap fails. - bool DeleteSnapshot(const std::string& name); + // Cancel an update; any snapshots will be deleted. This will fail if the + // state != Initiated or None. + bool CancelUpdate(); // Initiate a merge on all snapshot devices. This should only be used after an // update has been marked successful after booting. @@ -77,12 +94,129 @@ class SnapshotManager final { // Find the status of the current update, if any. // // |progress| depends on the returned status: - // None: 0 - // Unverified: 0 - // Merging: Value in the range [0, 100) + // Merging: Value in the range [0, 100] // MergeCompleted: 100 - UpdateStatus GetUpdateStatus(double* progress); + // Other: 0 + UpdateState GetUpdateState(double* progress = nullptr); + + private: + FRIEND_TEST(SnapshotTest, CreateSnapshot); + FRIEND_TEST(SnapshotTest, MapSnapshot); + FRIEND_TEST(SnapshotTest, MapPartialSnapshot); + friend class SnapshotTest; + + using IImageManager = android::fiemap::IImageManager; + + explicit SnapshotManager(IDeviceInfo* info); + + // This is created lazily since it connects via binder. + bool EnsureImageManager(); + + // Helper function for tests. + IImageManager* image_manager() const { return images_.get(); } + + // Since libsnapshot is included into multiple processes, we flock() our + // files for simple synchronization. LockedFile is a helper to assist with + // this. It also serves as a proof-of-lock for some functions. + class LockedFile final { + public: + LockedFile(const std::string& path, android::base::unique_fd&& fd) + : path_(path), fd_(std::move(fd)) {} + ~LockedFile(); + + const std::string& path() const { return path_; } + int fd() const { return fd_; } + + private: + std::string path_; + android::base::unique_fd fd_; + }; + std::unique_ptr OpenFile(const std::string& file, int open_flags, int lock_flags); + bool Truncate(LockedFile* file); + + // 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(). + // + // |device_size| should be the size of the base_device that will be passed + // via MapDevice(). |snapshot_size| should be the number of bytes in the + // base device, starting from 0, that will be snapshotted. The cow_size + // should be the amount of space that will be allocated to store snapshot + // deltas. + // + // If |snapshot_size| < device_size, then the device will always + // be mapped with two table entries: a dm-snapshot range covering + // snapshot_size, and a dm-linear range covering the remainder. + // + // All sizes are specified in bytes, and the device and snapshot sizes + // must be a multiple of the sector size (512 bytes). |cow_size| will + // be rounded up to the nearest sector. + bool CreateSnapshot(LockedFile* lock, const std::string& name, uint64_t device_size, + uint64_t snapshot_size, uint64_t cow_size); + + // Map a snapshot device that was previously created with CreateSnapshot. + // If a merge was previously initiated, the device-mapper table will have a + // snapshot-merge target instead of a snapshot target. If the timeout + // parameter greater than zero, this function will wait the given amount + // of time for |dev_path| to become available, and fail otherwise. If + // timeout_ms is 0, then no wait will occur and |dev_path| may not yet + // exist on return. + bool MapSnapshot(LockedFile* lock, const std::string& name, const std::string& base_device, + const std::chrono::milliseconds& timeout_ms, std::string* dev_path); + + // Remove the backing copy-on-write image for the named snapshot. If the + // device is still mapped, this will attempt an Unmap, and fail if the + // unmap fails. + bool DeleteSnapshot(LockedFile* lock, const std::string& name); + + // Unmap a snapshot device previously mapped with MapSnapshotDevice(). + bool UnmapSnapshot(LockedFile* lock, const std::string& name); + + // Unmap and remove all known snapshots. + bool RemoveAllSnapshots(LockedFile* lock); + + // List the known snapshot names. + bool ListSnapshots(LockedFile* lock, std::vector* snapshots); + + // Interact with /metadata/ota/state. + std::unique_ptr OpenStateFile(int open_flags, int lock_flags); + std::unique_ptr LockShared(); + std::unique_ptr LockExclusive(); + UpdateState ReadUpdateState(LockedFile* file); + bool WriteUpdateState(LockedFile* file, UpdateState state); + + // This state is persisted per-snapshot in /metadata/ota/snapshots/. + struct SnapshotStatus { + std::string state; + uint64_t device_size; + uint64_t snapshot_size; + // These are non-zero when merging. + uint64_t sectors_allocated = 0; + uint64_t metadata_sectors = 0; + }; + + // Interact with status files under /metadata/ota/snapshots. + std::unique_ptr OpenSnapshotStatusFile(const std::string& name, int open_flags, + int lock_flags); + bool WriteSnapshotStatus(LockedFile* file, const SnapshotStatus& status); + bool ReadSnapshotStatus(LockedFile* file, SnapshotStatus* status); + + // Return the name of the device holding the "snapshot" or "snapshot-merge" + // target. This may not be the final device presented via MapSnapshot(), if + // for example there is a linear segment. + std::string GetSnapshotDeviceName(const std::string& snapshot_name, + const SnapshotStatus& status); + + std::string gsid_dir_; + std::string metadata_dir_; + std::unique_ptr device_; + std::unique_ptr images_; }; } // namespace snapshot } // namespace android + +#ifdef DEFINED_FRIEND_TEST +#undef DEFINED_FRIEND_TEST +#undef FRIEND_TEST +#endif diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp index 3e8023979..7d36b0680 100644 --- a/fs_mgr/libsnapshot/snapshot.cpp +++ b/fs_mgr/libsnapshot/snapshot.cpp @@ -14,45 +14,309 @@ #include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + namespace android { namespace snapshot { -std::unique_ptr SnapshotManager::New() { - return std::make_unique(); +using android::base::unique_fd; +using android::dm::DeviceMapper; +using android::dm::DmDeviceState; +using android::dm::DmTable; +using android::dm::DmTargetLinear; +using android::dm::DmTargetSnapshot; +using android::dm::kSectorSize; +using android::dm::SnapshotStorageMode; +using android::fiemap::IImageManager; +using namespace std::chrono_literals; +using namespace std::string_literals; + +// Unit is sectors, this is a 4K chunk. +static constexpr uint32_t kSnapshotChunkSize = 8; + +class DeviceInfo final : public SnapshotManager::IDeviceInfo { + public: + std::string GetGsidDir() const override { return "ota"s; } + std::string GetMetadataDir() const override { return "/metadata/ota/test"s; } + bool IsRunningSnapshot() const override; +}; + +bool DeviceInfo::IsRunningSnapshot() const { + // :TODO: implement this check. + return true; } -bool SnapshotManager::CreateSnapshot(const std::string& name, const std::string& base_device, - uint64_t cow_size, std::string* dev_path, - const std::chrono::milliseconds& timeout_ms) { - // (1) Create COW device using libgsi_image. - // (2) Create snapshot device using libdm + DmTargetSnapshot. - // (3) Record partition in /metadata/ota. - (void)name; - (void)base_device; - (void)cow_size; - (void)dev_path; - (void)timeout_ms; - return false; +std::unique_ptr SnapshotManager::New(IDeviceInfo* info) { + if (!info) { + info = new DeviceInfo(); + } + return std::unique_ptr(new SnapshotManager(info)); } -bool SnapshotManager::MapSnapshotDevice(const std::string& name, const std::string& base_device, - const std::chrono::milliseconds& timeout_ms, - std::string* dev_path) { - (void)name; - (void)base_device; - (void)dev_path; - (void)timeout_ms; - return false; +SnapshotManager::SnapshotManager(IDeviceInfo* device) : device_(device) { + gsid_dir_ = device_->GetGsidDir(); + metadata_dir_ = device_->GetMetadataDir(); } -bool SnapshotManager::UnmapSnapshotDevice(const std::string& name) { - (void)name; - return false; +static std::string GetCowName(const std::string& snapshot_name) { + return snapshot_name + "-cow"; } -bool SnapshotManager::DeleteSnapshot(const std::string& name) { - (void)name; - return false; +bool SnapshotManager::BeginUpdate() { + auto file = LockExclusive(); + if (!file) return false; + + auto state = ReadUpdateState(file.get()); + if (state != UpdateState::None) { + LOG(ERROR) << "An update is already in progress, cannot begin a new update"; + return false; + } + return WriteUpdateState(file.get(), UpdateState::Initiated); +} + +bool SnapshotManager::CancelUpdate() { + auto file = LockExclusive(); + if (!file) return false; + + UpdateState state = ReadUpdateState(file.get()); + if (state == UpdateState::None) return true; + if (state != UpdateState::Initiated) { + LOG(ERROR) << "Cannot cancel update after it has completed or started merging"; + return false; + } + + if (!RemoveAllSnapshots(file.get())) { + LOG(ERROR) << "Could not remove all snapshots"; + return false; + } + + if (!WriteUpdateState(file.get(), UpdateState::None)) { + LOG(ERROR) << "Could not write new update state"; + return false; + } + return true; +} + +bool SnapshotManager::CreateSnapshot(LockedFile* lock, const std::string& name, + uint64_t device_size, uint64_t snapshot_size, + uint64_t cow_size) { + CHECK(lock); + if (!EnsureImageManager()) return false; + + // Sanity check these sizes. Like liblp, we guarantee the partition size + // is respected, which means it has to be sector-aligned. (This guarantee + // is useful for locating avb footers correctly). The COW size, however, + // can be arbitrarily larger than specified, so we can safely round it up. + if (device_size % kSectorSize != 0) { + LOG(ERROR) << "Snapshot " << name + << " device size is not a multiple of the sector size: " << device_size; + return false; + } + if (snapshot_size % kSectorSize != 0) { + LOG(ERROR) << "Snapshot " << name + << " snapshot size is not a multiple of the sector size: " << snapshot_size; + return false; + } + + // Round the COW size up to the nearest sector. + cow_size += kSectorSize - 1; + cow_size &= ~(kSectorSize - 1); + + LOG(INFO) << "Snapshot " << name << " will have COW size " << cow_size; + + auto status_file = OpenSnapshotStatusFile(name, O_RDWR | O_CREAT, LOCK_EX); + if (!status_file) return false; + + // Note, we leave the status file hanging around if we fail to create the + // actual backing image. This is harmless, since it'll get removed when + // CancelUpdate is called. + SnapshotStatus status = { + .state = "created", + .device_size = device_size, + .snapshot_size = snapshot_size, + }; + if (!WriteSnapshotStatus(status_file.get(), status)) { + PLOG(ERROR) << "Could not write snapshot status: " << name; + return false; + } + + auto cow_name = GetCowName(name); + int cow_flags = IImageManager::CREATE_IMAGE_ZERO_FILL; + return images_->CreateBackingImage(cow_name, cow_size, cow_flags); +} + +bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name, + const std::string& base_device, + const std::chrono::milliseconds& timeout_ms, + std::string* dev_path) { + CHECK(lock); + if (!EnsureImageManager()) return false; + + auto status_file = OpenSnapshotStatusFile(name, O_RDWR, LOCK_EX); + if (!status_file) return false; + + SnapshotStatus status; + if (!ReadSnapshotStatus(status_file.get(), &status)) { + return false; + } + + // Validate the block device size, as well as the requested snapshot size. + // During this we also compute the linear sector region if any. + { + unique_fd fd(open(base_device.c_str(), O_RDONLY | O_CLOEXEC)); + if (fd < 0) { + PLOG(ERROR) << "open failed: " << base_device; + return false; + } + auto dev_size = get_block_device_size(fd); + if (!dev_size) { + PLOG(ERROR) << "Could not determine block device size: " << base_device; + return false; + } + if (status.device_size != dev_size) { + LOG(ERROR) << "Block device size for " << base_device << " does not match" + << "(expected " << status.device_size << ", got " << dev_size << ")"; + return false; + } + } + if (status.device_size % kSectorSize != 0) { + LOG(ERROR) << "invalid blockdev size for " << base_device << ": " << status.device_size; + return false; + } + if (status.snapshot_size % kSectorSize != 0 || status.snapshot_size > status.device_size) { + LOG(ERROR) << "Invalid snapshot size for " << base_device << ": " << status.snapshot_size; + return false; + } + uint64_t snapshot_sectors = status.snapshot_size / kSectorSize; + uint64_t linear_sectors = (status.device_size - status.snapshot_size) / kSectorSize; + + auto cow_name = GetCowName(name); + + std::string cow_dev; + if (!images_->MapImageDevice(cow_name, timeout_ms, &cow_dev)) { + return false; + } + + auto& dm = DeviceMapper::Instance(); + + // Merging is a global state, not per-snapshot. We do however track the + // progress of individual snapshots' merges. + SnapshotStorageMode mode; + UpdateState update_state = ReadUpdateState(lock); + if (update_state == UpdateState::Merging || update_state == UpdateState::MergeCompleted) { + mode = SnapshotStorageMode::Merge; + } else { + mode = SnapshotStorageMode::Persistent; + } + + // The kernel (tested on 4.19) crashes horribly if a device has both a snapshot + // and a linear target in the same table. Instead, we stack them, and give the + // snapshot device a different name. It is not exposed to the caller in this + // case. + auto snap_name = (linear_sectors > 0) ? name + "-inner" : name; + + DmTable table; + table.Emplace(0, snapshot_sectors, base_device, cow_dev, mode, + kSnapshotChunkSize); + if (!dm.CreateDevice(snap_name, table, dev_path, timeout_ms)) { + LOG(ERROR) << "Could not create snapshot device: " << snap_name; + images_->UnmapImageDevice(cow_name); + return false; + } + + if (linear_sectors) { + // Our stacking will looks like this: + // [linear, linear] ; to snapshot, and non-snapshot region of base device + // [snapshot-inner] + // [base device] [cow] + DmTable table; + table.Emplace(0, snapshot_sectors, *dev_path, 0); + table.Emplace(snapshot_sectors, linear_sectors, base_device, + snapshot_sectors); + if (!dm.CreateDevice(name, table, dev_path, timeout_ms)) { + LOG(ERROR) << "Could not create outer snapshot device: " << name; + dm.DeleteDevice(snap_name); + images_->UnmapImageDevice(cow_name); + return false; + } + } + + // :TODO: when merging is implemented, we need to add an argument to the + // status indicating how much progress is left to merge. (device-mapper + // does not retain the initial values, so we can't derive them.) + return true; +} + +bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) { + CHECK(lock); + if (!EnsureImageManager()) return false; + + auto status_file = OpenSnapshotStatusFile(name, O_RDWR, LOCK_EX); + if (!status_file) return false; + + SnapshotStatus status; + if (!ReadSnapshotStatus(status_file.get(), &status)) { + return false; + } + + auto& dm = DeviceMapper::Instance(); + if (dm.GetState(name) != DmDeviceState::INVALID && !dm.DeleteDevice(name)) { + LOG(ERROR) << "Could not delete snapshot device: " << name; + return false; + } + + // There may be an extra device, since the kernel doesn't let us have a + // snapshot and linear target in the same table. + auto dm_name = GetSnapshotDeviceName(name, status); + if (name != dm_name && !dm.DeleteDevice(dm_name)) { + LOG(ERROR) << "Could not delete inner snapshot device: " << dm_name; + return false; + } + + auto cow_name = GetCowName(name); + if (images_->IsImageMapped(cow_name) && !images_->UnmapImageDevice(cow_name)) { + return false; + } + return true; +} + +bool SnapshotManager::DeleteSnapshot(LockedFile* lock, const std::string& name) { + CHECK(lock); + if (!EnsureImageManager()) return false; + + if (!UnmapSnapshot(lock, name)) { + LOG(ERROR) << "Snapshot could not be unmapped for deletion: " << name; + return false; + } + + // Take the snapshot's lock after Unmap, since it will also try to lock. + auto status_file = OpenSnapshotStatusFile(name, O_RDONLY, LOCK_EX); + if (!status_file) return false; + + auto cow_name = GetCowName(name); + if (!images_->BackingImageExists(cow_name)) { + return true; + } + if (!images_->DeleteBackingImage(cow_name)) { + return false; + } + + if (!android::base::RemoveFileIfExists(status_file->path())) { + LOG(ERROR) << "Failed to remove status file: " << status_file->path(); + return false; + } + return true; } bool SnapshotManager::InitiateMerge() { @@ -63,9 +327,242 @@ bool SnapshotManager::WaitForMerge() { return false; } -UpdateStatus SnapshotManager::GetUpdateStatus(double* progress) { - *progress = 0.0f; - return UpdateStatus::None; +bool SnapshotManager::RemoveAllSnapshots(LockedFile* lock) { + std::vector snapshots; + if (!ListSnapshots(lock, &snapshots)) { + LOG(ERROR) << "Could not list snapshots"; + return false; + } + + bool ok = true; + for (const auto& name : snapshots) { + ok &= DeleteSnapshot(lock, name); + } + return ok; +} + +UpdateState SnapshotManager::GetUpdateState(double* progress) { + auto file = LockShared(); + if (!file) { + return UpdateState::None; + } + + auto state = ReadUpdateState(file.get()); + if (progress) { + *progress = 0.0; + if (state == UpdateState::Merging) { + // :TODO: When merging is implemented, set progress_val. + } else if (state == UpdateState::MergeCompleted) { + *progress = 100.0; + } + } + return state; +} + +bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector* snapshots) { + CHECK(lock); + + auto dir_path = metadata_dir_ + "/snapshots"s; + std::unique_ptr dir(opendir(dir_path.c_str()), closedir); + if (!dir) { + PLOG(ERROR) << "opendir failed: " << dir_path; + return false; + } + + struct dirent* dp; + while ((dp = readdir(dir.get())) != nullptr) { + if (dp->d_type != DT_REG) continue; + snapshots->emplace_back(dp->d_name); + } + return true; +} + +auto SnapshotManager::OpenFile(const std::string& file, int open_flags, int lock_flags) + -> std::unique_ptr { + unique_fd fd(open(file.c_str(), open_flags | O_CLOEXEC | O_NOFOLLOW | O_SYNC, 0660)); + if (fd < 0) { + PLOG(ERROR) << "Open failed: " << file; + return nullptr; + } + if (flock(fd, lock_flags) < 0) { + PLOG(ERROR) << "Acquire flock failed: " << file; + return nullptr; + } + return std::make_unique(file, std::move(fd)); +} + +SnapshotManager::LockedFile::~LockedFile() { + if (flock(fd_, LOCK_UN) < 0) { + PLOG(ERROR) << "Failed to unlock file: " << path_; + } +} + +std::unique_ptr SnapshotManager::OpenStateFile(int open_flags, + int lock_flags) { + auto state_file = metadata_dir_ + "/state"s; + return OpenFile(state_file, open_flags, lock_flags); +} + +std::unique_ptr SnapshotManager::LockShared() { + return OpenStateFile(O_RDONLY, LOCK_SH); +} + +std::unique_ptr SnapshotManager::LockExclusive() { + return OpenStateFile(O_RDWR | O_CREAT, LOCK_EX); +} + +UpdateState SnapshotManager::ReadUpdateState(LockedFile* file) { + // Reset position since some calls read+write. + if (lseek(file->fd(), 0, SEEK_SET) < 0) { + PLOG(ERROR) << "lseek state file failed"; + return UpdateState::None; + } + + std::string contents; + if (!android::base::ReadFdToString(file->fd(), &contents)) { + PLOG(ERROR) << "Read state file failed"; + return UpdateState::None; + } + + if (contents.empty() || contents == "none") { + return UpdateState::None; + } else if (contents == "initiated") { + return UpdateState::Initiated; + } else if (contents == "unverified") { + return UpdateState::Unverified; + } else if (contents == "merging") { + return UpdateState::Merging; + } else if (contents == "merge-completed") { + return UpdateState::MergeCompleted; + } else { + LOG(ERROR) << "Unknown merge state in update state file"; + return UpdateState::None; + } +} + +bool SnapshotManager::WriteUpdateState(LockedFile* file, UpdateState state) { + std::string contents; + switch (state) { + case UpdateState::None: + contents = "none"; + break; + case UpdateState::Initiated: + contents = "initiated"; + break; + case UpdateState::Unverified: + contents = "unverified"; + break; + case UpdateState::Merging: + contents = "merging"; + break; + case UpdateState::MergeCompleted: + contents = "merge-completed"; + break; + default: + LOG(ERROR) << "Unknown update state"; + return false; + } + + if (!Truncate(file)) return false; + if (!android::base::WriteStringToFd(contents, file->fd())) { + PLOG(ERROR) << "Could not write to state file"; + return false; + } + return true; +} + +auto SnapshotManager::OpenSnapshotStatusFile(const std::string& name, int open_flags, + int lock_flags) -> std::unique_ptr { + auto file = metadata_dir_ + "/snapshots/"s + name; + return OpenFile(file, open_flags, lock_flags); +} + +bool SnapshotManager::ReadSnapshotStatus(LockedFile* file, SnapshotStatus* status) { + // Reset position since some calls read+write. + if (lseek(file->fd(), 0, SEEK_SET) < 0) { + PLOG(ERROR) << "lseek status file failed"; + return false; + } + + std::string contents; + if (!android::base::ReadFdToString(file->fd(), &contents)) { + PLOG(ERROR) << "read status file failed"; + return false; + } + auto pieces = android::base::Split(contents, " "); + if (pieces.size() != 5) { + LOG(ERROR) << "Invalid status line for snapshot: " << file->path(); + return false; + } + + status->state = pieces[0]; + if (!android::base::ParseUint(pieces[1], &status->device_size)) { + LOG(ERROR) << "Invalid device size in status line for: " << file->path(); + return false; + } + if (!android::base::ParseUint(pieces[2], &status->snapshot_size)) { + LOG(ERROR) << "Invalid snapshot size in status line for: " << file->path(); + return false; + } + if (!android::base::ParseUint(pieces[3], &status->sectors_allocated)) { + LOG(ERROR) << "Invalid snapshot size in status line for: " << file->path(); + return false; + } + if (!android::base::ParseUint(pieces[4], &status->metadata_sectors)) { + LOG(ERROR) << "Invalid snapshot size in status line for: " << file->path(); + return false; + } + return true; +} + +bool SnapshotManager::WriteSnapshotStatus(LockedFile* file, const SnapshotStatus& status) { + std::vector pieces = { + status.state, + std::to_string(status.device_size), + std::to_string(status.snapshot_size), + std::to_string(status.sectors_allocated), + std::to_string(status.metadata_sectors), + }; + auto contents = android::base::Join(pieces, " "); + + if (!Truncate(file)) return false; + if (!android::base::WriteStringToFd(contents, file->fd())) { + PLOG(ERROR) << "write to status file failed: " << file->path(); + return false; + } + return true; +} + +bool SnapshotManager::Truncate(LockedFile* file) { + if (lseek(file->fd(), 0, SEEK_SET) < 0) { + PLOG(ERROR) << "lseek file failed: " << file->path(); + return false; + } + if (ftruncate(file->fd(), 0) < 0) { + PLOG(ERROR) << "truncate failed: " << file->path(); + return false; + } + return true; +} + +std::string SnapshotManager::GetSnapshotDeviceName(const std::string& snapshot_name, + const SnapshotStatus& status) { + if (status.device_size != status.snapshot_size) { + return snapshot_name + "-inner"; + } + return snapshot_name; +} + +bool SnapshotManager::EnsureImageManager() { + if (images_) return true; + + // For now, use a preset timeout. + images_ = android::fiemap::IImageManager::Open(gsid_dir_, 15000ms); + if (!images_) { + LOG(ERROR) << "Could not open ImageManager"; + return false; + } + return true; } } // namespace snapshot diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp new file mode 100644 index 000000000..38ba3649b --- /dev/null +++ b/fs_mgr/libsnapshot/snapshot_test.cpp @@ -0,0 +1,189 @@ +// 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. + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace android { +namespace snapshot { + +using namespace std::chrono_literals; +using namespace std::string_literals; + +class TestDeviceInfo : public SnapshotManager::IDeviceInfo { + public: + std::string GetGsidDir() const override { return "ota/test"s; } + std::string GetMetadataDir() const override { return "/metadata/ota/test"s; } + bool IsRunningSnapshot() const override { return is_running_snapshot_; } + + void set_is_running_snapshot(bool value) { is_running_snapshot_ = value; } + + private: + bool is_running_snapshot_; +}; + +std::unique_ptr sm; +TestDeviceInfo* test_device = nullptr; + +class SnapshotTest : public ::testing::Test { + protected: + void SetUp() override { + test_device->set_is_running_snapshot(false); + + if (sm->GetUpdateState() != UpdateState::None) { + ASSERT_TRUE(sm->CancelUpdate()); + } + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->EnsureImageManager()); + + image_manager_ = sm->image_manager(); + ASSERT_NE(image_manager_, nullptr); + } + + void TearDown() override { + lock_ = nullptr; + + if (sm->GetUpdateState() != UpdateState::None) { + ASSERT_TRUE(sm->CancelUpdate()); + } + for (const auto& temp_image : temp_images_) { + image_manager_->UnmapImageDevice(temp_image); + image_manager_->DeleteBackingImage(temp_image); + } + } + + bool AcquireLock() { + lock_ = sm->OpenStateFile(O_RDWR, LOCK_EX); + return !!lock_; + } + + bool CreateTempDevice(const std::string& name, uint64_t size, std::string* path) { + if (!image_manager_->CreateBackingImage(name, size, false)) { + return false; + } + temp_images_.emplace_back(name); + return image_manager_->MapImageDevice(name, 10s, path); + } + + std::unique_ptr lock_; + std::vector temp_images_; + android::fiemap::IImageManager* image_manager_ = nullptr; +}; + +TEST_F(SnapshotTest, CreateSnapshot) { + ASSERT_TRUE(AcquireLock()); + + static const uint64_t kDeviceSize = 1024 * 1024; + ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kDeviceSize, + kDeviceSize)); + + std::vector snapshots; + ASSERT_TRUE(sm->ListSnapshots(lock_.get(), &snapshots)); + ASSERT_EQ(snapshots.size(), 1); + ASSERT_EQ(snapshots[0], "test-snapshot"); + + // Scope so delete can re-acquire the status file lock. + { + auto file = sm->OpenSnapshotStatusFile("test-snapshot", O_RDONLY, LOCK_SH); + ASSERT_NE(file, nullptr); + + SnapshotManager::SnapshotStatus status; + ASSERT_TRUE(sm->ReadSnapshotStatus(file.get(), &status)); + ASSERT_EQ(status.state, "created"); + ASSERT_EQ(status.device_size, kDeviceSize); + ASSERT_EQ(status.snapshot_size, kDeviceSize); + } + + ASSERT_TRUE(sm->DeleteSnapshot(lock_.get(), "test-snapshot")); +} + +TEST_F(SnapshotTest, MapSnapshot) { + ASSERT_TRUE(AcquireLock()); + + static const uint64_t kDeviceSize = 1024 * 1024; + ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kDeviceSize, + kDeviceSize)); + + std::string base_device; + ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device)); + + std::string snap_device; + ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device)); + ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-")); +} + +TEST_F(SnapshotTest, MapPartialSnapshot) { + ASSERT_TRUE(AcquireLock()); + + static const uint64_t kSnapshotSize = 1024 * 1024; + static const uint64_t kDeviceSize = 1024 * 1024 * 2; + ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kSnapshotSize, + kSnapshotSize)); + + std::string base_device; + ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device)); + + std::string snap_device; + ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device)); + ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-")); +} + +} // namespace snapshot +} // namespace android + +using namespace android::snapshot; + +bool Mkdir(const std::string& path) { + if (mkdir(path.c_str(), 0700) && errno != EEXIST) { + std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl; + return false; + } + return true; +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + + std::vector paths = { + "/data/gsi/ota/test", + "/metadata/gsi/ota/test", + "/metadata/ota/test", + "/metadata/ota/test/snapshots", + }; + for (const auto& path : paths) { + if (!Mkdir(path)) { + return 1; + } + } + + // Create this once, otherwise, gsid will start/stop between each test. + test_device = new TestDeviceInfo(); + sm = SnapshotManager::New(test_device); + if (!sm) { + std::cerr << "Could not create snapshot manager"; + return 1; + } + + return RUN_ALL_TESTS(); +} diff --git a/rootdir/init.rc b/rootdir/init.rc index d22e9a792..86d804285 100644 --- a/rootdir/init.rc +++ b/rootdir/init.rc @@ -415,6 +415,7 @@ on post-fs chmod 0700 /metadata/vold mkdir /metadata/password_slots 0771 root system mkdir /metadata/ota 0700 root system + mkdir /metadata/ota/snapshots 0700 root system mkdir /metadata/apex 0700 root system mkdir /metadata/apex/sessions 0700 root system