Revision control

Copy as Markdown

// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qioring_p.h"
QT_REQUIRE_CONFIG(windows_ioring);
#include <QtCore/qcompilerdetection.h>
#include <QtCore/qobject.h>
#include <QtCore/qscopedvaluerollback.h>
#include <qt_windows.h>
#include <ioringapi.h>
#include <QtCore/q26numeric.h>
QT_BEGIN_NAMESPACE
// We don't really build for 32-bit windows anymore, but this code is definitely wrong if someone
// does.
static_assert(sizeof(qsizetype) > sizeof(UINT32),
"This code is written with assuming 64-bit Windows.");
using namespace Qt::StringLiterals;
namespace QtPrivate {
#define FOREACH_WIN_IORING_FUNCTION(Fn) \
Fn(BuildIoRingReadFile) \
Fn(BuildIoRingWriteFile) \
Fn(BuildIoRingFlushFile) \
Fn(BuildIoRingCancelRequest) \
Fn(QueryIoRingCapabilities) \
Fn(CreateIoRing) \
Fn(GetIoRingInfo) \
Fn(SubmitIoRing) \
Fn(CloseIoRing) \
Fn(PopIoRingCompletion) \
Fn(SetIoRingCompletionEvent) \
/**/
struct IORingApiTable
{
#if QT_CONFIG(windows_ioring_runtime)
# define DefineIORingFunction(Name) \
using Name##Fn = decltype(&::Name); \
Name##Fn Name = nullptr;
#else // !windows_ioring_runtime:
# define DefineIORingFunction(Name) \
using Name##Fn = decltype(&::Name); \
Name##Fn Name = &::Name;
#endif // windows_ioring_runtime
FOREACH_WIN_IORING_FUNCTION(DefineIORingFunction)
#undef DefineIORingFunction
};
static const IORingApiTable *getApiTable()
{
#if !QT_CONFIG(windows_ioring_runtime)
Q_CONSTINIT static const IORingApiTable apiTable;
// The table is directly initialized, so we always succeed:
constexpr bool success = true;
#else // windows_ioring_runtime
static const IORingApiTable apiTable = []() {
IORingApiTable apiTable;
const HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
if (Q_UNLIKELY(!kernel32)) // how would this happen
return apiTable;
# define ResolveFunction(Name) \
apiTable.Name = IORingApiTable::Name##Fn(QFunctionPointer(GetProcAddress(kernel32, #Name)));
FOREACH_WIN_IORING_FUNCTION(ResolveFunction)
# undef ResolveFunction
return apiTable;
}();
# define TEST_TABLE_OK(X) apiTable.X && /* chain */
# define BOOL_CHAIN(...) (__VA_ARGS__ true)
static const bool success = BOOL_CHAIN(FOREACH_WIN_IORING_FUNCTION(TEST_TABLE_OK));
# undef BOOL_CHAIN
# undef TEST_TABLE_OK
#endif // windows_ioring_runtime
return success ? std::addressof(apiTable) : nullptr;
}
} // namespace QtPrivate
static HRESULT buildReadOperation(HIORING ioRingHandle, qintptr fd, QSpan<std::byte> destination,
quint64 offset, quintptr userData)
{
// NOLINTNEXTLINE(performance-no-int-to-ptr)
const IORING_HANDLE_REF fileRef((HANDLE(fd)));
const IORING_BUFFER_REF bufferRef(destination.data());
const auto maxSize = q26::saturating_cast<UINT32>(destination.size());
Q_ASSERT(maxSize == destination.size());
const auto *apiTable = QtPrivate::getApiTable();
Q_ASSERT(apiTable); // If we got this far it needs to be here
return apiTable->BuildIoRingReadFile(ioRingHandle, fileRef, bufferRef, maxSize, offset,
userData, IOSQE_FLAGS_NONE);
}
static HRESULT buildWriteOperation(HIORING ioRingHandle, qintptr fd, QSpan<const std::byte> source,
quint64 offset, quintptr userData)
{
// NOLINTNEXTLINE(performance-no-int-to-ptr)
const IORING_HANDLE_REF fileRef((HANDLE(fd)));
const IORING_BUFFER_REF bufferRef(const_cast<std::byte *>(source.data()));
const auto maxSize = q26::saturating_cast<UINT32>(source.size());
Q_ASSERT(maxSize == source.size());
const auto *apiTable = QtPrivate::getApiTable();
Q_ASSERT(apiTable); // If we got this far it needs to be here
// @todo: FILE_WRITE_FLAGS can be set to write-through, could be used for Unbuffered mode.
return apiTable->BuildIoRingWriteFile(ioRingHandle, fileRef, bufferRef, maxSize, offset,
FILE_WRITE_FLAGS_NONE, userData, IOSQE_FLAGS_NONE);
}
QIORing::~QIORing()
{
if (initialized) {
CloseHandle(eventHandle);
apiTable->CloseIoRing(ioRingHandle);
}
}
bool QIORing::initializeIORing()
{
if (initialized)
return true;
if (apiTable = QtPrivate::getApiTable(); !apiTable) {
qCWarning(lcQIORing, "Failed to retrieve API table");
return false;
}
IORING_CAPABILITIES capabilities;
apiTable->QueryIoRingCapabilities(&capabilities);
if (capabilities.MaxVersion < IORING_VERSION_3) // 3 adds write, flush and drain
return false;
if ((capabilities.FeatureFlags & IORING_FEATURE_SET_COMPLETION_EVENT) == 0)
return false; // We currently require the SET_COMPLETION_EVENT feature
qCDebug(lcQIORing) << "Creating QIORing, requesting space for" << sqEntries
<< "submission queue entries, and" << cqEntries
<< "completion queue entries";
IORING_CREATE_FLAGS flags;
memset(&flags, 0, sizeof(flags));
#if !defined(QT_DEBUG) && QT_CONFIG(windows_ioring_skip_builder_param_checks)
flags.Advisory |= IORING_CREATE_SKIP_BUILDER_PARAM_CHECKS;
#endif
HRESULT hr = apiTable->CreateIoRing(IORING_VERSION_3, flags, sqEntries, cqEntries,
&ioRingHandle);
if (FAILED(hr)) {
qErrnoWarning(hr, "failed to initialize QIORing");
return false;
}
auto earlyExitCleanup = qScopeGuard([this]() {
if (eventHandle != INVALID_HANDLE_VALUE)
CloseHandle(eventHandle);
apiTable->CloseIoRing(ioRingHandle);
});
eventHandle = CreateEvent(nullptr, TRUE, FALSE, nullptr);
if (eventHandle == INVALID_HANDLE_VALUE) {
qErrnoWarning("Failed to create event handle");
return false;
}
notifier.emplace(eventHandle);
hr = apiTable->SetIoRingCompletionEvent(ioRingHandle, eventHandle);
if (FAILED(hr)) {
qErrnoWarning(hr, "Failed to assign the event handle to QIORing");
return false;
}
IORING_INFO info;
if (SUCCEEDED(apiTable->GetIoRingInfo(ioRingHandle, &info))) {
sqEntries = info.SubmissionQueueSize;
cqEntries = info.CompletionQueueSize;
qCDebug(lcQIORing) << "QIORing configured with capacity for" << sqEntries
<< "submissions, and" << cqEntries << "completions.";
}
QObject::connect(std::addressof(*notifier), &QWinEventNotifier::activated,
std::addressof(*notifier), [this]() { completionReady(); });
initialized = true;
earlyExitCleanup.dismiss();
return true;
}
QFileDevice::FileError QIORing::mapFileError(NativeResultType error,
QFileDevice::FileError defaultValue)
{
Q_ASSERT(FAILED(error));
if (error == E_ABORT)
return QFileDevice::AbortError;
return defaultValue;
}
void QIORing::completionReady()
{
ResetEvent(eventHandle);
IORING_CQE entry;
while (apiTable->PopIoRingCompletion(ioRingHandle, &entry) == S_OK) {
// NOLINTNEXTLINE(performance-no-int-to-ptr)
auto *request = reinterpret_cast<GenericRequestType *>(entry.UserData);
if (!addrItMap.contains(request)) {
qCDebug(lcQIORing) << "Got completed entry, but cannot find it in the map. Likely "
"deleted, ignoring. UserData pointer:"
<< request;
continue;
}
qCDebug(lcQIORing) << "Got completed entry. Operation:" << request->operation()
<< "- UserData pointer:" << request
<< "- Result:" << qt_error_string(entry.ResultCode) << '('
<< QByteArray("0x"_ba + QByteArray::number(entry.ResultCode, 16)).data()
<< ')';
switch (request->operation()) {
case Operation::Open: // Synchronously finishes
Q_UNREACHABLE_RETURN();
case Operation::Close: {
auto closeRequest = request->takeRequestData<Operation::Close>();
// We ignore the result of the flush, we are closing the handle anyway.
// NOLINTNEXTLINE(performance-no-int-to-ptr)
if (CloseHandle(HANDLE(closeRequest.fd)))
closeRequest.result.emplace<QIORingResult<Operation::Close>>();
else
closeRequest.result.emplace<QFileDevice::FileError>(QFileDevice::OpenError);
invokeCallback(closeRequest);
break;
}
case Operation::Read: {
const ReadWriteStatus status = handleReadCompletion<Operation::Read>(
entry.ResultCode, entry.Information, request);
if (status == ReadWriteStatus::MoreToDo)
continue;
auto readRequest = request->takeRequestData<Operation::Read>();
invokeCallback(readRequest);
break;
}
case Operation::Write: {
const ReadWriteStatus status = handleWriteCompletion<Operation::Write>(
entry.ResultCode, entry.Information, request);
if (status == ReadWriteStatus::MoreToDo)
continue;
auto writeRequest = request->takeRequestData<Operation::Write>();
invokeCallback(writeRequest);
break;
}
case Operation::VectoredRead: {
const ReadWriteStatus status = handleReadCompletion<Operation::VectoredRead>(
entry.ResultCode, entry.Information, request);
if (status == ReadWriteStatus::MoreToDo)
continue;
auto vectoredReadRequest = request->takeRequestData<Operation::VectoredRead>();
invokeCallback(vectoredReadRequest);
break;
}
case Operation::VectoredWrite: {
const ReadWriteStatus status = handleWriteCompletion<Operation::VectoredWrite>(
entry.ResultCode, entry.Information, request);
if (status == ReadWriteStatus::MoreToDo)
continue;
auto vectoredWriteRequest = request->takeRequestData<Operation::VectoredWrite>();
invokeCallback(vectoredWriteRequest);
break;
}
case Operation::Flush: {
auto flushRequest = request->takeRequestData<Operation::Flush>();
if (FAILED(entry.ResultCode)) {
qErrnoWarning(entry.ResultCode, "Flush operation failed");
// @todo any FlushError?
flushRequest.result.emplace<QFileDevice::FileError>(
QFileDevice::FileError::WriteError);
} else {
flushRequest.result.emplace<QIORingResult<Operation::Flush>>();
}
invokeCallback(flushRequest);
break;
}
case QtPrivate::Operation::Cancel: {
auto cancelRequest = request->takeRequestData<Operation::Cancel>();
invokeCallback(cancelRequest);
break;
}
case QtPrivate::Operation::Stat:
Q_UNREACHABLE_RETURN(); // Completes synchronously
break;
case Operation::NumOperations:
Q_UNREACHABLE_RETURN();
break;
}
auto it = addrItMap.take(request);
pendingRequests.erase(it);
--inFlightRequests;
queueWasFull = false;
}
prepareRequests();
if (unstagedRequests > 0)
submitRequests();
}
bool QIORing::waitForCompletions(QDeadlineTimer deadline)
{
notifier->setEnabled(false);
auto reactivateNotifier = qScopeGuard([this]() {
notifier->setEnabled(true);
});
while (!deadline.hasExpired()) {
DWORD timeout = 0;
if (deadline.isForever()) {
timeout = INFINITE;
} else {
timeout = q26::saturating_cast<DWORD>(deadline.remainingTime());
if (timeout == INFINITE)
--timeout;
}
if (WaitForSingleObject(eventHandle, timeout) == WAIT_OBJECT_0)
return true;
}
return false;
}
static HANDLE openFile(const QIORingRequest<QIORing::Operation::Open> &openRequest)
{
DWORD access = 0;
if (openRequest.flags.testFlag(QIODevice::ReadOnly))
access |= GENERIC_READ;
if (openRequest.flags.testFlag(QIODevice::WriteOnly))
access |= GENERIC_WRITE;
DWORD disposition = 0;
if (openRequest.flags.testFlag(QIODevice::Append)) {
qCWarning(lcQIORing, "Opening file with Append not supported for random access file");
return INVALID_HANDLE_VALUE;
}
if (openRequest.flags.testFlag(QIODevice::NewOnly)) {
disposition = CREATE_NEW;
} else {
// If Write is specified we _may_ create a file.
// See qfsfileengine_p.h openModeCanCreate.
disposition = openRequest.flags.testFlag(QIODeviceBase::WriteOnly)
&& !openRequest.flags.testFlags(QIODeviceBase::ExistingOnly)
? OPEN_ALWAYS
: OPEN_EXISTING;
}
const DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
const DWORD flagsAndAttribs = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED;
HANDLE h = CreateFile(openRequest.path.native().c_str(), access, shareMode, nullptr,
disposition, flagsAndAttribs, nullptr);
if (h != INVALID_HANDLE_VALUE && openRequest.flags.testFlag(QIODeviceBase::Truncate)) {
FILE_END_OF_FILE_INFO info;
memset(&info, 0, sizeof(info));
SetFileInformationByHandle(h, FileEndOfFileInfo, &info, sizeof(info));
}
return h;
}
bool QIORing::supportsOperation(Operation op)
{
switch (op) {
case QtPrivate::Operation::Open:
case QtPrivate::Operation::Close:
case QtPrivate::Operation::Read:
case QtPrivate::Operation::Write:
case QtPrivate::Operation::Flush:
case QtPrivate::Operation::Cancel:
case QtPrivate::Operation::Stat:
case QtPrivate::Operation::VectoredRead:
case QtPrivate::Operation::VectoredWrite:
return true;
case QtPrivate::Operation::NumOperations:
return false;
}
return false; // Not unreachable, we could allow more for io_uring
}
void QIORing::submitRequests()
{
stagePending = false;
if (unstagedRequests == 0)
return;
// We perform a miniscule wait - to see if anything already in the queue is already completed -
// if we have been told the queue is full. Then we can try queuing more things right away
const bool shouldTryWait = std::exchange(queueWasFull, false);
const auto submitToRing = [this, &shouldTryWait] {
quint32 submittedEntries = 0;
HRESULT hr = apiTable->SubmitIoRing(ioRingHandle, shouldTryWait ? 1 : 0, 1,
&submittedEntries);
qCDebug(lcQIORing) << "Submitted" << submittedEntries << "requests";
unstagedRequests -= submittedEntries;
if (FAILED(hr)) {
// Too noisy, not a real problem
// qErrnoWarning(hr, "Failed to submit QIORing request: %u", submittedEntries);
return false;
}
return submittedEntries > 0;
};
if (submitToRing() && shouldTryWait) {
// We try to prepare some more request and submit more if able
prepareRequests();
if (unstagedRequests > 0)
submitToRing();
}
}
void QIORing::prepareRequests()
{
if (!lastUnqueuedIterator)
return;
Q_ASSERT(!preparingRequests);
QScopedValueRollback<bool> prepareGuard(preparingRequests, true);
auto it = *lastUnqueuedIterator;
lastUnqueuedIterator.reset();
const auto end = pendingRequests.end();
while (!queueWasFull && it != end) {
auto &request = *it;
switch (prepareRequest(request)) {
case RequestPrepResult::Ok:
++unstagedRequests;
++inFlightRequests;
break;
case RequestPrepResult::QueueFull:
qCDebug(lcQIORing) << "Queue was reported as full, in flight requests:"
<< inFlightRequests << "submission queue size:" << sqEntries
<< "completion queue size:" << cqEntries;
queueWasFull = true;
lastUnqueuedIterator = it;
return;
case RequestPrepResult::Defer:
qCDebug(lcQIORing) << "Request for" << request.operation()
<< "had to be deferred, will not queue any more requests at the "
"moment.";
lastUnqueuedIterator = it;
return; //
case RequestPrepResult::RequestCompleted:
// Used for requests that immediately finish. So we erase it:
qCDebug(lcQIORing) << "Request for" << request.operation()
<< "completed synchronously.";
addrItMap.remove(&request);
it = pendingRequests.erase(it);
continue; // Don't increment iterator again
}
++it;
}
if (it != pendingRequests.end())
lastUnqueuedIterator = it;
}
namespace QtPrivate {
template <typename T>
using DetectHasFd = decltype(std::declval<const T &>().fd);
template <typename T>
constexpr bool OperationHasFd_v = qxp::is_detected_v<DetectHasFd, T>;
} // namespace QtPrivate
auto QIORing::prepareRequest(GenericRequestType &request) -> RequestPrepResult
{
qCDebug(lcQIORing) << "Preparing a request with operation" << request.operation();
HRESULT hr = -1;
if (!verifyFd(request)) {
finishRequestWithError(request, QFileDevice::OpenError);
return RequestPrepResult::RequestCompleted;
}
switch (request.operation()) {
case Operation::Open: {
QIORingRequest<Operation::Open> openRequest = request.takeRequestData<Operation::Open>();
HANDLE fileDescriptor = openFile(openRequest);
if (fileDescriptor == INVALID_HANDLE_VALUE) {
openRequest.result.emplace<QFileDevice::FileError>(QFileDevice::FileError::OpenError);
} else {
auto &result = openRequest.result.emplace<QIORingResult<Operation::Open>>();
result.fd = qintptr(fileDescriptor);
}
invokeCallback(openRequest);
return RequestPrepResult::RequestCompleted;
}
case Operation::Close: {
if (ongoingSplitOperations > 0)
return RequestPrepResult::Defer;
// We need to wait until all previous OPS are done before we close the request.
// There is no no-op request in the Windows QIORing, so we issue a flush.
auto *closeRequest = request.requestData<Operation::Close>();
// NOLINTNEXTLINE(performance-no-int-to-ptr)
const IORING_HANDLE_REF fileRef(HANDLE(closeRequest->fd));
hr = apiTable->BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_MIN_METADATA,
quintptr(std::addressof(request)),
IOSQE_FLAGS_DRAIN_PRECEDING_OPS);
break;
}
case Operation::Read: {
auto *readRequest = request.requestData<Operation::Read>();
auto span = readRequest->destination;
auto offset = readRequest->offset;
if (span.size() > MaxReadWriteLen) {
qCDebug(lcQIORing) << "Requested Read of size" << span.size() << "has to be split";
auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
if (extra->spanOffset == 0)
++ongoingSplitOperations;
const qsizetype remaining = span.size() - extra->spanOffset;
span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen));
offset += extra->totalProcessed;
}
hr = buildReadOperation(ioRingHandle, readRequest->fd, span, offset,
quintptr(std::addressof(request)));
break;
}
case Operation::VectoredRead: {
auto *vectoredReadRequest = request.requestData<Operation::VectoredRead>();
auto span = vectoredReadRequest->destinations.front();
auto offset = vectoredReadRequest->offset;
if (Q_LIKELY(vectoredReadRequest->destinations.size() > 1
|| span.size() > MaxReadWriteLen)) {
auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
if (extra->spanOffset == 0 && extra->spanIndex == 0)
++ongoingSplitOperations;
extra->numSpans = vectoredReadRequest->destinations.size();
span = vectoredReadRequest->destinations[extra->spanIndex];
const qsizetype remaining = span.size() - extra->spanOffset;
span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen));
offset += extra->totalProcessed;
}
hr = buildReadOperation(ioRingHandle, vectoredReadRequest->fd, span,
offset, quintptr(std::addressof(request)));
break;
}
case Operation::Write: {
auto *writeRequest = request.requestData<Operation::Write>();
auto span = writeRequest->source;
auto offset = writeRequest->offset;
if (span.size() > MaxReadWriteLen) {
qCDebug(lcQIORing) << "Requested Write of size" << span.size() << "has to be split";
auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
if (extra->spanOffset == 0)
++ongoingSplitOperations;
const qsizetype remaining = span.size() - extra->spanOffset;
span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen));
offset += extra->totalProcessed;
}
hr = buildWriteOperation(ioRingHandle, writeRequest->fd, span, offset,
quintptr(std::addressof(request)));
break;
}
case Operation::VectoredWrite: {
auto *vectoredWriteRequest = request.requestData<Operation::VectoredWrite>();
auto span = vectoredWriteRequest->sources.front();
auto offset = vectoredWriteRequest->offset;
if (Q_LIKELY(vectoredWriteRequest->sources.size() > 1
|| span.size() > MaxReadWriteLen)) {
auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
if (extra->spanOffset == 0 && extra->spanIndex == 0)
++ongoingSplitOperations;
extra->numSpans = vectoredWriteRequest->sources.size();
span = vectoredWriteRequest->sources[extra->spanIndex];
const qsizetype remaining = span.size() - extra->spanOffset;
span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen));
offset += extra->totalProcessed;
}
hr = buildWriteOperation(ioRingHandle, vectoredWriteRequest->fd, span,
offset, quintptr(std::addressof(request)));
break;
}
case Operation::Flush: {
if (ongoingSplitOperations > 0)
return RequestPrepResult::Defer;
auto *flushRequest = request.requestData<Operation::Flush>();
// NOLINTNEXTLINE(performance-no-int-to-ptr)
const IORING_HANDLE_REF fileRef(HANDLE(flushRequest->fd));
hr = apiTable->BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_DEFAULT,
quintptr(std::addressof(request)),
IOSQE_FLAGS_DRAIN_PRECEDING_OPS);
break;
}
case QtPrivate::Operation::Stat: {
auto statRequest = request.takeRequestData<Operation::Stat>();
FILE_STANDARD_INFO info;
// NOLINTNEXTLINE(performance-no-int-to-ptr)
if (!GetFileInformationByHandleEx(HANDLE(statRequest.fd), FileStandardInfo, &info,
sizeof(info))) {
DWORD winErr = GetLastError();
QFileDevice::FileError error = QFileDevice::UnspecifiedError;
if (winErr == ERROR_FILE_NOT_FOUND || winErr == ERROR_INVALID_HANDLE)
error = QFileDevice::OpenError;
else if (winErr == ERROR_ACCESS_DENIED)
error = QFileDevice::PermissionsError;
statRequest.result.emplace<QFileDevice::FileError>(error);
} else {
auto &result = statRequest.result.emplace<QIORingResult<Operation::Stat>>();
result.size = info.EndOfFile.QuadPart;
}
invokeCallback(statRequest);
return RequestPrepResult::RequestCompleted;
}
case Operation::Cancel: {
auto *cancelRequest = request.requestData<Operation::Cancel>();
auto *otherOperation = reinterpret_cast<GenericRequestType *>(cancelRequest->handle);
if (!otherOperation || !addrItMap.contains(otherOperation)) {
qCDebug(lcQIORing, "Invalid cancel for non-existant operation");
invokeCallback(*cancelRequest);
return RequestPrepResult::RequestCompleted;
}
qCDebug(lcQIORing) << "Cancelling operation of type" << otherOperation->operation()
<< "which was"
<< (otherOperation->wasQueued() ? "queued" : "not queued");
Q_ASSERT(&request != otherOperation);
if (!otherOperation->wasQueued()) {
// The request hasn't been queued yet, so we can just drop it from
// the pending requests and call the callback.
auto it = addrItMap.take(otherOperation);
finishRequestWithError(*otherOperation, QFileDevice::AbortError);
pendingRequests.erase(it); // otherOperation is deleted
invokeCallback(*cancelRequest);
return RequestPrepResult::RequestCompleted;
}
qintptr fd = -1;
invokeOnOp(*otherOperation, [&fd](auto *request) {
if constexpr (QtPrivate::OperationHasFd_v<decltype(*request)>)
fd = request->fd;
});
if (fd == -1) {
qCDebug(lcQIORing, "Invalid cancel for non-existant fd");
invokeCallback(*cancelRequest);
return RequestPrepResult::RequestCompleted;
}
// NOLINTNEXTLINE(performance-no-int-to-ptr)
const IORING_HANDLE_REF fileRef((HANDLE(fd)));
hr = apiTable->BuildIoRingCancelRequest(ioRingHandle, fileRef, quintptr(otherOperation),
quintptr(std::addressof(request)));
break;
}
case Operation::NumOperations:
Q_UNREACHABLE_RETURN(RequestPrepResult::RequestCompleted);
break;
}
if (hr == IORING_E_SUBMISSION_QUEUE_FULL)
return RequestPrepResult::QueueFull;
if (FAILED(hr)) {
finishRequestWithError(request, QFileDevice::UnspecifiedError);
return RequestPrepResult::RequestCompleted;
}
request.setQueued(true);
return RequestPrepResult::Ok;
}
bool QIORing::verifyFd(GenericRequestType &req)
{
bool result = true;
invokeOnOp(req, [&](auto *request) {
if constexpr (QtPrivate::OperationHasFd_v<decltype(*request)>) {
result = quintptr(request->fd) > 0 && quintptr(request->fd) != quintptr(INVALID_HANDLE_VALUE);
}
});
return result;
}
void QIORing::GenericRequestType::cleanupExtra(Operation op, void *extra)
{
switch (op) {
case QtPrivate::Operation::Read:
case QtPrivate::Operation::VectoredRead:
case QtPrivate::Operation::Write:
case QtPrivate::Operation::VectoredWrite:
delete static_cast<QtPrivate::ReadWriteExtra *>(extra);
break;
case QtPrivate::Operation::Open:
case QtPrivate::Operation::Close:
case QtPrivate::Operation::Flush:
case QtPrivate::Operation::Stat:
case QtPrivate::Operation::Cancel:
case QtPrivate::Operation::NumOperations:
break;
}
}
QT_END_NAMESPACE