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
qrandomaccessasyncfile_qioring.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
6
9
10#include <QtCore/qfile.h> // QtPrivate::toFilesystemPath
11#include <QtCore/qtypes.h>
12#include <QtCore/private/qioring_p.h>
13
14#include <QtCore/q26numeric.h>
15
17
18Q_STATIC_LOGGING_CATEGORY(lcQRandomAccessIORing, "qt.core.qrandomaccessasyncfile.ioring",
19 QtCriticalMsg);
20
21QRandomAccessAsyncFileNativeBackend::QRandomAccessAsyncFileNativeBackend(QRandomAccessAsyncFile *owner)
22 : QRandomAccessAsyncFileBackend(owner)
23{}
24
25QRandomAccessAsyncFileNativeBackend::~QRandomAccessAsyncFileNativeBackend() = default;
26
27bool QRandomAccessAsyncFileNativeBackend::init()
28{
29 m_ioring = QIORing::sharedInstance();
30 if (!m_ioring)
31 qCWarning(lcQRandomAccessIORing, "QRandomAccessAsyncFile: ioring failed to initialize");
32 return m_ioring != nullptr;
33}
34
35QIORing::RequestHandle QRandomAccessAsyncFileNativeBackend::cancel(QIORing::RequestHandle handle)
36{
37 if (handle) {
38 QIORingRequest<QIORing::Operation::Cancel> cancelRequest;
39 cancelRequest.handle = handle;
40 return m_ioring->queueRequest(std::move(cancelRequest));
41 }
42 return nullptr;
43}
44
45void QRandomAccessAsyncFileNativeBackend::cancelAndWait(QIOOperation *op)
46{
47 auto *opHandle = m_opHandleMap.value(op);
48 if (auto *handle = cancel(opHandle)) {
49 m_ioring->waitForRequest(handle);
50 m_ioring->waitForRequest(opHandle);
51 }
52}
53
54void QRandomAccessAsyncFileNativeBackend::queueCompletion(QIOOperationPrivate *priv, QIOOperation::Error error)
55{
56 // Remove the handle now in case the user cancels or deletes the io-operation
57 // before operationComplete is called - the null-handle will protect from
58 // nasty issues that may occur when trying to cancel an operation that's no
59 // longer in the queue:
60 m_opHandleMap.remove(priv->q_func());
61 // @todo: Look into making it emit only if synchronously completed
62 QMetaObject::invokeMethod(priv->q_ptr, [priv, error](){
63 priv->operationComplete(error);
64 }, Qt::QueuedConnection);
65}
66
67QIOOperation *QRandomAccessAsyncFileNativeBackend::open(const QString &path, QIODeviceBase::OpenMode mode)
68{
69 auto *dataStorage = new QtPrivate::QIOOperationDataStorage();
70
71 auto *priv = new QIOOperationPrivate(dataStorage);
72 priv->type = QIOOperation::Type::Open;
73
74 auto *op = new QIOOperation(*priv, m_owner);
75 if (m_fileState != FileState::Closed) {
76 queueCompletion(priv, QIOOperation::Error::Open);
77 return op;
78 }
79 m_operations.append(op);
80 m_fileState = FileState::OpenPending;
81
82 QIORingRequest<QIORing::Operation::Open> openOperation;
83 openOperation.path = QtPrivate::toFilesystemPath(path);
84 openOperation.flags = mode;
85 openOperation.setCallback([this, op,
86 priv](const QIORingRequest<QIORing::Operation::Open> &request) {
87 if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
88 if (m_fileState != FileState::Opened) {
89 // We assume there was only one pending open() in flight.
90 m_fd = -1;
91 m_fileState = FileState::Closed;
92 }
93 if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError)
94 queueCompletion(priv, QIOOperation::Error::Aborted);
95 else
96 queueCompletion(priv, QIOOperation::Error::Open);
97 } else if (const auto *result = std::get_if<QIORingResult<QIORing::Operation::Open>>(
98 &request.result)) {
99 if (m_fileState == FileState::OpenPending) {
100 m_fileState = FileState::Opened;
101 m_fd = result->fd;
102 queueCompletion(priv, QIOOperation::Error::None);
103 } else { // Something went wrong, we did not expect a callback:
104 // So we close the new handle:
105 QIORingRequest<QIORing::Operation::Close> closeRequest;
106 closeRequest.fd = result->fd;
107 QIORing::RequestHandle handle = m_ioring->queueRequest(std::move(closeRequest));
108 // Since the user issued multiple open() calls they get to wait for the close() to
109 // finish:
110 m_ioring->waitForRequest(handle);
111 queueCompletion(priv, QIOOperation::Error::Open);
112 }
113 }
114 m_operations.removeOne(op);
115 });
116 m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(openOperation)));
117
118 return op;
119}
120
121void QRandomAccessAsyncFileNativeBackend::close()
122{
123 // all the operations should be aborted
124 const auto ops = std::exchange(m_operations, {});
125 QList<QIORing::RequestHandle> tasksToAwait;
126 // Request to cancel all of the in-flight operations:
127 for (const auto &op : ops) {
128 if (op) {
129 QIOOperationPrivate::get(op)->error = QIOOperation::Error::Aborted;
130 if (auto *opHandle = m_opHandleMap.value(op)) {
131 tasksToAwait.append(cancel(opHandle));
132 tasksToAwait.append(opHandle);
133 }
134 }
135 }
136
137 QIORingRequest<QIORing::Operation::Close> closeRequest;
138 closeRequest.fd = m_fd;
139 tasksToAwait.append(m_ioring->queueRequest(std::move(closeRequest)));
140
141 // Wait for completion:
142 for (const QIORing::RequestHandle &handle : tasksToAwait)
143 m_ioring->waitForRequest(handle);
144 m_fileState = FileState::Closed;
145 m_fd = -1;
146}
147
148qint64 QRandomAccessAsyncFileNativeBackend::size() const
149{
150 QIORingRequest<QIORing::Operation::Stat> statRequest;
151 statRequest.fd = m_fd;
152 qint64 finalSize = 0;
153 statRequest.setCallback([&finalSize](const QIORingRequest<QIORing::Operation::Stat> &request) {
154 if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
155 Q_UNUSED(err);
156 finalSize = -1;
157 } else if (const auto *res = std::get_if<QIORingResult<QIORing::Operation::Stat>>(&request.result)) {
158 finalSize = q26::saturate_cast<qint64>(res->size);
159 }
160 });
161 auto *handle = m_ioring->queueRequest(std::move(statRequest));
162 m_ioring->waitForRequest(handle);
163
164 return finalSize;
165}
166
167QIOOperation *QRandomAccessAsyncFileNativeBackend::flush()
168{
169 auto *dataStorage = new QtPrivate::QIOOperationDataStorage();
170
171 auto *priv = new QIOOperationPrivate(dataStorage);
172 priv->type = QIOOperation::Type::Flush;
173
174 auto *op = new QIOOperation(*priv, m_owner);
175 m_operations.append(op);
176
177 QIORingRequest<QIORing::Operation::Flush> flushRequest;
178 flushRequest.fd = m_fd;
179 flushRequest.setCallback([this, op](const QIORingRequest<QIORing::Operation::Flush> &request) {
180 auto *priv = QIOOperationPrivate::get(op);
181 if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
182 if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError)
183 queueCompletion(priv, QIOOperation::Error::Aborted);
184 else if (*err == QFileDevice::OpenError)
185 queueCompletion(priv, QIOOperation::Error::FileNotOpen);
186 else
187 queueCompletion(priv, QIOOperation::Error::Flush);
188 } else if (std::get_if<QIORingResult<QIORing::Operation::Flush>>(&request.result)) {
189 queueCompletion(priv, QIOOperation::Error::None);
190 }
191 m_operations.removeOne(op);
192 });
193 m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(flushRequest)));
194
195 return op;
196}
197
198void QRandomAccessAsyncFileNativeBackend::startReadIntoSingle(QIOOperation *op,
199 const QSpan<std::byte> &to)
200{
201 QIORingRequest<QIORing::Operation::Read> readRequest;
202 readRequest.fd = m_fd;
203 auto *priv = QIOOperationPrivate::get(op);
204 if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now
205 queueCompletion(priv, QIOOperation::Error::IncorrectOffset);
206 m_operations.removeOne(op);
207 return;
208 }
209 readRequest.offset = priv->offset;
210 readRequest.destination = to;
211 readRequest.setCallback([this, op](const QIORingRequest<QIORing::Operation::Read> &request) {
212 auto *priv = QIOOperationPrivate::get(op);
213 if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
214 if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError)
215 queueCompletion(priv, QIOOperation::Error::Aborted);
216 else if (*err == QFileDevice::OpenError)
217 queueCompletion(priv, QIOOperation::Error::FileNotOpen);
218 else if (*err == QFileDevice::PositionError)
219 queueCompletion(priv, QIOOperation::Error::IncorrectOffset);
220 else
221 queueCompletion(priv, QIOOperation::Error::Read);
222 } else if (const auto *result = std::get_if<QIORingResult<QIORing::Operation::Read>>(
223 &request.result)) {
224 priv->appendBytesProcessed(result->bytesRead);
225 if (priv->dataStorage->containsReadSpans())
226 priv->dataStorage->getReadSpans().first().slice(0, result->bytesRead);
227 else
228 priv->dataStorage->getByteArray().slice(0, result->bytesRead);
229
230 queueCompletion(priv, QIOOperation::Error::None);
231 }
232 m_operations.removeOne(op);
233 });
234 m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(readRequest)));
235}
236
237QIOReadOperation *QRandomAccessAsyncFileNativeBackend::read(qint64 offset, qint64 maxSize)
238{
239 QByteArray array;
240 array.resizeForOverwrite(maxSize);
241 auto *dataStorage = new QtPrivate::QIOOperationDataStorage(std::move(array));
242
243 auto *priv = new QIOOperationPrivate(dataStorage);
244 priv->offset = offset;
245 priv->type = QIOOperation::Type::Read;
246
247 auto *op = new QIOReadOperation(*priv, m_owner);
248 m_operations.append(op);
249
250 startReadIntoSingle(op, as_writable_bytes(QSpan(dataStorage->getByteArray())));
251
252 return op;
253}
254
255QIOWriteOperation *QRandomAccessAsyncFileNativeBackend::write(qint64 offset, const QByteArray &data)
256{
257 return write(offset, QByteArray(data));
258}
259
260void QRandomAccessAsyncFileNativeBackend::startWriteFromSingle(QIOOperation *op,
261 const QSpan<const std::byte> &from)
262{
263 QIORingRequest<QIORing::Operation::Write> writeRequest;
264 writeRequest.fd = m_fd;
265 auto *priv = QIOOperationPrivate::get(op);
266 if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now
267 queueCompletion(priv, QIOOperation::Error::IncorrectOffset);
268 m_operations.removeOne(op);
269 return;
270 }
271 writeRequest.offset = priv->offset;
272 writeRequest.source = from;
273 writeRequest.setCallback([this, op](const QIORingRequest<QIORing::Operation::Write> &request) {
274 auto *priv = QIOOperationPrivate::get(op);
275 if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
276 if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError)
277 queueCompletion(priv, QIOOperation::Error::Aborted);
278 else if (*err == QFileDevice::OpenError)
279 queueCompletion(priv, QIOOperation::Error::FileNotOpen);
280 else if (*err == QFileDevice::PositionError)
281 queueCompletion(priv, QIOOperation::Error::IncorrectOffset);
282 else
283 queueCompletion(priv, QIOOperation::Error::Write);
284 } else if (const auto *result = std::get_if<QIORingResult<QIORing::Operation::Write>>(
285 &request.result)) {
286 priv->appendBytesProcessed(result->bytesWritten);
287 queueCompletion(priv, QIOOperation::Error::None);
288 }
289 m_operations.removeOne(op);
290 });
291 m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(writeRequest)));
292}
293
294QIOWriteOperation *QRandomAccessAsyncFileNativeBackend::write(qint64 offset, QByteArray &&data)
295{
296 auto *dataStorage = new QtPrivate::QIOOperationDataStorage(std::move(data));
297
298 auto *priv = new QIOOperationPrivate(dataStorage);
299 priv->offset = offset;
300 priv->type = QIOOperation::Type::Write;
301
302 auto *op = new QIOWriteOperation(*priv, m_owner);
303 m_operations.append(op);
304
305 startWriteFromSingle(op, as_bytes(QSpan(dataStorage->getByteArray())));
306
307 return op;
308}
309
310QIOVectoredReadOperation *QRandomAccessAsyncFileNativeBackend::readInto(qint64 offset,
311 QSpan<std::byte> buffer)
312{
313 auto *dataStorage = new QtPrivate::QIOOperationDataStorage(
314 QSpan<const QSpan<std::byte>>{ buffer });
315
316 auto *priv = new QIOOperationPrivate(dataStorage);
317 priv->offset = offset;
318 priv->type = QIOOperation::Type::Read;
319
320 auto *op = new QIOVectoredReadOperation(*priv, m_owner);
321 m_operations.append(op);
322
323 startReadIntoSingle(op, dataStorage->getReadSpans().first());
324
325 return op;
326}
327
328QIOVectoredWriteOperation *QRandomAccessAsyncFileNativeBackend::writeFrom(qint64 offset,
329 QSpan<const std::byte> buffer)
330{
331 auto *dataStorage = new QtPrivate::QIOOperationDataStorage(
332 QSpan<const QSpan<const std::byte>>{ buffer });
333
334 auto *priv = new QIOOperationPrivate(dataStorage);
335 priv->offset = offset;
336 priv->type = QIOOperation::Type::Write;
337
338 auto *op = new QIOVectoredWriteOperation(*priv, m_owner);
339 m_operations.append(op);
340
341 startWriteFromSingle(op, dataStorage->getWriteSpans().first());
342
343 return op;
344}
345
346QIOVectoredReadOperation *
347QRandomAccessAsyncFileNativeBackend::readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers)
348{
349 if (!QIORing::supportsOperation(QtPrivate::Operation::VectoredRead))
350 return nullptr;
351 auto *dataStorage = new QtPrivate::QIOOperationDataStorage(buffers);
352
353 auto *priv = new QIOOperationPrivate(dataStorage);
354 priv->offset = offset;
355 priv->type = QIOOperation::Type::Read;
356
357 auto *op = new QIOVectoredReadOperation(*priv, m_owner);
358 if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now
359 queueCompletion(priv, QIOOperation::Error::IncorrectOffset);
360 return op;
361 }
362 m_operations.append(op);
363
364 QIORingRequest<QIORing::Operation::VectoredRead> readRequest;
365 readRequest.fd = m_fd;
366 readRequest.offset = priv->offset;
367 readRequest.destinations = dataStorage->getReadSpans();
368 readRequest.setCallback([this,
369 op](const QIORingRequest<QIORing::Operation::VectoredRead> &request) {
370 auto *priv = QIOOperationPrivate::get(op);
371 if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
372 if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError)
373 queueCompletion(priv, QIOOperation::Error::Aborted);
374 else
375 queueCompletion(priv, QIOOperation::Error::Read);
376 } else if (const auto
377 *result = std::get_if<QIORingResult<QIORing::Operation::VectoredRead>>(
378 &request.result)) {
379 priv->appendBytesProcessed(result->bytesRead);
380 qint64 processed = result->bytesRead;
381 for (auto &span : priv->dataStorage->getReadSpans()) {
382 if (span.size() < processed) {
383 processed -= span.size();
384 } else { // span.size >= processed
385 span.slice(0, processed);
386 processed = 0;
387 }
388 }
389 queueCompletion(priv, QIOOperation::Error::None);
390 }
391 m_operations.removeOne(op);
392 });
393 m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(readRequest)));
394
395 return op;
396}
397
398QIOVectoredWriteOperation *
399QRandomAccessAsyncFileNativeBackend::writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers)
400{
401 if (!QIORing::supportsOperation(QtPrivate::Operation::VectoredWrite))
402 return nullptr;
403 auto *dataStorage = new QtPrivate::QIOOperationDataStorage(buffers);
404
405 auto *priv = new QIOOperationPrivate(dataStorage);
406 priv->offset = offset;
407 priv->type = QIOOperation::Type::Write;
408
409 auto *op = new QIOVectoredWriteOperation(*priv, m_owner);
410 if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now
411 queueCompletion(priv, QIOOperation::Error::IncorrectOffset);
412 return op;
413 }
414 m_operations.append(op);
415
416 QIORingRequest<QIORing::Operation::VectoredWrite> writeRequest;
417 writeRequest.fd = m_fd;
418 writeRequest.offset = priv->offset;
419 writeRequest.sources = dataStorage->getWriteSpans();
420 writeRequest.setCallback(
421 [this, op](const QIORingRequest<QIORing::Operation::VectoredWrite> &request) {
422 auto *priv = QIOOperationPrivate::get(op);
423 if (const auto *err = std::get_if<QFileDevice::FileError>(&request.result)) {
424 if (priv->error == QIOOperation::Error::Aborted || *err == QFileDevice::AbortError)
425 queueCompletion(priv, QIOOperation::Error::Aborted);
426 else
427 queueCompletion(priv, QIOOperation::Error::Write);
428 } else if (const auto *result = std::get_if<
429 QIORingResult<QIORing::Operation::VectoredWrite>>(
430 &request.result)) {
431 priv->appendBytesProcessed(result->bytesWritten);
432 queueCompletion(priv, QIOOperation::Error::None);
433 }
434 m_operations.removeOne(op);
435 });
436 m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(writeRequest)));
437
438 return op;
439}
440
441QT_END_NAMESPACE
Combined button and popup list for selecting options.