Revision control
++ // 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"
++
++ #include <QtCore/q26numeric.h>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_LOGGING_CATEGORY(lcQIORing, "qt.core.ioring", QtCriticalMsg)
++
++ QIORing *QIORing::sharedInstance()
++ {
++ thread_local QIORing instance;
++ if (!instance.initializeIORing())
++ return nullptr;
++ return &instance;
++ }
++
++ QIORing::QIORing(quint32 submissionQueueSize, quint32 completionQueueSize)
++ : sqEntries(submissionQueueSize), cqEntries(completionQueueSize)
++ {
++ // Destructor in respective _<platform>.cpp
++ }
++
++ auto QIORing::queueRequestInternal(GenericRequestType &request) -> QueuedRequestStatus
++ {
++ if (!ensureInitialized() || preparingRequests) { // preparingRequests protects against recursing
++ // inside callbacks of synchronous completions.
++ finishRequestWithError(request, QFileDevice::ResourceError);
++ addrItMap.remove(&request);
++ return QueuedRequestStatus::CompletedImmediately;
++ }
++ if (!lastUnqueuedIterator) {
++ lastUnqueuedIterator.emplace(addrItMap[&request]);
++ } else if (request.operation() == QtPrivate::Operation::Cancel) {
++ // We want to fast-track cancellations because they may be cancelling
++ // unqueued things, so we push it up front in the queue:
++ auto &it = addrItMap[&request];
++ const auto where = *lastUnqueuedIterator;
++ pendingRequests.splice(where, pendingRequests, it);
++ it = std::prev(where);
++ lastUnqueuedIterator.emplace(it);
++ }
++
++ qCDebug(lcQIORing) << "Trying to submit request" << request.operation()
++ << "user data:" << std::addressof(request);
++ prepareRequests();
++ // If this is now true we have, in some way, fulfilled the request:
++ const bool requestCompleted = !addrItMap.contains(&request);
++ const QueuedRequestStatus requestQueuedState = requestCompleted
++ ? QueuedRequestStatus::CompletedImmediately
++ : QueuedRequestStatus::Pending;
++ // We want to avoid notifying the kernel too often of tasks, so only do it if the queue is full,
++ // otherwise do it when we return to the event loop.
++ if (unstagedRequests == sqEntries && inFlightRequests <= cqEntries) {
++ submitRequests();
++ return requestQueuedState;
++ }
++ if (stagePending || unstagedRequests == 0)
++ return requestQueuedState;
++ stagePending = true;
++ // We are not a QObject, but we always have the notifier, so use that for context:
++ QMetaObject::invokeMethod(
++ std::addressof(*notifier), [this] { submitRequests(); }, Qt::QueuedConnection);
++ return requestQueuedState;
++ }
++
++ bool QIORing::waitForRequest(RequestHandle handle, QDeadlineTimer deadline)
++ {
++ if (!handle || !addrItMap.contains(handle))
++ return true; // : It was never there to begin with (so it is finished)
++ if (unstagedRequests)
++ submitRequests();
++ completionReady(); // Try to process some pending completions
++ while (!deadline.hasExpired() && addrItMap.contains(handle)) {
++ if (!waitForCompletions(deadline))
++ return false;
++ completionReady();
++ }
++ return !addrItMap.contains(handle);
++ }
++
++ namespace QtPrivate {
++ template <typename T>
++ using DetectResult = decltype(std::declval<const T &>().result);
++
++ template <typename T>
++ constexpr bool HasResultMember = qxp::is_detected_v<DetectResult, T>;
++ }
++
++ void QIORing::setFileErrorResult(QIORing::GenericRequestType &req, QFileDevice::FileError error)
++ {
++ invokeOnOp(req, [error](auto *concreteRequest) {
++ if constexpr (QtPrivate::HasResultMember<decltype(*concreteRequest)>)
++ setFileErrorResult(*concreteRequest, error);
++ });
++ }
++
++ void QIORing::finishRequestWithError(QIORing::GenericRequestType &req, QFileDevice::FileError error)
++ {
++ invokeOnOp(req, [error](auto *concreteRequest) {
++ if constexpr (QtPrivate::HasResultMember<decltype(*concreteRequest)>)
++ setFileErrorResult(*concreteRequest, error);
++ invokeCallback(*concreteRequest);
++ });
++ }
++
++ QIORing::ReadWriteStatus QIORing::handleReadCompletion(size_t value, QSpan<std::byte> *destinations,
++ void *voidExtra, SetResultFn setResultFn)
++ {
++ if (value == 0) {
++ // Since we are reading, presumably this indicates EOF.
++ // In case this is our only callback, notify that it at least wasn't a
++ // failure:
++ setResultFn(qint64(0));
++ return ReadWriteStatus::Finished;
++ }
++ if (auto *extra = static_cast<QtPrivate::ReadWriteExtra *>(voidExtra)) {
++ const qsizetype bytesRead = q26::saturating_cast<qsizetype>(value);
++ qCDebug(lcQIORing) << "Partial read of" << bytesRead << "bytes completed";
++ extra->totalProcessed = setResultFn(bytesRead);
++ // [0/1] Add the number of bytes processed to the spanOffset - we use this to test how many
++ // spans were fully processed, and/or how far into the last span we have data.
++ extra->spanOffset += bytesRead;
++ qCDebug(lcQIORing) << "Read operation progress: span" << extra->spanIndex << "offset"
++ << extra->spanOffset << "of" << destinations[extra->spanIndex].size()
++ << "bytes. Total read:" << extra->totalProcessed << "bytes";
++ // [1/1] We subtract the size of the spans, in order, here. When a span's size is larger
++ // than the "spanOffset" it means we have a partially-used span. Otherwise it stops
++ // processing when we run out of spans to check, or we hit spanOffset == 0.
++ while (extra->spanOffset >= destinations[extra->spanIndex].size()) {
++ extra->spanOffset -= destinations[extra->spanIndex].size();
++ // Move to next span
++ if (++extra->spanIndex == extra->numSpans)
++ return ReadWriteStatus::Finished;
++ }
++ return ReadWriteStatus::MoreToDo;
++ }
++ setResultFn(q26::saturating_cast<qsizetype>(value));
++ return ReadWriteStatus::Finished;
++ }
++
++ QIORing::ReadWriteStatus QIORing::handleWriteCompletion(size_t value,
++ const QSpan<const std::byte> *sources,
++ void *voidExtra, SetResultFn setResultFn)
++ {
++ if (auto *extra = static_cast<QtPrivate::ReadWriteExtra *>(voidExtra)) {
++ const qsizetype bytesWritten = q26::saturating_cast<qsizetype>(value);
++ qCDebug(lcQIORing) << "Partial write of" << bytesWritten << "bytes completed";
++ extra->totalProcessed = setResultFn(bytesWritten);
++ // [0/1] Add the number of bytes processed to the spanOffset - we use this to test how many
++ // spans were fully processed, and/or how far into the last span we have data.
++ extra->spanOffset += bytesWritten;
++ qCDebug(lcQIORing) << "Write operation progress: span" << extra->spanIndex << "offset"
++ << extra->spanOffset << "of" << sources[extra->spanIndex].size()
++ << "bytes. Total written:" << extra->totalProcessed << "bytes";
++ // [1/1] We subtract the size of the spans, in order, here. When a span's size is larger
++ // than the "spanOffset" it means we have a partially-used span. Otherwise it stops
++ // processing when we run out of spans to check, or we hit spanOffset == 0.
++ while (extra->spanOffset >= sources[extra->spanIndex].size()) {
++ extra->spanOffset -= sources[extra->spanIndex].size();
++ // Move to next span
++ if (++extra->spanIndex == extra->numSpans)
++ return ReadWriteStatus::Finished;
++ }
++ return ReadWriteStatus::MoreToDo;
++ }
++ setResultFn(q26::saturating_cast<qsizetype>(value));
++ return ReadWriteStatus::Finished;
++ }
++
++ void QIORing::finalizeReadWriteCompletion(GenericRequestType *request, ReadWriteStatus rwstatus)
++ {
++ switch (rwstatus) {
++ case ReadWriteStatus::Finished:
++ if (request->getExtra<void>())
++ --ongoingSplitOperations;
++ break;
++ case ReadWriteStatus::MoreToDo: {
++ // Move the request such that it is next in the list to be processed:
++ auto &it = addrItMap[request];
++ const auto where = lastUnqueuedIterator.value_or(pendingRequests.end());
++ pendingRequests.splice(where, pendingRequests, it);
++ it = std::prev(where);
++ lastUnqueuedIterator = it;
++ break;
++ }
++ }
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qioring_p.cpp"