1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qplatformdefs.h"
41#include "private/qabstractfileengine_p.h"
42#include "private/qfsfileengine_p.h"
43#include "private/qcore_unix_p.h"
44#include "qfilesystementry_p.h"
45#include "qfilesystemengine_p.h"
46#include "qcoreapplication.h"
47
48#ifndef QT_NO_FSFILEENGINE
49
50#include "qfile.h"
51#include "qdir.h"
52#include "qdatetime.h"
53#include "qvarlengtharray.h"
54
55#include <sys/mman.h>
56#include <stdlib.h>
57#include <limits.h>
58#include <errno.h>
59#if !defined(QWS) && defined(Q_OS_MAC)
60# include <private/qcore_mac_p.h>
61#endif
62
63QT_BEGIN_NAMESPACE
64
65/*!
66 \internal
67
68 Returns the stdio open flags corresponding to a QIODevice::OpenMode.
69*/
70static inline int openModeToOpenFlags(QIODevice::OpenMode mode)
71{
72 int oflags = QT_OPEN_RDONLY;
73#ifdef QT_LARGEFILE_SUPPORT
74 oflags |= QT_OPEN_LARGEFILE;
75#endif
76
77 if ((mode & QFile::ReadWrite) == QFile::ReadWrite)
78 oflags = QT_OPEN_RDWR;
79 else if (mode & QFile::WriteOnly)
80 oflags = QT_OPEN_WRONLY;
81
82 if (QFSFileEnginePrivate::openModeCanCreate(mode))
83 oflags |= QT_OPEN_CREAT;
84
85 if (mode & QFile::Truncate)
86 oflags |= QT_OPEN_TRUNC;
87
88 if (mode & QFile::Append)
89 oflags |= QT_OPEN_APPEND;
90
91 if (mode & QFile::NewOnly)
92 oflags |= QT_OPEN_EXCL;
93
94 return oflags;
95}
96
97static inline QString msgOpenDirectory()
98{
99 const char message[] = QT_TRANSLATE_NOOP("QIODevice", "file to open is a directory");
100#if QT_CONFIG(translation)
101 return QIODevice::tr(message);
102#else
103 return QLatin1String(message);
104#endif
105}
106
107/*!
108 \internal
109*/
110bool QFSFileEnginePrivate::nativeOpen(QIODevice::OpenMode openMode)
111{
112 Q_Q(QFSFileEngine);
113
114 Q_ASSERT_X(openMode & QIODevice::Unbuffered, "QFSFileEngine::open",
115 "QFSFileEngine no longer supports buffered mode; upper layer must buffer");
116 if (openMode & QIODevice::Unbuffered) {
117 int flags = openModeToOpenFlags(openMode);
118
119 // Try to open the file in unbuffered mode.
120 do {
121 fd = QT_OPEN(fileEntry.nativeFilePath().constData(), flags, 0666);
122 } while (fd == -1 && errno == EINTR);
123
124 // On failure, return and report the error.
125 if (fd == -1) {
126 q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError,
127 qt_error_string(errno));
128 return false;
129 }
130
131 if (!(openMode & QIODevice::WriteOnly)) {
132 // we don't need this check if we tried to open for writing because then
133 // we had received EISDIR anyway.
134 if (QFileSystemEngine::fillMetaData(fd, metaData)
135 && metaData.isDirectory()) {
136 q->setError(QFile::OpenError, msgOpenDirectory());
137 QT_CLOSE(fd);
138 return false;
139 }
140 }
141
142 // Seek to the end when in Append mode.
143 if (flags & QFile::Append) {
144 int ret;
145 do {
146 ret = QT_LSEEK(fd, 0, SEEK_END);
147 } while (ret == -1 && errno == EINTR);
148
149 if (ret == -1) {
150 q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError,
151 qt_error_string(int(errno)));
152 return false;
153 }
154 }
155
156 fh = nullptr;
157 }
158
159 closeFileHandle = true;
160 return true;
161}
162
163/*!
164 \internal
165*/
166bool QFSFileEnginePrivate::nativeClose()
167{
168 return closeFdFh();
169}
170
171/*!
172 \internal
173
174*/
175bool QFSFileEnginePrivate::nativeFlush()
176{
177 return fh ? flushFh() : fd != -1;
178}
179
180/*!
181 \internal
182 \since 5.1
183*/
184bool QFSFileEnginePrivate::nativeSyncToDisk()
185{
186 Q_Q(QFSFileEngine);
187 int ret;
188#if defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO > 0
189 EINTR_LOOP(ret, fdatasync(nativeHandle()));
190#else
191 EINTR_LOOP(ret, fsync(nativeHandle()));
192#endif
193 if (ret != 0)
194 q->setError(QFile::WriteError, qt_error_string(errno));
195 return ret == 0;
196}
197
198/*!
199 \internal
200*/
201qint64 QFSFileEnginePrivate::nativeRead(char *data, qint64 len)
202{
203 Q_Q(QFSFileEngine);
204
205 if (fh && nativeIsSequential()) {
206 size_t readBytes = 0;
207 int oldFlags = fcntl(QT_FILENO(fh), F_GETFL);
208 for (int i = 0; i < 2; ++i) {
209 // Unix: Make the underlying file descriptor non-blocking
210 if ((oldFlags & O_NONBLOCK) == 0)
211 fcntl(QT_FILENO(fh), F_SETFL, oldFlags | O_NONBLOCK);
212
213 // Cross platform stdlib read
214 size_t read = 0;
215 do {
216 read = fread(data + readBytes, 1, size_t(len - readBytes), fh);
217 } while (read == 0 && !feof(fh) && errno == EINTR);
218 if (read > 0) {
219 readBytes += read;
220 break;
221 } else {
222 if (readBytes)
223 break;
224 readBytes = read;
225 }
226
227 // Unix: Restore the blocking state of the underlying socket
228 if ((oldFlags & O_NONBLOCK) == 0) {
229 fcntl(QT_FILENO(fh), F_SETFL, oldFlags);
230 if (readBytes == 0) {
231 int readByte = 0;
232 do {
233 readByte = fgetc(fh);
234 } while (readByte == -1 && errno == EINTR);
235 if (readByte != -1) {
236 *data = uchar(readByte);
237 readBytes += 1;
238 } else {
239 break;
240 }
241 }
242 }
243 }
244 // Unix: Restore the blocking state of the underlying socket
245 if ((oldFlags & O_NONBLOCK) == 0) {
246 fcntl(QT_FILENO(fh), F_SETFL, oldFlags);
247 }
248 if (readBytes == 0 && !feof(fh)) {
249 // if we didn't read anything and we're not at EOF, it must be an error
250 q->setError(QFile::ReadError, qt_error_string(int(errno)));
251 return -1;
252 }
253 return readBytes;
254 }
255
256 return readFdFh(data, len);
257}
258
259/*!
260 \internal
261*/
262qint64 QFSFileEnginePrivate::nativeReadLine(char *data, qint64 maxlen)
263{
264 return readLineFdFh(data, maxlen);
265}
266
267/*!
268 \internal
269*/
270qint64 QFSFileEnginePrivate::nativeWrite(const char *data, qint64 len)
271{
272 return writeFdFh(data, len);
273}
274
275/*!
276 \internal
277*/
278qint64 QFSFileEnginePrivate::nativePos() const
279{
280 return posFdFh();
281}
282
283/*!
284 \internal
285*/
286bool QFSFileEnginePrivate::nativeSeek(qint64 pos)
287{
288 return seekFdFh(pos);
289}
290
291/*!
292 \internal
293*/
294int QFSFileEnginePrivate::nativeHandle() const
295{
296 return fh ? fileno(fh) : fd;
297}
298
299/*!
300 \internal
301*/
302bool QFSFileEnginePrivate::nativeIsSequential() const
303{
304 return isSequentialFdFh();
305}
306
307bool QFSFileEngine::remove()
308{
309 Q_D(QFSFileEngine);
310 QSystemError error;
311 bool ret = QFileSystemEngine::removeFile(d->fileEntry, error);
312 d->metaData.clear();
313 if (!ret) {
314 setError(QFile::RemoveError, error.toString());
315 }
316 return ret;
317}
318
319bool QFSFileEngine::copy(const QString &newName)
320{
321 Q_D(QFSFileEngine);
322 QSystemError error;
323 bool ret = QFileSystemEngine::copyFile(d->fileEntry, QFileSystemEntry(newName), error);
324 if (!ret) {
325 setError(QFile::CopyError, error.toString());
326 }
327 return ret;
328}
329
330bool QFSFileEngine::renameOverwrite(const QString &newName)
331{
332 Q_D(QFSFileEngine);
333 QSystemError error;
334 bool ret = QFileSystemEngine::renameOverwriteFile(d->fileEntry, QFileSystemEntry(newName), error);
335
336 if (!ret)
337 setError(QFile::RenameError, error.toString());
338
339 return ret;
340}
341
342bool QFSFileEngine::rename(const QString &newName)
343{
344 Q_D(QFSFileEngine);
345 QSystemError error;
346 bool ret = QFileSystemEngine::renameFile(d->fileEntry, QFileSystemEntry(newName), error);
347
348 if (!ret) {
349 setError(QFile::RenameError, error.toString());
350 }
351
352 return ret;
353}
354
355bool QFSFileEngine::link(const QString &newName)
356{
357 Q_D(QFSFileEngine);
358 QSystemError error;
359 bool ret = QFileSystemEngine::createLink(d->fileEntry, QFileSystemEntry(newName), error);
360 if (!ret) {
361 setError(QFile::RenameError, error.toString());
362 }
363 return ret;
364}
365
366qint64 QFSFileEnginePrivate::nativeSize() const
367{
368 return sizeFdFh();
369}
370
371bool QFSFileEngine::mkdir(const QString &name, bool createParentDirectories) const
372{
373 return QFileSystemEngine::createDirectory(QFileSystemEntry(name), createParentDirectories);
374}
375
376bool QFSFileEngine::rmdir(const QString &name, bool recurseParentDirectories) const
377{
378 return QFileSystemEngine::removeDirectory(QFileSystemEntry(name), recurseParentDirectories);
379}
380
381bool QFSFileEngine::caseSensitive() const
382{
383 return true;
384}
385
386bool QFSFileEngine::setCurrentPath(const QString &path)
387{
388 return QFileSystemEngine::setCurrentPath(QFileSystemEntry(path));
389}
390
391QString QFSFileEngine::currentPath(const QString &)
392{
393 return QFileSystemEngine::currentPath().filePath();
394}
395
396QString QFSFileEngine::homePath()
397{
398 return QFileSystemEngine::homePath();
399}
400
401QString QFSFileEngine::rootPath()
402{
403 return QFileSystemEngine::rootPath();
404}
405
406QString QFSFileEngine::tempPath()
407{
408 return QFileSystemEngine::tempPath();
409}
410
411QFileInfoList QFSFileEngine::drives()
412{
413 QFileInfoList ret;
414 ret.append(QFileInfo(rootPath()));
415 return ret;
416}
417
418bool QFSFileEnginePrivate::doStat(QFileSystemMetaData::MetaDataFlags flags) const
419{
420 if (!tried_stat || !metaData.hasFlags(flags)) {
421 tried_stat = 1;
422
423 int localFd = fd;
424 if (fh && fileEntry.isEmpty())
425 localFd = QT_FILENO(fh);
426 if (localFd != -1)
427 QFileSystemEngine::fillMetaData(localFd, metaData);
428
429 if (metaData.missingFlags(flags) && !fileEntry.isEmpty())
430 QFileSystemEngine::fillMetaData(fileEntry, metaData, metaData.missingFlags(flags));
431 }
432
433 return metaData.exists();
434}
435
436bool QFSFileEnginePrivate::isSymlink() const
437{
438 if (!metaData.hasFlags(QFileSystemMetaData::LinkType))
439 QFileSystemEngine::fillMetaData(fileEntry, metaData, QFileSystemMetaData::LinkType);
440
441 return metaData.isLink();
442}
443
444/*!
445 \reimp
446*/
447QAbstractFileEngine::FileFlags QFSFileEngine::fileFlags(FileFlags type) const
448{
449 Q_D(const QFSFileEngine);
450
451 if (type & Refresh)
452 d->metaData.clear();
453
454 QAbstractFileEngine::FileFlags ret = { };
455
456 if (type & FlagsMask)
457 ret |= LocalDiskFlag;
458
459 bool exists;
460 {
461 QFileSystemMetaData::MetaDataFlags queryFlags = { };
462
463 queryFlags |= QFileSystemMetaData::MetaDataFlags(uint(type))
464 & QFileSystemMetaData::Permissions;
465
466 if (type & TypesMask)
467 queryFlags |= QFileSystemMetaData::AliasType
468 | QFileSystemMetaData::LinkType
469 | QFileSystemMetaData::FileType
470 | QFileSystemMetaData::DirectoryType
471 | QFileSystemMetaData::BundleType
472 | QFileSystemMetaData::WasDeletedAttribute;
473
474 if (type & FlagsMask)
475 queryFlags |= QFileSystemMetaData::HiddenAttribute
476 | QFileSystemMetaData::ExistsAttribute;
477 else if (type & ExistsFlag)
478 queryFlags |= QFileSystemMetaData::WasDeletedAttribute;
479
480 queryFlags |= QFileSystemMetaData::LinkType;
481
482 exists = d->doStat(queryFlags);
483 }
484
485 if (!exists && !d->metaData.isLink())
486 return ret;
487
488 if (exists && (type & PermsMask))
489 ret |= FileFlags(uint(d->metaData.permissions()));
490
491 if (type & TypesMask) {
492 if (d->metaData.isAlias()) {
493 ret |= LinkType;
494 } else {
495 if ((type & LinkType) && d->metaData.isLink())
496 ret |= LinkType;
497 if (exists) {
498 if (d->metaData.isFile()) {
499 ret |= FileType;
500 } else if (d->metaData.isDirectory()) {
501 ret |= DirectoryType;
502 if ((type & BundleType) && d->metaData.isBundle())
503 ret |= BundleType;
504 }
505 }
506 }
507 }
508
509 if (type & FlagsMask) {
510 // the inode existing does not mean the file exists
511 if (!d->metaData.wasDeleted())
512 ret |= ExistsFlag;
513 if (d->fileEntry.isRoot())
514 ret |= RootFlag;
515 else if (d->metaData.isHidden())
516 ret |= HiddenFlag;
517 }
518
519 return ret;
520}
521
522QByteArray QFSFileEngine::id() const
523{
524 Q_D(const QFSFileEngine);
525 if (d->fd != -1)
526 return QFileSystemEngine::id(d->fd);
527 return QFileSystemEngine::id(d->fileEntry);
528}
529
530QString QFSFileEngine::fileName(FileName file) const
531{
532 Q_D(const QFSFileEngine);
533 switch (file) {
534 case BundleName:
535 return QFileSystemEngine::bundleName(d->fileEntry);
536 case BaseName:
537 return d->fileEntry.fileName();
538 case PathName:
539 return d->fileEntry.path();
540 case AbsoluteName:
541 case AbsolutePathName: {
542 QFileSystemEntry entry(QFileSystemEngine::absoluteName(d->fileEntry));
543 return file == AbsolutePathName ? entry.path() : entry.filePath();
544 }
545 case CanonicalName:
546 case CanonicalPathName: {
547 QFileSystemEntry entry(QFileSystemEngine::canonicalName(d->fileEntry, d->metaData));
548 return file == CanonicalPathName ? entry.path() : entry.filePath();
549 }
550 case LinkName:
551 if (d->isSymlink()) {
552 QFileSystemEntry entry = QFileSystemEngine::getLinkTarget(d->fileEntry, d->metaData);
553 return entry.filePath();
554 }
555 return QString();
556 case DefaultName:
557 case NFileNames:
558 break;
559 }
560 return d->fileEntry.filePath();
561}
562
563bool QFSFileEngine::isRelativePath() const
564{
565 Q_D(const QFSFileEngine);
566 return d->fileEntry.filePath().length() ? d->fileEntry.filePath().at(0) != QLatin1Char('/') : true;
567}
568
569uint QFSFileEngine::ownerId(FileOwner own) const
570{
571 Q_D(const QFSFileEngine);
572 static const uint nobodyID = (uint) -2;
573
574 if (d->doStat(QFileSystemMetaData::OwnerIds))
575 return d->metaData.ownerId(own);
576
577 return nobodyID;
578}
579
580QString QFSFileEngine::owner(FileOwner own) const
581{
582 if (own == OwnerUser)
583 return QFileSystemEngine::resolveUserName(ownerId(own));
584 return QFileSystemEngine::resolveGroupName(ownerId(own));
585}
586
587bool QFSFileEngine::setPermissions(uint perms)
588{
589 Q_D(QFSFileEngine);
590 QSystemError error;
591 bool ok;
592 if (d->fd != -1)
593 ok = QFileSystemEngine::setPermissions(d->fd, QFile::Permissions(perms), error);
594 else
595 ok = QFileSystemEngine::setPermissions(d->fileEntry, QFile::Permissions(perms), error);
596 if (!ok) {
597 setError(QFile::PermissionsError, error.toString());
598 return false;
599 }
600 return true;
601}
602
603bool QFSFileEngine::setSize(qint64 size)
604{
605 Q_D(QFSFileEngine);
606 bool ret = false;
607 if (d->fd != -1)
608 ret = QT_FTRUNCATE(d->fd, size) == 0;
609 else if (d->fh)
610 ret = QT_FTRUNCATE(QT_FILENO(d->fh), size) == 0;
611 else
612 ret = QT_TRUNCATE(d->fileEntry.nativeFilePath().constData(), size) == 0;
613 if (!ret)
614 setError(QFile::ResizeError, qt_error_string(errno));
615 return ret;
616}
617
618bool QFSFileEngine::setFileTime(const QDateTime &newDate, FileTime time)
619{
620 Q_D(QFSFileEngine);
621
622 if (d->openMode == QIODevice::NotOpen) {
623 setError(QFile::PermissionsError, qt_error_string(EACCES));
624 return false;
625 }
626
627 QSystemError error;
628 if (!QFileSystemEngine::setFileTime(d->nativeHandle(), newDate, time, error)) {
629 setError(QFile::PermissionsError, error.toString());
630 return false;
631 }
632
633 d->metaData.clearFlags(QFileSystemMetaData::Times);
634 return true;
635}
636
637uchar *QFSFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
638{
639 qint64 maxFileOffset = std::numeric_limits<QT_OFF_T>::max();
640#if (defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)) && Q_PROCESSOR_WORDSIZE == 4
641 // The Linux mmap2 system call on 32-bit takes a page-shifted 32-bit
642 // integer so the maximum offset is 1 << (32+12) (the shift is always 12,
643 // regardless of the actual page size). Unfortunately, the mmap64()
644 // function is known to be broken in all Linux libcs (glibc, uclibc, musl
645 // and Bionic): all of them do the right shift, but don't confirm that the
646 // result fits into the 32-bit parameter to the kernel.
647
648 maxFileOffset = qMin((Q_INT64_C(1) << (32+12)) - 1, maxFileOffset);
649#endif
650
651 Q_Q(QFSFileEngine);
652 if (openMode == QIODevice::NotOpen) {
653 q->setError(QFile::PermissionsError, qt_error_string(int(EACCES)));
654 return nullptr;
655 }
656
657 if (offset < 0 || offset > maxFileOffset
658 || size < 0 || quint64(size) > quint64(size_t(-1))) {
659 q->setError(QFile::UnspecifiedError, qt_error_string(int(EINVAL)));
660 return nullptr;
661 }
662
663 // If we know the mapping will extend beyond EOF, fail early to avoid
664 // undefined behavior. Otherwise, let mmap have its say.
665 if (doStat(QFileSystemMetaData::SizeAttribute)
666 && (QT_OFF_T(size) > metaData.size() - QT_OFF_T(offset)))
667 qWarning("QFSFileEngine::map: Mapping a file beyond its size is not portable");
668
669 int access = 0;
670 if (openMode & QIODevice::ReadOnly) access |= PROT_READ;
671 if (openMode & QIODevice::WriteOnly) access |= PROT_WRITE;
672
673 int sharemode = MAP_SHARED;
674 if (flags & QFileDevice::MapPrivateOption) {
675 sharemode = MAP_PRIVATE;
676 access |= PROT_WRITE;
677 }
678
679#if defined(Q_OS_INTEGRITY)
680 int pageSize = sysconf(_SC_PAGESIZE);
681#else
682 int pageSize = getpagesize();
683#endif
684 int extra = offset % pageSize;
685
686 if (quint64(size + extra) > quint64((size_t)-1)) {
687 q->setError(QFile::UnspecifiedError, qt_error_string(int(EINVAL)));
688 return nullptr;
689 }
690
691 size_t realSize = (size_t)size + extra;
692 QT_OFF_T realOffset = QT_OFF_T(offset);
693 realOffset &= ~(QT_OFF_T(pageSize - 1));
694
695 void *mapAddress = QT_MMAP((void*)nullptr, realSize,
696 access, sharemode, nativeHandle(), realOffset);
697 if (MAP_FAILED != mapAddress) {
698 uchar *address = extra + static_cast<uchar*>(mapAddress);
699 maps[address] = QPair<int,size_t>(extra, realSize);
700 return address;
701 }
702
703 switch(errno) {
704 case EBADF:
705 q->setError(QFile::PermissionsError, qt_error_string(int(EACCES)));
706 break;
707 case ENFILE:
708 case ENOMEM:
709 q->setError(QFile::ResourceError, qt_error_string(int(errno)));
710 break;
711 case EINVAL:
712 // size are out of bounds
713 default:
714 q->setError(QFile::UnspecifiedError, qt_error_string(int(errno)));
715 break;
716 }
717 return nullptr;
718}
719
720bool QFSFileEnginePrivate::unmap(uchar *ptr)
721{
722#if !defined(Q_OS_INTEGRITY)
723 Q_Q(QFSFileEngine);
724 if (!maps.contains(ptr)) {
725 q->setError(QFile::PermissionsError, qt_error_string(EACCES));
726 return false;
727 }
728
729 uchar *start = ptr - maps[ptr].first;
730 size_t len = maps[ptr].second;
731 if (-1 == munmap(start, len)) {
732 q->setError(QFile::UnspecifiedError, qt_error_string(errno));
733 return false;
734 }
735 maps.remove(ptr);
736 return true;
737#else
738 return false;
739#endif
740}
741
742/*!
743 \reimp
744*/
745bool QFSFileEngine::cloneTo(QAbstractFileEngine *target)
746{
747 Q_D(QFSFileEngine);
748 if ((target->fileFlags(LocalDiskFlag) & LocalDiskFlag) == 0)
749 return false;
750
751 int srcfd = d->nativeHandle();
752 int dstfd = target->handle();
753 return QFileSystemEngine::cloneFile(srcfd, dstfd, d->metaData);
754}
755
756QT_END_NAMESPACE
757
758#endif // QT_NO_FSFILEENGINE
759