wibo/handles.cpp

239 lines
5.5 KiB
C++

#include "handles.h"
#include <cassert>
#include <cstdint>
namespace {
constexpr uint32_t kHandleAlignShift = 2;
// Max index that still yields HANDLE < 0x10000 with (index + 1) << 2
constexpr uint32_t kCompatMaxIndex = (0xFFFFu >> kHandleAlignShift) - 1;
// Delay reuse of small handles to avoid accidental stale aliasing
constexpr uint32_t kQuarantineLen = 64;
inline uint32_t indexOf(HANDLE h) {
uint32_t v = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(h));
if (v == 0 || (v & ((1U << kHandleAlignShift) - 1)) != 0) {
return UINT32_MAX;
}
return (v >> kHandleAlignShift) - 1;
}
inline HANDLE makeHandle(uint32_t index) {
uint32_t v = (index + 1) << kHandleAlignShift;
return reinterpret_cast<HANDLE>(static_cast<uintptr_t>(v));
}
inline bool isPseudo(HANDLE h) { return reinterpret_cast<int32_t>(h) < 0; }
} // namespace
HANDLE Handles::alloc(Pin<> obj, uint32_t grantedAccess, uint32_t flags) {
std::unique_lock lk(m);
// Attempt to, in order:
// 1) use a fresh index in the compat range (0..kCompatMaxIndex)
// 2) reuse a recently-freed index in the compat range
// 3) reuse a recently-freed index above the compat range
// 4) use a fresh index above the compat range
uint32_t idx;
if (nextIndex <= kCompatMaxIndex) {
idx = nextIndex++;
if (idx >= mSlots.size()) {
mSlots.emplace_back();
}
} else if (!mFreeBelow.empty()) {
idx = mFreeBelow.back();
mFreeBelow.pop_back();
} else if (!mFreeAbove.empty()) {
idx = mFreeAbove.back();
mFreeAbove.pop_back();
} else {
idx = static_cast<uint32_t>(mSlots.size());
mSlots.emplace_back();
}
// Initialize entry
auto &e = mSlots[idx];
e.obj = obj.release(); // Transfer ownership
e.meta.grantedAccess = grantedAccess;
e.meta.flags = flags;
e.meta.typeCache = e.obj->type;
if (e.meta.generation == 0) {
e.meta.generation = 1;
}
HANDLE h = makeHandle(idx);
e.obj->handleCount.fetch_add(1, std::memory_order_acq_rel);
return h;
}
Pin<> Handles::get(HANDLE h, HandleMeta *metaOut) {
if (isPseudo(h)) {
return {}; // pseudo-handles have no entries
}
std::shared_lock lk(m);
const auto idx = indexOf(h);
if (idx >= mSlots.size()) {
return {};
}
const auto &e = mSlots[idx];
if (!e.obj) {
return {};
}
if (metaOut) {
*metaOut = e.meta;
}
return Pin<>::acquire(e.obj);
}
bool Handles::release(HANDLE h) {
if (isPseudo(h)) {
return true; // no-op, success
}
std::unique_lock lk(m);
const auto idx = indexOf(h);
if (idx >= mSlots.size()) {
return false;
}
auto &e = mSlots[idx];
if (!e.obj || e.meta.flags & HANDLE_FLAG_PROTECT_FROM_CLOSE) {
return false;
}
ObjectBase *obj = e.obj;
const auto generation = e.meta.generation + 1;
e = {}; // Clear entry
e.meta.generation = generation;
uint32_t handleCount = obj->handleCount.fetch_sub(1, std::memory_order_acq_rel) - 1;
if (idx <= kCompatMaxIndex) {
mQuarantine.push_back(idx);
if (mQuarantine.size() > kQuarantineLen) {
mFreeBelow.push_back(mQuarantine.front());
mQuarantine.pop_front();
}
} else {
mFreeAbove.push_back(idx);
}
lk.unlock();
if (handleCount == 0 && mOnHandleZero) {
mOnHandleZero(obj);
}
detail::deref(obj);
return true;
}
bool Handles::setInformation(HANDLE h, uint32_t mask, uint32_t value) {
if (isPseudo(h)) {
return true; // no-op, success
}
std::unique_lock lk(m);
const auto idx = indexOf(h);
if (idx >= mSlots.size()) {
return false;
}
auto &e = mSlots[idx];
if (!e.obj) {
return false;
}
constexpr uint32_t kAllowedFlags = HANDLE_FLAG_INHERIT | HANDLE_FLAG_PROTECT_FROM_CLOSE;
mask &= kAllowedFlags;
e.meta.flags = (e.meta.flags & ~mask) | (value & mask);
return true;
}
bool Handles::getInformation(HANDLE h, uint32_t *outFlags) const {
if (!outFlags) {
return false;
}
if (isPseudo(h)) {
*outFlags = 0;
return true;
}
std::shared_lock lk(m);
const auto idx = indexOf(h);
if (idx >= mSlots.size()) {
return false;
}
const auto &e = mSlots[idx];
if (!e.obj) {
return false;
}
*outFlags = e.meta.flags;
return true;
}
bool Handles::duplicateTo(HANDLE src, Handles &dst, HANDLE &out, uint32_t desiredAccess, bool inherit,
uint32_t options) {
HandleMeta meta{};
Pin<> obj = get(src, &meta);
if (!obj) {
return false;
}
bool closeSource = (options & DUPLICATE_CLOSE_SOURCE) != 0;
if (closeSource && (meta.flags & HANDLE_FLAG_PROTECT_FROM_CLOSE) != 0) {
// Cannot close source if it is protected
return false;
}
uint32_t effAccess = (options & DUPLICATE_SAME_ACCESS) ? meta.grantedAccess : (desiredAccess & meta.grantedAccess);
const uint32_t flags = (inherit ? HANDLE_FLAG_INHERIT : 0);
out = dst.alloc(std::move(obj), effAccess, flags);
if (closeSource) {
release(src);
}
return true;
}
bool Namespace::insert(const std::u16string &name, ObjectBase *obj, bool permanent) {
if (name.empty() || !obj) {
return false;
}
std::unique_lock lk(m);
// Namespace holds a weak ref
const auto [_, inserted] = mTable.try_emplace(name, obj, permanent);
return inserted;
}
void Namespace::remove(ObjectBase *obj) {
std::unique_lock lk(m);
for (auto it = mTable.begin(); it != mTable.end(); ++it) {
if (it->second.obj == obj && !it->second.permanent) {
mTable.erase(it);
break;
}
}
}
Pin<> Namespace::get(const std::u16string &name) {
if (name.empty()) {
return {};
}
std::shared_lock lk(m);
auto it = mTable.find(name);
if (it == mTable.end()) {
return {};
}
assert(it->second.obj);
return Pin<>::acquire(it->second.obj);
}
namespace wibo {
Namespace g_namespace;
Handles &handles() {
static Handles table([](ObjectBase *obj) { g_namespace.remove(obj); });
return table;
}
} // namespace wibo