Windows: Fix QLockFile hanging when file cannot be created.

Return QLockFile::PermissionError when file does not exist, preventing
the stale file detection logic from triggering.

Add Windows-only autotest trying to create a lock file in
a system folder guarded with checks for elevated processes
and UAC virtualization.

Task-number: QTBUG-45631
Change-Id: I1790f8f925660f6bf1df94c2ced901e6ec57cbb0
Reviewed-by: Joerg Bornemann <joerg.bornemann@theqtcompany.com>
Reviewed-by: Oliver Wolff <oliver.wolff@theqtcompany.com>
This commit is contained in:
Friedemann Kleint 2015-04-20 10:57:20 +02:00
parent 1f2ce78f16
commit 6545404afa
3 changed files with 78 additions and 1 deletions

View File

@ -48,6 +48,12 @@ static inline QByteArray localHostName()
return qgetenv("COMPUTERNAME");
}
static inline bool fileExists(const wchar_t *fileName)
{
WIN32_FILE_ATTRIBUTE_DATA data;
return GetFileAttributesEx(fileName, GetFileExInfoStandard, &data);
}
QLockFile::LockError QLockFilePrivate::tryLock_sys()
{
const QFileSystemEntry fileEntry(fileName);
@ -79,8 +85,13 @@ QLockFile::LockError QLockFilePrivate::tryLock_sys()
case ERROR_SHARING_VIOLATION:
case ERROR_ALREADY_EXISTS:
case ERROR_FILE_EXISTS:
case ERROR_ACCESS_DENIED: // readonly file, or file still in use by another process. Assume the latter, since we don't create it readonly.
return QLockFile::LockFailedError;
case ERROR_ACCESS_DENIED:
// readonly file, or file still in use by another process.
// Assume the latter if the file exists, since we don't create it readonly.
return fileExists((const wchar_t*)fileEntry.nativeFilePath().utf16())
? QLockFile::LockFailedError
: QLockFile::PermissionError;
default:
qWarning() << "Got unexpected locking error" << lastError;
return QLockFile::UnknownError;

View File

@ -36,8 +36,11 @@
#include <QtConcurrentRun>
#include <qlockfile.h>
#include <qtemporarydir.h>
#include <qsysinfo.h>
#if defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS)
#include <unistd.h>
#elif defined(Q_OS_WIN) && !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT)
# include <qt_windows.h>
#endif
class tst_QLockFile : public QObject
@ -58,6 +61,7 @@ private slots:
void staleLongLockFromBusyProcess();
void staleLockRace();
void noPermissions();
void noPermissionsWindows();
public:
QString m_helperApp;
@ -415,5 +419,66 @@ void tst_QLockFile::noPermissions()
QCOMPARE(int(lockFile.error()), int(QLockFile::PermissionError));
}
enum ProcessProperty {
ElevatedProcess = 0x1,
VirtualStore = 0x2
};
Q_DECLARE_FLAGS(ProcessProperties, ProcessProperty)
Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessProperties)
static inline ProcessProperties processProperties()
{
ProcessProperties result;
#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT)
HANDLE processToken = NULL;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &processToken)) {
DWORD elevation; // struct containing a DWORD, not present in some MinGW headers.
DWORD cbSize = sizeof(elevation);
if (GetTokenInformation(processToken, TokenElevation, &elevation, cbSize, &cbSize)
&& elevation) {
result |= ElevatedProcess;
}
// Check for UAC virtualization (compatibility mode for old software
// allowing it to write to system folders by mirroring them under
// "\Users\...\AppData\Local\VirtualStore\", which is typically the case
// for MinGW).
DWORD virtualStoreEnabled = 0;
cbSize = sizeof(virtualStoreEnabled);
if (GetTokenInformation(processToken, TokenVirtualizationEnabled, &virtualStoreEnabled, cbSize, &cbSize)
&& virtualStoreEnabled) {
result |= VirtualStore;
}
CloseHandle(processToken);
}
#endif
return result;
}
void tst_QLockFile::noPermissionsWindows()
{
// Windows: Do the permissions test in a system directory in which
// files cannot be created.
#if !defined(Q_OS_WIN) || defined(Q_OS_WINCE) || defined(Q_OS_WINRT)
QSKIP("This test is for desktop Windows only");
#endif
#ifdef Q_OS_WIN
if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS7)
QSKIP("This test requires at least Windows 7");
#endif
if (const int p = processProperties()) {
const QByteArray message = "This test cannot be run (properties=0x"
+ QByteArray::number(p, 16) + ')';
QSKIP(message.constData());
}
const QString fileName = QFile::decodeName(qgetenv("ProgramFiles"))
+ QLatin1Char('/') + QCoreApplication::applicationName()
+ QDateTime::currentDateTime().toString(QStringLiteral("yyMMddhhmm"));
QLockFile lockFile(fileName);
QVERIFY(!lockFile.lock());
QCOMPARE(int(lockFile.error()), int(QLockFile::PermissionError));
}
QTEST_MAIN(tst_QLockFile)
#include "tst_qlockfile.moc"

View File

@ -4,3 +4,4 @@ TARGET = tst_qlockfile
SOURCES += tst_qlockfile.cpp
QT = core testlib concurrent
win32:!wince:!winrt:LIBS += -ladvapi32