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