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_darwin.mm
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#include "qplatformdefs.h"
10
11#include <QtCore/qdir.h>
12#include <QtCore/qfile.h>
13#include <QtCore/private/qfilesystemengine_p.h>
14
16
17namespace {
18
19static bool isBarrierOperation(QIOOperation::Type type)
20{
21 return type == QIOOperation::Type::Flush || type == QIOOperation::Type::Open;
22}
23
24} // anonymous namespace
25
26// Fine to provide the definition here, because all the usages are in this file
27// only!
28template <typename Operation, typename ...Args>
29Operation *
30QRandomAccessAsyncFileNativeBackend::addOperation(QIOOperation::Type type, qint64 offset, Args &&...args)
31{
32 auto dataStorage = new QtPrivate::QIOOperationDataStorage(std::forward<Args>(args)...);
33 auto *priv = new QIOOperationPrivate(dataStorage);
34 priv->offset = offset;
35 priv->type = type;
36
37 Operation *op = new Operation(*priv, m_owner);
38 auto opId = getNextId();
39 m_operations.push_back(OperationInfo(opId, op));
40 startOperationsUntilBarrier();
41
42 return op;
43}
44
45QRandomAccessAsyncFileNativeBackend::QRandomAccessAsyncFileNativeBackend(QRandomAccessAsyncFile *owner)
46 : QRandomAccessAsyncFileBackend(owner)
47{
48}
49
50QRandomAccessAsyncFileNativeBackend::~QRandomAccessAsyncFileNativeBackend()
51 = default;
52
53bool QRandomAccessAsyncFileNativeBackend::init()
54{
55 return true;
56}
57
58void QRandomAccessAsyncFileNativeBackend::cancelAndWait(QIOOperation *op)
59{
60 auto it = std::find_if(m_operations.cbegin(), m_operations.cend(),
61 [op](const auto &opInfo) {
62 return opInfo.operation.get() == op;
63 });
64 // not found
65 if (it == m_operations.cend())
66 return;
67
68 const auto opInfo = m_operations.takeAt(std::distance(m_operations.cbegin(), it));
69
70 if (opInfo.state == OpState::Running) {
71 // cancel this operation
72 m_mutex.lock();
73 if (m_runningOps.contains(opInfo.opId)) {
74 m_opToCancel = opInfo.opId;
75 closeIoChannel(opInfo.channel);
76 m_cancellationCondition.wait(&m_mutex);
77 m_opToCancel = kInvalidOperationId; // reset
78 }
79 m_mutex.unlock();
80 } // otherwise it was not started yet
81
82 // clean up the operation
83 releaseIoChannel(opInfo.channel);
84 auto *priv = QIOOperationPrivate::get(opInfo.operation);
85 priv->setError(QIOOperation::Error::Aborted);
86
87 // we could cancel a barrier operation, so try to execute next operations
88 startOperationsUntilBarrier();
89}
90
91void QRandomAccessAsyncFileNativeBackend::close()
92{
93 if (m_fileState == FileState::Closed)
94 return;
95
96 // cancel all operations
97 m_mutex.lock();
98 m_opToCancel = kAllOperationIds;
99 for (const auto &op : m_operations) {
100 if (op.channel) {
101 closeIoChannel(op.channel);
102 }
103 }
104 closeIoChannel(m_ioChannel);
105 // we're not interested in any results anymore
106 if (!m_runningOps.isEmpty() || m_ioChannel)
107 m_cancellationCondition.wait(&m_mutex);
108 m_opToCancel = kInvalidOperationId; // reset
109 m_mutex.unlock();
110
111 // clean up all operations
112 for (auto &opInfo : m_operations) {
113 releaseIoChannel(opInfo.channel);
114 auto *priv = QIOOperationPrivate::get(opInfo.operation);
115 priv->setError(QIOOperation::Error::Aborted);
116 }
117 m_operations.clear();
118
119 releaseIoChannel(m_ioChannel);
120
121 if (m_fd >= 0) {
122 ::close(m_fd);
123 m_fd = -1;
124 }
125
126 m_fileState = FileState::Closed;
127}
128
129qint64 QRandomAccessAsyncFileNativeBackend::size() const
130{
131 if (m_fileState != FileState::Opened)
132 return -1;
133
134 QFileSystemMetaData metaData;
135 if (QFileSystemEngine::fillMetaData(m_fd, metaData))
136 return metaData.size();
137
138 return -1;
139}
140
141QIOOperation *
142QRandomAccessAsyncFileNativeBackend::open(const QString &path, QIODeviceBase::OpenMode mode)
143{
144 if (m_fileState == FileState::Closed) {
145 m_filePath = path;
146 m_openMode = mode;
147 // Open is a barrier, so we won't have two open() operations running
148 // in parallel
149 m_fileState = FileState::OpenPending;
150 }
151
152 return addOperation<QIOOperation>(QIOOperation::Type::Open, 0);
153}
154
155QIOOperation *QRandomAccessAsyncFileNativeBackend::flush()
156{
157 return addOperation<QIOOperation>(QIOOperation::Type::Flush, 0);
158}
159
160QIOReadOperation *QRandomAccessAsyncFileNativeBackend::read(qint64 offset, qint64 maxSize)
161{
162 QByteArray array(maxSize, Qt::Uninitialized);
163 return addOperation<QIOReadOperation>(QIOOperation::Type::Read, offset, std::move(array));
164}
165
166QIOWriteOperation *QRandomAccessAsyncFileNativeBackend::write(qint64 offset, const QByteArray &data)
167{
168 QByteArray copy = data;
169 return write(offset, std::move(copy));
170}
171
172QIOWriteOperation *QRandomAccessAsyncFileNativeBackend::write(qint64 offset, QByteArray &&data)
173{
174 return addOperation<QIOWriteOperation>(QIOOperation::Type::Write, offset, std::move(data));
175}
176
177QIOVectoredReadOperation *
178QRandomAccessAsyncFileNativeBackend::readInto(qint64 offset, QSpan<std::byte> buffer)
179{
180 return addOperation<QIOVectoredReadOperation>(QIOOperation::Type::Read, offset,
181 QSpan<const QSpan<std::byte>>{buffer});
182}
183
184QIOVectoredWriteOperation *
185QRandomAccessAsyncFileNativeBackend::writeFrom(qint64 offset, QSpan<const std::byte> buffer)
186{
187 return addOperation<QIOVectoredWriteOperation>(QIOOperation::Type::Write, offset,
188 QSpan<const QSpan<const std::byte>>{buffer});
189}
190
191QIOVectoredReadOperation *
192QRandomAccessAsyncFileNativeBackend::readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers)
193{
194 // GCD implementation does not have vectored read. Spawning several read
195 // operations (each with an updated offset), is not ideal, because some
196 // of them could fail, and it wouldn't be clear what would be the return
197 // value in such case.
198 // So, we'll just execute several reads one-after-another, and complete the
199 // whole operation only when they all finish (or when an operation fails
200 // at some point).
201
202 return addOperation<QIOVectoredReadOperation>(QIOOperation::Type::Read, offset, buffers);
203}
204
205QIOVectoredWriteOperation *
206QRandomAccessAsyncFileNativeBackend::writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers)
207{
208 return addOperation<QIOVectoredWriteOperation>(QIOOperation::Type::Write, offset, buffers);
209}
210
211void QRandomAccessAsyncFileNativeBackend::notifyIfOperationsAreCompleted()
212{
213 QMutexLocker locker(&m_mutex);
214 --m_numChannelsToClose;
215 if (m_opToCancel == kAllOperationIds) {
216 if (m_numChannelsToClose == 0 && m_runningOps.isEmpty())
217 m_cancellationCondition.wakeOne();
218 }
219}
220
221dispatch_io_t QRandomAccessAsyncFileNativeBackend::createMainChannel(int fd)
222{
223 auto sharedThis = this;
224 auto channel =
225 dispatch_io_create(DISPATCH_IO_RANDOM, fd,
226 dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
227 ^(int /*error*/) {
228 // main I/O channel uses kInvalidOperationId
229 // as its identifier
230 sharedThis->notifyIfOperationsAreCompleted();
231 });
232 if (channel) {
233 QMutexLocker locker(&m_mutex);
234 ++m_numChannelsToClose;
235 }
236 return channel;
237}
238
239dispatch_io_t QRandomAccessAsyncFileNativeBackend::duplicateIoChannel(OperationId opId)
240{
241 if (!m_ioChannel)
242 return nullptr;
243 // We need to create a new channel for each operation, because the only way
244 // to cancel an operation is to call dispatch_io_close() with
245 // DISPATCH_IO_STOP flag.
246 auto sharedThis = this;
247 auto channel =
248 dispatch_io_create_with_io(DISPATCH_IO_RANDOM, m_ioChannel,
249 dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
250 ^(int /*error*/){
251 sharedThis->notifyIfOperationsAreCompleted();
252 });
253
254 if (channel) {
255 QMutexLocker locker(&m_mutex);
256 m_runningOps.insert(opId);
257 ++m_numChannelsToClose;
258 }
259 return channel;
260}
261
262void QRandomAccessAsyncFileNativeBackend::closeIoChannel(dispatch_io_t channel)
263{
264 if (channel)
265 dispatch_io_close(channel, DISPATCH_IO_STOP);
266}
267
268void QRandomAccessAsyncFileNativeBackend::releaseIoChannel(dispatch_io_t channel)
269{
270 if (channel) {
271 dispatch_release(channel);
272 channel = nullptr;
273 }
274}
275
276void QRandomAccessAsyncFileNativeBackend::handleOperationComplete(const OperationResult &opResult)
277{
278 // try to start next operations on return
279 auto onReturn = qScopeGuard([this] {
280 startOperationsUntilBarrier();
281 });
282
283 auto it = std::find_if(m_operations.cbegin(), m_operations.cend(),
284 [opId = opResult.opId](const auto &opInfo) {
285 return opInfo.opId == opId;
286 });
287 if (it == m_operations.cend())
288 return;
289 qsizetype idx = std::distance(m_operations.cbegin(), it);
290
291 const OperationInfo info = m_operations.takeAt(idx);
292 closeIoChannel(info.channel);
293 releaseIoChannel(info.channel);
294
295 if (!info.operation)
296 return;
297
298 auto convertError = [](int error, QIOOperation::Type type) {
299 if (error == 0) {
300 return QIOOperation::Error::None;
301 } else if (error == ECANCELED) {
302 return QIOOperation::Error::Aborted;
303 } else if (error == EBADF) {
304 return QIOOperation::Error::FileNotOpen;
305 } else if (error == EINVAL) {
306 switch (type) {
307 case QIOOperation::Type::Read:
308 case QIOOperation::Type::Write:
309 return QIOOperation::Error::IncorrectOffset;
310 case QIOOperation::Type::Flush:
311 return QIOOperation::Error::Flush;
312 case QIOOperation::Type::Open:
313 return QIOOperation::Error::Open;
314 case QIOOperation::Type::Unknown:
315 Q_UNREACHABLE_RETURN(QIOOperation::Error::FileNotOpen);
316 }
317 } else {
318 switch (type) {
319 case QIOOperation::Type::Read:
320 return QIOOperation::Error::Read;
321 case QIOOperation::Type::Write:
322 return QIOOperation::Error::Write;
323 case QIOOperation::Type::Flush:
324 return QIOOperation::Error::Flush;
325 case QIOOperation::Type::Open:
326 return QIOOperation::Error::Open;
327 case QIOOperation::Type::Unknown:
328 Q_UNREACHABLE_RETURN(QIOOperation::Error::FileNotOpen);
329 }
330 }
331 };
332
333 auto *priv = QIOOperationPrivate::get(info.operation);
334 switch (priv->type) {
335 case QIOOperation::Type::Read:
336 case QIOOperation::Type::Write:
337 priv->appendBytesProcessed(opResult.result);
338 // make sure that read buffers are truncated to the actual amount of
339 // bytes read
340 if (priv->type == QIOOperation::Type::Read) {
341 auto dataStorage = priv->dataStorage.get();
342 auto processed = priv->processed;
343 if (dataStorage->containsByteArray()) {
344 QByteArray &array = dataStorage->getByteArray();
345 array.truncate(processed);
346 } else if (dataStorage->containsReadSpans()) {
347 qint64 left = processed;
348 auto &readBuffers = dataStorage->getReadSpans();
349 for (auto &s : readBuffers) {
350 const qint64 spanSize = qint64(s.size_bytes());
351 const qint64 newSize = (std::min)(left, spanSize);
352 if (newSize < spanSize)
353 s.chop(spanSize - newSize);
354 left -= newSize;
355 }
356 }
357 }
358 priv->operationComplete(convertError(opResult.error, priv->type));
359 break;
360 case QIOOperation::Type::Flush: {
361 const QIOOperation::Error error = convertError(opResult.error, priv->type);
362 priv->operationComplete(error);
363 break;
364 }
365 case QIOOperation::Type::Open: {
366 const QIOOperation::Error error = convertError(opResult.error, priv->type);
367 if (opResult.result >= 0 && error == QIOOperation::Error::None) {
368 m_fd = (int)opResult.result;
369 m_ioChannel = createMainChannel(m_fd);
370 m_fileState = FileState::Opened;
371 } else {
372 m_fileState = FileState::Closed;
373 }
374 priv->operationComplete(error);
375 break;
376 }
377 case QIOOperation::Type::Unknown:
378 Q_UNREACHABLE();
379 break;
380 }
381}
382
383void QRandomAccessAsyncFileNativeBackend::queueCompletion(OperationId opId, int error)
384{
385 const OperationResult res = { opId, 0LL, error };
386 QMetaObject::invokeMethod(m_owner, [this, res] {
387 handleOperationComplete(res);
388 }, Qt::QueuedConnection);
389}
390
391void QRandomAccessAsyncFileNativeBackend::startOperationsUntilBarrier()
392{
393 // starts all operations until barrier, or a barrier operation if it's the
394 // first one
395 bool first = true;
396 for (auto &opInfo : m_operations) {
397 const bool isBarrier = isBarrierOperation(opInfo.operation->type());
398 const bool shouldExecute = (opInfo.state == OpState::Pending) && (!isBarrier || first);
399 first = false;
400 if (shouldExecute) {
401 opInfo.state = OpState::Running;
402 switch (opInfo.operation->type()) {
403 case QIOOperation::Type::Read:
404 executeRead(opInfo);
405 break;
406 case QIOOperation::Type::Write:
407 executeWrite(opInfo);
408 break;
409 case QIOOperation::Type::Flush:
410 executeFlush(opInfo);
411 break;
412 case QIOOperation::Type::Open:
413 executeOpen(opInfo);
414 break;
415 case QIOOperation::Type::Unknown:
416 Q_UNREACHABLE();
417 break;
418 }
419 }
420 if (isBarrier)
421 break;
422 }
423}
424
425void QRandomAccessAsyncFileNativeBackend::executeRead(OperationInfo &opInfo)
426{
427 opInfo.channel = duplicateIoChannel(opInfo.opId);
428 if (!opInfo.channel) {
429 queueCompletion(opInfo.opId, EBADF);
430 return;
431 }
432 auto priv = QIOOperationPrivate::get(opInfo.operation);
433 auto dataStorage = priv->dataStorage.get();
434 if (dataStorage->containsByteArray()) {
435 auto &array = dataStorage->getByteArray();
436 char *bytesPtr = array.data();
437 qint64 maxSize = array.size();
438 readOneBufferHelper(opInfo.opId, opInfo.channel, priv->offset,
439 bytesPtr, maxSize,
440 0, 1, 0);
441 } else {
442 Q_ASSERT(dataStorage->containsReadSpans());
443 auto &readBuffers = dataStorage->getReadSpans();
444 const auto totalBuffers = readBuffers.size();
445 if (totalBuffers == 0) {
446 queueCompletion(opInfo.opId, 0);
447 return;
448 }
449 auto buf = readBuffers[0];
450 readOneBufferHelper(opInfo.opId, opInfo.channel, priv->offset,
451 buf.data(), buf.size(),
452 0, totalBuffers, 0);
453 }
454}
455
456void QRandomAccessAsyncFileNativeBackend::executeWrite(OperationInfo &opInfo)
457{
458 opInfo.channel = duplicateIoChannel(opInfo.opId);
459 if (!opInfo.channel) {
460 queueCompletion(opInfo.opId, EBADF);
461 return;
462 }
463 auto priv = QIOOperationPrivate::get(opInfo.operation);
464 auto dataStorage = priv->dataStorage.get();
465 if (dataStorage->containsByteArray()) {
466 const auto &array = dataStorage->getByteArray();
467 const char *dataPtr = array.constData();
468 const qint64 dataSize = array.size();
469
470 dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
471 // We handle the bytes on our own, so we need to specify an empty block as
472 // a destructor.
473 // dataToWrite is retained, so should be properly cleaned up. We always do
474 // it in the callback.
475 dispatch_data_t dataToWrite = dispatch_data_create(dataPtr, dataSize, queue, ^{});
476
477 writeHelper(opInfo.opId, opInfo.channel, priv->offset, dataToWrite, dataSize);
478 } else {
479 Q_ASSERT(dataStorage->containsWriteSpans());
480
481 const auto &writeBuffers = dataStorage->getWriteSpans();
482 const auto totalBuffers = writeBuffers.size();
483 if (totalBuffers == 0) {
484 queueCompletion(opInfo.opId, 0);
485 return;
486 }
487
488 dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
489 qsizetype idx = 0;
490 dispatch_data_t dataToWrite = dispatch_data_empty;
491 qint64 totalSize = 0;
492 do {
493 const std::byte *dataPtr = writeBuffers[idx].data();
494 const qint64 dataSize = writeBuffers[idx].size();
495 dispatch_data_t data = dispatch_data_create(dataPtr, dataSize, queue, ^{});
496 dataToWrite = dispatch_data_create_concat(dataToWrite, data);
497 [data release];
498 totalSize += dataSize;
499 } while (++idx < totalBuffers);
500
501 writeHelper(opInfo.opId, opInfo.channel, priv->offset, dataToWrite, totalSize);
502 }
503}
504
505void QRandomAccessAsyncFileNativeBackend::executeFlush(OperationInfo &opInfo)
506{
507 opInfo.channel = duplicateIoChannel(opInfo.opId);
508 if (!opInfo.channel) {
509 queueCompletion(opInfo.opId, EBADF);
510 return;
511 }
512
513 // flush() is a barrier operation, but dispatch_io_barrier does not work
514 // as documented with multiple channels :(
515 auto sharedThis = this;
516 const int fd = m_fd;
517 const OperationId opId = opInfo.opId;
518 dispatch_io_barrier(opInfo.channel, ^{
519 const int err = fsync(fd);
520
521 QMutexLocker locker(&sharedThis->m_mutex);
522 sharedThis->m_runningOps.remove(opId);
523 const auto cancelId = sharedThis->m_opToCancel;
524 if (cancelId == kAllOperationIds || cancelId == opId) {
525 if (cancelId == opId) {
526 sharedThis->m_cancellationCondition.wakeOne();
527 } else { /* kAllOperationIds */
528 if (sharedThis->m_numChannelsToClose == 0
529 && sharedThis->m_runningOps.isEmpty()) {
530 sharedThis->m_cancellationCondition.wakeOne();
531 }
532 }
533 } else {
534 auto context = sharedThis->m_owner;
535 const OperationResult res = { opId, 0LL, err };
536 QMetaObject::invokeMethod(context, [sharedThis](const OperationResult &r) {
537 sharedThis->handleOperationComplete(r);
538 }, Qt::QueuedConnection, res);
539 }
540 });
541}
542
543// stolen from qfsfileengine_unix.cpp
544static inline int openModeToOpenFlags(QIODevice::OpenMode mode)
545{
546 int oflags = QT_OPEN_RDONLY;
547#ifdef QT_LARGEFILE_SUPPORT
548 oflags |= QT_OPEN_LARGEFILE;
549#endif
550 if ((mode & QIODevice::ReadWrite) == QIODevice::ReadWrite)
551 oflags = QT_OPEN_RDWR;
552 else if (mode & QIODevice::WriteOnly)
553 oflags = QT_OPEN_WRONLY;
554 if ((mode & QIODevice::WriteOnly)
555 && !(mode & QIODevice::ExistingOnly)) // QFSFileEnginePrivate::openModeCanCreate(mode))
556 oflags |= QT_OPEN_CREAT;
557 if (mode & QIODevice::Truncate)
558 oflags |= QT_OPEN_TRUNC;
559 if (mode & QIODevice::Append)
560 oflags |= QT_OPEN_APPEND;
561 if (mode & QIODevice::NewOnly)
562 oflags |= QT_OPEN_EXCL;
563 return oflags;
564}
565
566void QRandomAccessAsyncFileNativeBackend::executeOpen(OperationInfo &opInfo)
567{
568 if (m_fileState != FileState::OpenPending) {
569 queueCompletion(opInfo.opId, EINVAL);
570 return;
571 }
572
573 const QByteArray nativeName = QFile::encodeName(QDir::toNativeSeparators(m_filePath));
574
575 int openFlags = openModeToOpenFlags(m_openMode);
576 openFlags |= O_NONBLOCK;
577
578 auto sharedThis = this;
579 const OperationId opId = opInfo.opId;
580
581 // We don'd call duplicateIOChannel(), so need to update the running ops
582 // explicitly.
583 m_mutex.lock();
584 m_runningOps.insert(opId);
585 m_mutex.unlock();
586
587 dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
588 ^{
589 int err = 0;
590 const int fd = ::open(nativeName.data(), openFlags);
591 if (fd < 0)
592 err = errno;
593
594 QMutexLocker locker(&sharedThis->m_mutex);
595 sharedThis->m_runningOps.remove(opId);
596 const auto cancelId = sharedThis->m_opToCancel;
597 if (cancelId == kAllOperationIds || cancelId == opId) {
598 // open() is a barrier operation, so it's always the
599 // only executing operation.
600 // Also, the main IO channel is not created yet.
601 // So we need to notify the condition variable in
602 // both cases.
603 Q_ASSERT(sharedThis->m_runningOps.isEmpty());
604 Q_ASSERT(sharedThis->m_numChannelsToClose == 0);
605 sharedThis->m_cancellationCondition.wakeOne();
606 } else {
607 auto context = sharedThis->m_owner;
608 const OperationResult res = { opId, qint64(fd), err };
609 QMetaObject::invokeMethod(context,
610 [sharedThis](const OperationResult &r) {
611 sharedThis->handleOperationComplete(r);
612 }, Qt::QueuedConnection, res);
613 }
614 });
615}
616
617void QRandomAccessAsyncFileNativeBackend::readOneBuffer(OperationId opId, qsizetype bufferIdx,
618 qint64 alreadyRead)
619{
620 // we need to lookup the operation again, because it could have beed removed
621 // by the user...
622
623 auto it = std::find_if(m_operations.cbegin(), m_operations.cend(),
624 [opId](const auto &opInfo) {
625 return opId == opInfo.opId;
626 });
627 if (it == m_operations.cend())
628 return;
629
630 auto op = it->operation; // QPointer could be null
631 if (!op) {
632 closeIoChannel(it->channel);
633 return;
634 }
635
636 auto *priv = QIOOperationPrivate::get(op);
637 Q_ASSERT(priv->type == QIOOperation::Type::Read);
638 Q_ASSERT(priv->dataStorage->containsReadSpans());
639
640 auto &readBuffers = priv->dataStorage->getReadSpans();
641 Q_ASSERT(readBuffers.size() > bufferIdx);
642
643 qint64 newOffset = priv->offset;
644 for (qsizetype idx = 0; idx < bufferIdx; ++idx)
645 newOffset += readBuffers[idx].size();
646
647 std::byte *bytesPtr = readBuffers[bufferIdx].data();
648 qint64 maxSize = readBuffers[bufferIdx].size();
649
650 readOneBufferHelper(opId, it->channel, newOffset, bytesPtr, maxSize, bufferIdx,
651 readBuffers.size(), alreadyRead);
652}
653
654void QRandomAccessAsyncFileNativeBackend::readOneBufferHelper(OperationId opId, dispatch_io_t channel,
655 qint64 offset, void *bytesPtr,
656 qint64 maxSize, qsizetype currentBufferIdx,
657 qsizetype totalBuffers, qint64 alreadyRead)
658{
659 auto sharedThis = this;
660 __block size_t readFromBuffer = 0;
661 dispatch_io_read(channel, offset, maxSize,
662 dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
663 ^(bool done, dispatch_data_t data, int error) {
664 // Handle data. If there's an error, handle as much as
665 // we can.
666 if (data) {
667 dispatch_data_apply(data, ^(dispatch_data_t /*region*/, size_t offset,
668 const void *buffer, size_t size) {
669 const char *startPtr =
670 reinterpret_cast<const char *>(buffer) + offset;
671 // NOTE: This is a copy, but looks like we
672 // cannot do better :(
673 std::memcpy((std::byte *)bytesPtr + readFromBuffer,
674 startPtr, size);
675 readFromBuffer += size;
676 return true; // Keep processing if there is more data.
677 });
678 }
679
680 // We're interested in handling the results only when
681 // the operation is done. This can mean either
682 // successful completion or an error (including
683 // cancellation).
684 if (!done)
685 return;
686
687
688 QMutexLocker locker(&sharedThis->m_mutex);
689 const auto cancelId = sharedThis->m_opToCancel;
690 if (cancelId == kAllOperationIds || cancelId == opId) {
691 sharedThis->m_runningOps.remove(opId);
692 if (cancelId == opId) {
693 sharedThis->m_cancellationCondition.wakeOne();
694 } else { /* kAllOperationIds */
695 if (sharedThis->m_numChannelsToClose == 0
696 && sharedThis->m_runningOps.isEmpty()) {
697 sharedThis->m_cancellationCondition.wakeOne();
698 }
699 }
700 } else {
701 sharedThis->m_runningOps.remove(opId);
702 auto context = sharedThis->m_owner;
703 // if error, or last buffer, or read less than expected,
704 // report operation completion
705 qint64 totalRead = qint64(readFromBuffer) + alreadyRead;
706 qsizetype nextBufferIdx = currentBufferIdx + 1;
707 if (error || nextBufferIdx == totalBuffers
708 || qint64(readFromBuffer) != maxSize) {
709 const OperationResult res = { opId, totalRead, error };
710 QMetaObject::invokeMethod(context,
711 [sharedThis](const OperationResult &r) {
712 sharedThis->handleOperationComplete(r);
713 }, Qt::QueuedConnection, res);
714 } else {
715 // else execute read for the next buffer
716 QMetaObject::invokeMethod(context,
717 [sharedThis, opId, nextBufferIdx, totalRead] {
718 sharedThis->readOneBuffer(opId, nextBufferIdx, totalRead);
719 }, Qt::QueuedConnection);
720 }
721 }
722 });
723}
724
725void QRandomAccessAsyncFileNativeBackend::writeHelper(OperationId opId, dispatch_io_t channel,
726 qint64 offset, dispatch_data_t dataToWrite,
727 qint64 dataSize)
728{
729 auto sharedThis = this;
730 dispatch_io_write(channel, offset, dataToWrite,
731 dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
732 ^(bool done, dispatch_data_t data, int error) {
733 // We're interested in handling the results only when
734 // the operation is done. This can mean either
735 // successful completion or an error (including
736 // cancellation). In case of an error return the
737 // amount that we have written so far.
738 if (!done)
739 return;
740
741 QMutexLocker locker(&sharedThis->m_mutex);
742 const auto cancelId = sharedThis->m_opToCancel;
743 if (cancelId == kAllOperationIds || cancelId == opId) {
744 // Operation is canceled - do nothing
745 sharedThis->m_runningOps.remove(opId);
746 if (cancelId == opId) {
747 sharedThis->m_cancellationCondition.wakeOne();
748 } else { /* kAllOperationIds */
749 if (sharedThis->m_numChannelsToClose == 0
750 && sharedThis->m_runningOps.isEmpty()) {
751 sharedThis->m_cancellationCondition.wakeOne();
752 }
753 }
754 } else {
755 sharedThis->m_runningOps.remove(opId);
756 // if no error, an attempt to access the data will
757 // crash, because it seems to have no buffer
758 // allocated (as everything was written)
759 const size_t toBeWritten =
760 (error == 0) ? 0 : dispatch_data_get_size(data);
761 const size_t written = dataSize - toBeWritten;
762 [dataToWrite release];
763
764 auto context = sharedThis->m_owner;
765 const OperationResult res = { opId, qint64(written), error };
766 QMetaObject::invokeMethod(context,
767 [sharedThis](const OperationResult &r) {
768 sharedThis->handleOperationComplete(r);
769 }, Qt::QueuedConnection, res);
770 }
771 });
772}
773
774QRandomAccessAsyncFileNativeBackend::OperationId QRandomAccessAsyncFileNativeBackend::getNextId()
775{
776 // never return reserved values
777 static OperationId opId = kInvalidOperationId;
778 if (++opId == kAllOperationIds)
779 opId = kInvalidOperationId + 1;
780 return opId;
781}
782
783QT_END_NAMESPACE
Combined button and popup list for selecting options.
static bool isBarrierOperation(QIOOperation::Type type)
static int openModeToOpenFlags(QIODevice::OpenMode mode)