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

Commit 69def12f authored by David Anderson's avatar David Anderson
Browse files

overlayfs: Refactor how the scratch device is deduced, again.

The previous refactoring did not uncover the full breadth of issues that
arise when trying to use /data for adb remount. In fact, there are a few
distinct use cases for the scratch device, and one function cannot
sensibly provide them all.

(1) First-stage init needs to know if there are dependent devices. This
    would be userdata, super_<other>, or system_<other>. This knowledge
    is dependent on the state in /metadata, fstab, and the kernel
    command-line.

(2) First-stage init and fastbootd need to know where the scratch
    partition is. If it's not in super_<other> or system_<other>, and
    there is no indicator on /metadata, then it might be in a dynamic
    partition.

(3) "adb remount" needs to find a place to put scratch, which
    effectively amounts to the first writable space it can find.
    However, for Virtual A/B, devices, scratch wants to be stored in
    /data, which requires more complex checks and binder calls.

Trying to encapsulate all of this into one function is too difficult, so
instead, this patch breaks GetScratchStrategy into separate functions:
one to return a physical location if a candidate exists, and another to
deduce the "boot" scratch device.

"adb remount" no longer calls GetScratchDevice, since it only needs to
know whether or not a physical candidate was possible.

fs_mgr_overlayfs_teardown calls GetBootScratchDevice, but now only
attempts to make a dynamic "scratch" partition if one definitely exists.
This makes the functionality clearer and reduces fastbootd uart spam.

Bug: 134949511
Test: adb_remount_test.sh on coral
      adb_remount_test.sh on crosshatch
      adb_remount_test.sh on walleye
Change-Id: I5f6a3677bc6adcaaf54c8ab3594643e4f285c04e
parent d5745cce
Loading
Loading
Loading
Loading
+108 −83
Original line number Diff line number Diff line
@@ -811,66 +811,48 @@ std::string fs_mgr_overlayfs_scratch_mount_type() {
    return "auto";
}

enum class ScratchStrategy {
    kNone,
    // DAP device, use logical partitions.
    kDynamicPartition,
    // Retrofit DAP device, use super_<other>.
    kSuperOther,
    // Pre-DAP device, uses the other slot.
    kSystemOther
};

