Revision control
Copy as Markdown
// 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 <QtCore/qfile.h> // QtPrivate::toFilesystemPath
#include <QtCore/qtypes.h>
#include <QtCore/private/qioring_p.h>
#include <QtCore/q26numeric.h>
QT_BEGIN_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcQRandomAccessIORing, "qt.core.qrandomaccessasyncfile.ioring",
QtCriticalMsg);
QRandomAccessAsyncFileNativeBackend::QRandomAccessAsyncFileNativeBackend(QRandomAccessAsyncFile *owner)
: QRandomAccessAsyncFileBackend(owner)
{}
QRandomAccessAsyncFileNativeBackend::~QRandomAccessAsyncFileNativeBackend() = default;
bool QRandomAccessAsyncFileNativeBackend::init()
{
m_ioring = QIORing::sharedInstance();
if (!m_ioring)
qCWarning(lcQRandomAccessIORing, "QRandomAccessAsyncFile: ioring failed to initialize");
return m_ioring != nullptr;
}
QIORing::RequestHandle QRandomAccessAsyncFileNativeBackend::cancel(QIORing::RequestHandle handle)
{
if (handle) {
QIORingRequest<QIORing::Operation::Cancel> cancelRequest;
cancelRequest.handle = handle;
return m_ioring->queueRequest(std::move(cancelRequest));
}
return nullptr;
}
void QRandomAccessAsyncFileNativeBackend::cancelAndWait(QIOOperation *op)
{
auto *opHandle = m_opHandleMap.value(op);
if (auto *handle = cancel(opHandle)) {
m_ioring->waitForRequest(handle);
m_ioring->waitForRequest(opHandle);
}
}
void QRandomAccessAsyncFileNativeBackend::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 *QRandomAccessAsyncFileNativeBackend::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, m_owner);
if (m_fileState != FileState::Closed) {
queueCompletion(priv, QIOOperation::Error::Open);
return op;
}
m_operations.append(op);
m_fileState = FileState::OpenPending;
QIORingRequest<QIORing::Operation::Open> openOperation;
openOperation.path = QtPrivate::toFilesystemPath(path);
openOperation.flags = mode;
openOperation.setCallback([this, op,
priv](const QIORingRequest<QIORing::Operation::Open> &request) {
if (const auto *err = std::get_if<QFileDevice::FileError>(&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<QIORingResult<QIORing::Operation::Open>>(
&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<QIORing::Operation::Close> 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 QRandomAccessAsyncFileNativeBackend::close()
{
// all the operations should be aborted
const auto ops = std::exchange(m_operations, {});
QList<QIORing::RequestHandle> tasksToAwait;
// Request to cancel all of the in-flight operations:
for (const auto &op : ops) {
if (op) {
QIOOperationPrivate::get(op)->error = QIOOperation::Error::Aborted;
if (auto *opHandle = m_opHandleMap.value(op)) {
tasksToAwait.append(cancel(opHandle));
tasksToAwait.append(opHandle);
}
}
}
QIORingRequest<QIORing::Operation::Close> 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 QRandomAccessAsyncFileNativeBackend::size() const
{
QIORingRequest<QIORing::Operation::Stat> statRequest;
statRequest.fd = m_fd;
qint64 finalSize = 0;
statRequest.setCallback([&finalSize](const QIORingRequest<QIORing::Operation::Stat> &request) {
if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
Q_UNUSED(err);
finalSize = -1;
} else if (const auto *res = std::get_if<QIORingResult<QIORing::Operation::Stat>>(&request.result)) {
finalSize = q26::saturating_cast<qint64>(res->size);
}
});
auto *handle = m_ioring->queueRequest(std::move(statRequest));
m_ioring->waitForRequest(handle);
return finalSize;
}
QIOOperation *QRandomAccessAsyncFileNativeBackend::flush()
{
auto *dataStorage = new QtPrivate::QIOOperationDataStorage();
auto *priv = new QIOOperationPrivate(dataStorage);
priv->type = QIOOperation::Type::Flush;
auto *op = new QIOOperation(*priv, m_owner);
m_operations.append(op);
QIORingRequest<QIORing::Operation::Flush> flushRequest;
flushRequest.fd = m_fd;
flushRequest.setCallback([this, op](const QIORingRequest<QIORing::Operation::Flush> &request) {
auto *priv = QIOOperationPrivate::get(op);
if (const auto *err = std::get_if<QFileDevice::FileError>(&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<QIORingResult<QIORing::Operation::Flush>>(&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 QRandomAccessAsyncFileNativeBackend::startReadIntoSingle(QIOOperation *op,
const QSpan<std::byte> &to)
{
QIORingRequest<QIORing::Operation::Read> 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<QIORing::Operation::Read> &request) {
auto *priv = QIOOperationPrivate::get(op);
if (const auto *err = std::get_if<QFileDevice::FileError>(&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<QIORingResult<QIORing::Operation::Read>>(
&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 *QRandomAccessAsyncFileNativeBackend::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, m_owner);
m_operations.append(op);
startReadIntoSingle(op, as_writable_bytes(QSpan(dataStorage->getByteArray())));
return op;
}
QIOWriteOperation *QRandomAccessAsyncFileNativeBackend::write(qint64 offset, const QByteArray &data)
{
return write(offset, QByteArray(data));
}
void QRandomAccessAsyncFileNativeBackend::startWriteFromSingle(QIOOperation *op,
const QSpan<const std::byte> &from)
{
QIORingRequest<QIORing::Operation::Write> 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<QIORing::Operation::Write> &request) {
auto *priv = QIOOperationPrivate::get(op);
if (const auto *err = std::get_if<QFileDevice::FileError>(&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<QIORingResult<QIORing::Operation::Write>>(
&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 *QRandomAccessAsyncFileNativeBackend::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, m_owner);
m_operations.append(op);
startWriteFromSingle(op, as_bytes(QSpan(dataStorage->getByteArray())));
return op;
}
QIOVectoredReadOperation *QRandomAccessAsyncFileNativeBackend::readInto(qint64 offset,
QSpan<std::byte> buffer)
{
auto *dataStorage = new QtPrivate::QIOOperationDataStorage(
QSpan<const QSpan<std::byte>>{ buffer });
auto *priv = new QIOOperationPrivate(dataStorage);
priv->offset = offset;
priv->type = QIOOperation::Type::Read;
auto *op = new QIOVectoredReadOperation(*priv, m_owner);
m_operations.append(op);
startReadIntoSingle(op, dataStorage->getReadSpans().first());
return op;
}
QIOVectoredWriteOperation *QRandomAccessAsyncFileNativeBackend::writeFrom(qint64 offset,
QSpan<const std::byte> buffer)
{
auto *dataStorage = new QtPrivate::QIOOperationDataStorage(
QSpan<const QSpan<const std::byte>>{ buffer });
auto *priv = new QIOOperationPrivate(dataStorage);
priv->offset = offset;
priv->type = QIOOperation::Type::Write;
auto *op = new QIOVectoredWriteOperation(*priv, m_owner);
m_operations.append(op);
startWriteFromSingle(op, dataStorage->getWriteSpans().first());
return op;
}
QIOVectoredReadOperation *
QRandomAccessAsyncFileNativeBackend::readInto(qint64 offset, QSpan<const QSpan<std::byte>> 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, m_owner);
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<QIORing::Operation::VectoredRead> readRequest;
readRequest.fd = m_fd;
readRequest.offset = priv->offset;
readRequest.destinations = dataStorage->getReadSpans();
readRequest.setCallback([this,
op](const QIORingRequest<QIORing::Operation::VectoredRead> &request) {
auto *priv = QIOOperationPrivate::get(op);
if (const auto *err = std::get_if<QFileDevice::FileError>(&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<QIORingResult<QIORing::Operation::VectoredRead>>(
&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 *
QRandomAccessAsyncFileNativeBackend::writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> 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, m_owner);
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<QIORing::Operation::VectoredWrite> writeRequest;
writeRequest.fd = m_fd;
writeRequest.offset = priv->offset;
writeRequest.sources = dataStorage->getWriteSpans();
writeRequest.setCallback(
[this, op](const QIORingRequest<QIORing::Operation::VectoredWrite> &request) {
auto *priv = QIOOperationPrivate::get(op);
if (const auto *err = std::get_if<QFileDevice::FileError>(&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<QIORing::Operation::VectoredWrite>>(
&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