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

source code of qtbase/src/corelib/io/qfsfileengine_unix.cpp