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 
63 QT_BEGIN_NAMESPACE
64 
65 /*!
66     \internal
67 
68     Returns the stdio open flags corresponding to a QIODevice::OpenMode.
69 */
openModeToOpenFlags(QIODevice::OpenMode mode)70 static 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 
msgOpenDirectory()97 static 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 */
nativeOpen(QIODevice::OpenMode openMode)110 bool 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 */
nativeClose()166 bool QFSFileEnginePrivate::nativeClose()
167 {
168     return closeFdFh();
169 }
170 
171 /*!
172     \internal
173 
174 */
nativeFlush()175 bool QFSFileEnginePrivate::nativeFlush()
176 {
177     return fh ? flushFh() : fd != -1;
178 }
179 
180 /*!
181     \internal
182     \since 5.1
183 */
nativeSyncToDisk()184 bool 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 */
nativeRead(char * data,qint64 len)201 qint64 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 */
nativeReadLine(char * data,qint64 maxlen)262 qint64 QFSFileEnginePrivate::nativeReadLine(char *data, qint64 maxlen)
263 {
264     return readLineFdFh(data, maxlen);
265 }
266 
267 /*!
268     \internal
269 */
nativeWrite(const char * data,qint64 len)270 qint64 QFSFileEnginePrivate::nativeWrite(const char *data, qint64 len)
271 {
272     return writeFdFh(data, len);
273 }
274 
275 /*!
276     \internal
277 */
nativePos() const278 qint64 QFSFileEnginePrivate::nativePos() const
279 {
280     return posFdFh();
281 }
282 
283 /*!
284     \internal
285 */
nativeSeek(qint64 pos)286 bool QFSFileEnginePrivate::nativeSeek(qint64 pos)
287 {
288     return seekFdFh(pos);
289 }
290 
291 /*!
292     \internal
293 */
nativeHandle() const294 int QFSFileEnginePrivate::nativeHandle() const
295 {
296     return fh ? fileno(fh) : fd;
297 }
298 
299 /*!
300     \internal
301 */
nativeIsSequential() const302 bool QFSFileEnginePrivate::nativeIsSequential() const
303 {
304     return isSequentialFdFh();
305 }
306 
link(const QString & newName)307 bool QFSFileEngine::link(const QString &newName)
308 {
309     Q_D(QFSFileEngine);
310     QSystemError error;
311     bool ret = QFileSystemEngine::createLink(d->fileEntry, QFileSystemEntry(newName), error);
312     if (!ret) {
313         setError(QFile::RenameError, error.toString());
314     }
315     return ret;
316 }
317 
nativeSize() const318 qint64 QFSFileEnginePrivate::nativeSize() const
319 {
320     return sizeFdFh();
321 }
322 
caseSensitive() const323 bool QFSFileEngine::caseSensitive() const
324 {
325     return true;
326 }
327 
currentPath(const QString &)328 QString QFSFileEngine::currentPath(const QString &)
329 {
330     return QFileSystemEngine::currentPath().filePath();
331 }
332 
333 
drives()334 QFileInfoList QFSFileEngine::drives()
335 {
336     QFileInfoList ret;
337     ret.append(QFileInfo(rootPath()));
338     return ret;
339 }
340 
doStat(QFileSystemMetaData::MetaDataFlags flags) const341 bool QFSFileEnginePrivate::doStat(QFileSystemMetaData::MetaDataFlags flags) const
342 {
343     if (!tried_stat || !metaData.hasFlags(flags)) {
344         tried_stat = 1;
345 
346         int localFd = fd;
347         if (fh && fileEntry.isEmpty())
348             localFd = QT_FILENO(fh);
349         if (localFd != -1)
350             QFileSystemEngine::fillMetaData(localFd, metaData);
351 
352         if (metaData.missingFlags(flags) && !fileEntry.isEmpty())
353             QFileSystemEngine::fillMetaData(fileEntry, metaData, metaData.missingFlags(flags));
354     }
355 
356     return metaData.exists();
357 }
358 
isSymlink() const359 bool QFSFileEnginePrivate::isSymlink() const
360 {
361     if (!metaData.hasFlags(QFileSystemMetaData::LinkType))
362         QFileSystemEngine::fillMetaData(fileEntry, metaData, QFileSystemMetaData::LinkType);
363 
364     return metaData.isLink();
365 }
366 
367 /*!
368     \reimp
369 */
fileFlags(FileFlags type) const370 QAbstractFileEngine::FileFlags QFSFileEngine::fileFlags(FileFlags type) const
371 {
372     Q_D(const QFSFileEngine);
373 
374     if (type & Refresh)
375         d->metaData.clear();
376 
377     QAbstractFileEngine::FileFlags ret = { };
378 
379     if (type & FlagsMask)
380         ret |= LocalDiskFlag;
381 
382     bool exists;
383     {
384         QFileSystemMetaData::MetaDataFlags queryFlags = { };
385 
386         queryFlags |= QFileSystemMetaData::MetaDataFlags(uint(type))
387                 & QFileSystemMetaData::Permissions;
388 
389         if (type & TypesMask)
390             queryFlags |= QFileSystemMetaData::AliasType
391                     | QFileSystemMetaData::LinkType
392                     | QFileSystemMetaData::FileType
393                     | QFileSystemMetaData::DirectoryType
394                     | QFileSystemMetaData::BundleType
395                     | QFileSystemMetaData::WasDeletedAttribute;
396 
397         if (type & FlagsMask)
398             queryFlags |= QFileSystemMetaData::HiddenAttribute
399                     | QFileSystemMetaData::ExistsAttribute;
400         else if (type & ExistsFlag)
401             queryFlags |= QFileSystemMetaData::WasDeletedAttribute;
402 
403         queryFlags |= QFileSystemMetaData::LinkType;
404 
405         exists = d->doStat(queryFlags);
406     }
407 
408     if (!exists && !d->metaData.isLink())
409         return ret;
410 
411     if (exists && (type & PermsMask))
412         ret |= FileFlags(uint(d->metaData.permissions()));
413 
414     if (type & TypesMask) {
415         if (d->metaData.isAlias()) {
416             ret |= LinkType;
417         } else {
418             if ((type & LinkType) && d->metaData.isLink())
419                 ret |= LinkType;
420             if (exists) {
421                 if (d->metaData.isFile()) {
422                     ret |= FileType;
423                 } else if (d->metaData.isDirectory()) {
424                     ret |= DirectoryType;
425                     if ((type & BundleType) && d->metaData.isBundle())
426                         ret |= BundleType;
427                 }
428             }
429         }
430     }
431 
432     if (type & FlagsMask) {
433         // the inode existing does not mean the file exists
434         if (!d->metaData.wasDeleted())
435             ret |= ExistsFlag;
436         if (d->fileEntry.isRoot())
437             ret |= RootFlag;
438         else if (d->metaData.isHidden())
439             ret |= HiddenFlag;
440     }
441 
442     return ret;
443 }
444 
id() const445 QByteArray QFSFileEngine::id() const
446 {
447     Q_D(const QFSFileEngine);
448     if (d->fd != -1)
449         return QFileSystemEngine::id(d->fd);
450     return QFileSystemEngine::id(d->fileEntry);
451 }
452 
fileName(FileName file) const453 QString QFSFileEngine::fileName(FileName file) const
454 {
455     Q_D(const QFSFileEngine);
456     switch (file) {
457     case BundleName:
458         return QFileSystemEngine::bundleName(d->fileEntry);
459     case BaseName:
460         return d->fileEntry.fileName();
461     case PathName:
462         return d->fileEntry.path();
463     case AbsoluteName:
464     case AbsolutePathName: {
465         QFileSystemEntry entry(QFileSystemEngine::absoluteName(d->fileEntry));
466         return file == AbsolutePathName ? entry.path() : entry.filePath();
467     }
468     case CanonicalName:
469     case CanonicalPathName: {
470         QFileSystemEntry entry(QFileSystemEngine::canonicalName(d->fileEntry, d->metaData));
471         return file == CanonicalPathName ? entry.path() : entry.filePath();
472     }
473     case LinkName:
474         if (d->isSymlink()) {
475             QFileSystemEntry entry = QFileSystemEngine::getLinkTarget(d->fileEntry, d->metaData);
476             return entry.filePath();
477         }
478         return QString();
479     case DefaultName:
480     case NFileNames:
481         break;
482     }
483     return d->fileEntry.filePath();
484 }
485 
isRelativePath() const486 bool QFSFileEngine::isRelativePath() const
487 {
488     Q_D(const QFSFileEngine);
489     return d->fileEntry.filePath().length() ? d->fileEntry.filePath().at(0) != QLatin1Char('/') : true;
490 }
491 
ownerId(FileOwner own) const492 uint QFSFileEngine::ownerId(FileOwner own) const
493 {
494     Q_D(const QFSFileEngine);
495     static const uint nobodyID = (uint) -2;
496 
497     if (d->doStat(QFileSystemMetaData::OwnerIds))
498         return d->metaData.ownerId(own);
499 
500     return nobodyID;
501 }
502 
owner(FileOwner own) const503 QString QFSFileEngine::owner(FileOwner own) const
504 {
505     if (own == OwnerUser)
506         return QFileSystemEngine::resolveUserName(ownerId(own));
507     return QFileSystemEngine::resolveGroupName(ownerId(own));
508 }
509 
setPermissions(uint perms)510 bool QFSFileEngine::setPermissions(uint perms)
511 {
512     Q_D(QFSFileEngine);
513     QSystemError error;
514     bool ok;
515     if (d->fd != -1)
516         ok = QFileSystemEngine::setPermissions(d->fd, QFile::Permissions(perms), error);
517     else
518         ok = QFileSystemEngine::setPermissions(d->fileEntry, QFile::Permissions(perms), error);
519     if (!ok) {
520         setError(QFile::PermissionsError, error.toString());
521         return false;
522     }
523     return true;
524 }
525 
setSize(qint64 size)526 bool QFSFileEngine::setSize(qint64 size)
527 {
528     Q_D(QFSFileEngine);
529     bool ret = false;
530     if (d->fd != -1)
531         ret = QT_FTRUNCATE(d->fd, size) == 0;
532     else if (d->fh)
533         ret = QT_FTRUNCATE(QT_FILENO(d->fh), size) == 0;
534     else
535         ret = QT_TRUNCATE(d->fileEntry.nativeFilePath().constData(), size) == 0;
536     if (!ret)
537         setError(QFile::ResizeError, qt_error_string(errno));
538     return ret;
539 }
540 
setFileTime(const QDateTime & newDate,FileTime time)541 bool QFSFileEngine::setFileTime(const QDateTime &newDate, FileTime time)
542 {
543     Q_D(QFSFileEngine);
544 
545     if (d->openMode == QIODevice::NotOpen) {
546         setError(QFile::PermissionsError, qt_error_string(EACCES));
547         return false;
548     }
549 
550     QSystemError error;
551     if (!QFileSystemEngine::setFileTime(d->nativeHandle(), newDate, time, error)) {
552         setError(QFile::PermissionsError, error.toString());
553         return false;
554     }
555 
556     d->metaData.clearFlags(QFileSystemMetaData::Times);
557     return true;
558 }
559 
map(qint64 offset,qint64 size,QFile::MemoryMapFlags flags)560 uchar *QFSFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
561 {
562     qint64 maxFileOffset = std::numeric_limits<QT_OFF_T>::max();
563 #if (defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)) && Q_PROCESSOR_WORDSIZE == 4
564     // The Linux mmap2 system call on 32-bit takes a page-shifted 32-bit
565     // integer so the maximum offset is 1 << (32+12) (the shift is always 12,
566     // regardless of the actual page size). Unfortunately, the mmap64()
567     // function is known to be broken in all Linux libcs (glibc, uclibc, musl
568     // and Bionic): all of them do the right shift, but don't confirm that the
569     // result fits into the 32-bit parameter to the kernel.
570 
571     maxFileOffset = qMin((Q_INT64_C(1) << (32+12)) - 1, maxFileOffset);
572 #endif
573 
574     Q_Q(QFSFileEngine);
575     if (openMode == QIODevice::NotOpen) {
576         q->setError(QFile::PermissionsError, qt_error_string(int(EACCES)));
577         return nullptr;
578     }
579 
580     if (offset < 0 || offset > maxFileOffset
581             || size < 0 || quint64(size) > quint64(size_t(-1))) {
582         q->setError(QFile::UnspecifiedError, qt_error_string(int(EINVAL)));
583         return nullptr;
584     }
585 
586     // If we know the mapping will extend beyond EOF, fail early to avoid
587     // undefined behavior. Otherwise, let mmap have its say.
588     if (doStat(QFileSystemMetaData::SizeAttribute)
589             && (QT_OFF_T(size) > metaData.size() - QT_OFF_T(offset)))
590         qWarning("QFSFileEngine::map: Mapping a file beyond its size is not portable");
591 
592     int access = 0;
593     if (openMode & QIODevice::ReadOnly) access |= PROT_READ;
594     if (openMode & QIODevice::WriteOnly) access |= PROT_WRITE;
595 
596     int sharemode = MAP_SHARED;
597     if (flags & QFileDevice::MapPrivateOption) {
598         sharemode = MAP_PRIVATE;
599         access |= PROT_WRITE;
600     }
601 
602 #if defined(Q_OS_INTEGRITY)
603     int pageSize = sysconf(_SC_PAGESIZE);
604 #else
605     int pageSize = getpagesize();
606 #endif
607     int extra = offset % pageSize;
608 
609     if (quint64(size + extra) > quint64((size_t)-1)) {
610         q->setError(QFile::UnspecifiedError, qt_error_string(int(EINVAL)));
611         return nullptr;
612     }
613 
614     size_t realSize = (size_t)size + extra;
615     QT_OFF_T realOffset = QT_OFF_T(offset);
616     realOffset &= ~(QT_OFF_T(pageSize - 1));
617 
618     void *mapAddress = QT_MMAP((void*)nullptr, realSize,
619                    access, sharemode, nativeHandle(), realOffset);
620     if (MAP_FAILED != mapAddress) {
621         uchar *address = extra + static_cast<uchar*>(mapAddress);
622         maps[address] = QPair<int,size_t>(extra, realSize);
623         return address;
624     }
625 
626     switch(errno) {
627     case EBADF:
628         q->setError(QFile::PermissionsError, qt_error_string(int(EACCES)));
629         break;
630     case ENFILE:
631     case ENOMEM:
632         q->setError(QFile::ResourceError, qt_error_string(int(errno)));
633         break;
634     case EINVAL:
635         // size are out of bounds
636     default:
637         q->setError(QFile::UnspecifiedError, qt_error_string(int(errno)));
638         break;
639     }
640     return nullptr;
641 }
642 
unmap(uchar * ptr)643 bool QFSFileEnginePrivate::unmap(uchar *ptr)
644 {
645 #if !defined(Q_OS_INTEGRITY)
646     Q_Q(QFSFileEngine);
647     if (!maps.contains(ptr)) {
648         q->setError(QFile::PermissionsError, qt_error_string(EACCES));
649         return false;
650     }
651 
652     uchar *start = ptr - maps[ptr].first;
653     size_t len = maps[ptr].second;
654     if (-1 == munmap(start, len)) {
655         q->setError(QFile::UnspecifiedError, qt_error_string(errno));
656         return false;
657     }
658     maps.remove(ptr);
659     return true;
660 #else
661     return false;
662 #endif
663 }
664 
665 /*!
666     \reimp
667 */
cloneTo(QAbstractFileEngine * target)668 bool QFSFileEngine::cloneTo(QAbstractFileEngine *target)
669 {
670     Q_D(QFSFileEngine);
671     if ((target->fileFlags(LocalDiskFlag) & LocalDiskFlag) == 0)
672         return false;
673 
674     int srcfd = d->nativeHandle();
675     int dstfd = target->handle();
676     return QFileSystemEngine::cloneFile(srcfd, dstfd, d->metaData);
677 }
678 
679 QT_END_NAMESPACE
680 
681 #endif // QT_NO_FSFILEENGINE
682