Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qioring_win.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qioring_p.h"
6
8
9#include <QtCore/qcompilerdetection.h>
10#include <QtCore/qobject.h>
11#include <QtCore/qscopedvaluerollback.h>
12
13#include <qt_windows.h>
14#include <ioringapi.h>
15
16#include <QtCore/q26numeric.h>
17
18QT_BEGIN_NAMESPACE
19
20// We don't really build for 32-bit windows anymore, but this code is definitely wrong if someone
21// does.
22static_assert(sizeof(qsizetype) > sizeof(UINT32),
23 "This code is written with assuming 64-bit Windows.");
24
25using namespace Qt::StringLiterals;
26
27namespace QtPrivate {
28#define FOREACH_WIN_IORING_FUNCTION(Fn)
29 Fn(BuildIoRingReadFile)
30 Fn(BuildIoRingWriteFile)
31 Fn(BuildIoRingFlushFile)
32 Fn(BuildIoRingCancelRequest)
33 Fn(QueryIoRingCapabilities)
34 Fn(CreateIoRing)
35 Fn(GetIoRingInfo)
36 Fn(SubmitIoRing)
37 Fn(CloseIoRing)
38 Fn(PopIoRingCompletion)
39 Fn(SetIoRingCompletionEvent)
40 /**/
42{
43#if QT_CONFIG(windows_ioring_runtime)
44# define DefineIORingFunction(Name)
45 using Name##Fn = decltype(&::Name);
46 Name##Fn Name = nullptr;
47#else // !windows_ioring_runtime:
48# define DefineIORingFunction(Name)
49 using Name##Fn = decltype(&::Name);
50 Name##Fn Name = &::Name;
51#endif // windows_ioring_runtime
52
54
55#undef DefineIORingFunction
56};
57
59{
60#if !QT_CONFIG(windows_ioring_runtime)
61 Q_CONSTINIT static const IORingApiTable apiTable;
62 // The table is directly initialized, so we always succeed:
63 constexpr bool success = true;
64#else // windows_ioring_runtime
65 static const IORingApiTable apiTable = []() {
66 IORingApiTable apiTable;
67 const HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
68 if (Q_UNLIKELY(!kernel32)) // how would this happen
69 return apiTable;
70
71# define ResolveFunction(Name)
72 apiTable.Name = IORingApiTable::Name##Fn(QFunctionPointer(GetProcAddress(kernel32, #Name)));
73
74 FOREACH_WIN_IORING_FUNCTION(ResolveFunction)
75
76# undef ResolveFunction
77 return apiTable;
78 }();
79
80# define TEST_TABLE_OK(X) apiTable.X && /* chain */
81# define BOOL_CHAIN(...) (__VA_ARGS__ true)
82 static const bool success = BOOL_CHAIN(FOREACH_WIN_IORING_FUNCTION(TEST_TABLE_OK));
83
84# undef BOOL_CHAIN
85# undef TEST_TABLE_OK
86#endif // windows_ioring_runtime
87 return success ? std::addressof(apiTable) : nullptr;
88}
89} // namespace QtPrivate
90
91static HRESULT buildReadOperation(HIORING ioRingHandle, qintptr fd, QSpan<std::byte> destination,
92 quint64 offset, quintptr userData)
93{
94 // NOLINTNEXTLINE(performance-no-int-to-ptr)
95 const IORING_HANDLE_REF fileRef((HANDLE(fd)));
96 const IORING_BUFFER_REF bufferRef(destination.data());
97 const auto maxSize = q26::saturate_cast<UINT32>(destination.size());
98 Q_ASSERT(maxSize == destination.size());
99 const auto *apiTable = QtPrivate::getApiTable();
100 Q_ASSERT(apiTable); // If we got this far it needs to be here
101 return apiTable->BuildIoRingReadFile(ioRingHandle, fileRef, bufferRef, maxSize, offset,
102 userData, IOSQE_FLAGS_NONE);
103}
104
105static HRESULT buildWriteOperation(HIORING ioRingHandle, qintptr fd, QSpan<const std::byte> source,
106 quint64 offset, quintptr userData)
107{
108 // NOLINTNEXTLINE(performance-no-int-to-ptr)
109 const IORING_HANDLE_REF fileRef((HANDLE(fd)));
110 const IORING_BUFFER_REF bufferRef(const_cast<std::byte *>(source.data()));
111 const auto maxSize = q26::saturate_cast<UINT32>(source.size());
112 Q_ASSERT(maxSize == source.size());
113 const auto *apiTable = QtPrivate::getApiTable();
114 Q_ASSERT(apiTable); // If we got this far it needs to be here
115 // @todo: FILE_WRITE_FLAGS can be set to write-through, could be used for Unbuffered mode.
116 return apiTable->BuildIoRingWriteFile(ioRingHandle, fileRef, bufferRef, maxSize, offset,
117 FILE_WRITE_FLAGS_NONE, userData, IOSQE_FLAGS_NONE);
118}
119
120QIORing::~QIORing()
121{
122 if (initialized) {
123 CloseHandle(eventHandle);
124 apiTable->CloseIoRing(ioRingHandle);
125 }
126}
127
128bool QIORing::initializeIORing()
129{
130 if (initialized)
131 return true;
132
133 if (apiTable = QtPrivate::getApiTable(); !apiTable) {
134 qCWarning(lcQIORing, "Failed to retrieve API table");
135 return false;
136 }
137
138 IORING_CAPABILITIES capabilities;
139 apiTable->QueryIoRingCapabilities(&capabilities);
140 if (capabilities.MaxVersion < IORING_VERSION_3) // 3 adds write, flush and drain
141 return false;
142 if ((capabilities.FeatureFlags & IORING_FEATURE_SET_COMPLETION_EVENT) == 0)
143 return false; // We currently require the SET_COMPLETION_EVENT feature
144
145 qCDebug(lcQIORing) << "Creating QIORing, requesting space for" << sqEntries
146 << "submission queue entries, and" << cqEntries
147 << "completion queue entries";
148
149 IORING_CREATE_FLAGS flags;
150 memset(&flags, 0, sizeof(flags));
151#if !defined(QT_DEBUG) && QT_CONFIG(windows_ioring_skip_builder_param_checks)
152 flags.Advisory |= IORING_CREATE_SKIP_BUILDER_PARAM_CHECKS;
153#endif
154 HRESULT hr = apiTable->CreateIoRing(IORING_VERSION_3, flags, sqEntries, cqEntries,
155 &ioRingHandle);
156 if (FAILED(hr)) {
157 qErrnoWarning(hr, "failed to initialize QIORing");
158 return false;
159 }
160 auto earlyExitCleanup = qScopeGuard([this]() {
161 if (eventHandle != INVALID_HANDLE_VALUE)
162 CloseHandle(eventHandle);
163 apiTable->CloseIoRing(ioRingHandle);
164 });
165 eventHandle = CreateEvent(nullptr, TRUE, FALSE, nullptr);
166 if (eventHandle == INVALID_HANDLE_VALUE) {
167 qErrnoWarning("Failed to create event handle");
168 return false;
169 }
170 notifier.emplace(eventHandle);
171 hr = apiTable->SetIoRingCompletionEvent(ioRingHandle, eventHandle);
172 if (FAILED(hr)) {
173 qErrnoWarning(hr, "Failed to assign the event handle to QIORing");
174 return false;
175 }
176 IORING_INFO info;
177 if (SUCCEEDED(apiTable->GetIoRingInfo(ioRingHandle, &info))) {
178 sqEntries = info.SubmissionQueueSize;
179 cqEntries = info.CompletionQueueSize;
180 qCDebug(lcQIORing) << "QIORing configured with capacity for" << sqEntries
181 << "submissions, and" << cqEntries << "completions.";
182 }
183 QObject::connect(std::addressof(*notifier), &QWinEventNotifier::activated,
184 std::addressof(*notifier), [this]() { completionReady(); });
185 initialized = true;
186 earlyExitCleanup.dismiss();
187 return true;
188}
189
190QFileDevice::FileError QIORing::mapFileError(NativeResultType error,
191 QFileDevice::FileError defaultValue)
192{
193 Q_ASSERT(FAILED(error));
194 if (error == E_ABORT)
195 return QFileDevice::AbortError;
196 return defaultValue;
197}
198
199void QIORing::completionReady()
200{
201 ResetEvent(eventHandle);
202 IORING_CQE entry;
203 while (apiTable->PopIoRingCompletion(ioRingHandle, &entry) == S_OK) {
204 // NOLINTNEXTLINE(performance-no-int-to-ptr)
205 auto *request = reinterpret_cast<GenericRequestType *>(entry.UserData);
206 if (!addrItMap.contains(request)) {
207 qCDebug(lcQIORing) << "Got completed entry, but cannot find it in the map. Likely "
208 "deleted, ignoring. UserData pointer:"
209 << request;
210 continue;
211 }
212 qCDebug(lcQIORing) << "Got completed entry. Operation:" << request->operation()
213 << "- UserData pointer:" << request
214 << "- Result:" << qt_error_string(entry.ResultCode) << '('
215 << QByteArray("0x"_ba + QByteArray::number(entry.ResultCode, 16)).data()
216 << ')';
217 switch (request->operation()) {
218 case Operation::Open: // Synchronously finishes
219 Q_UNREACHABLE_RETURN();
220 case Operation::Close: {
221 auto closeRequest = request->takeRequestData<Operation::Close>();
222 // We ignore the result of the flush, we are closing the handle anyway.
223 // NOLINTNEXTLINE(performance-no-int-to-ptr)
224 if (CloseHandle(HANDLE(closeRequest.fd)))
225 closeRequest.result.emplace<QIORingResult<Operation::Close>>();
226 else
227 closeRequest.result.emplace<QFileDevice::FileError>(QFileDevice::OpenError);
228 invokeCallback(closeRequest);
229 break;
230 }
231 case Operation::Read: {
232 const ReadWriteStatus status = handleReadCompletion<Operation::Read>(
233 entry.ResultCode, entry.Information, request);
234 if (status == ReadWriteStatus::MoreToDo)
235 continue;
236 auto readRequest = request->takeRequestData<Operation::Read>();
237 invokeCallback(readRequest);
238 break;
239 }
240 case Operation::Write: {
241 const ReadWriteStatus status = handleWriteCompletion<Operation::Write>(
242 entry.ResultCode, entry.Information, request);
243 if (status == ReadWriteStatus::MoreToDo)
244 continue;
245 auto writeRequest = request->takeRequestData<Operation::Write>();
246 invokeCallback(writeRequest);
247 break;
248 }
249 case Operation::VectoredRead: {
250 const ReadWriteStatus status = handleReadCompletion<Operation::VectoredRead>(
251 entry.ResultCode, entry.Information, request);
252 if (status == ReadWriteStatus::MoreToDo)
253 continue;
254 auto vectoredReadRequest = request->takeRequestData<Operation::VectoredRead>();
255 invokeCallback(vectoredReadRequest);
256 break;
257 }
258 case Operation::VectoredWrite: {
259 const ReadWriteStatus status = handleWriteCompletion<Operation::VectoredWrite>(
260 entry.ResultCode, entry.Information, request);
261 if (status == ReadWriteStatus::MoreToDo)
262 continue;
263 auto vectoredWriteRequest = request->takeRequestData<Operation::VectoredWrite>();
264 invokeCallback(vectoredWriteRequest);
265 break;
266 }
267 case Operation::Flush: {
268 auto flushRequest = request->takeRequestData<Operation::Flush>();
269 if (FAILED(entry.ResultCode)) {
270 qErrnoWarning(entry.ResultCode, "Flush operation failed");
271 // @todo any FlushError?
272 flushRequest.result.emplace<QFileDevice::FileError>(
273 QFileDevice::FileError::WriteError);
274 } else {
275 flushRequest.result.emplace<QIORingResult<Operation::Flush>>();
276 }
277 invokeCallback(flushRequest);
278 break;
279 }
280 case QtPrivate::Operation::Cancel: {
281 auto cancelRequest = request->takeRequestData<Operation::Cancel>();
282 invokeCallback(cancelRequest);
283 break;
284 }
285 case QtPrivate::Operation::Stat:
286 Q_UNREACHABLE_RETURN(); // Completes synchronously
287 break;
289 Q_UNREACHABLE_RETURN();
290 break;
291 }
292 auto it = addrItMap.take(request);
293 pendingRequests.erase(it);
294 --inFlightRequests;
295 queueWasFull = false;
296 }
297 prepareRequests();
298 if (unstagedRequests > 0)
299 submitRequests();
300}
301
302bool QIORing::waitForCompletions(QDeadlineTimer deadline)
303{
304 notifier->setEnabled(false);
305 auto reactivateNotifier = qScopeGuard([this]() {
306 notifier->setEnabled(true);
307 });
308
309 while (!deadline.hasExpired()) {
310 DWORD timeout = 0;
311 if (deadline.isForever()) {
312 timeout = INFINITE;
313 } else {
314 timeout = q26::saturate_cast<DWORD>(deadline.remainingTime());
315 if (timeout == INFINITE)
316 --timeout;
317 }
318 if (WaitForSingleObject(eventHandle, timeout) == WAIT_OBJECT_0)
319 return true;
320 }
321 return false;
322}
323
324static HANDLE openFile(const QIORingRequest<QIORing::Operation::Open> &openRequest)
325{
326 DWORD access = 0;
327 if (openRequest.flags.testFlag(QIODevice::ReadOnly))
328 access |= GENERIC_READ;
329 if (openRequest.flags.testFlag(QIODevice::WriteOnly))
330 access |= GENERIC_WRITE;
331
332 DWORD disposition = 0;
333 if (openRequest.flags.testFlag(QIODevice::Append)) {
334 qCWarning(lcQIORing, "Opening file with Append not supported for random access file");
335 return INVALID_HANDLE_VALUE;
336 }
337 if (openRequest.flags.testFlag(QIODevice::NewOnly)) {
338 disposition = CREATE_NEW;
339 } else {
340 // If Write is specified we _may_ create a file.
341 // See qfsfileengine_p.h openModeCanCreate.
342 disposition = openRequest.flags.testFlag(QIODeviceBase::WriteOnly)
343 && !openRequest.flags.testFlags(QIODeviceBase::ExistingOnly)
344 ? OPEN_ALWAYS
345 : OPEN_EXISTING;
346 }
347 const DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
348 const DWORD flagsAndAttribs = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED;
349 HANDLE h = CreateFile(openRequest.path.native().c_str(), access, shareMode, nullptr,
350 disposition, flagsAndAttribs, nullptr);
351 if (h != INVALID_HANDLE_VALUE && openRequest.flags.testFlag(QIODeviceBase::Truncate)) {
352 FILE_END_OF_FILE_INFO info;
353 memset(&info, 0, sizeof(info));
354 SetFileInformationByHandle(h, FileEndOfFileInfo, &info, sizeof(info));
355 }
356 return h;
357}
358
359bool QIORing::supportsOperation(Operation op)
360{
361 switch (op) {
362 case QtPrivate::Operation::Open:
363 case QtPrivate::Operation::Close:
364 case QtPrivate::Operation::Read:
365 case QtPrivate::Operation::Write:
366 case QtPrivate::Operation::Flush:
367 case QtPrivate::Operation::Cancel:
368 case QtPrivate::Operation::Stat:
369 case QtPrivate::Operation::VectoredRead:
370 case QtPrivate::Operation::VectoredWrite:
371 return true;
373 return false;
374 }
375 return false; // Not unreachable, we could allow more for io_uring
376}
377
378void QIORing::submitRequests()
379{
380 stagePending = false;
381 if (unstagedRequests == 0)
382 return;
383
384 // We perform a miniscule wait - to see if anything already in the queue is already completed -
385 // if we have been told the queue is full. Then we can try queuing more things right away
386 const bool shouldTryWait = std::exchange(queueWasFull, false);
387 const auto submitToRing = [this, &shouldTryWait] {
388 quint32 submittedEntries = 0;
389 HRESULT hr = apiTable->SubmitIoRing(ioRingHandle, shouldTryWait ? 1 : 0, 1,
390 &submittedEntries);
391 qCDebug(lcQIORing) << "Submitted" << submittedEntries << "requests";
392 unstagedRequests -= submittedEntries;
393 if (FAILED(hr)) {
394 // Too noisy, not a real problem
395 // qErrnoWarning(hr, "Failed to submit QIORing request: %u", submittedEntries);
396 return false;
397 }
398 return submittedEntries > 0;
399 };
400 if (submitToRing() && shouldTryWait) {
401 // We try to prepare some more request and submit more if able
402 prepareRequests();
403 if (unstagedRequests > 0)
404 submitToRing();
405 }
406}
407
408void QIORing::prepareRequests()
409{
410 if (!lastUnqueuedIterator)
411 return;
412 Q_ASSERT(!preparingRequests);
413 QScopedValueRollback<bool> prepareGuard(preparingRequests, true);
414
415 auto it = *lastUnqueuedIterator;
416 lastUnqueuedIterator.reset();
417 const auto end = pendingRequests.end();
418 while (!queueWasFull && it != end) {
419 auto &request = *it;
420 switch (prepareRequest(request)) {
421 case RequestPrepResult::Ok:
422 ++unstagedRequests;
423 ++inFlightRequests;
424 break;
425 case RequestPrepResult::QueueFull:
426 qCDebug(lcQIORing) << "Queue was reported as full, in flight requests:"
427 << inFlightRequests << "submission queue size:" << sqEntries
428 << "completion queue size:" << cqEntries;
429 queueWasFull = true;
430 lastUnqueuedIterator = it;
431 return;
432 case RequestPrepResult::Defer:
433 qCDebug(lcQIORing) << "Request for" << request.operation()
434 << "had to be deferred, will not queue any more requests at the "
435 "moment.";
436 lastUnqueuedIterator = it;
437 return; //
438 case RequestPrepResult::RequestCompleted:
439 // Used for requests that immediately finish. So we erase it:
440 qCDebug(lcQIORing) << "Request for" << request.operation()
441 << "completed synchronously.";
442 addrItMap.remove(&request);
443 it = pendingRequests.erase(it);
444 continue; // Don't increment iterator again
445 }
446 ++it;
447 }
448 if (it != pendingRequests.end())
449 lastUnqueuedIterator = it;
450}
451
452namespace QtPrivate {
453template <typename T>
454using DetectHasFd = decltype(std::declval<const T &>().fd);
455
456template <typename T>
457constexpr bool OperationHasFd_v = qxp::is_detected_v<DetectHasFd, T>;
458} // namespace QtPrivate
459
460auto QIORing::prepareRequest(GenericRequestType &request) -> RequestPrepResult
461{
462 qCDebug(lcQIORing) << "Preparing a request with operation" << request.operation();
463 HRESULT hr = -1;
464
465 if (!verifyFd(request)) {
466 finishRequestWithError(request, QFileDevice::OpenError);
467 return RequestPrepResult::RequestCompleted;
468 }
469
470 switch (request.operation()) {
471 case Operation::Open: {
472 QIORingRequest<Operation::Open> openRequest = request.takeRequestData<Operation::Open>();
473 HANDLE fileDescriptor = openFile(openRequest);
474 if (fileDescriptor == INVALID_HANDLE_VALUE) {
475 openRequest.result.emplace<QFileDevice::FileError>(QFileDevice::FileError::OpenError);
476 } else {
477 auto &result = openRequest.result.emplace<QIORingResult<Operation::Open>>();
478 result.fd = qintptr(fileDescriptor);
479 }
480 invokeCallback(openRequest);
481 return RequestPrepResult::RequestCompleted;
482 }
483 case Operation::Close: {
484 if (ongoingSplitOperations > 0)
485 return RequestPrepResult::Defer;
486
487 // We need to wait until all previous OPS are done before we close the request.
488 // There is no no-op request in the Windows QIORing, so we issue a flush.
489 auto *closeRequest = request.requestData<Operation::Close>();
490 // NOLINTNEXTLINE(performance-no-int-to-ptr)
491 const IORING_HANDLE_REF fileRef(HANDLE(closeRequest->fd));
492 hr = apiTable->BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_MIN_METADATA,
493 quintptr(std::addressof(request)),
494 IOSQE_FLAGS_DRAIN_PRECEDING_OPS);
495 break;
496 }
497 case Operation::Read: {
498 auto *readRequest = request.requestData<Operation::Read>();
499 auto span = readRequest->destination;
500 auto offset = readRequest->offset;
501 if (span.size() > MaxReadWriteLen) {
502 qCDebug(lcQIORing) << "Requested Read of size" << span.size() << "has to be split";
503 auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
504 if (extra->spanOffset == 0)
505 ++ongoingSplitOperations;
506 const qsizetype remaining = span.size() - extra->spanOffset;
507 span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen));
508 offset += extra->totalProcessed;
509 }
510 hr = buildReadOperation(ioRingHandle, readRequest->fd, span, offset,
511 quintptr(std::addressof(request)));
512 break;
513 }
514 case Operation::VectoredRead: {
515 auto *vectoredReadRequest = request.requestData<Operation::VectoredRead>();
516 auto span = vectoredReadRequest->destinations.front();
517 auto offset = vectoredReadRequest->offset;
518 if (Q_LIKELY(vectoredReadRequest->destinations.size() > 1
519 || span.size() > MaxReadWriteLen)) {
520 auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
521 if (extra->spanOffset == 0 && extra->spanIndex == 0)
522 ++ongoingSplitOperations;
523 extra->numSpans = vectoredReadRequest->destinations.size();
524
525 span = vectoredReadRequest->destinations[extra->spanIndex];
526
527 const qsizetype remaining = span.size() - extra->spanOffset;
528 span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen));
529 offset += extra->totalProcessed;
530 }
531 hr = buildReadOperation(ioRingHandle, vectoredReadRequest->fd, span,
532 offset, quintptr(std::addressof(request)));
533 break;
534 }
535 case Operation::Write: {
536 auto *writeRequest = request.requestData<Operation::Write>();
537 auto span = writeRequest->source;
538 auto offset = writeRequest->offset;
539 if (span.size() > MaxReadWriteLen) {
540 qCDebug(lcQIORing) << "Requested Write of size" << span.size() << "has to be split";
541 auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
542 if (extra->spanOffset == 0)
543 ++ongoingSplitOperations;
544 const qsizetype remaining = span.size() - extra->spanOffset;
545 span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen));
546 offset += extra->totalProcessed;
547 }
548 hr = buildWriteOperation(ioRingHandle, writeRequest->fd, span, offset,
549 quintptr(std::addressof(request)));
550 break;
551 }
552 case Operation::VectoredWrite: {
553 auto *vectoredWriteRequest = request.requestData<Operation::VectoredWrite>();
554 auto span = vectoredWriteRequest->sources.front();
555 auto offset = vectoredWriteRequest->offset;
556 if (Q_LIKELY(vectoredWriteRequest->sources.size() > 1
557 || span.size() > MaxReadWriteLen)) {
558 auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
559 if (extra->spanOffset == 0 && extra->spanIndex == 0)
560 ++ongoingSplitOperations;
561 extra->numSpans = vectoredWriteRequest->sources.size();
562
563 span = vectoredWriteRequest->sources[extra->spanIndex];
564
565 const qsizetype remaining = span.size() - extra->spanOffset;
566 span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen));
567 offset += extra->totalProcessed;
568 }
569 hr = buildWriteOperation(ioRingHandle, vectoredWriteRequest->fd, span,
570 offset, quintptr(std::addressof(request)));
571 break;
572 }
573 case Operation::Flush: {
574 if (ongoingSplitOperations > 0)
575 return RequestPrepResult::Defer;
576 auto *flushRequest = request.requestData<Operation::Flush>();
577 // NOLINTNEXTLINE(performance-no-int-to-ptr)
578 const IORING_HANDLE_REF fileRef(HANDLE(flushRequest->fd));
579 hr = apiTable->BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_DEFAULT,
580 quintptr(std::addressof(request)),
581 IOSQE_FLAGS_DRAIN_PRECEDING_OPS);
582 break;
583 }
584 case QtPrivate::Operation::Stat: {
585 auto statRequest = request.takeRequestData<Operation::Stat>();
586 FILE_STANDARD_INFO info;
587 // NOLINTNEXTLINE(performance-no-int-to-ptr)
588 if (!GetFileInformationByHandleEx(HANDLE(statRequest.fd), FileStandardInfo, &info,
589 sizeof(info))) {
590 DWORD winErr = GetLastError();
591 QFileDevice::FileError error = QFileDevice::UnspecifiedError;
592 if (winErr == ERROR_FILE_NOT_FOUND || winErr == ERROR_INVALID_HANDLE)
593 error = QFileDevice::OpenError;
594 else if (winErr == ERROR_ACCESS_DENIED)
595 error = QFileDevice::PermissionsError;
596 statRequest.result.emplace<QFileDevice::FileError>(error);
597 } else {
598 auto &result = statRequest.result.emplace<QIORingResult<Operation::Stat>>();
599 result.size = info.EndOfFile.QuadPart;
600 }
601 invokeCallback(statRequest);
602 return RequestPrepResult::RequestCompleted;
603 }
604 case Operation::Cancel: {
605 auto *cancelRequest = request.requestData<Operation::Cancel>();
606 auto *otherOperation = reinterpret_cast<GenericRequestType *>(cancelRequest->handle);
607 if (!otherOperation || !addrItMap.contains(otherOperation)) {
608 qCDebug(lcQIORing, "Invalid cancel for non-existant operation");
609 invokeCallback(*cancelRequest);
610 return RequestPrepResult::RequestCompleted;
611 }
612 qCDebug(lcQIORing) << "Cancelling operation of type" << otherOperation->operation()
613 << "which was"
614 << (otherOperation->wasQueued() ? "queued" : "not queued");
615 Q_ASSERT(&request != otherOperation);
616 if (!otherOperation->wasQueued()) {
617 // The request hasn't been queued yet, so we can just drop it from
618 // the pending requests and call the callback.
619 auto it = addrItMap.take(otherOperation);
620 finishRequestWithError(*otherOperation, QFileDevice::AbortError);
621 pendingRequests.erase(it); // otherOperation is deleted
622 invokeCallback(*cancelRequest);
623 return RequestPrepResult::RequestCompleted;
624 }
625 qintptr fd = -1;
626 invokeOnOp(*otherOperation, [&fd](auto *request) {
627 if constexpr (QtPrivate::OperationHasFd_v<decltype(*request)>)
628 fd = request->fd;
629 });
630 if (fd == -1) {
631 qCDebug(lcQIORing, "Invalid cancel for non-existant fd");
632 invokeCallback(*cancelRequest);
633 return RequestPrepResult::RequestCompleted;
634 }
635 // NOLINTNEXTLINE(performance-no-int-to-ptr)
636 const IORING_HANDLE_REF fileRef((HANDLE(fd)));
637 hr = apiTable->BuildIoRingCancelRequest(ioRingHandle, fileRef, quintptr(otherOperation),
638 quintptr(std::addressof(request)));
639 break;
640 }
642 Q_UNREACHABLE_RETURN(RequestPrepResult::RequestCompleted);
643 break;
644 }
645 if (hr == IORING_E_SUBMISSION_QUEUE_FULL)
646 return RequestPrepResult::QueueFull;
647 if (FAILED(hr)) {
648 finishRequestWithError(request, QFileDevice::UnspecifiedError);
649 return RequestPrepResult::RequestCompleted;
650 }
651 request.setQueued(true);
652 return RequestPrepResult::Ok;
653}
654
655bool QIORing::verifyFd(GenericRequestType &req)
656{
657 bool result = true;
658 invokeOnOp(req, [&](auto *request) {
659 if constexpr (QtPrivate::OperationHasFd_v<decltype(*request)>) {
660 result = quintptr(request->fd) > 0 && quintptr(request->fd) != quintptr(INVALID_HANDLE_VALUE);
661 }
662 });
663 return result;
664}
665
666void QIORing::GenericRequestType::cleanupExtra(Operation op, void *extra)
667{
668 switch (op) {
669 case QtPrivate::Operation::Read:
670 case QtPrivate::Operation::VectoredRead:
671 case QtPrivate::Operation::Write:
672 case QtPrivate::Operation::VectoredWrite:
673 delete static_cast<QtPrivate::ReadWriteExtra *>(extra);
674 break;
675 case QtPrivate::Operation::Open:
676 case QtPrivate::Operation::Close:
677 case QtPrivate::Operation::Flush:
678 case QtPrivate::Operation::Stat:
679 case QtPrivate::Operation::Cancel:
681 break;
682 }
683}
684
685QT_END_NAMESPACE
Q_CORE_EXPORT void submitRequests()
Q_CORE_EXPORT ~QIORing()
QtPrivate::Operation Operation
Definition qioring_p.h:114
decltype(std::declval< const T & >().fd) DetectHasFd
constexpr bool OperationHasFd_v
static const IORingApiTable * getApiTable()
QT_REQUIRE_CONFIG(liburing)
static HRESULT buildWriteOperation(HIORING ioRingHandle, qintptr fd, QSpan< const std::byte > source, quint64 offset, quintptr userData)
#define FOREACH_WIN_IORING_FUNCTION(Fn)
static HANDLE openFile(const QIORingRequest< QIORing::Operation::Open > &openRequest)
static HRESULT buildReadOperation(HIORING ioRingHandle, qintptr fd, QSpan< std::byte > destination, quint64 offset, quintptr userData)