// Return the strategy this device must use for creating a scratch partition.
static ScratchStrategy GetScratchStrategy(std::string* backing_device = nullptr) {
// Note: we do not check access() here except for the super partition, since
// in first-stage init we wouldn't have registed by-name symlinks for "other"
// partitions that won't be mounted.
static std::string GetPhysicalScratchDevice() {
    auto slot_number = fs_mgr_overlayfs_slot_number();
    auto super_device = fs_mgr_overlayfs_super_device(slot_number);
    auto path = fs_mgr_overlayfs_super_device(slot_number == 0);
    if (super_device != path) {
        // Note: we do not check access() here, since in first-stage init we
        // wouldn't have registed by-name symlinks for the device as it's
        // normally not needed. The access checks elsewhere in this function
        // are safe because system/super are always required.
        if (backing_device) *backing_device = path;
        return ScratchStrategy::kSuperOther;
        return path;
    }
    if (fs_mgr_access(super_device)) {
        if (backing_device) *backing_device = super_device;
        return ScratchStrategy::kDynamicPartition;
        // Do not try to use system_other on a DAP device.
        return "";
    }

    auto other_slot = fs_mgr_get_other_slot_suffix();
    if (!other_slot.empty()) {
        path = kPhysicalDevice + "system" + other_slot;
        if (fs_mgr_access(path)) {
            if (backing_device) *backing_device = path;
            return ScratchStrategy::kSystemOther;
        }
        return kPhysicalDevice + "system" + other_slot;
    }
    return ScratchStrategy::kNone;
    return "";
}

// Return the scratch device if it exists.
static std::string GetScratchDevice() {
    std::string device;
    ScratchStrategy strategy = GetScratchStrategy(&device);

    switch (strategy) {
        case ScratchStrategy::kSuperOther:
        case ScratchStrategy::kSystemOther:
            return device;
        case ScratchStrategy::kDynamicPartition: {
// This returns the scratch device that was detected during early boot (first-
// stage init). If the device was created later, for example during setup for
// the adb remount command, it can return an empty string since it does not
// query ImageManager.
static std::string GetBootScratchDevice() {
    auto& dm = DeviceMapper::Instance();

    // If there is a scratch partition allocated in /data or on super, we
    // automatically prioritize that over super_other or system_other.
    // Some devices, for example, have a write-protected eMMC and the
    // super partition cannot be used even if it exists.
    std::string device;
    auto partition_name = android::base::Basename(kScratchMountPoint);
    if (dm.GetState(partition_name) != DmDeviceState::INVALID &&
        dm.GetDmDevicePathByName(partition_name, &device)) {
        return device;
    }
            return "";
        }
        default:
            return "";
    }

    // There is no dynamic scratch, so try and find a physical one.
    return GetPhysicalScratchDevice();
}

bool fs_mgr_overlayfs_make_scratch(const std::string& scratch_device, const std::string& mnt_type) {
@@ -915,8 +897,8 @@ static void TruncatePartitionsWithSuffix(MetadataBuilder* builder, const std::st
}

// Create or update a scratch partition within super.
static bool CreateDynamicScratch(const Fstab& fstab, std::string* scratch_device,
                                 bool* partition_exists, bool* change) {
static bool CreateDynamicScratch(std::string* scratch_device, bool* partition_exists,
                                 bool* change) {
    const auto partition_name = android::base::Basename(kScratchMountPoint);

    auto& dm = DeviceMapper::Instance();
@@ -925,8 +907,6 @@ static bool CreateDynamicScratch(const Fstab& fstab, std::string* scratch_device
    auto partition_create = !*partition_exists;
    auto slot_number = fs_mgr_overlayfs_slot_number();
    auto super_device = fs_mgr_overlayfs_super_device(slot_number);
    if (!fs_mgr_rw_access(super_device)) return false;
    if (!fs_mgr_overlayfs_has_logical(fstab)) return false;
    auto builder = MetadataBuilder::New(super_device, slot_number);
    if (!builder) {
        LERROR << "open " << super_device << " metadata";
@@ -1012,24 +992,32 @@ static bool CreateDynamicScratch(const Fstab& fstab, std::string* scratch_device
    return true;
}

static bool CanUseSuperPartition(const Fstab& fstab) {
    auto slot_number = fs_mgr_overlayfs_slot_number();
    auto super_device = fs_mgr_overlayfs_super_device(slot_number);
    if (!fs_mgr_rw_access(super_device) || !fs_mgr_overlayfs_has_logical(fstab)) {
        return false;
    }
    return true;
}

bool fs_mgr_overlayfs_create_scratch(const Fstab& fstab, std::string* scratch_device,
                                     bool* partition_exists, bool* change) {
    auto strategy = GetScratchStrategy();
    if (strategy == ScratchStrategy::kDynamicPartition) {
        return CreateDynamicScratch(fstab, scratch_device, partition_exists, change);
    // Try a physical partition first.
    *scratch_device = GetPhysicalScratchDevice();
    if (!scratch_device->empty() && fs_mgr_rw_access(*scratch_device)) {
        *partition_exists = true;
        return true;
    }

    // If that fails, see if we can land on super.
    if (CanUseSuperPartition(fstab)) {
        return CreateDynamicScratch(scratch_device, partition_exists, change);
    }

    // The scratch partition can only be landed on a physical partition if we
    // get here. If there are no viable candidates that are R/W, just return
    // that there is no device.
    *scratch_device = GetScratchDevice();
    if (scratch_device->empty()) {
    errno = ENXIO;
    return false;
}
    *partition_exists = true;
    return true;
}

// Create and mount kScratchMountPoint storage if we have logical partitions
bool fs_mgr_overlayfs_setup_scratch(const Fstab& fstab, bool* change) {
@@ -1120,7 +1108,12 @@ Fstab fs_mgr_overlayfs_candidate_list(const Fstab& fstab) {
}

static void TryMountScratch() {
    auto scratch_device = GetScratchDevice();
    // Note we get the boot scratch device here, which means if scratch was
    // just created through ImageManager, this could fail. In practice this
    // should not happen because "remount" detects this scenario (by checking
    // if verity is still disabled, i.e. no reboot occurred), and skips calling
    // fs_mgr_overlayfs_mount_all().
    auto scratch_device = GetBootScratchDevice();
    if (!fs_mgr_overlayfs_scratch_can_be_mounted(scratch_device)) {
        return;
    }
@@ -1166,11 +1159,23 @@ std::vector<std::string> fs_mgr_overlayfs_required_devices(Fstab* fstab) {
        return {};
    }

    bool want_scratch = false;
    for (const auto& entry : fs_mgr_overlayfs_candidate_list(*fstab)) {
        if (fs_mgr_is_verity_enabled(entry)) continue;
        if (fs_mgr_overlayfs_already_mounted(fs_mgr_mount_point(entry.mount_point))) continue;
        auto device = GetScratchDevice();
        if (!fs_mgr_overlayfs_scratch_can_be_mounted(device)) break;
        if (fs_mgr_is_verity_enabled(entry)) {
            continue;
        }
        if (fs_mgr_overlayfs_already_mounted(fs_mgr_mount_point(entry.mount_point))) {
            continue;
        }
        want_scratch = true;
        break;
    }
    if (!want_scratch) {
        return {};
    }

    auto device = GetBootScratchDevice();
    if (!device.empty()) {
        return {device};
    }
    return {};
@@ -1241,38 +1246,54 @@ bool fs_mgr_overlayfs_setup(const char* backing, const char* mount_point, bool*
    return ret;
}

static bool GetAndMapScratchDeviceIfNeeded(std::string* device) {
    *device = GetScratchDevice();
static bool GetAndMapScratchDeviceIfNeeded(std::string* device, bool* mapped) {
    *mapped = false;
    *device = GetBootScratchDevice();
    if (!device->empty()) {
        return true;
    }

    auto strategy = GetScratchStrategy();
    if (strategy == ScratchStrategy::kDynamicPartition) {
    // Avoid uart spam by first checking for a scratch partition.
    auto metadata_slot = fs_mgr_overlayfs_slot_number();
    auto super_device = fs_mgr_overlayfs_super_device(metadata_slot);
    auto metadata = ReadCurrentMetadata(super_device);
    if (!metadata) {
        return false;
    }

    auto partition_name = android::base::Basename(kScratchMountPoint);
    auto partition = FindPartition(*metadata.get(), partition_name);
    if (!partition) {
        return false;
    }

    CreateLogicalPartitionParams params = {
                .block_device = fs_mgr_overlayfs_super_device(metadata_slot),
                .metadata_slot = metadata_slot,
                .partition_name = android::base::Basename(kScratchMountPoint),
            .block_device = super_device,
            .metadata = metadata.get(),
            .partition = partition,
            .force_writable = true,
            .timeout_ms = 10s,
    };
        return CreateLogicalPartition(params, device);
    }
    if (!CreateLogicalPartition(params, device)) {
        return false;
    }
    *mapped = true;
    return true;
}

// Returns false if teardown not permitted, errno set to last error.
// If something is altered, set *change.
bool fs_mgr_overlayfs_teardown(const char* mount_point, bool* change) {
    if (change) *change = false;
    auto ret = true;

    // If scratch exists, but is not mounted, lets gain access to clean
    // specific override entries.
    auto mount_scratch = false;
    bool unmap = false;
    if ((mount_point != nullptr) && !fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) {
        std::string scratch_device;
        if (GetAndMapScratchDeviceIfNeeded(&scratch_device)) {
        if (GetAndMapScratchDeviceIfNeeded(&scratch_device, &unmap)) {
            mount_scratch = fs_mgr_overlayfs_mount_scratch(scratch_device,
                                                           fs_mgr_overlayfs_scratch_mount_type());
        }
@@ -1294,8 +1315,12 @@ bool fs_mgr_overlayfs_teardown(const char* mount_point, bool* change) {
        PERROR << "teardown";
        ret = false;
    }
    if (mount_scratch) fs_mgr_overlayfs_umount_scratch();

    if (mount_scratch) {
        fs_mgr_overlayfs_umount_scratch();
    }
    if (unmap) {
        DestroyLogicalPartition(android::base::Basename(kScratchMountPoint));
    }
    return ret;
}