Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 456e5019 authored by David Anderson's avatar David Anderson
Browse files

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
parent 549ea480
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -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<std::string> argv = {
            cipher_,
+2 −0
Original line number Diff line number Diff line
@@ -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 {
+2 −0
Original line number Diff line number Diff line
@@ -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_;
+51 −7
Original line number Diff line number Diff line
@@ -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",
    ],
}
+165 −31
Original line number Diff line number Diff line
@@ -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;

        // 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;
    };

    // 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 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);

    // 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);
    // Begin an update. This must be called before creating any snapshots. It
    // will fail if GetUpdateState() != None.
    bool BeginUpdate();

    // Unmap a snapshot device previously mapped with MapSnapshotDevice().
    bool UnmapSnapshotDevice(const std::string& name);

    // 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
Loading