blob: fcaa73ad72f1252e3c5ff8301e276d321cb5723b [file] [log] [blame]
// Copyright (C) 2019 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.
#pragma once
#include <stdint.h>
#include <chrono>
#include <map>
#include <memory>
#include <ostream>
#include <string>
#include <string_view>
#include <vector>
#include <android-base/unique_fd.h>
#include <fs_mgr_dm_linear.h>
#include <libdm/dm.h>
#include <libfiemap/image_manager.h>
#include <liblp/builder.h>
#include <liblp/liblp.h>
#include <update_engine/update_metadata.pb.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 fiemap {
class IImageManager;
} // namespace fiemap
namespace fs_mgr {
struct CreateLogicalPartitionParams;
class IPartitionOpener;
} // namespace fs_mgr
// Forward declare IBootControl types since we cannot include only the headers
// with Soong. Note: keep the enum width in sync.
namespace hardware {
namespace boot {
namespace V1_1 {
enum class MergeStatus : int32_t;
} // namespace V1_1
} // namespace boot
} // namespace hardware
namespace snapshot {
struct AutoDeleteCowImage;
struct AutoDeleteSnapshot;
struct AutoDeviceList;
struct PartitionCowCreator;
class SnapshotStatus;
static constexpr const std::string_view kCowGroupName = "cow";
enum class UpdateState : unsigned int {
// 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,
// The kernel is merging in the background.
Merging,
// Post-merge cleanup steps could not be completed due to a transient
// error, but the next reboot will finish any pending operations.
MergeNeedsReboot,
// Merging is complete, and needs to be acknowledged.
MergeCompleted,
// Merging failed due to an unrecoverable error.
MergeFailed,
// The update was implicitly cancelled, either by a rollback or a flash
// operation via fastboot. This state can only be returned by WaitForMerge.
Cancelled
};
std::ostream& operator<<(std::ostream& os, UpdateState state);
class SnapshotManager final {
using CreateLogicalPartitionParams = android::fs_mgr::CreateLogicalPartitionParams;
using IPartitionOpener = android::fs_mgr::IPartitionOpener;
using LpMetadata = android::fs_mgr::LpMetadata;
using MetadataBuilder = android::fs_mgr::MetadataBuilder;
using DeltaArchiveManifest = chromeos_update_engine::DeltaArchiveManifest;
using MergeStatus = android::hardware::boot::V1_1::MergeStatus;
public:
// Dependency injection for testing.
class IDeviceInfo {
public:
virtual ~IDeviceInfo() {}
virtual std::string GetGsidDir() const = 0;
virtual std::string GetMetadataDir() const = 0;
virtual std::string GetSlotSuffix() const = 0;
virtual std::string GetOtherSlotSuffix() const = 0;
virtual std::string GetSuperDevice(uint32_t slot) const = 0;
virtual const IPartitionOpener& GetPartitionOpener() const = 0;
virtual bool IsOverlayfsSetup() const = 0;
virtual bool SetBootControlMergeStatus(MergeStatus status) = 0;
};
~SnapshotManager();
// 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);
// This is similar to New(), except designed specifically for first-stage
// init.
static std::unique_ptr<SnapshotManager> NewForFirstStageMount(IDeviceInfo* device = nullptr);
// Helper function for first-stage init to check whether a SnapshotManager
// might be needed to perform first-stage mounts.
static bool IsSnapshotManagerNeeded();
// Begin an update. This must be called before creating any snapshots. It
// will fail if GetUpdateState() != None.
bool BeginUpdate();
// Cancel an update; any snapshots will be deleted. This is allowed if the
// state == Initiated, None, or Unverified (before rebooting to the new
// slot).
bool CancelUpdate();
// Mark snapshot writes as having completed. After this, new snapshots cannot
// be created, and the device must either cancel the OTA (either before
// rebooting or after rolling back), or merge the OTA.
bool FinishedSnapshotWrites();
// Initiate a merge on all snapshot devices. This should only be used after an
// update has been marked successful after booting.
bool InitiateMerge();
// Perform any necessary post-boot actions. This should be run soon after
// /data is mounted.
//
// If a merge is in progress, this function will block until the merge is
// completed. If a merge or update was cancelled, this will clean up any
// update artifacts and return.
//
// Note that after calling this, GetUpdateState() may still return that a
// merge is in progress:
// MergeFailed indicates that a fatal error occurred. WaitForMerge() may
// called any number of times again to attempt to make more progress, but
// we do not expect it to succeed if a catastrophic error occurred.
//
// MergeNeedsReboot indicates that the merge has completed, but cleanup
// failed. This can happen if for some reason resources were not closed
// properly. In this case another reboot is needed before we can take
// another OTA. However, WaitForMerge() can be called again without
// rebooting, to attempt to finish cleanup anyway.
//
// MergeCompleted indicates that the update has fully completed.
// GetUpdateState will return None, and a new update can begin.
UpdateState ProcessUpdateState();
// Find the status of the current update, if any.
//
// |progress| depends on the returned status:
// Merging: Value in the range [0, 100]
// MergeCompleted: 100
// Other: 0
UpdateState GetUpdateState(double* progress = nullptr);
// 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.
bool CreateUpdateSnapshots(const DeltaArchiveManifest& manifest);
// Map a snapshotted partition for OTA clients to write to. Write-protected regions are
// determined previously in CreateSnapshots.
bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params, std::string* snapshot_path);
// Unmap a snapshot device that's previously mapped with MapUpdateSnapshot.
bool UnmapUpdateSnapshot(const std::string& target_partition_name);
// If this returns true, first-stage mount must call
// CreateLogicalAndSnapshotPartitions rather than CreateLogicalPartitions.
bool NeedSnapshotsInFirstStageMount();
// Perform first-stage mapping of snapshot targets. This replaces init's
// call to CreateLogicalPartitions when snapshots are present.
bool CreateLogicalAndSnapshotPartitions(const std::string& super_device);
// Dump debug information.
bool Dump(std::ostream& os);
private:
FRIEND_TEST(SnapshotTest, CleanFirstStageMount);
FRIEND_TEST(SnapshotTest, CreateSnapshot);
FRIEND_TEST(SnapshotTest, FirstStageMountAfterRollback);
FRIEND_TEST(SnapshotTest, FirstStageMountAndMerge);
FRIEND_TEST(SnapshotTest, FlashSuperDuringMerge);
FRIEND_TEST(SnapshotTest, FlashSuperDuringUpdate);
FRIEND_TEST(SnapshotTest, MapPartialSnapshot);
FRIEND_TEST(SnapshotTest, MapSnapshot);
FRIEND_TEST(SnapshotTest, Merge);
FRIEND_TEST(SnapshotTest, MergeCannotRemoveCow);
FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
FRIEND_TEST(SnapshotTest, UpdateBootControlHal);
FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow);
friend class SnapshotTest;
friend class SnapshotUpdateTest;
friend struct AutoDeleteCowImage;
friend struct AutoDeleteSnapshot;
friend struct PartitionCowCreator;
using DmTargetSnapshot = android::dm::DmTargetSnapshot;
using IImageManager = android::fiemap::IImageManager;
using TargetInfo = android::dm::DeviceMapper::TargetInfo;
explicit SnapshotManager(IDeviceInfo* info);
// This is created lazily since it can connect via binder.
bool EnsureImageManager();
// Helper for first-stage init.
bool ForceLocalImageManager();
// 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, int lock_mode)
: path_(path), fd_(std::move(fd)), lock_mode_(lock_mode) {}
~LockedFile();
const std::string& path() const { return path_; }
int fd() const { return fd_; }
int lock_mode() const { return lock_mode_; }
private:
std::string path_;
android::base::unique_fd fd_;
int lock_mode_;
};
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().
//
// |status|.device_size should be the size of the base_device that will be passed
// via MapDevice(). |status|.snapshot_size should be the number of bytes in the
// base device, starting from 0, that will be snapshotted. |status|.cow_file_size
// should be the amount of space that will be allocated to store snapshot
// deltas.
//
// If |status|.snapshot_size < |status|.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, snapshot, COW partition and COW file sizes
// must be a multiple of the sector size (512 bytes).
bool CreateSnapshot(LockedFile* lock, SnapshotStatus* status);
// |name| should be the base partition name (e.g. "system_a"). Create the
// backing COW image using the size previously passed to CreateSnapshot().
bool CreateCowImage(LockedFile* lock, const std::string& name);
// 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::string& cow_device, const std::chrono::milliseconds& timeout_ms,
std::string* dev_path);
// Map a COW image that was previous created with CreateCowImage.
bool MapCowImage(const std::string& name, const std::chrono::milliseconds& timeout_ms);
// Remove the backing copy-on-write image and snapshot states for the named snapshot. The
// caller is responsible for ensuring that the snapshot is unmapped.
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 a COW image device previously mapped with MapCowImage().
bool UnmapCowImage(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);
// Check for a cancelled or rolled back merge, returning true if such a
// condition was detected and handled.
bool HandleCancelledUpdate(LockedFile* lock);
// Remove artifacts created by the update process, such as snapshots, and
// set the update state to None.
bool RemoveAllUpdateState(LockedFile* lock);
// 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);
std::string GetStateFilePath() const;
// Helpers for merging.
bool SwitchSnapshotToMerge(LockedFile* lock, const std::string& name);
bool RewriteSnapshotDeviceTable(const std::string& dm_name);
bool MarkSnapshotMergeCompleted(LockedFile* snapshot_lock, const std::string& snapshot_name);
void AcknowledgeMergeSuccess(LockedFile* lock);
void AcknowledgeMergeFailure();
bool IsCancelledSnapshot(const std::string& snapshot_name);
// Note that these require the name of the device containing the snapshot,
// which may be the "inner" device. Use GetsnapshotDeviecName().
bool QuerySnapshotStatus(const std::string& dm_name, std::string* target_type,
DmTargetSnapshot::Status* status);
bool IsSnapshotDevice(const std::string& dm_name, TargetInfo* target = nullptr);
// 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);
// Only the following UpdateStates are used here:
// UpdateState::Merging
// UpdateState::MergeCompleted
// UpdateState::MergeFailed
// UpdateState::MergeNeedsReboot
UpdateState CheckMergeState();
UpdateState CheckMergeState(LockedFile* lock);
UpdateState CheckTargetMergeState(LockedFile* lock, const std::string& name);
// Interact with status files under /metadata/ota/snapshots.
bool WriteSnapshotStatus(LockedFile* lock, const SnapshotStatus& status);
bool ReadSnapshotStatus(LockedFile* lock, const std::string& name, SnapshotStatus* status);
std::string GetSnapshotStatusFilePath(const std::string& name);
std::string GetSnapshotBootIndicatorPath();
void RemoveSnapshotBootIndicator();
// 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);
// Map the base device, COW devices, and snapshot device.
bool MapPartitionWithSnapshot(LockedFile* lock, CreateLogicalPartitionParams params,
std::string* path);
// Map the COW devices, including the partition in super and the images.
// |params|:
// - |partition_name| should be the name of the top-level partition (e.g. system_b),
// not system_b-cow-img
// - |device_name| and |partition| is ignored
// - |timeout_ms| and the rest is respected
// Return the path in |cow_device_path| (e.g. /dev/block/dm-1) and major:minor in
// |cow_device_string|
bool MapCowDevices(LockedFile* lock, const CreateLogicalPartitionParams& params,
const SnapshotStatus& snapshot_status, AutoDeviceList* created_devices,
std::string* cow_name);
// The reverse of MapCowDevices.
bool UnmapCowDevices(LockedFile* lock, const std::string& name);
// The reverse of MapPartitionWithSnapshot.
bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_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
// update and delete any snapshots. Return true if successful. |needs_merge| is set to false.
// If there is a previous update and the device has boot into it, do nothing and return true.
// |needs_merge| is set to true.
bool TryCancelUpdate(bool* needs_merge);
// Helper for CreateUpdateSnapshots.
// Creates all underlying images, COW partitions and snapshot files. Does not initialize them.
bool CreateUpdateSnapshotsInternal(LockedFile* lock, const DeltaArchiveManifest& manifest,
PartitionCowCreator* cow_creator,
AutoDeviceList* created_devices,
std::map<std::string, SnapshotStatus>* all_snapshot_status);
// Initialize snapshots so that they can be mapped later.
// Map the COW partition and zero-initialize the header.
bool InitializeUpdateSnapshots(
LockedFile* lock, MetadataBuilder* target_metadata,
const LpMetadata* exported_target_metadata, const std::string& target_suffix,
const std::map<std::string, SnapshotStatus>& all_snapshot_status);
std::string gsid_dir_;
std::string metadata_dir_;
std::unique_ptr<IDeviceInfo> device_;
std::unique_ptr<IImageManager> images_;
bool has_local_image_manager_ = false;
};
} // namespace snapshot
} // namespace android
#ifdef DEFINED_FRIEND_TEST
#undef DEFINED_FRIEND_TEST
#undef FRIEND_TEST
#endif