Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#include "qplatformdefs.h"
#include "private/qabstractfileengine_p.h"
#include "private/qfiledevice_p.h"
#include "private/qfsfileengine_p.h"
#include "private/qcore_unix_p.h"
#include "qfilesystementry_p.h"
#include "qfilesystemengine_p.h"
#include "qcoreapplication.h"
#ifndef QT_NO_FSFILEENGINE
#include "qfile.h"
#include "qdir.h"
#include "qdatetime.h"
#include "qvarlengtharray.h"
#include <sys/mman.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#if defined(Q_OS_MACOS)
# include <private/qcore_mac_p.h>
#endif
QT_BEGIN_NAMESPACE
static inline QString msgOpenDirectory()
{
const char message[] = QT_TRANSLATE_NOOP("QIODevice", "file to open is a directory");
#if QT_CONFIG(translation)
return QIODevice::tr(message);
#else
return QLatin1StringView(message);
#endif
}
/*!
\internal
*/
bool QFSFileEnginePrivate::nativeOpen(QIODevice::OpenMode openMode,
std::optional<QFile::Permissions> permissions)
{
return nativeOpenImpl(openMode, permissions ? QtPrivate::toMode_t(*permissions) : 0666);
}
/*!
\internal
*/
bool QFSFileEnginePrivate::nativeOpenImpl(QIODevice::OpenMode openMode, mode_t mode)
{
Q_Q(QFSFileEngine);
Q_ASSERT_X(openMode & QIODevice::Unbuffered, "QFSFileEngine::open",
"QFSFileEngine no longer supports buffered mode; upper layer must buffer");
if (openMode & QIODevice::Unbuffered) {
int flags = openModeToOpenFlags(openMode);
// Try to open the file in unbuffered mode.
do {
fd = QT_OPEN(fileEntry.nativeFilePath().constData(), flags, mode);
} while (fd == -1 && errno == EINTR);
// On failure, return and report the error.
if (fd == -1) {
q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError,
qt_error_string(errno));
return false;
}
if (!(openMode & QIODevice::WriteOnly)) {
// we don't need this check if we tried to open for writing because then
// we had received EISDIR anyway.
if (QFileSystemEngine::fillMetaData(fd, metaData)
&& metaData.isDirectory()) {
q->setError(QFile::OpenError, msgOpenDirectory());
QT_CLOSE(fd);
return false;
}
}
// Seek to the end when in Append mode.
if (flags & QFile::Append) {
QT_OFF_T ret;
do {
ret = QT_LSEEK(fd, 0, SEEK_END);
} while (ret == -1 && errno == EINTR);
if (ret == -1) {
q->setError(errno == EMFILE ? QFile::ResourceError : QFile::OpenError,
qt_error_string(errno));
return false;
}
}
fh = nullptr;
}
closeFileHandle = true;
return true;
}
/*!
\internal
*/
bool QFSFileEnginePrivate::nativeClose()
{
return closeFdFh();
}
/*!
\internal
*/
bool QFSFileEnginePrivate::nativeFlush()
{
return fh ? flushFh() : fd != -1;
}
/*!
\internal
\since 5.1
*/
bool QFSFileEnginePrivate::nativeSyncToDisk()
{
Q_Q(QFSFileEngine);
int ret;
#if defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO > 0
QT_EINTR_LOOP(ret, fdatasync(nativeHandle()));
#else
QT_EINTR_LOOP(ret, fsync(nativeHandle()));
#endif
if (ret != 0)
q->setError(QFile::WriteError, qt_error_string(errno));
return ret == 0;
}
/*!
\internal
*/
qint64 QFSFileEnginePrivate::nativeRead(char *data, qint64 len)
{
Q_Q(QFSFileEngine);
if (fh && nativeIsSequential()) {
size_t readBytes = 0;
int oldFlags = fcntl(QT_FILENO(fh), F_GETFL);
for (int i = 0; i < 2; ++i) {
// Unix: Make the underlying file descriptor non-blocking
if ((oldFlags & O_NONBLOCK) == 0)
fcntl(QT_FILENO(fh), F_SETFL, oldFlags | O_NONBLOCK);
// Cross platform stdlib read
size_t read = 0;
do {
read = fread(data + readBytes, 1, size_t(len - readBytes), fh);
} while (read == 0 && !feof(fh) && errno == EINTR);
if (read > 0) {
readBytes += read;
break;
} else {
if (readBytes)
break;
readBytes = read;
}
// Unix: Restore the blocking state of the underlying socket
if ((oldFlags & O_NONBLOCK) == 0) {
fcntl(QT_FILENO(fh), F_SETFL, oldFlags);
if (readBytes == 0) {
int readByte = 0;
do {
readByte = fgetc(fh);
} while (readByte == -1 && errno == EINTR);
if (readByte != -1) {
*data = uchar(readByte);
readBytes += 1;
} else {
break;
}
}
}
}
// Unix: Restore the blocking state of the underlying socket
if ((oldFlags & O_NONBLOCK) == 0) {
fcntl(QT_FILENO(fh), F_SETFL, oldFlags);
}
if (readBytes == 0 && !feof(fh)) {
// if we didn't read anything and we're not at EOF, it must be an error
q->setError(QFile::ReadError, qt_error_string(errno));
return -1;
}
return readBytes;
}
return readFdFh(data, len);
}
/*!
\internal
*/
qint64 QFSFileEnginePrivate::nativeReadLine(char *data, qint64 maxlen)
{
return readLineFdFh(data, maxlen);
}
/*!
\internal
*/
qint64 QFSFileEnginePrivate::nativeWrite(const char *data, qint64 len)
{
return writeFdFh(data, len);
}
/*!
\internal
*/
qint64 QFSFileEnginePrivate::nativePos() const
{
return posFdFh();
}
/*!
\internal
*/
bool QFSFileEnginePrivate::nativeSeek(qint64 pos)
{
return seekFdFh(pos);
}
/*!
\internal
*/
int QFSFileEnginePrivate::nativeHandle() const
{
return fh ? fileno(fh) : fd;
}
/*!
\internal
*/
bool QFSFileEnginePrivate::nativeIsSequential() const
{
return isSequentialFdFh();
}
bool QFSFileEngine::link(const QString &newName)
{
Q_D(QFSFileEngine);
QSystemError error;
bool ret = QFileSystemEngine::createLink(d->fileEntry, QFileSystemEntry(newName), error);
if (!ret) {
setError(QFile::RenameError, error.toString());
}
return ret;
}
qint64 QFSFileEnginePrivate::nativeSize() const
{
return sizeFdFh();
}
QString QFSFileEngine::currentPath(const QString &)
{
return QFileSystemEngine::currentPath().filePath();
}
QFileInfoList QFSFileEngine::drives()
{
QFileInfoList ret;
ret.append(QFileInfo(QFileSystemEngine::rootPath()));
return ret;
}
bool QFSFileEnginePrivate::doStat(QFileSystemMetaData::MetaDataFlags flags) const
{
if (!tried_stat || !metaData.hasFlags(flags)) {
tried_stat = 1;
int localFd = fd;
if (fh && fileEntry.isEmpty())
localFd = QT_FILENO(fh);
if (localFd != -1)
QFileSystemEngine::fillMetaData(localFd, metaData);
if (metaData.missingFlags(flags) && !fileEntry.isEmpty())
QFileSystemEngine::fillMetaData(fileEntry, metaData, metaData.missingFlags(flags));
}
return metaData.exists();
}
bool QFSFileEnginePrivate::isSymlink() const
{
if (!metaData.hasFlags(QFileSystemMetaData::LinkType))
QFileSystemEngine::fillMetaData(fileEntry, metaData, QFileSystemMetaData::LinkType);
return metaData.isLink();
}
/*!
\reimp
*/
QAbstractFileEngine::FileFlags QFSFileEngine::fileFlags(FileFlags type) const
{
Q_D(const QFSFileEngine);
if (type & Refresh)
d->metaData.clear();
QAbstractFileEngine::FileFlags ret = { };
if (type & FlagsMask)
ret |= LocalDiskFlag;
bool exists;
{
QFileSystemMetaData::MetaDataFlags queryFlags = { };
queryFlags |= QFileSystemMetaData::MetaDataFlags(uint(type.toInt()))
& QFileSystemMetaData::Permissions;
if (type & TypesMask)
queryFlags |= QFileSystemMetaData::AliasType
| QFileSystemMetaData::LinkType
| QFileSystemMetaData::FileType
| QFileSystemMetaData::DirectoryType
| QFileSystemMetaData::BundleType
| QFileSystemMetaData::WasDeletedAttribute;
if (type & FlagsMask)
queryFlags |= QFileSystemMetaData::HiddenAttribute
| QFileSystemMetaData::ExistsAttribute;
else if (type & ExistsFlag)
queryFlags |= QFileSystemMetaData::WasDeletedAttribute;
queryFlags |= QFileSystemMetaData::LinkType;
exists = d->doStat(queryFlags);
}
if (!exists && !d->metaData.isLink())
return ret;
if (exists && (type & PermsMask))
ret |= FileFlags(uint(d->metaData.permissions().toInt()));
if (type & TypesMask) {
if (d->metaData.isAlias()) {
ret |= LinkType;
} else {
if ((type & LinkType) && d->metaData.isLink())
ret |= LinkType;
if (exists) {
if (d->metaData.isFile()) {
ret |= FileType;
} else if (d->metaData.isDirectory()) {
ret |= DirectoryType;
if ((type & BundleType) && d->metaData.isBundle())
ret |= BundleType;
}
}
}
}
if (type & FlagsMask) {
// the inode existing does not mean the file exists
if (!d->metaData.wasDeleted())
ret |= ExistsFlag;
if (d->fileEntry.isRoot())
ret |= RootFlag;
else if (d->metaData.isHidden())
ret |= HiddenFlag;
}
return ret;
}
QByteArray QFSFileEngine::id() const
{
Q_D(const QFSFileEngine);
if (d->fd != -1)
return QFileSystemEngine::id(d->fd);
return QFileSystemEngine::id(d->fileEntry);
}
QString QFSFileEngine::fileName(FileName file) const
{
Q_D(const QFSFileEngine);
switch (file) {
case BundleName:
return QFileSystemEngine::bundleName(d->fileEntry);
case BaseName:
return d->fileEntry.fileName();
case PathName:
return d->fileEntry.path();
case AbsoluteName:
case AbsolutePathName: {
QFileSystemEntry entry(QFileSystemEngine::absoluteName(d->fileEntry));
return file == AbsolutePathName ? entry.path() : entry.filePath();
}
case CanonicalName:
case CanonicalPathName: {
QFileSystemEntry entry(QFileSystemEngine::canonicalName(d->fileEntry, d->metaData));
return file == CanonicalPathName ? entry.path() : entry.filePath();
}
case AbsoluteLinkTarget:
if (d->isSymlink()) {
QFileSystemEntry entry = QFileSystemEngine::getLinkTarget(d->fileEntry, d->metaData);
return entry.filePath();
}
return QString();
case RawLinkPath:
if (d->isSymlink()) {
QFileSystemEntry entry = QFileSystemEngine::getRawLinkPath(d->fileEntry, d->metaData);
return entry.filePath();
}
return QString();
case JunctionName:
return QString();
case DefaultName:
case NFileNames:
break;
}
return d->fileEntry.filePath();
}
bool QFSFileEngine::isRelativePath() const
{
Q_D(const QFSFileEngine);
const QString fp = d->fileEntry.filePath();
return fp.isEmpty() || fp.at(0) != u'/';
}
uint QFSFileEngine::ownerId(FileOwner own) const
{
Q_D(const QFSFileEngine);
static const uint nobodyID = (uint) -2;
if (d->doStat(QFileSystemMetaData::OwnerIds))
return d->metaData.ownerId(own);
return nobodyID;
}
QString QFSFileEngine::owner(FileOwner own) const
{
if (own == OwnerUser)
return QFileSystemEngine::resolveUserName(ownerId(own));
return QFileSystemEngine::resolveGroupName(ownerId(own));
}
bool QFSFileEngine::setPermissions(uint perms)
{
Q_D(QFSFileEngine);
QSystemError error;
bool ok;
// clear cached state (if any)
d->metaData.clearFlags(QFileSystemMetaData::Permissions);
if (d->fd != -1)
ok = QFileSystemEngine::setPermissions(d->fd, QFile::Permissions(perms), error);
else
ok = QFileSystemEngine::setPermissions(d->fileEntry, QFile::Permissions(perms), error);
if (!ok) {
setError(QFile::PermissionsError, error.toString());
return false;
}
return true;
}
bool QFSFileEngine::setSize(qint64 size)
{
Q_D(QFSFileEngine);
bool ret = false;
if (d->fd != -1)
ret = QT_FTRUNCATE(d->fd, size) == 0;
else if (d->fh)
ret = QT_FTRUNCATE(QT_FILENO(d->fh), size) == 0;
else
ret = QT_TRUNCATE(d->fileEntry.nativeFilePath().constData(), size) == 0;
if (!ret)
setError(QFile::ResizeError, qt_error_string(errno));
return ret;
}
bool QFSFileEngine::setFileTime(const QDateTime &newDate, QFile::FileTime time)
{
Q_D(QFSFileEngine);
if (d->openMode == QIODevice::NotOpen) {
setError(QFile::PermissionsError, qt_error_string(EACCES));
return false;
}
QSystemError error;
if (!QFileSystemEngine::setFileTime(d->nativeHandle(), newDate, time, error)) {
setError(QFile::PermissionsError, error.toString());
return false;
}
d->metaData.clearFlags(QFileSystemMetaData::Times);
return true;
}
uchar *QFSFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
{
qint64 maxFileOffset = std::numeric_limits<QT_OFF_T>::max();
#if (defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)) && Q_PROCESSOR_WORDSIZE == 4
// The Linux mmap2 system call on 32-bit takes a page-shifted 32-bit
// integer so the maximum offset is 1 << (32+12) (the shift is always 12,
// regardless of the actual page size). Unfortunately, the mmap64()
// function is known to be broken in all Linux libcs (glibc, uclibc, musl
// and Bionic): all of them do the right shift, but don't confirm that the
// result fits into the 32-bit parameter to the kernel.
maxFileOffset = qMin((Q_INT64_C(1) << (32+12)) - 1, maxFileOffset);
#endif
Q_Q(QFSFileEngine);
if (openMode == QIODevice::NotOpen) {
q->setError(QFile::PermissionsError, qt_error_string(EACCES));
return nullptr;
}
if (offset < 0 || offset > maxFileOffset
|| size <= 0
|| quint64(size) > quint64(size_t(-1))) {
q->setError(QFile::UnspecifiedError, qt_error_string(EINVAL));
return nullptr;
}
// If we know the mapping will extend beyond EOF, fail early to avoid
// undefined behavior. Otherwise, let mmap have its say.
if (doStat(QFileSystemMetaData::SizeAttribute)
&& (QT_OFF_T(size) > metaData.size() - QT_OFF_T(offset)))
qWarning("QFSFileEngine::map: Mapping a file beyond its size is not portable");
int access = 0;
if (openMode & QIODevice::ReadOnly) access |= PROT_READ;
if (openMode & QIODevice::WriteOnly) access |= PROT_WRITE;
int sharemode = MAP_SHARED;
if (flags & QFileDevice::MapPrivateOption) {
sharemode = MAP_PRIVATE;
access |= PROT_WRITE;
}
#if defined(Q_OS_INTEGRITY)
int pageSize = sysconf(_SC_PAGESIZE);
#else
int pageSize = getpagesize();
#endif
int extra = offset % pageSize;
if (quint64(size + extra) > quint64((size_t)-1)) {
q->setError(QFile::UnspecifiedError, qt_error_string(EINVAL));
return nullptr;
}
size_t realSize = (size_t)size + extra;
QT_OFF_T realOffset = QT_OFF_T(offset);
realOffset &= ~(QT_OFF_T(pageSize - 1));
void *mapAddress = QT_MMAP((void*)nullptr, realSize,
access, sharemode, nativeHandle(), realOffset);
if (MAP_FAILED != mapAddress) {
uchar *address = extra + static_cast<uchar*>(mapAddress);
maps[address] = {extra, realSize};
return address;
}
switch(errno) {
case EBADF:
q->setError(QFile::PermissionsError, qt_error_string(EACCES));
break;
case ENFILE:
case ENOMEM:
q->setError(QFile::ResourceError, qt_error_string(errno));
break;
case EINVAL:
// size are out of bounds
default:
q->setError(QFile::UnspecifiedError, qt_error_string(errno));
break;
}
return nullptr;
}
bool QFSFileEnginePrivate::unmap(uchar *ptr)
{
#if !defined(Q_OS_INTEGRITY)
Q_Q(QFSFileEngine);
const auto it = std::as_const(maps).find(ptr);
if (it == maps.cend()) {
q->setError(QFile::PermissionsError, qt_error_string(EACCES));
return false;
}
uchar *start = ptr - it->start;
size_t len = it->length;
if (-1 == munmap(start, len)) {
q->setError(QFile::UnspecifiedError, qt_error_string(errno));
return false;
}
maps.erase(it);
return true;
#else
return false;
#endif
}
/*!
\reimp
*/
QAbstractFileEngine::TriStateResult QFSFileEngine::cloneTo(QAbstractFileEngine *target)
{
Q_D(QFSFileEngine);
if ((target->fileFlags(LocalDiskFlag) & LocalDiskFlag) == 0)
return TriStateResult::NotSupported;
int srcfd = d->nativeHandle();
int dstfd = target->handle();
TriStateResult r = QFileSystemEngine::cloneFile(srcfd, dstfd, d->metaData);
if (r == TriStateResult::Failed)
setError(QFile::CopyError, qt_error_string(errno));
return r;
}
QT_END_NAMESPACE
#endif // QT_NO_FSFILEENGINE