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 EINTR_LOOP(ret, fdatasync(nativeHandle()));
164#else
165 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
297bool QFSFileEngine::caseSensitive() const
298{
299 return true;
300}
301
302QString QFSFileEngine::currentPath(const QString &)
303{
304 return QFileSystemEngine::currentPath().filePath();
305}
306
307
308QFileInfoList QFSFileEngine::drives()
309{
310 QFileInfoList ret;
311 ret.append(t: QFileInfo(rootPath()));
312 return ret;
313}
314
315bool QFSFileEnginePrivate::doStat(QFileSystemMetaData::MetaDataFlags flags) const
316{
317 if (!tried_stat || !metaData.hasFlags(flags)) {
318 tried_stat = 1;
319
320 int localFd = fd;
321 if (fh && fileEntry.isEmpty())
322 localFd = QT_FILENO(stream: fh);
323 if (localFd != -1)
324 QFileSystemEngine::fillMetaData(fd: localFd, data&: metaData);
325
326 if (metaData.missingFlags(flags) && !fileEntry.isEmpty())
327 QFileSystemEngine::fillMetaData(entry: fileEntry, data&: metaData, what: metaData.missingFlags(flags));
328 }
329
330 return metaData.exists();
331}
332
333bool QFSFileEnginePrivate::isSymlink() const
334{
335 if (!metaData.hasFlags(flags: QFileSystemMetaData::LinkType))
336 QFileSystemEngine::fillMetaData(entry: fileEntry, data&: metaData, what: QFileSystemMetaData::LinkType);
337
338 return metaData.isLink();
339}
340
341/*!
342 \reimp
343*/
344QAbstractFileEngine::FileFlags QFSFileEngine::fileFlags(FileFlags type) const
345{
346 Q_D(const QFSFileEngine);
347
348 if (type & Refresh)
349 d->metaData.clear();
350
351 QAbstractFileEngine::FileFlags ret = { };
352
353 if (type & FlagsMask)
354 ret |= LocalDiskFlag;
355
356 bool exists;
357 {
358 QFileSystemMetaData::MetaDataFlags queryFlags = { };
359
360 queryFlags |= QFileSystemMetaData::MetaDataFlags(uint(type.toInt()))
361 & QFileSystemMetaData::Permissions;
362
363 if (type & TypesMask)
364 queryFlags |= QFileSystemMetaData::AliasType
365 | QFileSystemMetaData::LinkType
366 | QFileSystemMetaData::FileType
367 | QFileSystemMetaData::DirectoryType
368 | QFileSystemMetaData::BundleType
369 | QFileSystemMetaData::WasDeletedAttribute;
370
371 if (type & FlagsMask)
372 queryFlags |= QFileSystemMetaData::HiddenAttribute
373 | QFileSystemMetaData::ExistsAttribute;
374 else if (type & ExistsFlag)
375 queryFlags |= QFileSystemMetaData::WasDeletedAttribute;
376
377 queryFlags |= QFileSystemMetaData::LinkType;
378
379 exists = d->doStat(flags: queryFlags);
380 }
381
382 if (!exists && !d->metaData.isLink())
383 return ret;
384
385 if (exists && (type & PermsMask))
386 ret |= FileFlags(uint(d->metaData.permissions().toInt()));
387
388 if (type & TypesMask) {
389 if (d->metaData.isAlias()) {
390 ret |= LinkType;
391 } else {
392 if ((type & LinkType) && d->metaData.isLink())
393 ret |= LinkType;
394 if (exists) {
395 if (d->metaData.isFile()) {
396 ret |= FileType;
397 } else if (d->metaData.isDirectory()) {
398 ret |= DirectoryType;
399 if ((type & BundleType) && d->metaData.isBundle())
400 ret |= BundleType;
401 }
402 }
403 }
404 }
405
406 if (type & FlagsMask) {
407 // the inode existing does not mean the file exists
408 if (!d->metaData.wasDeleted())
409 ret |= ExistsFlag;
410 if (d->fileEntry.isRoot())
411 ret |= RootFlag;
412 else if (d->metaData.isHidden())
413 ret |= HiddenFlag;
414 }
415
416 return ret;
417}
418
419QByteArray QFSFileEngine::id() const
420{
421 Q_D(const QFSFileEngine);
422 if (d->fd != -1)
423 return QFileSystemEngine::id(fd: d->fd);
424 return QFileSystemEngine::id(entry: d->fileEntry);
425}
426
427QString QFSFileEngine::fileName(FileName file) const
428{
429 Q_D(const QFSFileEngine);
430 switch (file) {
431 case BundleName:
432 return QFileSystemEngine::bundleName(d->fileEntry);
433 case BaseName:
434 return d->fileEntry.fileName();
435 case PathName:
436 return d->fileEntry.path();
437 case AbsoluteName:
438 case AbsolutePathName: {
439 QFileSystemEntry entry(QFileSystemEngine::absoluteName(entry: d->fileEntry));
440 return file == AbsolutePathName ? entry.path() : entry.filePath();
441 }
442 case CanonicalName:
443 case CanonicalPathName: {
444 QFileSystemEntry entry(QFileSystemEngine::canonicalName(entry: d->fileEntry, data&: d->metaData));
445 return file == CanonicalPathName ? entry.path() : entry.filePath();
446 }
447 case AbsoluteLinkTarget:
448 if (d->isSymlink()) {
449 QFileSystemEntry entry = QFileSystemEngine::getLinkTarget(link: d->fileEntry, data&: d->metaData);
450 return entry.filePath();
451 }
452 return QString();
453 case RawLinkPath:
454 if (d->isSymlink()) {
455 QFileSystemEntry entry = QFileSystemEngine::getRawLinkPath(link: d->fileEntry, data&: d->metaData);
456 return entry.filePath();
457 }
458 return QString();
459 case JunctionName:
460 return QString();
461 case DefaultName:
462 case NFileNames:
463 break;
464 }
465 return d->fileEntry.filePath();
466}
467
468bool QFSFileEngine::isRelativePath() const
469{
470 Q_D(const QFSFileEngine);
471 const QString fp = d->fileEntry.filePath();
472 return fp.isEmpty() || fp.at(i: 0) != u'/';
473}
474
475uint QFSFileEngine::ownerId(FileOwner own) const
476{
477 Q_D(const QFSFileEngine);
478 static const uint nobodyID = (uint) -2;
479
480 if (d->doStat(flags: QFileSystemMetaData::OwnerIds))
481 return d->metaData.ownerId(owner: own);
482
483 return nobodyID;
484}
485
486QString QFSFileEngine::owner(FileOwner own) const
487{
488 if (own == OwnerUser)
489 return QFileSystemEngine::resolveUserName(userId: ownerId(own));
490 return QFileSystemEngine::resolveGroupName(groupId: ownerId(own));
491}
492
493bool QFSFileEngine::setPermissions(uint perms)
494{
495 Q_D(QFSFileEngine);
496 QSystemError error;
497 bool ok;
498
499 // clear cached state (if any)
500 d->metaData.clearFlags(flags: QFileSystemMetaData::Permissions);
501
502 if (d->fd != -1)
503 ok = QFileSystemEngine::setPermissions(fd: d->fd, permissions: QFile::Permissions(perms), error);
504 else
505 ok = QFileSystemEngine::setPermissions(entry: d->fileEntry, permissions: QFile::Permissions(perms), error);
506 if (!ok) {
507 setError(error: QFile::PermissionsError, str: error.toString());
508 return false;
509 }
510 return true;
511}
512
513bool QFSFileEngine::setSize(qint64 size)
514{
515 Q_D(QFSFileEngine);
516 bool ret = false;
517 if (d->fd != -1)
518 ret = QT_FTRUNCATE(fd: d->fd, length: size) == 0;
519 else if (d->fh)
520 ret = QT_FTRUNCATE(QT_FILENO(stream: d->fh), length: size) == 0;
521 else
522 ret = QT_TRUNCATE(file: d->fileEntry.nativeFilePath().constData(), length: size) == 0;
523 if (!ret)
524 setError(error: QFile::ResizeError, str: qt_error_string(errno));
525 return ret;
526}
527
528bool QFSFileEngine::setFileTime(const QDateTime &newDate, FileTime time)
529{
530 Q_D(QFSFileEngine);
531
532 if (d->openMode == QIODevice::NotOpen) {
533 setError(error: QFile::PermissionsError, str: qt_error_string(EACCES));
534 return false;
535 }
536
537 QSystemError error;
538 if (!QFileSystemEngine::setFileTime(fd: d->nativeHandle(), newDate, whatTime: time, error)) {
539 setError(error: QFile::PermissionsError, str: error.toString());
540 return false;
541 }
542
543 d->metaData.clearFlags(flags: QFileSystemMetaData::Times);
544 return true;
545}
546
547uchar *QFSFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
548{
549 qint64 maxFileOffset = std::numeric_limits<QT_OFF_T>::max();
550#if (defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)) && Q_PROCESSOR_WORDSIZE == 4
551 // The Linux mmap2 system call on 32-bit takes a page-shifted 32-bit
552 // integer so the maximum offset is 1 << (32+12) (the shift is always 12,
553 // regardless of the actual page size). Unfortunately, the mmap64()
554 // function is known to be broken in all Linux libcs (glibc, uclibc, musl
555 // and Bionic): all of them do the right shift, but don't confirm that the
556 // result fits into the 32-bit parameter to the kernel.
557
558 maxFileOffset = qMin((Q_INT64_C(1) << (32+12)) - 1, maxFileOffset);
559#endif
560
561 Q_Q(QFSFileEngine);
562 if (openMode == QIODevice::NotOpen) {
563 q->setError(error: QFile::PermissionsError, str: qt_error_string(EACCES));
564 return nullptr;
565 }
566
567 if (offset < 0 || offset > maxFileOffset
568 || size < 0 || quint64(size) > quint64(size_t(-1))) {
569 q->setError(error: QFile::UnspecifiedError, str: qt_error_string(EINVAL));
570 return nullptr;
571 }
572
573 // If we know the mapping will extend beyond EOF, fail early to avoid
574 // undefined behavior. Otherwise, let mmap have its say.
575 if (doStat(flags: QFileSystemMetaData::SizeAttribute)
576 && (QT_OFF_T(size) > metaData.size() - QT_OFF_T(offset)))
577 qWarning(msg: "QFSFileEngine::map: Mapping a file beyond its size is not portable");
578
579 int access = 0;
580 if (openMode & QIODevice::ReadOnly) access |= PROT_READ;
581 if (openMode & QIODevice::WriteOnly) access |= PROT_WRITE;
582
583 int sharemode = MAP_SHARED;
584 if (flags & QFileDevice::MapPrivateOption) {
585 sharemode = MAP_PRIVATE;
586 access |= PROT_WRITE;
587 }
588
589#if defined(Q_OS_INTEGRITY)
590 int pageSize = sysconf(_SC_PAGESIZE);
591#else
592 int pageSize = getpagesize();
593#endif
594 int extra = offset % pageSize;
595
596 if (quint64(size + extra) > quint64((size_t)-1)) {
597 q->setError(error: QFile::UnspecifiedError, str: qt_error_string(EINVAL));
598 return nullptr;
599 }
600
601 size_t realSize = (size_t)size + extra;
602 QT_OFF_T realOffset = QT_OFF_T(offset);
603 realOffset &= ~(QT_OFF_T(pageSize - 1));
604
605 void *mapAddress = QT_MMAP(addr: (void*)nullptr, len: realSize,
606 prot: access, flags: sharemode, fd: nativeHandle(), offset: realOffset);
607 if (MAP_FAILED != mapAddress) {
608 uchar *address = extra + static_cast<uchar*>(mapAddress);
609 maps[address] = {.start: extra, .length: realSize};
610 return address;
611 }
612
613 switch(errno) {
614 case EBADF:
615 q->setError(error: QFile::PermissionsError, str: qt_error_string(EACCES));
616 break;
617 case ENFILE:
618 case ENOMEM:
619 q->setError(error: QFile::ResourceError, str: qt_error_string(errno));
620 break;
621 case EINVAL:
622 // size are out of bounds
623 default:
624 q->setError(error: QFile::UnspecifiedError, str: qt_error_string(errno));
625 break;
626 }
627 return nullptr;
628}
629
630bool QFSFileEnginePrivate::unmap(uchar *ptr)
631{
632#if !defined(Q_OS_INTEGRITY)
633 Q_Q(QFSFileEngine);
634 const auto it = std::as_const(t&: maps).find(key: ptr);
635 if (it == maps.cend()) {
636 q->setError(error: QFile::PermissionsError, str: qt_error_string(EACCES));
637 return false;
638 }
639
640 uchar *start = ptr - it->start;
641 size_t len = it->length;
642 if (-1 == munmap(addr: start, len: len)) {
643 q->setError(error: QFile::UnspecifiedError, str: qt_error_string(errno));
644 return false;
645 }
646 maps.erase(it);
647 return true;
648#else
649 return false;
650#endif
651}
652
653/*!
654 \reimp
655*/
656bool QFSFileEngine::cloneTo(QAbstractFileEngine *target)
657{
658 Q_D(QFSFileEngine);
659 if ((target->fileFlags(type: LocalDiskFlag) & LocalDiskFlag) == 0)
660 return false;
661
662 int srcfd = d->nativeHandle();
663 int dstfd = target->handle();
664 return QFileSystemEngine::cloneFile(srcfd, dstfd, knownData: d->metaData);
665}
666
667QT_END_NAMESPACE
668
669#endif // QT_NO_FSFILEENGINE
670

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