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_linux.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/qobject.h>
10#include <QtCore/qscopedvaluerollback.h>
11#include <QtCore/private/qcore_unix_p.h>
12#include <QtCore/private/qfiledevice_p.h>
13#include <QtCore/private/qfsfileengine_p.h>
14
15#include <liburing.h>
16#include <sys/mman.h>
17#include <sys/eventfd.h>
18#include <sys/stat.h>
19
20QT_BEGIN_NAMESPACE
21
22// We pretend that iovec and QSpans are the same, assert that size and alignment match:
23static_assert(sizeof(iovec)
24 == sizeof(decltype(std::declval<QIORingRequest<QIORing::Operation::VectoredRead>>()
25 .destinations)));
26static_assert(alignof(iovec)
27 == alignof(decltype(std::declval<QIORingRequest<QIORing::Operation::VectoredRead>>()
28 .destinations)));
29
30static io_uring_op toUringOp(QIORing::Operation op);
31
32// From man write.2:
33// On Linux, write() (and similar system calls) will transfer at most 0x7ffff000 (2,147,479,552)
34// bytes, returning the number of bytes actually transferred. (This is true on both 32-bit and
35// 64-bit systems.)
36static constexpr qsizetype MaxReadWriteLen = 0x7ffff000; // aka. MAX_RW_COUNT
37
38// Assert that this instantiation of std::atomic is always lock-free so we
39// know that no code will execute on destruction.
40static_assert(std::atomic<qsizetype>::is_always_lock_free);
41
42// For test purposes we want to be able to decrease the max value.
43// For that, expose a helper variable that can be adjusted in the unit tests.
44Q_CONSTINIT std::atomic<qsizetype> QtPrivate::testMaxReadWriteLen{MaxReadWriteLen};
45
47{
48#ifndef QT_DEBUG
49 return MaxReadWriteLen;
50#else
51 return QtPrivate::testMaxReadWriteLen.load(std::memory_order_relaxed);
52#endif
53}
54
55QIORing::~QIORing()
56{
57 if (eventDescriptor != -1)
58 close(eventDescriptor);
59 if (io_uringFd != -1)
60 close(io_uringFd);
61}
62
63bool QIORing::initializeIORing()
64{
65 if (io_uringFd != -1)
66 return true;
67
68 io_uring_params params{};
69 params.flags = IORING_SETUP_CQSIZE;
70 params.cq_entries = cqEntries;
71 const int fd = io_uring_setup(sqEntries, &params);
72 if (fd < 0) {
73 qErrnoWarning(-fd, "Failed to setup io_uring");
74 return false;
75 }
76 io_uringFd = fd;
77 size_t submissionQueueSize = params.sq_off.array + (params.sq_entries * sizeof(quint32));
78 size_t completionQueueSize = params.cq_off.cqes + (params.cq_entries * sizeof(io_uring_cqe));
79 if (params.features & IORING_FEAT_SINGLE_MMAP)
80 submissionQueueSize = std::max(submissionQueueSize, completionQueueSize);
81 submissionQueue = mmap(nullptr, submissionQueueSize, PROT_READ | PROT_WRITE,
82 MAP_SHARED | MAP_POPULATE, io_uringFd, IORING_OFF_SQ_RING);
83 if (submissionQueue == MAP_FAILED) {
84 qErrnoWarning(errno, "Failed to mmap io_uring submission queue");
85 close(io_uringFd);
86 io_uringFd = -1;
87 return false;
88 }
89 const size_t submissionQueueEntriesSize = params.sq_entries * sizeof(io_uring_sqe);
90 submissionQueueEntries = static_cast<io_uring_sqe *>(
91 mmap(nullptr, submissionQueueEntriesSize, PROT_READ | PROT_WRITE,
92 MAP_SHARED | MAP_POPULATE, io_uringFd, IORING_OFF_SQES));
93 if (submissionQueueEntries == MAP_FAILED) {
94 qErrnoWarning(errno, "Failed to mmap io_uring submission queue entries");
95 munmap(submissionQueue, submissionQueueSize);
96 close(io_uringFd);
97 io_uringFd = -1;
98 return false;
99 }
100 void *completionQueue = nullptr;
101 if (params.features & IORING_FEAT_SINGLE_MMAP) {
102 completionQueue = submissionQueue;
103 } else {
104 completionQueue = mmap(nullptr, completionQueueSize, PROT_READ | PROT_WRITE,
105 MAP_SHARED | MAP_POPULATE, io_uringFd, IORING_OFF_CQ_RING);
106 if (completionQueue == MAP_FAILED) {
107 qErrnoWarning(errno, "Failed to mmap io_uring completion queue");
108 munmap(submissionQueue, submissionQueueSize);
109 munmap(submissionQueueEntries, submissionQueueEntriesSize);
110 close(io_uringFd);
111 io_uringFd = -1;
112 return false;
113 }
114 }
115 sqEntries = params.sq_entries;
116 cqEntries = params.cq_entries;
117
118 char *sq = static_cast<char *>(submissionQueue);
119 sqHead = reinterpret_cast<quint32 *>(sq + params.sq_off.head);
120 sqTail = reinterpret_cast<quint32 *>(sq + params.sq_off.tail);
121 sqIndexMask = reinterpret_cast<quint32 *>(sq + params.sq_off.ring_mask);
122 sqIndexArray = reinterpret_cast<quint32 *>(sq + params.sq_off.array);
123
124 char *cq = static_cast<char *>(completionQueue);
125 cqHead = reinterpret_cast<quint32 *>(cq + params.cq_off.head);
126 cqTail = reinterpret_cast<quint32 *>(cq + params.cq_off.tail);
127 cqIndexMask = reinterpret_cast<quint32 *>(cq + params.cq_off.ring_mask);
128 completionQueueEntries = reinterpret_cast<io_uring_cqe *>(cq + params.cq_off.cqes);
129
130 eventDescriptor = eventfd(0, 0);
131 io_uring_register(io_uringFd, IORING_REGISTER_EVENTFD, &eventDescriptor, 1);
132
133 notifier.emplace(eventDescriptor, QSocketNotifier::Read);
134 QObject::connect(std::addressof(*notifier), &QSocketNotifier::activated,
135 std::addressof(*notifier), [this]() { completionReady(); });
136 return true;
137}
138
139QFileDevice::FileError QIORing::mapFileError(NativeResultType error,
140 QFileDevice::FileError defaultValue)
141{
142 Q_ASSERT(error < 0);
143 if (-error == ECANCELED)
144 return QFileDevice::AbortError;
145 return defaultValue;
146}
147
148void QIORing::completionReady()
149{
150 // Drain the eventfd:
151 [[maybe_unused]]
152 quint64 ignored = 0;
153 std::ignore = read(eventDescriptor, &ignored, sizeof(ignored));
154
155 quint32 head = __atomic_load_n(cqHead, __ATOMIC_RELAXED);
156 const quint32 tail = __atomic_load_n(cqTail, __ATOMIC_ACQUIRE);
157 if (tail == head)
158 return;
159
160 qCDebug(lcQIORing,
161 "Status of completion queue, total entries: %u, tail: %u, head: %u, to process: %u",
162 cqEntries, tail, head, (tail - head));
163 while (head != tail) {
164 /* Get the entry */
165 const io_uring_cqe *cqe = &completionQueueEntries[head & *cqIndexMask];
166 ++head;
167 --inFlightRequests;
168 GenericRequestType *request = reinterpret_cast<GenericRequestType *>(cqe->user_data);
169 qCDebug(lcQIORing) << "Got completed entry. Operation:" << request->operation()
170 << "- user_data pointer:" << request;
171 switch (request->operation()) {
172 case Operation::Open: {
174 openRequest = request->template takeRequestData<Operation::Open>();
175 if (cqe->res < 0) {
176 // qErrnoWarning(-cqe->res, "Failed to open");
177 if (-cqe->res == ECANCELED)
178 openRequest.result.template emplace<QFileDevice::FileError>(
179 QFileDevice::AbortError);
180 else
181 openRequest.result.template emplace<QFileDevice::FileError>(
182 QFileDevice::OpenError);
183 } else {
184 auto &result = openRequest.result
185 .template emplace<QIORingResult<Operation::Open>>();
186 result.fd = cqe->res;
187 }
188 invokeCallback(openRequest);
189 break;
190 }
191 case Operation::Close: {
193 closeRequest = request->template takeRequestData<Operation::Close>();
194 if (cqe->res < 0) {
195 closeRequest.result.emplace<QFileDevice::FileError>(QFileDevice::OpenError);
196 } else {
197 closeRequest.result.emplace<QIORingResult<Operation::Close>>();
198 }
199 invokeCallback(closeRequest);
200 break;
201 }
202 case Operation::Read: {
203 const ReadWriteStatus status = handleReadCompletion<Operation::Read>(
204 cqe->res, size_t(cqe->res), request);
205 if (status == ReadWriteStatus::MoreToDo)
206 continue;
207 auto readRequest = request->takeRequestData<Operation::Read>();
208 invokeCallback(readRequest);
209 break;
210 }
211 case Operation::Write: {
212 const ReadWriteStatus status = handleWriteCompletion<Operation::Write>(
213 cqe->res, size_t(cqe->res), request);
214 if (status == ReadWriteStatus::MoreToDo)
215 continue;
216 auto writeRequest = request->takeRequestData<Operation::Write>();
217 invokeCallback(writeRequest);
218 break;
219 }
220 case Operation::VectoredRead: {
221 const ReadWriteStatus status = handleReadCompletion<Operation::VectoredRead>(
222 cqe->res, size_t(cqe->res), request);
223 if (status == ReadWriteStatus::MoreToDo)
224 continue;
225 auto readvRequest = request->takeRequestData<Operation::VectoredRead>();
226 invokeCallback(readvRequest);
227 break;
228 }
229 case Operation::VectoredWrite: {
230 const ReadWriteStatus status = handleWriteCompletion<Operation::VectoredWrite>(
231 cqe->res, size_t(cqe->res), request);
232 if (status == ReadWriteStatus::MoreToDo)
233 continue;
234 auto writevRequest = request->takeRequestData<Operation::VectoredWrite>();
235 invokeCallback(writevRequest);
236 break;
237 }
238 case Operation::Flush: {
240 flushRequest = request->template takeRequestData<Operation::Flush>();
241 if (cqe->res < 0) {
242 flushRequest.result.emplace<QFileDevice::FileError>(QFileDevice::WriteError);
243 } else {
244 // No members to fill out, so just initialize to indicate success
245 flushRequest.result.emplace<QIORingResult<Operation::Flush>>();
246 }
247 flushInProgress = false;
248 invokeCallback(flushRequest);
249 break;
250 }
251 case Operation::Cancel: {
253 cancelRequest = request->template takeRequestData<Operation::Cancel>();
254 invokeCallback(cancelRequest);
255 break;
256 }
257 case Operation::Stat: {
259 statRequest = request->template takeRequestData<Operation::Stat>();
260 if (cqe->res < 0) {
261 statRequest.result.emplace<QFileDevice::FileError>(QFileDevice::OpenError);
262 } else {
263 struct statx *st = request->getExtra<struct statx>();
264 Q_ASSERT(st);
265 auto &res = statRequest.result.emplace<QIORingResult<Operation::Stat>>();
266 res.size = st->stx_size;
267 }
268 invokeCallback(statRequest);
269 break;
270 }
272 Q_UNREACHABLE_RETURN();
273 break;
274 }
275 auto it = addrItMap.take(request);
276 pendingRequests.erase(it);
277 }
278 __atomic_store_n(cqHead, head, __ATOMIC_RELEASE);
279 qCDebug(lcQIORing,
280 "Done processing available completions, updated pointers, tail: %u, head: %u", tail,
281 head);
282 prepareRequests();
283 if (!stagePending && unstagedRequests > 0)
284 submitRequests();
285}
286
287bool QIORing::waitForCompletions(QDeadlineTimer deadline)
288{
289 notifier->setEnabled(false);
290 auto reactivateNotifier = qScopeGuard([this]() {
291 notifier->setEnabled(true);
292 });
293
294 pollfd pfd = qt_make_pollfd(eventDescriptor, POLLIN);
295 return qt_safe_poll(&pfd, 1, deadline) > 0;
296}
297
298bool QIORing::supportsOperation(Operation op)
299{
300 switch (op) {
301 case QtPrivate::Operation::Open:
302 case QtPrivate::Operation::Close:
303 case QtPrivate::Operation::Read:
304 case QtPrivate::Operation::Write:
305 case QtPrivate::Operation::VectoredRead:
306 case QtPrivate::Operation::VectoredWrite:
307 case QtPrivate::Operation::Flush:
308 case QtPrivate::Operation::Cancel:
309 case QtPrivate::Operation::Stat:
310 return true;
312 return false;
313 }
314 return false; // May not always be unreachable!
315}
316
317void QIORing::submitRequests()
318{
319 stagePending = false;
320 if (unstagedRequests == 0)
321 return;
322
323 auto submitToRing = [this] {
324 int ret = io_uring_enter(io_uringFd, unstagedRequests, 0, 0, nullptr);
325 if (ret < 0)
326 qErrnoWarning("Error occurred notifying kernel about requests...");
327 else
328 unstagedRequests -= ret;
329 qCDebug(lcQIORing) << "io_uring_enter returned" << ret;
330 return ret >= 0;
331 };
332 if (submitToRing()) {
333 prepareRequests();
334 if (unstagedRequests)
335 submitToRing();
336 }
337}
338
339namespace QtPrivate {
340template <typename T>
341using DetectFd = decltype(std::declval<const T &>().fd);
342
343template <typename T>
344constexpr bool HasFdMember = qxp::is_detected_v<DetectFd, T>;
345} // namespace QtPrivate
346
347bool QIORing::verifyFd(QIORing::GenericRequestType &req)
348{
349 bool result = true;
350 invokeOnOp(req, [&](auto *request) {
351 if constexpr (QtPrivate::HasFdMember<decltype(*request)>) {
352 result = request->fd > 0;
353 }
354 });
355 return result;
356}
357
358void QIORing::prepareRequests()
359{
360 if (!lastUnqueuedIterator) {
361 qCDebug(lcQIORing, "Nothing left to queue");
362 return;
363 }
364 Q_ASSERT(!preparingRequests);
365 QScopedValueRollback<bool> prepareGuard(preparingRequests, true);
366
367 quint32 tail = __atomic_load_n(sqTail, __ATOMIC_RELAXED);
368 const quint32 head = __atomic_load_n(sqHead, __ATOMIC_ACQUIRE);
369 qCDebug(lcQIORing,
370 "Status of submission queue, total entries: %u, tail: %u, head: %u, free: %u",
371 sqEntries, tail, head, sqEntries - (tail - head));
372
373 auto it = *lastUnqueuedIterator;
374 lastUnqueuedIterator.reset();
375 const auto end = pendingRequests.end();
376 bool anyQueued = false;
377 // Loop until we either:
378 // 1. Run out of requests to prepare for submission (it == end),
379 // 2. Have filled the submission queue (unstagedRequests == sqEntries) or,
380 // 3. The number of staged requests + currently processing/potentially finished requests is
381 // enough to fill the completion queue (inFlightRequests == cqEntries).
382 while (!flushInProgress && unstagedRequests != sqEntries && inFlightRequests != cqEntries
383 && it != end) {
384 const quint32 index = tail & *sqIndexMask;
385 io_uring_sqe *sqe = &submissionQueueEntries[index];
386 *sqe = {};
387 RequestPrepResult result = prepareRequest(sqe, *it);
388
389 // QueueFull is unused on Linux:
390 Q_ASSERT(result != RequestPrepResult::QueueFull);
391 if (result == RequestPrepResult::Defer) {
392 qCDebug(lcQIORing) << "Request for" << it->operation()
393 << "had to be deferred, will not queue any more requests at the moment.";
394 break;
395 }
396 if (result == RequestPrepResult::RequestCompleted) {
397 addrItMap.remove(std::addressof(*it));
398 it = pendingRequests.erase(it); // Completed synchronously, either failure or success.
399 continue;
400 }
401 anyQueued = true;
402 it->setQueued(true);
403
404 sqIndexArray[index] = index;
405 ++inFlightRequests;
406 ++unstagedRequests;
407 ++tail;
408 ++it;
409 }
410 if (it != end)
411 lastUnqueuedIterator = it;
412
413 if (anyQueued) {
414 qCDebug(lcQIORing, "Queued %u operation(s)",
415 tail - __atomic_load_n(sqTail, __ATOMIC_RELAXED));
416 __atomic_store_n(sqTail, tail, __ATOMIC_RELEASE);
417 }
418}
419
420static io_uring_op toUringOp(QIORing::Operation op)
421{
422 switch (op) {
423 case QIORing::Operation::Open:
424 return IORING_OP_OPENAT;
425 case QIORing::Operation::Read:
426 return IORING_OP_READ;
427 case QIORing::Operation::Close:
428 return IORING_OP_CLOSE;
429 case QIORing::Operation::Write:
430 return IORING_OP_WRITE;
431 case QIORing::Operation::VectoredRead:
432 return IORING_OP_READV;
433 case QIORing::Operation::VectoredWrite:
434 return IORING_OP_WRITEV;
435 case QIORing::Operation::Flush:
436 return IORING_OP_FSYNC;
437 case QIORing::Operation::Cancel:
438 return IORING_OP_ASYNC_CANCEL;
439 case QIORing::Operation::Stat:
440 return IORING_OP_STATX;
441 case QIORing::Operation::NumOperations:
442 break;
443 }
444 Q_UNREACHABLE_RETURN(IORING_OP_NOP);
445}
446
448static void prepareFileIOCommon(io_uring_sqe *sqe, const QIORingRequestOffsetFdBase &request, quint64 offset)
449{
450 sqe->fd = qint32(request.fd);
451 sqe->off = offset;
452}
453
455static void prepareFileReadWrite(io_uring_sqe *sqe, const QIORingRequestOffsetFdBase &request,
456 const void *address, quint64 offset, qsizetype size)
457{
458 prepareFileIOCommon(sqe, request, offset);
459 sqe->len = quint32(size);
460 sqe->addr = quint64(address);
461}
462
463/*!
464 \internal
465 Because vectored operations are also affected by the maximum size
466 limitation, and we don't want that limitation to bubble its way up to users,
467 we may have to split a vectored operation into multiple parts.
468
469 This function will return what operation needs to be used, along with the
470 parameter to pass for address and size.
471
472 The logic is such:
473
474 Given the initial span of spans, ignore any already-processed span from the
475 front, using \c{extra->spanIndex}. Then sum up the size of all spans until
476 we reach the end \e{or} the total sum exceeds \c{MaxReadWriteLen}.
477 Depending on the result one of three things happens:
478
479 1. We reached the end of the span-of-spans. Nothing needs to be split up,
480 we issue a vectored read/write for the whole, remaining, sequence.
481 2. The sum exceeded \c{MaxReadWriteLen} at an index n, n > 0. We issue a
482 vectored read/write for indices [0, n).
483 3. The sum exceeded \c{MaxReadWriteLen} at index == 0. We have to change to
484 a standard read/write operation, operating on just a subset of spans[0].
485 If this happens once, it will happen again for the same span, since we don't
486 permanently adjust them in any way. Because of that, the second (or third,
487 or fourth, etc.) time that we try to issue a read/write with the same span
488 we have to adjust the offset into spans[0].
489 For this we use \c{extra->spanOffset}.
490*/
491template <typename SpanOfBytes>
492auto QIORing::getVectoredOpAddressAndSize(QIORing::GenericRequestType &request, QSpan<SpanOfBytes> spans)
493{
494 using TypeErasedPtr = std::conditional_t<
495 std::is_const_v<std::remove_pointer_t<typename SpanOfBytes::pointer>>, const void *,
496 void *>;
497 struct R {
498 QIORing::Operation op;
499 TypeErasedPtr address;
500 qsizetype size;
501 } r;
502
503 // Skip the spans we have already processed, if any:
504 if (auto *extra = request.getExtra<QtPrivate::ReadWriteExtra>())
505 spans.slice(extra->spanIndex);
506
507 // Defaults, may change:
508 r.op = request.operation();
509 r.address = spans.data();
510 r.size = spans.size();
511
512 // Find the next span at which we would exhaust the MaxReadWriteLen limit:
513 const qsizetype exceedAtIndex = [&]() {
514 qint64 total = 0;
515 qsizetype i = 0;
516 for (; i < spans.size(); ++i) {
517 total += spans[i].size();
518 if (total > maxReadWriteLen())
519 break;
520 }
521 return i;
522 }();
523 if (exceedAtIndex != spans.size()) {
524 // We have to split up the read/write a bit:
525 auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
526 if (extra->spanIndex == 0 && extra->spanOffset == 0) { // First time setup
527 ++ongoingSplitOperations;
528 extra->numSpans = spans.size();
529 }
530 if (exceedAtIndex == 0) { // The first span by itself is already too large!
531 // Change to single Read/Write:
532 const bool isWrite = r.op == QIORing::Operation::VectoredWrite;
533 r.op = isWrite ? QIORing::Operation::Write : QIORing::Operation::Read;
534 auto singleSpan = spans.front();
535 // Since we know that spans.front() in its _entirety_ is too large
536 // for a single read/write operation, we have to take into
537 // consideration that we may have _already_ processed a part of it:
538 const qsizetype remaining = singleSpan.size() - extra->spanOffset;
539 singleSpan.slice(extra->spanOffset, std::min(remaining, maxReadWriteLen()));
540 r.address = singleSpan.data();
541 r.size = singleSpan.size();
542 } else {
543 // Unlike the branch above, we don't have to (and shouldn't) care
544 // about extra->spanOffset. Firstly, since we are giving an address
545 // to a span of spans, not a single span, we cannot influence the
546 // size of any singular span in itself.
547 // Secondly, we know, by virtue of checking the size above, that all
548 // these spans, in their entirety, fit inside the size limitation.
549 auto limitedSpans = spans.first(exceedAtIndex);
550 r.address = limitedSpans.data();
551 r.size = limitedSpans.size();
552 }
553 }
554 return r;
555}
556
557auto QIORing::prepareRequest(io_uring_sqe *sqe, GenericRequestType &request) -> RequestPrepResult
558{
559 sqe->user_data = qint64(&request);
560 sqe->opcode = toUringOp(request.operation());
561
562 if (!verifyFd(request)) {
563 finishRequestWithError(request, QFileDevice::OpenError);
564 return RequestPrepResult::RequestCompleted;
565 }
566
567 switch (request.operation()) {
568 case Operation::Open: {
569 const QIORingRequest<Operation::Open>
570 *openRequest = request.template requestData<Operation::Open>();
571 sqe->fd = AT_FDCWD; // Could also support proper openat semantics
572 sqe->addr = reinterpret_cast<quint64>(openRequest->path.native().c_str());
573 sqe->open_flags = QFSFileEnginePrivate::openModeToOpenFlags(openRequest->flags);
574 auto &mode = sqe->len;
575 mode = 0666; // With an explicit API we can use QtPrivate::toMode_t() for this
576 break;
577 }
578 case Operation::Close: {
579 if (ongoingSplitOperations)
580 return Defer;
581 const QIORingRequest<Operation::Close>
582 *closeRequest = request.template requestData<Operation::Close>();
583 sqe->fd = closeRequest->fd;
584 // Force all earlier entries in the sq to finish before this is processed:
585 sqe->flags |= IOSQE_IO_DRAIN;
586 break;
587 }
588 case Operation::Read: {
589 const QIORingRequest<Operation::Read>
590 *readRequest = request.template requestData<Operation::Read>();
591 auto span = readRequest->destination;
592 auto offset = readRequest->offset;
593 if (span.size() >= maxReadWriteLen()) {
594 qCDebug(lcQIORing) << "Requested Read of size" << span.size() << "has to be split";
595 auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
596 if (extra->spanOffset == 0) // First time setup
597 ++ongoingSplitOperations;
598 qsizetype remaining = span.size() - extra->spanOffset;
599 span.slice(extra->spanOffset, std::min(remaining, maxReadWriteLen()));
600 offset += extra->totalProcessed;
601 }
602 prepareFileReadWrite(sqe, *readRequest, span.data(), offset, span.size());
603 break;
604 }
605 case Operation::Write: {
606 const QIORingRequest<Operation::Write>
607 *writeRequest = request.template requestData<Operation::Write>();
608 auto span = writeRequest->source;
609 auto offset = writeRequest->offset;
610 if (span.size() >= maxReadWriteLen()) {
611 qCDebug(lcQIORing) << "Requested Write of size" << span.size() << "has to be split";
612 auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
613 if (extra->spanOffset == 0) // First time setup
614 ++ongoingSplitOperations;
615 qsizetype remaining = span.size() - extra->spanOffset;
616 span.slice(extra->spanOffset, std::min(remaining, maxReadWriteLen()));
617 offset += extra->totalProcessed;
618 }
619 prepareFileReadWrite(sqe, *writeRequest, span.data(), offset, span.size());
620 break;
621 }
622 case Operation::VectoredRead: {
623 const QIORingRequest<Operation::VectoredRead>
624 *readvRequest = request.template requestData<Operation::VectoredRead>();
625 quint64 offset = readvRequest->offset;
626 if (auto *extra = request.getExtra<QtPrivate::ReadWriteExtra>())
627 offset += extra->totalProcessed;
628 const auto r = getVectoredOpAddressAndSize(request, readvRequest->destinations);
629 sqe->opcode = toUringOp(r.op);
630 prepareFileReadWrite(sqe, *readvRequest, r.address, offset, r.size);
631 break;
632 }
633 case Operation::VectoredWrite: {
634 const QIORingRequest<Operation::VectoredWrite>
635 *writevRequest = request.template requestData<Operation::VectoredWrite>();
636 quint64 offset = writevRequest->offset;
637 if (auto *extra = request.getExtra<QtPrivate::ReadWriteExtra>())
638 offset += extra->totalProcessed;
639 const auto r = getVectoredOpAddressAndSize(request, writevRequest->sources);
640 sqe->opcode = toUringOp(r.op);
641 prepareFileReadWrite(sqe, *writevRequest, r.address, offset, r.size);
642 break;
643 }
644 case Operation::Flush: {
645 if (ongoingSplitOperations)
646 return Defer;
647 const QIORingRequest<Operation::Flush>
648 *flushRequest = request.template requestData<Operation::Flush>();
649 sqe->fd = qint32(flushRequest->fd);
650 // Force all earlier entries in the sq to finish before this is processed:
651 sqe->flags |= IOSQE_IO_DRAIN;
652 flushInProgress = true;
653 break;
654 }
655 case Operation::Cancel: {
656 const QIORingRequest<Operation::Cancel>
657 *cancelRequest = request.template requestData<Operation::Cancel>();
658 auto *otherOperation = reinterpret_cast<GenericRequestType *>(cancelRequest->handle);
659 auto it = std::as_const(addrItMap).find(otherOperation);
660 if (it == addrItMap.cend()) { // : The request to cancel doesn't exist
661 invokeCallback(*cancelRequest);
662 return RequestPrepResult::RequestCompleted;
663 }
664 if (!otherOperation->wasQueued()) {
665 // The request hasn't been queued yet, so we can just drop it from
666 // the pending requests and call the callback.
667 Q_ASSERT(!lastUnqueuedIterator);
668 finishRequestWithError(*otherOperation, QFileDevice::AbortError);
669 pendingRequests.erase(*it); // otherOperation is deleted
670 addrItMap.erase(it);
671 invokeCallback(*cancelRequest);
672 return RequestPrepResult::RequestCompleted;
673 }
674 sqe->addr = quint64(otherOperation);
675 break;
676 }
677 case Operation::Stat: {
678 const QIORingRequest<Operation::Stat>
679 *statRequest = request.template requestData<Operation::Stat>();
680 // We need to store the statx struct somewhere:
681 struct statx *st = request.getOrInitializeExtra<struct statx>();
682
683 sqe->fd = statRequest->fd;
684 // We want to use the fd as the target of query instead of as the fd of the relative dir,
685 // so we set addr to an empty string, and specify the AT_EMPTY_PATH flag.
686 static const char emptystr[] = "";
687 sqe->addr = qint64(emptystr);
688 sqe->statx_flags = AT_EMPTY_PATH;
689 sqe->len = STATX_ALL; // @todo configure somehow
690 sqe->off = quint64(st);
691 break;
692 }
694 Q_UNREACHABLE_RETURN(RequestPrepResult::RequestCompleted);
695 break;
696 }
697 return RequestPrepResult::Ok;
698}
699
700void QIORing::GenericRequestType::cleanupExtra(Operation op, void *extra)
701{
702 switch (op) {
703 case Operation::Open:
704 case Operation::Close:
705 case Operation::Cancel:
706 case Operation::Flush:
708 break;
709 case Operation::Read:
710 case Operation::Write:
711 case Operation::VectoredRead:
712 case Operation::VectoredWrite:
713 delete static_cast<QtPrivate::ReadWriteExtra *>(extra);
714 return;
715 case Operation::Stat:
716 delete static_cast<struct statx *>(extra);
717 return;
718 }
719}
720
721QT_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) DetectFd
constexpr bool HasFdMember
static qsizetype maxReadWriteLen()
static Q_ALWAYS_INLINE void prepareFileIOCommon(io_uring_sqe *sqe, const QIORingRequestOffsetFdBase &request, quint64 offset)
static io_uring_op toUringOp(QIORing::Operation op)
static constexpr qsizetype MaxReadWriteLen
QT_REQUIRE_CONFIG(liburing)
static Q_ALWAYS_INLINE void prepareFileReadWrite(io_uring_sqe *sqe, const QIORingRequestOffsetFdBase &request, const void *address, quint64 offset, qsizetype size)
#define MAP_FAILED