C++程序  |  1248行  |  40.42 KB

/*
 * 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.
 */

#include "gsi_service.h"

#include <errno.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <sys/vfs.h>
#include <unistd.h>

#include <chrono>
#include <string>
#include <vector>

#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android/gsi/IGsiService.h>
#include <ext4_utils/ext4_utils.h>
#include <fs_mgr.h>
#include <fs_mgr_dm_linear.h>
#include <fstab/fstab.h>
#include <libdm/dm.h>
#include <libfiemap_writer/fiemap_writer.h>
#include <logwrap/logwrap.h>
#include <private/android_filesystem_config.h>

#include "file_paths.h"
#include "libgsi_private.h"

namespace android {
namespace gsi {

using namespace std::literals;
using namespace android::dm;
using namespace android::fs_mgr;
using namespace android::fiemap_writer;
using android::base::StringPrintf;
using android::base::unique_fd;

static constexpr char kUserdataDevice[] = "/dev/block/by-name/userdata";

// The default size of userdata.img for GSI.
// We are looking for /data to have atleast 40% free space
static constexpr uint32_t kMinimumFreeSpaceThreshold = 40;
// We determine the fragmentation by making sure the files
// we create don't have more than 16 extents.
static constexpr uint32_t kMaximumExtents = 512;
// Default userdata image size.
static constexpr int64_t kDefaultUserdataSize = int64_t(8) * 1024 * 1024 * 1024;
static constexpr std::chrono::milliseconds kDmTimeout = 5000ms;

void GsiService::Register() {
    auto ret = android::BinderService<GsiService>::publish();
    if (ret != android::OK) {
        LOG(FATAL) << "Could not register gsi service: " << ret;
    }
}

GsiService::GsiService() {
    progress_ = {};
}

GsiService::~GsiService() {
    PostInstallCleanup();
}

#define ENFORCE_SYSTEM                          \
    do {                                        \
        binder::Status status = CheckUid();     \
        if (!status.isOk()) return status;      \
    } while (0)

#define ENFORCE_SYSTEM_OR_SHELL                                         \
    do {                                                                \
        binder::Status status = CheckUid(AccessLevel::SystemOrShell);   \
        if (!status.isOk()) return status;                              \
    } while (0)

binder::Status GsiService::startGsiInstall(int64_t gsiSize, int64_t userdataSize, bool wipeUserdata,
                                           int* _aidl_return) {
    GsiInstallParams params;
    params.gsiSize = gsiSize;
    params.userdataSize = userdataSize;
    params.wipeUserdata = wipeUserdata;
    return beginGsiInstall(params, _aidl_return);
}

binder::Status GsiService::beginGsiInstall(const GsiInstallParams& given_params, int* _aidl_return) {
    ENFORCE_SYSTEM;
    std::lock_guard<std::mutex> guard(main_lock_);

    // Make sure any interrupted installations are cleaned up.
    PostInstallCleanup();

    // Do some precursor validation on the arguments before diving into the
    // install process.
    GsiInstallParams params = given_params;
    if (int status = ValidateInstallParams(&params)) {
        *_aidl_return = status;
        return binder::Status::ok();
    }

    int status = StartInstall(params);
    if (status != INSTALL_OK) {
        // Perform local cleanup and delete any lingering files.
        PostInstallCleanup();
        RemoveGsiFiles(params.installDir, wipe_userdata_on_failure_);
    }
    *_aidl_return = status;

    // Clear the progress indicator.
    UpdateProgress(STATUS_NO_OPERATION, 0);
    return binder::Status::ok();
}

binder::Status GsiService::commitGsiChunkFromStream(const android::os::ParcelFileDescriptor& stream,
                                                    int64_t bytes, bool* _aidl_return) {
    ENFORCE_SYSTEM;
    std::lock_guard<std::mutex> guard(main_lock_);

    *_aidl_return = CommitGsiChunk(stream.get(), bytes);

    // Clear the progress indicator.
    UpdateProgress(STATUS_NO_OPERATION, 0);
    return binder::Status::ok();
}

void GsiService::StartAsyncOperation(const std::string& step, int64_t total_bytes) {
    std::lock_guard<std::mutex> guard(progress_lock_);

    progress_.step = step;
    progress_.status = STATUS_WORKING;
    progress_.bytes_processed = 0;
    progress_.total_bytes = total_bytes;
}

void GsiService::UpdateProgress(int status, int64_t bytes_processed) {
    std::lock_guard<std::mutex> guard(progress_lock_);

    progress_.status = status;
    if (status == STATUS_COMPLETE) {
        progress_.bytes_processed = progress_.total_bytes;
    } else {
        progress_.bytes_processed = bytes_processed;
    }
}

binder::Status GsiService::getInstallProgress(::android::gsi::GsiProgress* _aidl_return) {
    ENFORCE_SYSTEM;
    std::lock_guard<std::mutex> guard(progress_lock_);

    *_aidl_return = progress_;
    return binder::Status::ok();
}

binder::Status GsiService::commitGsiChunkFromMemory(const std::vector<uint8_t>& bytes,
                                                    bool* _aidl_return) {
    ENFORCE_SYSTEM;
    std::lock_guard<std::mutex> guard(main_lock_);

    *_aidl_return = CommitGsiChunk(bytes.data(), bytes.size());
    return binder::Status::ok();
}

binder::Status GsiService::setGsiBootable(bool one_shot, int* _aidl_return) {
    std::lock_guard<std::mutex> guard(main_lock_);

    if (installing_) {
        ENFORCE_SYSTEM;
        int error = SetGsiBootable(one_shot);
        PostInstallCleanup();
        if (error) {
            RemoveGsiFiles(install_dir_, wipe_userdata_on_failure_);
            *_aidl_return = error;
        } else {
            *_aidl_return = INSTALL_OK;
        }
    } else {
        ENFORCE_SYSTEM_OR_SHELL;
        *_aidl_return = ReenableGsi(one_shot);
        PostInstallCleanup();
    }

    return binder::Status::ok();
}

binder::Status GsiService::isGsiEnabled(bool* _aidl_return) {
    ENFORCE_SYSTEM_OR_SHELL;
    std::lock_guard<std::mutex> guard(main_lock_);
    std::string boot_key;
    if (!GetInstallStatus(&boot_key)) {
        *_aidl_return = false;
    } else {
        *_aidl_return = (boot_key == kInstallStatusOk);
    }
    return binder::Status::ok();
}

binder::Status GsiService::removeGsiInstall(bool* _aidl_return) {
    ENFORCE_SYSTEM_OR_SHELL;
    std::lock_guard<std::mutex> guard(main_lock_);

    // Just in case an install was left hanging.
    std::string install_dir;
    if (installing_) {
        install_dir = install_dir_;
        PostInstallCleanup();
    } else {
        install_dir = GetInstalledImageDir();
    }

    if (IsGsiRunning()) {
        // Can't remove gsi files while running.
        *_aidl_return = UninstallGsi();
    } else {
        *_aidl_return = RemoveGsiFiles(install_dir, true /* wipeUserdata */);
    }
    return binder::Status::ok();
}

binder::Status GsiService::disableGsiInstall(bool* _aidl_return) {
    ENFORCE_SYSTEM_OR_SHELL;
    std::lock_guard<std::mutex> guard(main_lock_);

    *_aidl_return = DisableGsiInstall();
    return binder::Status::ok();
}

binder::Status GsiService::isGsiRunning(bool* _aidl_return) {
    ENFORCE_SYSTEM_OR_SHELL;
    std::lock_guard<std::mutex> guard(main_lock_);

    *_aidl_return = IsGsiRunning();
    return binder::Status::ok();
}

binder::Status GsiService::isGsiInstalled(bool* _aidl_return) {
    ENFORCE_SYSTEM_OR_SHELL;
    std::lock_guard<std::mutex> guard(main_lock_);

    *_aidl_return = IsGsiInstalled();
    return binder::Status::ok();
}

binder::Status GsiService::isGsiInstallInProgress(bool* _aidl_return) {
    ENFORCE_SYSTEM_OR_SHELL;
    std::lock_guard<std::mutex> guard(main_lock_);

    *_aidl_return = installing_;
    return binder::Status::ok();
}

binder::Status GsiService::cancelGsiInstall(bool* _aidl_return) {
    ENFORCE_SYSTEM;
    should_abort_ = true;
    std::lock_guard<std::mutex> guard(main_lock_);

    should_abort_ = false;
    if (installing_) {
        PostInstallCleanup();
        RemoveGsiFiles(install_dir_, wipe_userdata_on_failure_);
    }

    *_aidl_return = true;
    return binder::Status::ok();
}

binder::Status GsiService::getGsiBootStatus(int* _aidl_return) {
    ENFORCE_SYSTEM_OR_SHELL;
    std::lock_guard<std::mutex> guard(main_lock_);

    if (!IsGsiInstalled()) {
        *_aidl_return = BOOT_STATUS_NOT_INSTALLED;
        return binder::Status::ok();
    }

    std::string boot_key;
    if (!GetInstallStatus(&boot_key)) {
        PLOG(ERROR) << "read " << kGsiInstallStatusFile;
        *_aidl_return = BOOT_STATUS_NOT_INSTALLED;
        return binder::Status::ok();
    }

    bool single_boot = !access(kGsiOneShotBootFile, F_OK);

    if (boot_key == kInstallStatusWipe) {
        // This overrides all other statuses.
        *_aidl_return = BOOT_STATUS_WILL_WIPE;
    } else if (boot_key == kInstallStatusDisabled) {
        // A single-boot GSI will have a "disabled" status, because it's
        // disabled immediately upon reading the one_shot_boot file. However,
        // we still want to return SINGLE_BOOT, because it makes the
        // transition clearer to the user.
        if (single_boot) {
            *_aidl_return = BOOT_STATUS_SINGLE_BOOT;
        } else {
            *_aidl_return = BOOT_STATUS_DISABLED;
        }
    } else if (single_boot) {
        *_aidl_return = BOOT_STATUS_SINGLE_BOOT;
    } else {
        *_aidl_return = BOOT_STATUS_ENABLED;
    }
    return binder::Status::ok();
}

binder::Status GsiService::getUserdataImageSize(int64_t* _aidl_return) {
    ENFORCE_SYSTEM;
    std::lock_guard<std::mutex> guard(main_lock_);

    *_aidl_return = -1;

    if (installing_) {
        // Size has already been computed.
        *_aidl_return = userdata_size_;
    } else if (IsGsiRunning()) {
        // :TODO: libdm
        unique_fd fd(open(kUserdataDevice, O_RDONLY | O_NOFOLLOW | O_CLOEXEC));
        if (fd < 0) {
            PLOG(ERROR) << "open " << kUserdataDevice;
            return binder::Status::ok();
        }

        int64_t size;
        if (ioctl(fd, BLKGETSIZE64, &size)) {
            PLOG(ERROR) << "BLKGETSIZE64 " << kUserdataDevice;
            return binder::Status::ok();
        }
        *_aidl_return = size;
    } else {
        // Stat the size of the userdata file.
        auto userdata_gsi = GetInstalledImagePath("userdata_gsi");
        struct stat s;
        if (stat(userdata_gsi.c_str(), &s)) {
            if (errno != ENOENT) {
                PLOG(ERROR) << "open " << userdata_gsi;
                return binder::Status::ok();
            }
            *_aidl_return = 0;
        } else {
            *_aidl_return = s.st_size;
        }
    }
    return binder::Status::ok();
}

binder::Status GsiService::getInstalledGsiImageDir(std::string* _aidl_return) {
    ENFORCE_SYSTEM;
    std::lock_guard<std::mutex> guard(main_lock_);

    if (IsGsiInstalled()) {
        *_aidl_return = GetInstalledImageDir();
    }
    return binder::Status::ok();
}

binder::Status GsiService::wipeGsiUserdata(int* _aidl_return) {
    ENFORCE_SYSTEM_OR_SHELL;
    std::lock_guard<std::mutex> guard(main_lock_);

    if (IsGsiRunning() || !IsGsiInstalled()) {
        *_aidl_return = IGsiService::INSTALL_ERROR_GENERIC;
        return binder::Status::ok();
    }

    *_aidl_return = WipeUserdata();

    return binder::Status::ok();
}

binder::Status GsiService::CheckUid(AccessLevel level) {
    std::vector<uid_t> allowed_uids{AID_ROOT, AID_SYSTEM};
    if (level == AccessLevel::SystemOrShell) {
        allowed_uids.push_back(AID_SHELL);
    }

    uid_t uid = IPCThreadState::self()->getCallingUid();
    for (const auto& allowed_uid : allowed_uids) {
        if (allowed_uid == uid) {
            return binder::Status::ok();
        }
    }

    auto message = StringPrintf("UID %d is not allowed", uid);
    return binder::Status::fromExceptionCode(binder::Status::EX_SECURITY,
                                             String8(message.c_str()));
}

void GsiService::PostInstallCleanup() {
    // This must be closed before unmapping partitions.
    system_writer_ = nullptr;

    const auto& dm = DeviceMapper::Instance();
    if (dm.GetState("userdata_gsi") != DmDeviceState::INVALID) {
        DestroyLogicalPartition("userdata_gsi", kDmTimeout);
    }
    if (dm.GetState("system_gsi") != DmDeviceState::INVALID) {
        DestroyLogicalPartition("system_gsi", kDmTimeout);
    }

    installing_ = false;
    partitions_ .clear();
}

static bool IsExternalStoragePath(const std::string& path) {
    if (!android::base::StartsWith(path, "/mnt/media_rw/")) {
        return false;
    }
    unique_fd fd(open(path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
    if (fd < 0) {
        PLOG(ERROR) << "open failed: " << path;
        return false;
    }
    struct statfs info;
    if (fstatfs(fd, &info)) {
        PLOG(ERROR) << "statfs failed: " << path;
        return false;
    }
    LOG(ERROR) << "fs type: " << info.f_type;
    return info.f_type == MSDOS_SUPER_MAGIC;
}

int GsiService::ValidateInstallParams(GsiInstallParams* params) {
    // If no install path was specified, use the default path. We also allow
    // specifying the top-level folder, and then we choose the correct location
    // underneath.
    if (params->installDir.empty() || params->installDir == "/data/gsi") {
        params->installDir = kDefaultGsiImageFolder;
    }

    // Normalize the path and add a trailing slash.
    std::string origInstallDir = params->installDir;
    if (!android::base::Realpath(origInstallDir, &params->installDir)) {
        PLOG(ERROR) << "realpath failed: " << origInstallDir;
        return INSTALL_ERROR_GENERIC;
    }
    // Ensure the path ends in / for consistency. Even though GetImagePath()
    // does this already, we want it to appear this way in install_dir.
    if (!android::base::EndsWith(params->installDir, "/")) {
        params->installDir += "/";
    }

    // Currently, we can only install to /data/gsi/ or external storage.
    if (IsExternalStoragePath(params->installDir)) {
        Fstab fstab;
        if (!ReadDefaultFstab(&fstab)) {
            LOG(ERROR) << "cannot read default fstab";
            return INSTALL_ERROR_GENERIC;
        }
        FstabEntry* system = GetEntryForMountPoint(&fstab, "/system");
        if (!system) {
            LOG(ERROR) << "cannot find /system fstab entry";
            return INSTALL_ERROR_GENERIC;
        }
        if (fs_mgr_verity_is_check_at_most_once(*system)) {
            LOG(ERROR) << "cannot install GSIs to external media if verity uses check_at_most_once";
            return INSTALL_ERROR_GENERIC;
        }
    } else if (params->installDir != kDefaultGsiImageFolder) {
        LOG(ERROR) << "cannot install GSI to " << params->installDir;
        return INSTALL_ERROR_GENERIC;
    }

    if (params->gsiSize % LP_SECTOR_SIZE) {
        LOG(ERROR) << "GSI size " << params->gsiSize << " is not a multiple of " << LP_SECTOR_SIZE;
        return INSTALL_ERROR_GENERIC;
    }
    if (params->userdataSize % LP_SECTOR_SIZE) {
        LOG(ERROR) << "userdata size " << params->userdataSize << " is not a multiple of "
                   << LP_SECTOR_SIZE;
        return INSTALL_ERROR_GENERIC;
    }
    return INSTALL_OK;
}

int GsiService::StartInstall(const GsiInstallParams& params) {
    installing_ = true;
    userdata_block_size_ = 0;
    system_block_size_ = 0;
    gsi_size_ = params.gsiSize;
    userdata_size_ = (params.userdataSize) ? params.userdataSize : kDefaultUserdataSize;
    wipe_userdata_ = params.wipeUserdata;
    can_use_devicemapper_ = false;
    gsi_bytes_written_ = 0;
    install_dir_ = params.installDir;

    userdata_gsi_path_ = GetImagePath(install_dir_, "userdata_gsi");
    system_gsi_path_ = GetImagePath(install_dir_, "system_gsi");

    // Only rm userdata_gsi if one didn't already exist.
    wipe_userdata_on_failure_ = wipe_userdata_ || access(userdata_gsi_path_.c_str(), F_OK);

    if (int status = PerformSanityChecks()) {
        return status;
    }
    if (int status = PreallocateFiles()) {
        return status;
    }
    if (int status = DetermineReadWriteMethod()) {
        return status;
    }
    if (!FormatUserdata()) {
        return INSTALL_ERROR_GENERIC;
    }

    // Map system_gsi so we can write to it.
    system_writer_ = OpenPartition("system_gsi");
    if (!system_writer_) {
        return INSTALL_ERROR_GENERIC;
    }
    return INSTALL_OK;
}

int GsiService::DetermineReadWriteMethod() {
    // If there is a device-mapper node wrapping the block device, then we're
    // able to create another node around it; the dm layer does not carry the
    // exclusion lock down the stack when a mount occurs.
    //
    // If there is no intermediate device-mapper node, then partitions cannot be
    // opened writable due to sepolicy and exclusivity of having a mounted
    // filesystem. This should only happen on devices with no encryption, or
    // devices with FBE and no metadata encryption. For these cases it suffices
    // to perform normal file writes to /data/gsi (which is unencrypted).
    std::string block_device;
    if (!FiemapWriter::GetBlockDeviceForFile(system_gsi_path_.c_str(), &block_device,
                                             &can_use_devicemapper_)) {
        return INSTALL_ERROR_GENERIC;
    }
    if (install_dir_ != kDefaultGsiImageFolder && can_use_devicemapper_) {
        // Never use device-mapper on external media. We don't support adopted
        // storage yet, and accidentally using device-mapper could be dangerous
        // as we hardcode the userdata device as backing storage.
        LOG(ERROR) << "unexpected device-mapper node used to mount external media";
        return INSTALL_ERROR_GENERIC;
    }
    return INSTALL_OK;
}

std::string GsiService::GetImagePath(const std::string& image_dir, const std::string& name) {
    std::string dir = image_dir;
    if (!android::base::EndsWith(dir, "/")) {
        dir += "/";
    }
    return dir + name + ".img";
}

std::string GsiService::GetInstalledImageDir() {
    // If there's no install left, just return /data/gsi since that's where
    // installs go by default.
    std::string dir;
    if (android::base::ReadFileToString(kGsiInstallDirFile, &dir)) {
        return dir;
    }
    return kDefaultGsiImageFolder;
}

std::string GsiService::GetInstalledImagePath(const std::string& name) {
    return GetImagePath(GetInstalledImageDir(), name);
}

int GsiService::PerformSanityChecks() {
    if (gsi_size_ < 0) {
        LOG(ERROR) << "image size " << gsi_size_ << " is negative";
        return INSTALL_ERROR_GENERIC;
    }
    if (android::gsi::IsGsiRunning()) {
        LOG(ERROR) << "cannot install gsi inside a live gsi";
        return INSTALL_ERROR_GENERIC;
    }

    struct statvfs sb;
    if (statvfs(install_dir_.c_str(), &sb)) {
        PLOG(ERROR) << "failed to read file system stats";
        return INSTALL_ERROR_GENERIC;
    }

    // This is the same as android::vold::GetFreebytes() but we also
    // need the total file system size so we open code it here.
    uint64_t free_space = 1ULL * sb.f_bavail * sb.f_frsize;
    uint64_t fs_size = sb.f_blocks * sb.f_frsize;
    if (free_space <= (gsi_size_ + userdata_size_)) {
        LOG(ERROR) << "not enough free space (only " << free_space << " bytes available)";
        return INSTALL_ERROR_NO_SPACE;
    }
    // We are asking for 40% of the /data to be empty.
    // TODO: may be not hard code it like this
    double free_space_percent = ((1.0 * free_space) / fs_size) * 100;
    if (free_space_percent < kMinimumFreeSpaceThreshold) {
        LOG(ERROR) << "free space " << static_cast<uint64_t>(free_space_percent)
                   << "% is below the minimum threshold of " << kMinimumFreeSpaceThreshold << "%";
        return INSTALL_ERROR_FILE_SYSTEM_CLUTTERED;
    }
    return INSTALL_OK;
}

int GsiService::PreallocateFiles() {
    if (wipe_userdata_) {
        SplitFiemap::RemoveSplitFiles(userdata_gsi_path_);
    }
    SplitFiemap::RemoveSplitFiles(system_gsi_path_);

    // TODO: trigger GC from fiemap writer.

    // Create fallocated files.
    if (int status = PreallocateUserdata()) {
        return status;
    }
    if (int status = PreallocateSystem()) {
        return status;
    }

    // Save the extent information in liblp.
    metadata_ = CreateMetadata();
    if (!metadata_) {
        return INSTALL_ERROR_GENERIC;
    }

    UpdateProgress(STATUS_COMPLETE, 0);
    return INSTALL_OK;
}

int GsiService::PreallocateUserdata() {
    int error;
    std::unique_ptr<SplitFiemap> userdata_image;
    if (wipe_userdata_ || access(userdata_gsi_path_.c_str(), F_OK)) {
        StartAsyncOperation("create userdata", userdata_size_);
        userdata_image = CreateFiemapWriter(userdata_gsi_path_, userdata_size_, &error);
        if (!userdata_image) {
            LOG(ERROR) << "Could not create userdata image: " << userdata_gsi_path_;
            return error;
        }
        // Signal that we need to reformat userdata.
        wipe_userdata_ = true;
    } else {
        userdata_image = CreateFiemapWriter(userdata_gsi_path_, 0, &error);
        if (!userdata_image) {
            LOG(ERROR) << "Could not open userdata image: " << userdata_gsi_path_;
            return error;
        }
        if (userdata_size_ && userdata_image->size() < userdata_size_) {
            // :TODO: need to fallocate more blocks and resizefs.
        }
        userdata_size_ = userdata_image->size();
    }

    userdata_block_size_ = userdata_image->block_size();

    Image image = {
            .writer = std::move(userdata_image),
            .actual_size = userdata_size_,
    };
    partitions_.emplace(std::make_pair("userdata_gsi", std::move(image)));
    return INSTALL_OK;
}

int GsiService::PreallocateSystem() {
    StartAsyncOperation("create system", gsi_size_);

    int error;
    auto system_image = CreateFiemapWriter(system_gsi_path_, gsi_size_, &error);
    if (!system_image) {
        return error;
    }

    system_block_size_ = system_image->block_size();

    Image image = {
            .writer = std::move(system_image),
            .actual_size = gsi_size_,
    };
    partitions_.emplace(std::make_pair("system_gsi", std::move(image)));
    return INSTALL_OK;
}

std::unique_ptr<SplitFiemap> GsiService::CreateFiemapWriter(const std::string& path,
                                                            uint64_t size, int* error) {
    bool create = (size != 0);

    std::function<bool(uint64_t, uint64_t)> progress;
    if (create) {
        // TODO: allow cancelling inside cancelGsiInstall.
        progress = [this](uint64_t bytes, uint64_t /* total */) -> bool {
            UpdateProgress(STATUS_WORKING, bytes);
            if (should_abort_) return false;
            return true;
        };
    }

    std::unique_ptr<SplitFiemap> file;
    if (!size) {
        file = SplitFiemap::Open(path);
    } else {
        file = SplitFiemap::Create(path, size, 0, std::move(progress));
    }
    if (!file) {
        LOG(ERROR) << "failed to create or open " << path;
        *error = INSTALL_ERROR_GENERIC;
        return nullptr;
    }

    uint64_t extents = file->extents().size();
    if (extents > kMaximumExtents) {
        LOG(ERROR) << "file " << path << " has too many extents: " << extents;
        *error = INSTALL_ERROR_FILE_SYSTEM_CLUTTERED;
        return nullptr;
    }
    return file;
}

// Write data through an fd.
class FdWriter final : public GsiService::WriteHelper {
  public:
    FdWriter(const std::string& path, unique_fd&& fd) : path_(path), fd_(std::move(fd)) {}

    bool Write(const void* data, uint64_t bytes) override {
        return android::base::WriteFully(fd_, data, bytes);
    }
    bool Flush() override {
        if (fsync(fd_)) {
            PLOG(ERROR) << "fsync failed: " << path_;
            return false;
        }
        return true;
    }
    uint64_t Size() override { return get_block_device_size(fd_); }

  private:
    std::string path_;
    unique_fd fd_;
};

// Write data through a SplitFiemap.
class SplitFiemapWriter final : public GsiService::WriteHelper {
  public:
    explicit SplitFiemapWriter(SplitFiemap* writer) : writer_(writer) {}

    bool Write(const void* data, uint64_t bytes) override {
        return writer_->Write(data, bytes);
    }
    bool Flush() override {
        return writer_->Flush();
    }
    uint64_t Size() override { return writer_->size(); }

  private:
    SplitFiemap* writer_;
};

std::unique_ptr<GsiService::WriteHelper> GsiService::OpenPartition(const std::string& name) {
    if (can_use_devicemapper_) {
        std::string path;
        if (!CreateLogicalPartition(kUserdataDevice, *metadata_.get(), name, true, kDmTimeout,
                                    &path)) {
            LOG(ERROR) << "Error creating device-mapper node for " << name;
            return {};
        }

        static const int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
        unique_fd fd(open(path.c_str(), kOpenFlags));
        if (fd < 0) {
            PLOG(ERROR) << "could not open " << path;
        }
        return std::make_unique<FdWriter>(GetImagePath(install_dir_, name), std::move(fd));
    }

    auto iter = partitions_.find(name);
    if (iter == partitions_.end()) {
        LOG(ERROR) << "could not find partition " << name;
        return {};
    }
    return std::make_unique<SplitFiemapWriter>(iter->second.writer.get());
}

bool GsiService::CommitGsiChunk(int stream_fd, int64_t bytes) {
    StartAsyncOperation("write gsi", gsi_size_);

    if (bytes < 0) {
        LOG(ERROR) << "chunk size " << bytes << " is negative";
        return false;
    }

    auto buffer = std::make_unique<char[]>(system_block_size_);

    int progress = -1;
    uint64_t remaining = bytes;
    while (remaining) {
        // :TODO: check file pin status!
        size_t max_to_read = std::min(system_block_size_, remaining);
        ssize_t rv = TEMP_FAILURE_RETRY(read(stream_fd, buffer.get(), max_to_read));
        if (rv < 0) {
            PLOG(ERROR) << "read gsi chunk";
            return false;
        }
        if (rv == 0) {
            LOG(ERROR) << "no bytes left in stream";
            return false;
        }
        if (!CommitGsiChunk(buffer.get(), rv)) {
            return false;
        }
        CHECK(static_cast<uint64_t>(rv) <= remaining);
        remaining -= rv;

        // Only update the progress when the % (or permille, in this case)
        // significantly changes.
        int new_progress = ((gsi_size_ - remaining) * 1000) / gsi_size_;
        if (new_progress != progress) {
            UpdateProgress(STATUS_WORKING, gsi_size_ - remaining);
        }
    }

    UpdateProgress(STATUS_COMPLETE, gsi_size_);
    return true;
}

bool GsiService::CommitGsiChunk(const void* data, size_t bytes) {
    if (!installing_) {
        LOG(ERROR) << "no gsi installation in progress";
        return false;
    }
    if (static_cast<uint64_t>(bytes) > gsi_size_ - gsi_bytes_written_) {
        // We cannot write past the end of the image file.
        LOG(ERROR) << "chunk size " << bytes << " exceeds remaining image size (" << gsi_size_
                   << " expected, " << gsi_bytes_written_ << " written)";
        return false;
    }

    if (!system_writer_->Write(data, bytes)) {
        PLOG(ERROR) << "write failed";
        return false;
    }
    gsi_bytes_written_ += bytes;
    return true;
}

int GsiService::SetGsiBootable(bool one_shot) {
    if (gsi_bytes_written_ != gsi_size_) {
        // We cannot boot if the image is incomplete.
        LOG(ERROR) << "image incomplete; expected " << gsi_size_ << " bytes, waiting for "
                   << (gsi_size_ - gsi_bytes_written_) << " bytes";
        return INSTALL_ERROR_GENERIC;
    }

    if (!system_writer_->Flush()) {
        return INSTALL_ERROR_GENERIC;
    }

    // If files moved (are no longer pinned), the metadata file will be invalid.
    for (const auto& [name, image] : partitions_) {
        if (!image.writer->HasPinnedExtents()) {
            LOG(ERROR) << name << " no longer has pinned extents";
            return INSTALL_ERROR_GENERIC;
        }
    }

    // Remember the installation directory.
    if (!android::base::WriteStringToFile(install_dir_, kGsiInstallDirFile)) {
        PLOG(ERROR) << "write failed: " << kGsiInstallDirFile;
        return INSTALL_ERROR_GENERIC;
    }

    // Note: create the install status file last, since this is the actual boot
    // indicator.
    if (!CreateMetadataFile() || !SetBootMode(one_shot) || !CreateInstallStatusFile()) {
        return INSTALL_ERROR_GENERIC;
    }
    return INSTALL_OK;
}

int GsiService::ReenableGsi(bool one_shot) {
    if (!android::gsi::IsGsiInstalled()) {
        LOG(ERROR) << "no gsi installed - cannot re-enable";
        return INSTALL_ERROR_GENERIC;
    }

    std::string boot_key;
    if (!GetInstallStatus(&boot_key)) {
        PLOG(ERROR) << "read " << kGsiInstallStatusFile;
        return INSTALL_ERROR_GENERIC;
    }
    if (boot_key != kInstallStatusDisabled) {
        LOG(ERROR) << "GSI is not currently disabled";
        return INSTALL_ERROR_GENERIC;
    }

    if (IsGsiRunning()) {
        if (!SetBootMode(one_shot) || !CreateInstallStatusFile()) {
            return INSTALL_ERROR_GENERIC;
        }
        return INSTALL_OK;
    }
    // Note: this metadata is only used to recover the original partition sizes.
    // We do not trust the extent information, which will get rebuilt later.
    auto old_metadata = ReadFromImageFile(kGsiLpMetadataFile);
    if (!old_metadata) {
        LOG(ERROR) << "GSI install is incomplete";
        return INSTALL_ERROR_GENERIC;
    }

    // Set up enough installer state so that we can use various helper
    // methods.
    //
    // TODO(dvander) Extract all of the installer state into a separate
    // class so this is more manageable.
    install_dir_ = GetInstalledImageDir();
    system_gsi_path_ = GetImagePath(install_dir_, "system_gsi");
    if (int error = DetermineReadWriteMethod()) {
        return error;
    }

    // Recover parition information.
    Image userdata_image;
    if (int error = GetExistingImage(*old_metadata.get(), "userdata_gsi", &userdata_image)) {
        return error;
    }
    partitions_.emplace(std::make_pair("userdata_gsi", std::move(userdata_image)));

    Image system_image;
    if (int error = GetExistingImage(*old_metadata.get(), "system_gsi", &system_image)) {
        return error;
    }
    partitions_.emplace(std::make_pair("system_gsi", std::move(system_image)));

    metadata_ = CreateMetadata();
    if (!metadata_) {
        return INSTALL_ERROR_GENERIC;
    }
    if (!CreateMetadataFile() || !SetBootMode(one_shot) || !CreateInstallStatusFile()) {
        return INSTALL_ERROR_GENERIC;
    }
    return INSTALL_OK;
}

int GsiService::WipeUserdata() {
    // Note: this metadata is only used to recover the original partition sizes.
    // We do not trust the extent information, which will get rebuilt later.
    auto old_metadata = ReadFromImageFile(kGsiLpMetadataFile);
    if (!old_metadata) {
        LOG(ERROR) << "GSI install is incomplete";
        return INSTALL_ERROR_GENERIC;
    }

    install_dir_ = GetInstalledImageDir();
    system_gsi_path_ = GetImagePath(install_dir_, "system_gsi");
    if (int error = DetermineReadWriteMethod()) {
        return error;
    }

    // Recover parition information.
    Image userdata_image;
    if (int error = GetExistingImage(*old_metadata.get(), "userdata_gsi", &userdata_image)) {
        return error;
    }
    partitions_.emplace(std::make_pair("userdata_gsi", std::move(userdata_image)));

    metadata_ = CreateMetadata();
    if (!metadata_) {
        return INSTALL_ERROR_GENERIC;
    }

    auto writer = OpenPartition("userdata_gsi");
    if (!writer) {
        return IGsiService::INSTALL_ERROR_GENERIC;
    }

    // Wipe the first 1MiB of the device, ensuring both the first block and
    // the superblock are destroyed.
    static constexpr uint64_t kEraseSize = 1024 * 1024;

    std::string zeroes(4096, 0);
    uint64_t erase_size = std::min(kEraseSize, writer->Size());
    for (uint64_t i = 0; i < erase_size; i += zeroes.size()) {
        if (!writer->Write(zeroes.data(), zeroes.size())) {
            PLOG(ERROR) << "write userdata_gsi";
            return IGsiService::INSTALL_ERROR_GENERIC;
        }
    }
    return INSTALL_OK;
}

static uint64_t GetPartitionSize(const LpMetadata& metadata, const LpMetadataPartition& partition) {
    uint64_t total = 0;
    for (size_t i = 0; i < partition.num_extents; i++) {
        const auto& extent = metadata.extents[partition.first_extent_index + i];
        if (extent.target_type != LP_TARGET_TYPE_LINEAR) {
            LOG(ERROR) << "non-linear extent detected";
            return 0;
        }
        total += extent.num_sectors * LP_SECTOR_SIZE;
    }
    return total;
}

static uint64_t GetPartitionSize(const LpMetadata& metadata, const std::string& name) {
    for (const auto& partition : metadata.partitions) {
        if (GetPartitionName(partition) == name) {
            return GetPartitionSize(metadata, partition);
        }
    }
    return 0;
}

int GsiService::GetExistingImage(const LpMetadata& metadata, const std::string& name,
                                 Image* image) {
    int error;
    std::string path = GetInstalledImagePath(name);
    auto writer = CreateFiemapWriter(path.c_str(), 0, &error);
    if (!writer) {
        return error;
    }

    // Even after recovering the FIEMAP, we also need to know the exact intended
    // size of the image, since FiemapWriter may have extended the final block.
    uint64_t actual_size = GetPartitionSize(metadata, name);
    if (!actual_size) {
        LOG(ERROR) << "Could not determine the pre-existing size of " << name;
        return INSTALL_ERROR_GENERIC;
    }
    image->writer = std::move(writer);
    image->actual_size = actual_size;
    return INSTALL_OK;
}

bool GsiService::RemoveGsiFiles(const std::string& install_dir, bool wipeUserdata) {
    bool ok = true;
    std::string message;
    if (!SplitFiemap::RemoveSplitFiles(GetImagePath(install_dir, "system_gsi"), &message)) {
        LOG(ERROR) << message;
        ok = false;
    }
    if (wipeUserdata &&
        !SplitFiemap::RemoveSplitFiles(GetImagePath(install_dir, "userdata_gsi"), &message)) {
        LOG(ERROR) << message;
        ok = false;
    }

    std::vector<std::string> files{
            kGsiInstallStatusFile,
            kGsiLpMetadataFile,
            kGsiOneShotBootFile,
            kGsiInstallDirFile,
    };
    for (const auto& file : files) {
        if (!android::base::RemoveFileIfExists(file, &message)) {
            LOG(ERROR) << message;
            ok = false;
        }
    }
    return ok;
}

bool GsiService::DisableGsiInstall() {
    if (!android::gsi::IsGsiInstalled()) {
        LOG(ERROR) << "cannot disable gsi install - no install detected";
        return false;
    }
    if (installing_) {
        LOG(ERROR) << "cannot disable gsi during GSI installation";
        return false;
    }
    if (!DisableGsi()) {
        PLOG(ERROR) << "could not write gsi status";
        return false;
    }
    return true;
}

std::unique_ptr<LpMetadata> GsiService::CreateMetadata() {
    std::string data_device_path;
    if (install_dir_ == kDefaultGsiImageFolder && !access(kUserdataDevice, F_OK)) {
        data_device_path = kUserdataDevice;
    } else {
        auto writer = partitions_["system_gsi"].writer.get();
        data_device_path = writer->bdev_path();
    }
    auto data_device_name = android::base::Basename(data_device_path);

    PartitionOpener opener;
    BlockDeviceInfo data_device_info;
    if (!opener.GetInfo(data_device_path, &data_device_info)) {
        LOG(ERROR) << "Error reading userdata partition";
        return nullptr;
    }

    std::vector<BlockDeviceInfo> block_devices = {data_device_info};
    auto builder = MetadataBuilder::New(block_devices, data_device_name, 128 * 1024, 1);
    if (!builder) {
        LOG(ERROR) << "Error creating metadata builder";
        return nullptr;
    }
    builder->IgnoreSlotSuffixing();

    for (const auto& [name, image] : partitions_) {
        uint32_t flags = LP_PARTITION_ATTR_NONE;
        if (name == "system_gsi") {
            flags |= LP_PARTITION_ATTR_READONLY;
        }
        Partition* partition = builder->AddPartition(name, flags);
        if (!partition) {
            LOG(ERROR) << "Error adding " << name << " to partition table";
            return nullptr;
        }
        if (!AddPartitionFiemap(builder.get(), partition, image, data_device_name)) {
            return nullptr;
        }
    }

    auto metadata = builder->Export();
    if (!metadata) {
        LOG(ERROR) << "Error exporting partition table";
        return nullptr;
    }
    return metadata;
}

bool GsiService::CreateMetadataFile() {
    if (!WriteToImageFile(kGsiLpMetadataFile, *metadata_.get())) {
        LOG(ERROR) << "Error writing GSI partition table image";
        return false;
    }
    return true;
}

bool GsiService::FormatUserdata() {
    auto writer = OpenPartition("userdata_gsi");
    if (!writer) {
        return false;
    }

    // libcutils checks the first 4K, no matter the block size.
    std::string zeroes(4096, 0);
    if (!writer->Write(zeroes.data(), zeroes.size())) {
        PLOG(ERROR) << "write userdata_gsi";
        return false;
    }
    return true;
}

bool GsiService::AddPartitionFiemap(MetadataBuilder* builder, Partition* partition,
                                    const Image& image, const std::string& block_device) {
    uint64_t sectors_needed = image.actual_size / LP_SECTOR_SIZE;
    for (const auto& extent : image.writer->extents()) {
        // :TODO: block size check for length, not sector size
        if (extent.fe_length % LP_SECTOR_SIZE != 0) {
            LOG(ERROR) << "Extent is not sector-aligned: " << extent.fe_length;
            return false;
        }
        if (extent.fe_physical % LP_SECTOR_SIZE != 0) {
            LOG(ERROR) << "Extent physical sector is not sector-aligned: " << extent.fe_physical;
            return false;
        }

        uint64_t num_sectors =
                std::min(static_cast<uint64_t>(extent.fe_length / LP_SECTOR_SIZE), sectors_needed);
        if (!num_sectors || !sectors_needed) {
            // This should never happen, but we include it just in case. It would
            // indicate that the last filesystem block had multiple extents.
            LOG(WARNING) << "FiemapWriter allocated extra blocks";
            break;
        }

        uint64_t physical_sector = extent.fe_physical / LP_SECTOR_SIZE;
        if (!builder->AddLinearExtent(partition, block_device, num_sectors, physical_sector)) {
            LOG(ERROR) << "Could not add extent to lp metadata";
            return false;
        }

        sectors_needed -= num_sectors;
    }
    return true;
}

bool GsiService::SetBootMode(bool one_shot) {
    if (one_shot) {
        if (!android::base::WriteStringToFile("1", kGsiOneShotBootFile)) {
            PLOG(ERROR) << "write " << kGsiOneShotBootFile;
            return false;
        }
    } else if (!access(kGsiOneShotBootFile, F_OK)) {
        std::string error;
        if (!android::base::RemoveFileIfExists(kGsiOneShotBootFile, &error)) {
            LOG(ERROR) << error;
            return false;
        }
    }
    return true;
}

bool GsiService::CreateInstallStatusFile() {
    if (!android::base::WriteStringToFile("0", kGsiInstallStatusFile)) {
        PLOG(ERROR) << "write " << kGsiInstallStatusFile;
        return false;
    }
    return true;
}

void GsiService::RunStartupTasks() {
    if (!IsGsiInstalled()) {
        return;
    }

    std::string boot_key;
    if (!GetInstallStatus(&boot_key)) {
        PLOG(ERROR) << "read " << kGsiInstallStatusFile;
        return;
    }

    if (!IsGsiRunning()) {
        // Check if a wipe was requested from fastboot or adb-in-gsi.
        if (boot_key == kInstallStatusWipe) {
            RemoveGsiFiles(GetInstalledImageDir(), true /* wipeUserdata */);
        }
    } else {
        // NB: When single-boot is enabled, init will write "disabled" into the
        // install_status file, which will cause GetBootAttempts to return
        // false. Thus, we won't write "ok" here.
        int ignore;
        if (GetBootAttempts(boot_key, &ignore)) {
            // Mark the GSI as having successfully booted.
            if (!android::base::WriteStringToFile(kInstallStatusOk, kGsiInstallStatusFile)) {
                PLOG(ERROR) << "write " << kGsiInstallStatusFile;
            }
        }
    }
}

}  // namespace gsi
}  // namespace android