From b853157f582d9d8b573d13c3b8fc6b8b2bc1b4da Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Thu, 6 Dec 2012 09:33:03 +0100 Subject: [PATCH] Add API for file system types in QFileSystemEngine. Add file system type detection code based on kfilesystemtype_p.cpp and Windows code. Add flags indicating case sensitivity and read-only mounts. Add a manual test for various file operations. Task-number: QTBUG-3570 Task-number: QTBUG-28246 Change-Id: Id8115520ecded7c685400dab32ead463a2aabb96 --- src/corelib/io/qfilesystemengine.cpp | 6 ++ src/corelib/io/qfilesystemengine_p.h | 25 ++++- src/corelib/io/qfilesystemengine_unix.cpp | 163 ++++++++++++++++++++++++++++++ src/corelib/io/qfilesystemengine_win.cpp | 41 ++++++++ src/corelib/io/qfilesystementry.cpp | 37 +++++++ src/corelib/io/qfilesystementry_p.h | 1 + tests/manual/filetest/filetest.pro | 6 ++ tests/manual/filetest/main.cpp | 141 ++++++++++++++++++++++++++ tests/manual/manual.pro | 1 + 9 files changed, 419 insertions(+), 2 deletions(-) create mode 100644 tests/manual/filetest/filetest.pro create mode 100644 tests/manual/filetest/main.cpp diff --git a/src/corelib/io/qfilesystemengine.cpp b/src/corelib/io/qfilesystemengine.cpp index 4564ede..99656d6 100644 --- a/src/corelib/io/qfilesystemengine.cpp +++ b/src/corelib/io/qfilesystemengine.cpp @@ -422,4 +422,10 @@ QString QFileSystemEngine::resolveGroupName(const QFileSystemEntry &entry, QFile #endif } +// static +QFileSystemEngine::FileSystemFlags QFileSystemEngine::fileSystemFlags(const QString &path, bool *ok) +{ + return fileSystemFlags(QFileSystemEntry(path), ok); +} + QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystemengine_p.h b/src/corelib/io/qfilesystemengine_p.h index bb91cf1..3263574 100644 --- a/src/corelib/io/qfilesystemengine_p.h +++ b/src/corelib/io/qfilesystemengine_p.h @@ -54,16 +54,35 @@ // #include "qfile.h" +#include "qflags.h" #include "qfilesystementry_p.h" #include "qfilesystemmetadata_p.h" #include QT_BEGIN_NAMESPACE -class QFileSystemEngine +class Q_AUTOTEST_EXPORT QFileSystemEngine { public: - static bool isCaseSensitive() + enum FileSystemFlag { + UnixFileSystemType = 0x001, + NtfsFileSystemType = 0x002, + FatFileSystemType = 0x004, + NfsFileSystemType = 0x008, // NFS, autofs, etc. + SmbFileSystemType = 0x010, // SMB, CIFS + UsbFileSystemType = 0x020, + IsoUdfFileSystemType = 0x020, // CD, DVD, etc. (ISO or UDF) + FileSystemTypeMask = 0xFFFF, + CaseSensitiveFileSystem = 0x10000, + ReadOnlyFileSystem = 0x20000 + }; + + Q_DECLARE_FLAGS(FileSystemFlags, FileSystemFlag) + + static FileSystemFlags fileSystemFlags(const QFileSystemEntry &entry, bool *ok = 0); + static FileSystemFlags fileSystemFlags(const QString &path, bool *ok = 0); + + static bool isCaseSensitive() // Do not use, see fileSystemFlags(). { #ifndef Q_OS_WIN return true; @@ -135,6 +154,8 @@ private: #endif }; +Q_DECLARE_OPERATORS_FOR_FLAGS(QFileSystemEngine::FileSystemFlags) + QT_END_NAMESPACE #endif // include guard diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp index 5870cdf..d4a278f 100644 --- a/src/corelib/io/qfilesystemengine_unix.cpp +++ b/src/corelib/io/qfilesystemengine_unix.cpp @@ -50,6 +50,28 @@ #include #include +// Figure out how to determine the file system type. +#if defined(Q_OS_BSD4) && !defined(Q_OS_NETBSD) // Includes Mac. +# define Q_FILESYSTEMTYPE_USE_STATFS +#elif defined(Q_OS_LINUX) || defined(Q_OS_HURD) +# define Q_FILESYSTEMTYPE_LINUX +#elif defined(Q_OS_SOLARIS) || defined(Q_OS_IRIX) || defined(Q_OS_AIX) || defined(Q_OS_HPUX) \ + || defined(Q_OS_OSF) || defined(Q_OS_QNX) || defined(Q_OS_RELIANT) || defined(Q_OS_NETBSD) +# define Q_FILESYSTEMTYPE_USE_STATVFS +#else +# define Q_FILESYSTEMTYPE_NOT_IMPLEMENTED +#endif + +#if defined(Q_FILESYSTEMTYPE_USE_STATFS) +# include +# include +# include +#elif defined(Q_FILESYSTEMTYPE_LINUX) +# include +# include // For mountflags. +#elif defined(Q_FILESYSTEMTYPE_USE_STATVFS) +# include +#endif #if defined(Q_OS_MAC) # include @@ -676,4 +698,145 @@ QFileSystemEntry QFileSystemEngine::currentPath() } return result; } + +#if defined(Q_FILESYSTEMTYPE_USE_STATFS) || defined(Q_FILESYSTEMTYPE_USE_STATVFS) +// Convert BSD statfs type names. +static inline QFileSystemEngine::FileSystemFlags fileSystemTypeFromName(const char *name) +{ + if (qstrncmp(name, "hfs", 3) == 0 || qstrncmp(name, "ufs", 3) == 0) + return QFileSystemEngine::UnixFileSystemType; + if (qstrncmp(name, "nfs", 3) == 0 + || qstrncmp(name, "autofs", 6) == 0 + || qstrncmp(name, "cachefs", 7) == 0 + || qstrncmp(name, "fuse.sshfs", 10) == 0 + || qstrncmp(name, "xtreemfs@", 9) == 0) + return QFileSystemEngine::NfsFileSystemType; + if (qstrncmp(name, "fat", 3) == 0 + || qstrncmp(name, "vfat", 4) == 0 + || qstrncmp(name, "msdos", 5) == 0) + return QFileSystemEngine::FatFileSystemType; + if (qstrncmp(name, "cifs", 4) == 0 || qstrncmp(name, "smbfs", 5) == 0) + return QFileSystemEngine::SmbFileSystemType; + if (qstrncmp(name, "iso9660", 7) == 0) + return QFileSystemEngine::IsoUdfFileSystemType; + return QFileSystemEngine::UnixFileSystemType; +} +#endif // Q_FILESYSTEMTYPE_USE_STATFS || Q_FILESYSTEMTYPE_USE_STATVFS + +QFileSystemEngine::FileSystemFlags QFileSystemEngine::fileSystemFlags(const QFileSystemEntry &entry, bool *ok) +{ +#ifdef Q_OS_MAC + FileSystemFlags defaultType = UnixFileSystemType; // Usually case insensitive (configureable per file system) +#else + FileSystemFlags defaultType = UnixFileSystemType | CaseSensitiveFileSystem; +#endif + if (ok) + *ok = false; + const QByteArray absolutePath = entry.nativeFilePath(); + if (absolutePath.isEmpty()) + return defaultType; +#if defined(Q_FILESYSTEMTYPE_USE_STATFS) || defined(Q_FILESYSTEMTYPE_LINUX) + // Do statfs in order to determine mount point for Mac. On Linux, use the file system type. + struct statfs statResult; + if (statfs(absolutePath.constData(), &statResult)) { + qErrnoWarning("statfs failed for '%s'.", qPrintable(entry.filePath())); + return defaultType; + } +#endif // Q_FILESYSTEMTYPE_USE_STATFS || Q_FILESYSTEMTYPE_LINUX + +#ifdef Q_FILESYSTEMTYPE_USE_STATFS // (BSD ish, Mac) + struct VolumeCapabilitiesAttribute { + u_int32_t length; + u_int32_t capabilities[4]; + u_int32_t valid[4]; + }; + + FileSystemFlags result = fileSystemTypeFromName(statResult.f_fstypename); + if (statResult.f_flags & MNT_RDONLY) + result |= ReadOnlyFileSystem; + // Check capabilities via getattrlist on mount point. + attrlist attrList; + memset(&attrList, 0, sizeof(attrList)); + attrList.bitmapcount = ATTR_BIT_MAP_COUNT;; + attrList.volattr = ATTR_VOL_INFO | ATTR_VOL_CAPABILITIES; + VolumeCapabilitiesAttribute capabilitiesAttribute; + if (getattrlist(statResult.f_mntonname, &attrList, &capabilitiesAttribute, sizeof(capabilitiesAttribute), 0)) { + qErrnoWarning("getattrlist failed for '%s' ('%s').", qPrintable(entry.filePath()), statResult.f_mntonname); + return defaultType; + } + if (capabilitiesAttribute.capabilities[VOL_CAPABILITIES_FORMAT] & VOL_CAP_FMT_CASE_SENSITIVE) + result |= CaseSensitiveFileSystem; + if (ok) + *ok = true; + return result; +#endif // Q_FILESYSTEMTYPE_USE_STATFS + +#ifdef Q_FILESYSTEMTYPE_LINUX + // Linux: check statfs types. + enum { Q_CIFS_MAGIC_NUMBER = 0xFF534D42, Q_MSDOS_SUPER_MAGIC = 0x4d44, + Q_NTFS_SB_MAGIC = 0x5346544e, Q_SMB_SUPER_MAGIC = 0x517B, + Q_USBDEVICE_SUPER_MAGIC = 0x9fa2, Q_ISOFS_SUPER_MAGIC = 0x9660, + Q_NFS_SUPER_MAGIC = 0x6969, Q_UDF_SUPER_MAGIC = 0x15013346, + Q_AUTOFS_SUPER_MAGIC = 0x00000187, Q_AUTOFSNG_SUPER_MAGIC = 0x7d92b1a0, + Q_FUSE_SUPER_MAGIC = 0x65735546 }; + + FileSystemFlags result; + switch (statResult.f_type) { + case Q_MSDOS_SUPER_MAGIC: + result = FatFileSystemType; + break; + case Q_NTFS_SB_MAGIC: + result = NtfsFileSystemType; + break; + case Q_CIFS_MAGIC_NUMBER: + case Q_SMB_SUPER_MAGIC: + result = SmbFileSystemType; + break; + case Q_USBDEVICE_SUPER_MAGIC: + result = UsbFileSystemType; + break; + case Q_ISOFS_SUPER_MAGIC: + case Q_UDF_SUPER_MAGIC: + result = IsoUdfFileSystemType; + break; + case Q_NFS_SUPER_MAGIC: + case Q_AUTOFS_SUPER_MAGIC: + case Q_AUTOFSNG_SUPER_MAGIC: + case Q_FUSE_SUPER_MAGIC: + result = NfsFileSystemType; + break; + default: + result = defaultType; + break; + } + struct statvfs statVResult; + if (!statvfs(absolutePath.constData(), &statVResult) && (statVResult.f_flag & ST_RDONLY)) + result |= ReadOnlyFileSystem; + if (ok) + *ok = true; + return result; +#endif // Q_FILESYSTEMTYPE_LINUX + +#ifdef Q_FILESYSTEMTYPE_USE_STATVFS // QNX, Solaris, IRIX, etx. #fixme: This is untested and the attributes are missing. + struct statvfs statVResult; + if (statvfs(absolutePath.constData(), &statVResult)) { + qErrnoWarning("statvfs failed for '%s'.", qPrintable(entry.filePath())); + return defaultType; + } +# ifdef Q_OS_NETBSD + FileSystemFlags result = fileSystemTypeFromName(statVResult._fstypename); +# else + FileSystemFlags result = fileSystemTypeFromName(statVResult.f_basetype); +# endif + if (ok) + *ok = true; + return result; +#endif // Q_FILESYSTEMTYPE_USE_STATVFS + +#ifdef Q_FILESYSTEMTYPE_NOT_IMPLEMENTED + Q_UNIMPLEMENTED(); + return defaultType; +#endif +} + QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystemengine_win.cpp b/src/corelib/io/qfilesystemengine_win.cpp index 5364a44..4e4cb7a 100644 --- a/src/corelib/io/qfilesystemengine_win.cpp +++ b/src/corelib/io/qfilesystemengine_win.cpp @@ -1252,4 +1252,45 @@ QDateTime QFileSystemMetaData::accessTime() const return fileTimeToQDateTime(&lastAccessTime_); } +QFileSystemEngine::FileSystemFlags QFileSystemEngine::fileSystemFlags(const QFileSystemEntry &entry, bool *ok) +{ + FileSystemFlags defaultType = NtfsFileSystemType; + if (ok) + *ok = false; + const QString root = entry.driveRoot(); + if (root.isEmpty()) + return defaultType; + DWORD fileSystemFlags = 0; + wchar_t fileSystemTypeC[MAX_PATH]; + if (!GetVolumeInformation(reinterpret_cast(root.utf16()), NULL, 0, NULL, NULL, &fileSystemFlags, fileSystemTypeC, MAX_PATH)) { + qErrnoWarning("GetVolumeInformation() failed for '%s' ('%s').", + qPrintable(entry.filePath()), qPrintable(root)); + return defaultType; + } + // Type + FileSystemFlags result = NtfsFileSystemType; + const QString fileSystemType = QString::fromWCharArray(fileSystemTypeC); + if (fileSystemType == QStringLiteral("NTFS")) { + result = NtfsFileSystemType; + } else if (fileSystemType.contains(QStringLiteral("FAT"))) { + result = FatFileSystemType; + } else if (fileSystemType == QStringLiteral("UDF") || fileSystemType.contains(QStringLiteral("ISO"))) { + result = IsoUdfFileSystemType; + } + // Case sensitivity: GetVolumeInformation() reports: + // NTFS: FILE_CASE_PRESERVED_NAMES|FILE_CASE_SENSITIVE_SEARCH + // FAT : FILE_CASE_SENSITIVE_SEARCH (USB sticks, etc with VFAT/FAT32) + // We consider both to be case insensitive. + if (result != FatFileSystemType && result != NtfsFileSystemType + && (fileSystemFlags & FILE_CASE_PRESERVED_NAMES) + && (fileSystemFlags & FILE_CASE_SENSITIVE_SEARCH)) { + result |= CaseSensitiveFileSystem; + } + if (fileSystemFlags & FILE_READ_ONLY_VOLUME) + result |= ReadOnlyFileSystem; + if (ok) + *ok = true; + return result; +} + QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystementry.cpp b/src/corelib/io/qfilesystementry.cpp index b4eff3d..e71a5c7 100644 --- a/src/corelib/io/qfilesystementry.cpp +++ b/src/corelib/io/qfilesystementry.cpp @@ -290,6 +290,43 @@ bool QFileSystemEntry::isDriveRoot() const && m_filePath.at(0).isLetter() && m_filePath.at(1) == QLatin1Char(':') && m_filePath.at(2) == QLatin1Char('/')); } + +// Return the drive root as expected by Windows API like GetVolumeInformation(), +// with the "\\?\UNC\" and "\\?\" prefixes stripped and trailing slash added. +QString QFileSystemEntry::driveRoot() const +{ + QString nativePath = nativeFilePath(); + // Strip: // "\\?\D:\file..." or "\\?\UNC\server\path\file" (as returned + // by QFileEntry.nativeFilePath() to "D:\file..." or "\\server\path\file". + if (nativePath.startsWith(QLatin1String("\\\\?\\UNC\\"))) { + nativePath.remove(2, 6); + } else if (nativePath.size() > 5 && nativePath.at(5) == QLatin1Char(':') + && nativePath.startsWith(QLatin1String("\\\\?\\"))) { + nativePath.remove(0, 4); + } + // Strip to drive letter "c:\" or "\\server\path\". + if (nativePath.size() >= 2 && nativePath.at(1) == QLatin1Char(':')) { + if (nativePath.size() >= 3) { + nativePath.truncate(3); + } else { + nativePath.append(QLatin1Char('\\')); + } + } else if (nativePath.startsWith(QLatin1String("\\\\"))) { + int slashPos = nativePath.indexOf(QLatin1Char('\\'), 2); + if (slashPos == -1) + return QString(); + slashPos = nativePath.indexOf(QLatin1Char('\\'), slashPos + 1); + if (slashPos != -1) { + if (slashPos + 1 < nativePath.size()) + nativePath.truncate(slashPos + 1); + } else { + nativePath.append(QLatin1Char('\\')); + } + } else { + return QString(); + } + return nativePath; +} #endif bool QFileSystemEntry::isRoot() const diff --git a/src/corelib/io/qfilesystementry_p.h b/src/corelib/io/qfilesystementry_p.h index 5b861aa..a0e5e7e 100644 --- a/src/corelib/io/qfilesystementry_p.h +++ b/src/corelib/io/qfilesystementry_p.h @@ -95,6 +95,7 @@ public: #if defined(Q_OS_WIN) bool isDriveRoot() const; + QString driveRoot() const; #endif bool isRoot() const; diff --git a/tests/manual/filetest/filetest.pro b/tests/manual/filetest/filetest.pro new file mode 100644 index 0000000..7441671 --- /dev/null +++ b/tests/manual/filetest/filetest.pro @@ -0,0 +1,6 @@ +TEMPLATE = app +QT = core core-private +CONFIG += console +CONFIG -= app_bundle + +SOURCES += main.cpp diff --git a/tests/manual/filetest/main.cpp b/tests/manual/filetest/main.cpp new file mode 100644 index 0000000..bdb0655 --- /dev/null +++ b/tests/manual/filetest/main.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include + +static const char usage[] = +"\nTests various file functionality in Qt\n\n" +"Usage: %s [KEYWORD] [ARGUMENTS]\n\n" +"Keywords: ls FILES list file information\n" +" mv SOURCE TARGET renames files using QFile::rename\n" +" cp SOURCE TARGET copy files using QFile::copy\n" +" rm FILE remove file using QFile::remove\n" +" rmr DIR remove directory recursively using QDir::removeRecursively\n"; + +static int ls(int argCount, char **args) +{ + for (int i = 0 ; i < argCount; ++i) { + QDebug debug = qDebug().nospace(); + const QFileInfo fi(QString::fromLocal8Bit(args[i])); + debug << QDir::toNativeSeparators(fi.absoluteFilePath()) << ' ' << fi.size(); + if (fi.exists()) + debug << " [exists]"; + if (fi.isFile()) + debug << " [file]"; + if (fi.isSymLink()) { + debug << " [symlink to " << QDir::toNativeSeparators(fi.symLinkTarget()) << ']'; + } + if (fi.isDir()) + debug << " [dir]"; +#ifdef QT_BUILD_INTERNAL // #fixme: QFileSystemEngine is Q_AUTOTEST_EXPORT. Need public API. + bool ok; + const QFileSystemEngine::FileSystemFlags type = QFileSystemEngine::fileSystemFlags(fi.absoluteFilePath(), &ok); + debug << " FileSystem type: " << type << " [ok=" << ok << ']'; +#endif // QT_BUILD_INTERNAL + debug << '\n'; + } + return 0; +} + +static int mv(const char *sourceFileName, const char *targetFileName) +{ + QFile sourceFile(QString::fromLocal8Bit(sourceFileName)); + if (!sourceFile.rename(QString::fromLocal8Bit(targetFileName))) { + qWarning().nospace() << sourceFile.errorString(); + return -1; + } + return 0; +} + +static int cp(const char *sourceFileName, const char *targetFileName) +{ + QFile sourceFile(QString::fromLocal8Bit(sourceFileName)); + if (!sourceFile.copy(QString::fromLocal8Bit(targetFileName))) { + qWarning().nospace() << sourceFile.errorString(); + return -1; + } + return 0; +} + +static int rm(const char *fileName) +{ + QFile file(QString::fromLocal8Bit(fileName)); + if (!file.remove()) { + qWarning().nospace() << file.errorString(); + return -1; + } + return 0; +} + +static int rmr(const char *dirName) +{ + QDir dir(QString::fromLocal8Bit(dirName)); + if (!dir.removeRecursively()) { + qWarning().nospace() << "Failed to remove " << dir.absolutePath(); + return -1; + } + return 0; +} + +int main(int argc, char *argv[]) +{ + if (argc >= 3 && !qstrcmp(argv[1], "ls")) + return ls(argc -2, argv + 2); + + if (argc == 4 && !qstrcmp(argv[1], "mv")) + return mv(argv[2], argv[3]); + + if (argc == 4 && !qstrcmp(argv[1], "cp")) + return cp(argv[2], argv[3]); + + if (argc == 3 && !qstrcmp(argv[1], "rm")) + return rm(argv[2]); + + if (argc == 3 && !qstrcmp(argv[1], "rmr")) + return rmr(argv[2]); + + qDebug(usage, argv[0]); + return 0; +} diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro index cfa812a..525f596 100644 --- a/tests/manual/manual.pro +++ b/tests/manual/manual.pro @@ -1,6 +1,7 @@ TEMPLATE=subdirs SUBDIRS = bearerex \ +filetest \ gestures \ inputmethodhints \ keypadnavigation \ -- 2.5.0.windows.1