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