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