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::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
318qint64 QFSFileEnginePrivate::nativeSize() const
319{
320 return sizeFdFh();
321}
322
323bool QFSFileEngine::caseSensitive() const
324{
325 return true;
326}
327
328QString QFSFileEngine::currentPath(const QString &)
329{
330 return QFileSystemEngine::currentPath().filePath();
331}
332
333
334QFileInfoList QFSFileEngine::drives()
335{
336 QFileInfoList ret;
337 ret.append(QFileInfo(rootPath()));
338 return ret;
339}
340
341bool 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
359bool 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*/
370QAbstractFileEngine::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
445QByteArray 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
453QString 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
486bool QFSFileEngine::isRelativePath() const
487{
488 Q_D(const QFSFileEngine);
489 return d->fileEntry.filePath().length() ? d->fileEntry.filePath().at(0) != QLatin1Char('/') : true;
490}
491
492uint 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
503QString QFSFileEngine::owner(FileOwner own) const
504{
505 if (own == OwnerUser)
506 return QFileSystemEngine::resolveUserName(ownerId(own));
507 return QFileSystemEngine::resolveGroupName(ownerId(own));
508}
509
510bool 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
526bool 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
541bool 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
560uchar *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
643bool 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*/
668bool 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
679QT_END_NAMESPACE
680
681#endif // QT_NO_FSFILEENGINE
682