Merge "ImageManager returns FiemapStatus."

This commit is contained in:
Yifan Hong 2020-01-08 02:57:37 +00:00 committed by Gerrit Code Review
commit 70768af0fe
7 changed files with 220 additions and 46 deletions

View file

@ -17,6 +17,7 @@
#if !defined(__ANDROID_RECOVERY__)
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android/gsi/BnProgressCallback.h>
#include <android/gsi/IGsiService.h>
#include <android/gsi/IGsid.h>
#include <binder/IServiceManager.h>
@ -29,10 +30,29 @@ namespace fiemap {
using namespace android::gsi;
using namespace std::chrono_literals;
class ProgressCallback final : public BnProgressCallback {
public:
ProgressCallback(std::function<bool(uint64_t, uint64_t)>&& callback)
: callback_(std::move(callback)) {
CHECK(callback_);
}
android::binder::Status onProgress(int64_t current, int64_t total) {
if (callback_(static_cast<uint64_t>(current), static_cast<uint64_t>(total))) {
return android::binder::Status::ok();
}
return android::binder::Status::fromServiceSpecificError(UNKNOWN_ERROR,
"Progress callback failed");
}
private:
std::function<bool(uint64_t, uint64_t)> callback_;
};
class ImageManagerBinder final : public IImageManager {
public:
ImageManagerBinder(android::sp<IGsiService>&& service, android::sp<IImageService>&& manager);
bool CreateBackingImage(const std::string& name, uint64_t size, int flags) override;
FiemapStatus CreateBackingImage(const std::string& name, uint64_t size, int flags,
std::function<bool(uint64_t, uint64_t)>&& on_progress) override;
bool DeleteBackingImage(const std::string& name) override;
bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
std::string* path) override;
@ -41,7 +61,7 @@ class ImageManagerBinder final : public IImageManager {
bool IsImageMapped(const std::string& name) override;
bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name,
std::string* dev) override;
bool ZeroFillNewImage(const std::string& name, uint64_t bytes) override;
FiemapStatus ZeroFillNewImage(const std::string& name, uint64_t bytes) override;
bool RemoveAllImages() override;
bool DisableImage(const std::string& name) override;
bool RemoveDisabledImages() override;
@ -55,18 +75,31 @@ class ImageManagerBinder final : public IImageManager {
android::sp<IImageService> manager_;
};
static FiemapStatus ToFiemapStatus(const char* func, const binder::Status& status) {
if (!status.isOk()) {
LOG(ERROR) << func << " binder returned: " << status.toString8().string();
if (status.serviceSpecificErrorCode() != 0) {
return FiemapStatus::FromErrorCode(status.serviceSpecificErrorCode());
} else {
return FiemapStatus::Error();
}
}
return FiemapStatus::Ok();
}
ImageManagerBinder::ImageManagerBinder(android::sp<IGsiService>&& service,
android::sp<IImageService>&& manager)
: service_(std::move(service)), manager_(std::move(manager)) {}
bool ImageManagerBinder::CreateBackingImage(const std::string& name, uint64_t size, int flags) {
auto status = manager_->createBackingImage(name, size, flags);
if (!status.isOk()) {
LOG(ERROR) << __PRETTY_FUNCTION__
<< " binder returned: " << status.exceptionMessage().string();
return false;
FiemapStatus ImageManagerBinder::CreateBackingImage(
const std::string& name, uint64_t size, int flags,
std::function<bool(uint64_t, uint64_t)>&& on_progress) {
sp<IProgressCallback> callback = nullptr;
if (on_progress) {
callback = new ProgressCallback(std::move(on_progress));
}
return true;
auto status = manager_->createBackingImage(name, size, flags, callback);
return ToFiemapStatus(__PRETTY_FUNCTION__, status);
}
bool ImageManagerBinder::DeleteBackingImage(const std::string& name) {
@ -147,14 +180,9 @@ std::vector<std::string> ImageManagerBinder::GetAllBackingImages() {
return retval;
}
bool ImageManagerBinder::ZeroFillNewImage(const std::string& name, uint64_t bytes) {
FiemapStatus ImageManagerBinder::ZeroFillNewImage(const std::string& name, uint64_t bytes) {
auto status = manager_->zeroFillNewImage(name, bytes);
if (!status.isOk()) {
LOG(ERROR) << __PRETTY_FUNCTION__
<< " binder returned: " << status.exceptionMessage().string();
return false;
}
return true;
return ToFiemapStatus(__PRETTY_FUNCTION__, status);
}
bool ImageManagerBinder::RemoveAllImages() {

View file

@ -133,27 +133,25 @@ bool ImageManager::BackingImageExists(const std::string& name) {
return access(header_file.c_str(), F_OK) == 0;
}
bool ImageManager::CreateBackingImage(const std::string& name, uint64_t size, int flags) {
return CreateBackingImage(name, size, flags, nullptr);
}
static bool IsUnreliablePinningAllowed(const std::string& path) {
return android::base::StartsWith(path, "/data/gsi/dsu/") ||
android::base::StartsWith(path, "/data/gsi/test/") ||
android::base::StartsWith(path, "/data/gsi/ota/test/");
}
bool ImageManager::CreateBackingImage(const std::string& name, uint64_t size, int flags,
std::function<bool(uint64_t, uint64_t)>&& on_progress) {
FiemapStatus ImageManager::CreateBackingImage(
const std::string& name, uint64_t size, int flags,
std::function<bool(uint64_t, uint64_t)>&& on_progress) {
auto data_path = GetImageHeaderPath(name);
auto fw = SplitFiemap::Create(data_path, size, 0, on_progress);
if (!fw) {
return false;
std::unique_ptr<SplitFiemap> fw;
auto status = SplitFiemap::Create(data_path, size, 0, &fw, on_progress);
if (!status.is_ok()) {
return status;
}
bool reliable_pinning;
if (!FilesystemHasReliablePinning(data_path, &reliable_pinning)) {
return false;
return FiemapStatus::Error();
}
if (!reliable_pinning && !IsUnreliablePinningAllowed(data_path)) {
// For historical reasons, we allow unreliable pinning for certain use
@ -164,7 +162,7 @@ bool ImageManager::CreateBackingImage(const std::string& name, uint64_t size, in
// proper pinning.
LOG(ERROR) << "File system does not have reliable block pinning";
SplitFiemap::RemoveSplitFiles(data_path);
return false;
return FiemapStatus::Error();
}
// Except for testing, we do not allow persisting metadata that references
@ -180,24 +178,25 @@ bool ImageManager::CreateBackingImage(const std::string& name, uint64_t size, in
fw = {};
SplitFiemap::RemoveSplitFiles(data_path);
return false;
return FiemapStatus::Error();
}
bool readonly = !!(flags & CREATE_IMAGE_READONLY);
if (!UpdateMetadata(metadata_dir_, name, fw.get(), size, readonly)) {
return false;
return FiemapStatus::Error();
}
if (flags & CREATE_IMAGE_ZERO_FILL) {
if (!ZeroFillNewImage(name, 0)) {
auto res = ZeroFillNewImage(name, 0);
if (!res.is_ok()) {
DeleteBackingImage(name);
return false;
return res;
}
}
return true;
return FiemapStatus::Ok();
}
bool ImageManager::ZeroFillNewImage(const std::string& name, uint64_t bytes) {
FiemapStatus ImageManager::ZeroFillNewImage(const std::string& name, uint64_t bytes) {
auto data_path = GetImageHeaderPath(name);
// See the comment in MapImageDevice() about how this works.
@ -205,13 +204,13 @@ bool ImageManager::ZeroFillNewImage(const std::string& name, uint64_t bytes) {
bool can_use_devicemapper;
if (!FiemapWriter::GetBlockDeviceForFile(data_path, &block_device, &can_use_devicemapper)) {
LOG(ERROR) << "Could not determine block device for " << data_path;
return false;
return FiemapStatus::Error();
}
if (!can_use_devicemapper) {
// We've backed with loop devices, and since we store files in an
// unencrypted folder, the initial zeroes we wrote will suffice.
return true;
return FiemapStatus::Ok();
}
// data is dm-crypt, or FBE + dm-default-key. This means the zeroes written
@ -219,7 +218,7 @@ bool ImageManager::ZeroFillNewImage(const std::string& name, uint64_t bytes) {
// this.
auto device = MappedDevice::Open(this, 10s, name);
if (!device) {
return false;
return FiemapStatus::Error();
}
static constexpr size_t kChunkSize = 4096;
@ -232,7 +231,7 @@ bool ImageManager::ZeroFillNewImage(const std::string& name, uint64_t bytes) {
remaining = get_block_device_size(device->fd());
if (!remaining) {
PLOG(ERROR) << "Could not get block device size for " << device->path();
return false;
return FiemapStatus::FromErrno(errno);
}
}
while (remaining) {
@ -240,11 +239,11 @@ bool ImageManager::ZeroFillNewImage(const std::string& name, uint64_t bytes) {
if (!android::base::WriteFully(device->fd(), zeroes.data(),
static_cast<size_t>(to_write))) {
PLOG(ERROR) << "write failed: " << device->path();
return false;
return FiemapStatus::FromErrno(errno);
}
remaining -= to_write;
}
return true;
return FiemapStatus::Ok();
}
bool ImageManager::DeleteBackingImage(const std::string& name) {

View file

@ -37,6 +37,12 @@ class FiemapStatus {
// Create from a given errno (specified in errno,h)
static FiemapStatus FromErrno(int error_num) { return FiemapStatus(CastErrorCode(-error_num)); }
// Create from an integer error code that is expected to be an ErrorCode
// value. If it isn't, Error() is returned.
static FiemapStatus FromErrorCode(int32_t error_code) {
return FiemapStatus(CastErrorCode(error_code));
}
// Generic error.
static FiemapStatus Error() { return FiemapStatus(ErrorCode::ERROR); }

View file

@ -25,6 +25,7 @@
#include <string>
#include <android-base/unique_fd.h>
#include <libfiemap/fiemap_status.h>
#include <liblp/partition_opener.h>
namespace android {
@ -52,7 +53,9 @@ class IImageManager {
// of the image is undefined. If zero-fill is requested, and the operation
// cannot be completed, the image will be deleted and this function will
// return false.
virtual bool CreateBackingImage(const std::string& name, uint64_t size, int flags) = 0;
virtual FiemapStatus CreateBackingImage(
const std::string& name, uint64_t size, int flags,
std::function<bool(uint64_t, uint64_t)>&& on_progress = nullptr) = 0;
// Delete an image created with CreateBackingImage. Its entry will be
// removed from the associated lp_metadata file.
@ -113,7 +116,7 @@ class IImageManager {
// Writes |bytes| zeros to |name| file. If |bytes| is 0, then the
// whole file if filled with zeros.
virtual bool ZeroFillNewImage(const std::string& name, uint64_t bytes) = 0;
virtual FiemapStatus ZeroFillNewImage(const std::string& name, uint64_t bytes) = 0;
// Find and remove all images and metadata for this manager.
virtual bool RemoveAllImages() = 0;
@ -133,7 +136,8 @@ class ImageManager final : public IImageManager {
static std::unique_ptr<ImageManager> Open(const std::string& dir_prefix);
// Methods that must be implemented from IImageManager.
bool CreateBackingImage(const std::string& name, uint64_t size, int flags) override;
FiemapStatus CreateBackingImage(const std::string& name, uint64_t size, int flags,
std::function<bool(uint64_t, uint64_t)>&& on_progress) override;
bool DeleteBackingImage(const std::string& name) override;
bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
std::string* path) override;
@ -149,9 +153,6 @@ class ImageManager final : public IImageManager {
bool MapAllImages(const std::function<bool(std::set<std::string>)>& init) override;
std::vector<std::string> GetAllBackingImages();
// Same as CreateBackingImage, but provides a progress notification.
bool CreateBackingImage(const std::string& name, uint64_t size, int flags,
std::function<bool(uint64_t, uint64_t)>&& on_progress);
// Returns true if the named partition exists. This does not check the
// consistency of the backing image/data file.
@ -164,7 +165,7 @@ class ImageManager final : public IImageManager {
void set_partition_opener(std::unique_ptr<IPartitionOpener>&& opener);
// Writes |bytes| zeros at the beginning of the passed image
bool ZeroFillNewImage(const std::string& name, uint64_t bytes);
FiemapStatus ZeroFillNewImage(const std::string& name, uint64_t bytes);
private:
ImageManager(const std::string& metadata_dir, const std::string& data_dir);

View file

@ -14,10 +14,12 @@
#pragma once
#include <memory>
#include <optional>
#include <string>
#include <unordered_set>
#include <android-base/file.h>
#include <android/hardware/boot/1.1/IBootControl.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@ -155,5 +157,28 @@ void SetSize(PartitionUpdate* partition_update, uint64_t size);
// Get partition size from update package metadata.
uint64_t GetSize(PartitionUpdate* partition_update);
// Util class for test cases on low space scenario. These tests assumes image manager
// uses /data as backup device.
class LowSpaceUserdata {
public:
// Set the maximum free space allowed for this test. If /userdata has more space than the given
// number, a file is allocated to consume space.
AssertionResult Init(uint64_t max_free_space);
uint64_t free_space() const;
uint64_t available_space() const;
uint64_t bsize() const;
private:
AssertionResult ReadUserdataStats();
static constexpr const char* kUserDataDevice = "/data";
std::unique_ptr<TemporaryFile> big_file_;
bool initialized_ = false;
uint64_t free_space_ = 0;
uint64_t available_space_ = 0;
uint64_t bsize_ = 0;
};
} // namespace snapshot
} // namespace android

View file

@ -47,6 +47,7 @@ namespace snapshot {
using android::base::unique_fd;
using android::dm::DeviceMapper;
using android::dm::DmDeviceState;
using android::fiemap::FiemapStatus;
using android::fiemap::IImageManager;
using android::fs_mgr::BlockDeviceInfo;
using android::fs_mgr::CreateLogicalPartitionParams;
@ -1700,6 +1701,55 @@ INSTANTIATE_TEST_SUITE_P(Snapshot, FlashAfterUpdateTest, Combine(Values(0, 1), B
"Merge"s;
});
// Test behavior of ImageManager::Create on low space scenario. These tests assumes image manager
// uses /data as backup device.
class ImageManagerTest : public SnapshotTest, public WithParamInterface<uint64_t> {
public:
void SetUp() override {
SnapshotTest::SetUp();
userdata_ = std::make_unique<LowSpaceUserdata>();
ASSERT_TRUE(userdata_->Init(GetParam()));
}
void TearDown() override {
EXPECT_TRUE(!image_manager_->BackingImageExists(kImageName) ||
image_manager_->DeleteBackingImage(kImageName));
}
static constexpr const char* kImageName = "my_image";
std::unique_ptr<LowSpaceUserdata> userdata_;
};
TEST_P(ImageManagerTest, CreateImageEnoughAvailSpace) {
if (userdata_->available_space() == 0) {
GTEST_SKIP() << "/data is full (" << userdata_->available_space()
<< " bytes available), skipping";
}
ASSERT_TRUE(image_manager_->CreateBackingImage(kImageName, userdata_->available_space(),
IImageManager::CREATE_IMAGE_DEFAULT))
<< "Should be able to create image with size = " << userdata_->available_space()
<< " bytes";
ASSERT_TRUE(image_manager_->DeleteBackingImage(kImageName))
<< "Should be able to delete created image";
}
TEST_P(ImageManagerTest, CreateImageNoSpace) {
uint64_t to_allocate = userdata_->free_space() + userdata_->bsize();
auto res = image_manager_->CreateBackingImage(kImageName, to_allocate,
IImageManager::CREATE_IMAGE_DEFAULT);
ASSERT_FALSE(res) << "Should not be able to create image with size = " << to_allocate
<< " bytes because only " << userdata_->free_space() << " bytes are free";
ASSERT_EQ(FiemapStatus::ErrorCode::NO_SPACE, res.error_code()) << res.string();
}
std::vector<uint64_t> ImageManagerTestParams() {
std::vector<uint64_t> ret;
for (uint64_t size = 1_MiB; size <= 512_MiB; size *= 2) {
ret.push_back(size);
}
return ret;
}
INSTANTIATE_TEST_SUITE_P(ImageManagerTest, ImageManagerTest, ValuesIn(ImageManagerTestParams()));
} // namespace snapshot
} // namespace android

View file

@ -14,8 +14,11 @@
#include <libsnapshot/test_helpers.h>
#include <sys/statvfs.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
#include <openssl/sha.h>
@ -167,5 +170,67 @@ uint64_t GetSize(PartitionUpdate* partition_update) {
return partition_update->mutable_new_partition_info()->size();
}
AssertionResult LowSpaceUserdata::Init(uint64_t max_free_space) {
auto res = ReadUserdataStats();
if (!res) return res;
// Try to fill up the disk as much as possible until free_space_ <= max_free_space.
big_file_ = std::make_unique<TemporaryFile>();
if (big_file_->fd == -1) {
return AssertionFailure() << strerror(errno);
}
if (!android::base::StartsWith(big_file_->path, kUserDataDevice)) {
return AssertionFailure() << "Temp file allocated to " << big_file_->path << ", not in "
<< kUserDataDevice;
}
uint64_t next_consume =
std::min(free_space_ - max_free_space, (uint64_t)std::numeric_limits<off_t>::max());
off_t allocated = 0;
while (next_consume > 0 && free_space_ > max_free_space) {
int status = fallocate(big_file_->fd, 0, allocated, next_consume);
if (status == -1 && errno == ENOSPC) {
next_consume /= 2;
continue;
}
if (status == -1) {
return AssertionFailure() << strerror(errno);
}
allocated += next_consume;
res = ReadUserdataStats();
if (!res) return res;
}
LOG(INFO) << allocated << " bytes allocated to " << big_file_->path;
initialized_ = true;
return AssertionSuccess();
}
AssertionResult LowSpaceUserdata::ReadUserdataStats() {
struct statvfs buf;
if (statvfs(kUserDataDevice, &buf) == -1) {
return AssertionFailure() << strerror(errno);
}
bsize_ = buf.f_bsize;
free_space_ = buf.f_bsize * buf.f_bfree;
available_space_ = buf.f_bsize * buf.f_bavail;
return AssertionSuccess();
}
uint64_t LowSpaceUserdata::free_space() const {
CHECK(initialized_);
return free_space_;
}
uint64_t LowSpaceUserdata::available_space() const {
CHECK(initialized_);
return available_space_;
}
uint64_t LowSpaceUserdata::bsize() const {
CHECK(initialized_);
return bsize_;
}
} // namespace snapshot
} // namespace android