blob: f5da5973875b9f5982c157a5f7d6d4201a6076c2 [file]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_STORAGE_LIB_OPERATION_OPERATION_TREE_H_
#define SRC_STORAGE_LIB_OPERATION_OPERATION_TREE_H_
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <algorithm>
#include <cstdint>
#include <utility>
#include <vector>
#include <safemath/checked_math.h>
#include "src/storage/lib/operation/operation.h"
#include "src/storage/lib/range/interval-tree.h"
#include "src/storage/lib/range/range.h"
#ifdef __Fuchsia__
#include "src/storage/lib/operation/unbuffered_operation.h"
#endif
namespace storage {
namespace internal {
inline bool EqualVmoDeviceOffsetSkew(const Operation& a, const Operation& b) {
return (a.vmo_offset - b.vmo_offset) == (a.dev_offset - b.dev_offset);
}
inline bool IsMergeableIoType(OperationType type) {
return type == OperationType::kWrite || type == OperationType::kRead;
}
inline bool EqualSource(const BufferedOperation& a, const BufferedOperation& b) {
#ifdef __Fuchsia__
return a.vmoid == b.vmoid;
#else
return a.data == b.data;
#endif
}
#ifdef __Fuchsia__
inline bool EqualSource(const UnbufferedOperation& a, const UnbufferedOperation& b) {
zx_handle_t a_handle = a.vmo->is_valid() ? a.vmo->get() : ZX_HANDLE_INVALID;
zx_handle_t b_handle = b.vmo->is_valid() ? b.vmo->get() : ZX_HANDLE_INVALID;
return a_handle == b_handle && a.data == b.data;
}
#endif
template <typename Op>
bool CanMergeOperations(const Op& a, const Op& b) {
if (!EqualSource(a, b)) {
return false;
}
if (a.op.type != b.op.type) {
return false;
}
if (!IsMergeableIoType(a.op.type)) {
return false;
}
return EqualVmoDeviceOffsetSkew(a.op, b.op);
}
// Container for operations stored in an OperationTree.
//
// The "dev_offset" is used as a key for determining overlap.
template <typename Op>
class OperationRangeContainer {
public:
explicit OperationRangeContainer(Op op) : operation_(std::move(op)) {}
uint64_t Start() const { return operation_.op.dev_offset; }
uint64_t End() const { return operation_.op.dev_offset + operation_.op.length; }
const Op& operation() const { return operation_; }
Op& operation() { return operation_; }
void Update(uint64_t start, uint64_t end) {
// Update is called during range merges and splits. During these operations, vmo_offset stays a
// constant distance away from dev_offset. Calculate the movement of dev_offset in this
// operation and move vmo_offset accordingly.
int64_t diff = static_cast<int64_t>(start) - static_cast<int64_t>(operation_.op.dev_offset);
operation_.op.vmo_offset += diff;
operation_.op.dev_offset = start;
operation_.op.length = end - start;
}
private:
Op operation_;
};
// Traits which enable an operation to exist in an interval tree.
template <typename Op>
struct OperationRangeTraits {
static uint64_t Start(const OperationRangeContainer<Op>& obj) { return obj.Start(); }
static uint64_t End(const OperationRangeContainer<Op>& obj) { return obj.End(); }
static zx_status_t Update(const OperationRangeContainer<Op>* other, uint64_t start, uint64_t end,
OperationRangeContainer<Op>* obj) {
if (other) {
if (!CanMergeOperations(other->operation(), obj->operation())) {
return ZX_ERR_INVALID_ARGS;
}
}
obj->Update(start, end);
return ZX_OK;
}
};
template <typename Op>
using OperationRange =
range::Range<uint64_t, OperationRangeContainer<Op>, OperationRangeTraits<Op>>;
template <typename Op>
using OperationIntervalTree = range::IntervalTree<OperationRange<Op>>;
} // namespace internal
// An interval tree which collects and coalesces storage operations targeting device offsets.
//
// On insertion, the tree ensures that the latest operation touching a particular destination block
// overwrites any prior operations. If possible, contiguous operations with the same source are
// merged. If an operation partially overlaps or overwrites with a different source, overlapping
// ranges are split and updated, guaranteeing there are no duplicate destination offsets.
template <typename Op>
class OperationTree {
public:
using RangeType = internal::OperationRange<Op>;
using TreeType = internal::OperationIntervalTree<Op>;
using IterType = typename TreeType::IterType;
using ConstIterType = typename TreeType::ConstIterType;
OperationTree() = default;
// Inserts an operation into the tree.
//
// First, removes all overlapping prior operations which target the same device offset, and then
// inserts |operation|. This ensures that the "latest operation touching block B" will be the only
// operation targeting that block.
void insert(Op operation) {
if (operation.op.length == 0) {
return;
}
RangeType range((internal::OperationRangeContainer<Op>(std::move(operation))));
// Erase all prior operations which touch the same dev_offset.
tree_.erase(range);
// Utilize the newest operations touching dev_offset.
tree_.insert(range);
}
// Returns the total number of blocks in all requests.
[[nodiscard]] uint64_t BlockCount() const {
safemath::CheckedNumeric<uint64_t> count = 0;
for (const auto& [_, range] : tree_) {
count += range.Length();
}
// ValueOrDie may kill this process, but it practically should never happen and the failure here
// would likely mean that the filesystem is not going to be able to continue anyways.
return count.ValueOrDie();
}
// Removes the operations from the tree, and returns them to the caller sorted by dev_offset.
std::vector<Op> TakeOperations() {
std::vector<Op> operations;
operations.reserve(tree_.size());
for (auto& [_, range] : tree_) {
operations.push_back(std::move(range.container().operation()));
}
tree_.clear();
return operations;
}
void clear() { tree_.clear(); }
[[nodiscard]] bool empty() const { return tree_.empty(); }
[[nodiscard]] size_t size() const { return tree_.size(); }
IterType begin() { return tree_.begin(); }
ConstIterType begin() const { return tree_.begin(); }
IterType end() { return tree_.end(); }
ConstIterType end() const { return tree_.end(); }
private:
TreeType tree_;
};
} // namespace storage
#endif // SRC_STORAGE_LIB_OPERATION_OPERATION_TREE_H_