// 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 "qrandomaccessasyncfile_p_p.h" #include "qiooperation_p.h" #include "qiooperation_p_p.h" #include // QtPrivate::toFilesystemPath #include #include #include QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcQRandomAccessIORing, "qt.core.qrandomaccessasyncfile.ioring", QtCriticalMsg); QRandomAccessAsyncFilePrivate::QRandomAccessAsyncFilePrivate() = default; QRandomAccessAsyncFilePrivate::~QRandomAccessAsyncFilePrivate() = default; void QRandomAccessAsyncFilePrivate::init() { m_ioring = QIORing::sharedInstance(); if (!m_ioring) qCCritical(lcQRandomAccessIORing, "QRandomAccessAsyncFile: ioring failed to initialize"); } QIORing::RequestHandle QRandomAccessAsyncFilePrivate::cancel(QIORing::RequestHandle handle) { if (handle) { QIORingRequest cancelRequest; cancelRequest.handle = handle; return m_ioring->queueRequest(std::move(cancelRequest)); } return nullptr; } void QRandomAccessAsyncFilePrivate::cancelAndWait(QIOOperation *op) { auto *opHandle = m_opHandleMap.value(op); if (auto *handle = cancel(opHandle)) { m_ioring->waitForRequest(handle); m_ioring->waitForRequest(opHandle); } } void QRandomAccessAsyncFilePrivate::queueCompletion(QIOOperationPrivate *priv, QIOOperation::Error error) { // Remove the handle now in case the user cancels or deletes the io-operation // before operationComplete is called - the null-handle will protect from // nasty issues that may occur when trying to cancel an operation that's no // longer in the queue: m_opHandleMap.remove(priv->q_func()); // @todo: Look into making it emit only if synchronously completed QMetaObject::invokeMethod(priv->q_ptr, [priv, error](){ priv->operationComplete(error); }, Qt::QueuedConnection); } QIOOperation *QRandomAccessAsyncFilePrivate::open(const QString &path, QIODeviceBase::OpenMode mode) { auto *dataStorage = new QtPrivate::QIOOperationDataStorage(); auto *priv = new QIOOperationPrivate(dataStorage); priv->type = QIOOperation::Type::Open; auto *op = new QIOOperation(*priv, q_ptr); if (m_fileState != FileState::Closed) { queueCompletion(priv, QIOOperation::Error::Open); return op; } m_operations.append(op); m_fileState = FileState::OpenPending; QIORingRequest openOperation; openOperation.path = QtPrivate::toFilesystemPath(path); openOperation.flags = mode; openOperation.setCallback([this, op, priv](const QIORingRequest &request) { if (const auto *err = std::get_if(&request.result)) { if (m_fileState != FileState::Opened) { // We assume there was only one pending open() in flight. m_fd = -1; m_fileState = FileState::Closed; } if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError) queueCompletion(priv, QIOOperation::Error::Aborted); else queueCompletion(priv, QIOOperation::Error::Open); } else if (const auto *result = std::get_if>( &request.result)) { if (m_fileState == FileState::OpenPending) { m_fileState = FileState::Opened; m_fd = result->fd; queueCompletion(priv, QIOOperation::Error::None); } else { // Something went wrong, we did not expect a callback: // So we close the new handle: QIORingRequest closeRequest; closeRequest.fd = result->fd; QIORing::RequestHandle handle = m_ioring->queueRequest(std::move(closeRequest)); // Since the user issued multiple open() calls they get to wait for the close() to // finish: m_ioring->waitForRequest(handle); queueCompletion(priv, QIOOperation::Error::Open); } } m_operations.removeOne(op); }); m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(openOperation))); return op; } void QRandomAccessAsyncFilePrivate::close() { // all the operations should be aborted const auto ops = std::exchange(m_operations, {}); QList tasksToAwait; // Request to cancel all of the in-flight operations: for (const auto &op : ops) { if (op) { op->d_func()->error = QIOOperation::Error::Aborted; if (auto *opHandle = m_opHandleMap.value(op)) { tasksToAwait.append(cancel(opHandle)); tasksToAwait.append(opHandle); } } } QIORingRequest closeRequest; closeRequest.fd = m_fd; tasksToAwait.append(m_ioring->queueRequest(std::move(closeRequest))); // Wait for completion: for (const QIORing::RequestHandle &handle : tasksToAwait) m_ioring->waitForRequest(handle); m_fileState = FileState::Closed; m_fd = -1; } qint64 QRandomAccessAsyncFilePrivate::size() const { QIORingRequest statRequest; statRequest.fd = m_fd; qint64 finalSize = 0; statRequest.setCallback([&finalSize](const QIORingRequest &request) { if (const auto *err = std::get_if(&request.result)) { Q_UNUSED(err); finalSize = -1; } else if (const auto *res = std::get_if>(&request.result)) { finalSize = q26::saturate_cast(res->size); } }); auto *handle = m_ioring->queueRequest(std::move(statRequest)); m_ioring->waitForRequest(handle); return finalSize; } QIOOperation *QRandomAccessAsyncFilePrivate::flush() { auto *dataStorage = new QtPrivate::QIOOperationDataStorage(); auto *priv = new QIOOperationPrivate(dataStorage); priv->type = QIOOperation::Type::Flush; auto *op = new QIOOperation(*priv, q_ptr); m_operations.append(op); QIORingRequest flushRequest; flushRequest.fd = m_fd; flushRequest.setCallback([this, op](const QIORingRequest &request) { auto *priv = QIOOperationPrivate::get(op); if (const auto *err = std::get_if(&request.result)) { if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError) queueCompletion(priv, QIOOperation::Error::Aborted); else if (*err == QFileDevice::OpenError) queueCompletion(priv, QIOOperation::Error::FileNotOpen); else queueCompletion(priv, QIOOperation::Error::Flush); } else if (std::get_if>(&request.result)) { queueCompletion(priv, QIOOperation::Error::None); } m_operations.removeOne(op); }); m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(flushRequest))); return op; } void QRandomAccessAsyncFilePrivate::startReadIntoSingle(QIOOperation *op, const QSpan &to) { QIORingRequest readRequest; readRequest.fd = m_fd; auto *priv = QIOOperationPrivate::get(op); if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now queueCompletion(priv, QIOOperation::Error::IncorrectOffset); m_operations.removeOne(op); return; } readRequest.offset = priv->offset; readRequest.destination = to; readRequest.setCallback([this, op](const QIORingRequest &request) { auto *priv = QIOOperationPrivate::get(op); if (const auto *err = std::get_if(&request.result)) { if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError) queueCompletion(priv, QIOOperation::Error::Aborted); else if (*err == QFileDevice::OpenError) queueCompletion(priv, QIOOperation::Error::FileNotOpen); else if (*err == QFileDevice::PositionError) queueCompletion(priv, QIOOperation::Error::IncorrectOffset); else queueCompletion(priv, QIOOperation::Error::Read); } else if (const auto *result = std::get_if>( &request.result)) { priv->appendBytesProcessed(result->bytesRead); if (priv->dataStorage->containsReadSpans()) priv->dataStorage->getReadSpans().first().slice(0, result->bytesRead); else priv->dataStorage->getByteArray().slice(0, result->bytesRead); queueCompletion(priv, QIOOperation::Error::None); } m_operations.removeOne(op); }); m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(readRequest))); } QIOReadOperation *QRandomAccessAsyncFilePrivate::read(qint64 offset, qint64 maxSize) { QByteArray array; array.resizeForOverwrite(maxSize); auto *dataStorage = new QtPrivate::QIOOperationDataStorage(std::move(array)); auto *priv = new QIOOperationPrivate(dataStorage); priv->offset = offset; priv->type = QIOOperation::Type::Read; auto *op = new QIOReadOperation(*priv, q_ptr); m_operations.append(op); startReadIntoSingle(op, as_writable_bytes(QSpan(dataStorage->getByteArray()))); return op; } QIOWriteOperation *QRandomAccessAsyncFilePrivate::write(qint64 offset, const QByteArray &data) { return write(offset, QByteArray(data)); } void QRandomAccessAsyncFilePrivate::startWriteFromSingle(QIOOperation *op, const QSpan &from) { QIORingRequest writeRequest; writeRequest.fd = m_fd; auto *priv = QIOOperationPrivate::get(op); if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now queueCompletion(priv, QIOOperation::Error::IncorrectOffset); m_operations.removeOne(op); return; } writeRequest.offset = priv->offset; writeRequest.source = from; writeRequest.setCallback([this, op](const QIORingRequest &request) { auto *priv = QIOOperationPrivate::get(op); if (const auto *err = std::get_if(&request.result)) { if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError) queueCompletion(priv, QIOOperation::Error::Aborted); else if (*err == QFileDevice::OpenError) queueCompletion(priv, QIOOperation::Error::FileNotOpen); else if (*err == QFileDevice::PositionError) queueCompletion(priv, QIOOperation::Error::IncorrectOffset); else queueCompletion(priv, QIOOperation::Error::Write); } else if (const auto *result = std::get_if>( &request.result)) { priv->appendBytesProcessed(result->bytesWritten); queueCompletion(priv, QIOOperation::Error::None); } m_operations.removeOne(op); }); m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(writeRequest))); } QIOWriteOperation *QRandomAccessAsyncFilePrivate::write(qint64 offset, QByteArray &&data) { auto *dataStorage = new QtPrivate::QIOOperationDataStorage(std::move(data)); auto *priv = new QIOOperationPrivate(dataStorage); priv->offset = offset; priv->type = QIOOperation::Type::Write; auto *op = new QIOWriteOperation(*priv, q_ptr); m_operations.append(op); startWriteFromSingle(op, as_bytes(QSpan(dataStorage->getByteArray()))); return op; } QIOVectoredReadOperation *QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan buffer) { auto *dataStorage = new QtPrivate::QIOOperationDataStorage( QSpan>{ buffer }); auto *priv = new QIOOperationPrivate(dataStorage); priv->offset = offset; priv->type = QIOOperation::Type::Read; auto *op = new QIOVectoredReadOperation(*priv, q_ptr); m_operations.append(op); startReadIntoSingle(op, dataStorage->getReadSpans().first()); return op; } QIOVectoredWriteOperation *QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan buffer) { auto *dataStorage = new QtPrivate::QIOOperationDataStorage( QSpan>{ buffer }); auto *priv = new QIOOperationPrivate(dataStorage); priv->offset = offset; priv->type = QIOOperation::Type::Write; auto *op = new QIOVectoredWriteOperation(*priv, q_ptr); m_operations.append(op); startWriteFromSingle(op, dataStorage->getWriteSpans().first()); return op; } QIOVectoredReadOperation * QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan> buffers) { if (!QIORing::supportsOperation(QtPrivate::Operation::VectoredRead)) return nullptr; auto *dataStorage = new QtPrivate::QIOOperationDataStorage(buffers); auto *priv = new QIOOperationPrivate(dataStorage); priv->offset = offset; priv->type = QIOOperation::Type::Read; auto *op = new QIOVectoredReadOperation(*priv, q_ptr); if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now queueCompletion(priv, QIOOperation::Error::IncorrectOffset); return op; } m_operations.append(op); QIORingRequest readRequest; readRequest.fd = m_fd; readRequest.offset = priv->offset; readRequest.destinations = dataStorage->getReadSpans(); readRequest.setCallback([this, op](const QIORingRequest &request) { auto *priv = QIOOperationPrivate::get(op); if (const auto *err = std::get_if(&request.result)) { if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError) queueCompletion(priv, QIOOperation::Error::Aborted); else queueCompletion(priv, QIOOperation::Error::Read); } else if (const auto *result = std::get_if>( &request.result)) { priv->appendBytesProcessed(result->bytesRead); qint64 processed = result->bytesRead; for (auto &span : priv->dataStorage->getReadSpans()) { if (span.size() < processed) { processed -= span.size(); } else { // span.size >= processed span.slice(0, processed); processed = 0; } } queueCompletion(priv, QIOOperation::Error::None); } m_operations.removeOne(op); }); m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(readRequest))); return op; } QIOVectoredWriteOperation * QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan> buffers) { if (!QIORing::supportsOperation(QtPrivate::Operation::VectoredWrite)) return nullptr; auto *dataStorage = new QtPrivate::QIOOperationDataStorage(buffers); auto *priv = new QIOOperationPrivate(dataStorage); priv->offset = offset; priv->type = QIOOperation::Type::Write; auto *op = new QIOVectoredWriteOperation(*priv, q_ptr); if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now queueCompletion(priv, QIOOperation::Error::IncorrectOffset); return op; } m_operations.append(op); QIORingRequest writeRequest; writeRequest.fd = m_fd; writeRequest.offset = priv->offset; writeRequest.sources = dataStorage->getWriteSpans(); writeRequest.setCallback( [this, op](const QIORingRequest &request) { auto *priv = QIOOperationPrivate::get(op); if (const auto *err = std::get_if(&request.result)) { if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError) queueCompletion(priv, QIOOperation::Error::Aborted); else queueCompletion(priv, QIOOperation::Error::Write); } else if (const auto *result = std::get_if< QIORingResult>( &request.result)) { priv->appendBytesProcessed(result->bytesWritten); queueCompletion(priv, QIOOperation::Error::None); } m_operations.removeOne(op); }); m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(writeRequest))); return op; } QT_END_NAMESPACE