Merge "Implement basic libsnapshot functionality."

This commit is contained in:
David Anderson 2019-08-03 01:30:49 +00:00 committed by Gerrit Code Review
commit 23a87716b5
8 changed files with 946 additions and 65 deletions

View file

@ -210,6 +210,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<std::string> argv = {
cipher_,

View file

@ -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 {

View file

@ -219,6 +219,8 @@ class DmTargetSnapshot final : public DmTarget {
static double MergePercent(const Status& status, uint64_t sectors_initial = 0);
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_;

View file

@ -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",
],
}

View file

@ -19,14 +19,28 @@
#include <chrono>
#include <memory>
#include <string>
#include <vector>
#include <android-base/unique_fd.h>
#include <libdm/dm_target.h>
#include <libfiemap/image_manager.h>
#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<SnapshotManager> 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<SnapshotManager> 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<LockedFile> 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<std::string>* snapshots);
// Interact with /metadata/ota/state.
std::unique_ptr<LockedFile> OpenStateFile(int open_flags, int lock_flags);
std::unique_ptr<LockedFile> LockShared();
std::unique_ptr<LockedFile> 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<LockedFile> 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<IDeviceInfo> device_;
std::unique_ptr<IImageManager> images_;
};
} // namespace snapshot
} // namespace android
#ifdef DEFINED_FRIEND_TEST
#undef DEFINED_FRIEND_TEST
#undef FRIEND_TEST
#endif

View file

@ -14,45 +14,309 @@
#include <libsnapshot/snapshot.h>
#include <dirent.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/unistd.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <ext4_utils/ext4_utils.h>
#include <libdm/dm.h>
namespace android {
namespace snapshot {
std::unique_ptr<SnapshotManager> SnapshotManager::New() {
return std::make_unique<SnapshotManager>();
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> SnapshotManager::New(IDeviceInfo* info) {
if (!info) {
info = new DeviceInfo();
}
return std::unique_ptr<SnapshotManager>(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<DmTargetSnapshot>(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<DmTargetLinear>(0, snapshot_sectors, *dev_path, 0);
table.Emplace<DmTargetLinear>(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<std::string> 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<std::string>* snapshots) {
CHECK(lock);
auto dir_path = metadata_dir_ + "/snapshots"s;
std::unique_ptr<DIR, decltype(&closedir)> 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<LockedFile> {
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<LockedFile>(file, std::move(fd));
}
SnapshotManager::LockedFile::~LockedFile() {
if (flock(fd_, LOCK_UN) < 0) {
PLOG(ERROR) << "Failed to unlock file: " << path_;
}
}
std::unique_ptr<SnapshotManager::LockedFile> 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::LockedFile> SnapshotManager::LockShared() {
return OpenStateFile(O_RDONLY, LOCK_SH);
}
std::unique_ptr<SnapshotManager::LockedFile> 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<LockedFile> {
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<std::string> 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

View file

@ -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 <libsnapshot/snapshot.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <chrono>
#include <iostream>
#include <android-base/strings.h>
#include <gtest/gtest.h>
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<SnapshotManager> 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<SnapshotManager::LockedFile> lock_;
std::vector<std::string> 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<std::string> 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<std::string> 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();
}

View file

@ -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