Revision control
Copy as Markdown
// Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
// Copyright (C) 2016 The Qt Company Ltd.
// Copyright (C) 2017 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "private/qlockfile_p.h"
#include "private/qfilesystementry_p.h"
#include "wcharhelpers_win_p.h"
#include "QtCore/qdatetime.h"
#include "QtCore/qdir.h"
#include "QtCore/qdebug.h"
#include "QtCore/qfileinfo.h"
#include "QtCore/qthread.h"
#include <qt_windows.h>
#include <psapi.h>
#include <io.h>
#include <fcntl.h>
QT_BEGIN_NAMESPACE
static inline bool fileExists(const wchar_t *fileName)
{
WIN32_FILE_ATTRIBUTE_DATA data;
return GetFileAttributesEx(fileName, GetFileExInfoStandard, &data);
}
static bool deleteFile(const QString &fileName)
{
const DWORD dwShareMode = 0; // no sharing
SECURITY_ATTRIBUTES securityAtts = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };
HANDLE fh = CreateFile(qt_castToWchar(QDir::toNativeSeparators(fileName)),
GENERIC_READ | GENERIC_WRITE,
dwShareMode,
&securityAtts,
OPEN_EXISTING, // error if it doesn't exist
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
NULL);
bool success = (fh != INVALID_HANDLE_VALUE);
if (success) {
CloseHandle(fh);
// the file is now deleted
} else {
const DWORD lastError = GetLastError();
if (lastError == ERROR_FILE_NOT_FOUND)
success = true;
}
return success;
}
QLockFile::LockError QLockFilePrivate::tryLock_sys(const QLockFilePrivate::LockFileInfo ¤t)
{
const QFileSystemEntry fileEntry(fileName);
// When writing, allow others to read.
// When reading, QFile will allow others to read and write, all good.
// ### Open the file with DELETE permission and use
// SetFileInformationByHandle to delete the file without needing to close
// the handle first, to avoid someone opening the handle again without the
// FILE_SHARE_DELETE flag in-between closure and deletion.
const DWORD dwShareMode = FILE_SHARE_READ;
SECURITY_ATTRIBUTES securityAtts = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };
HANDLE fh = CreateFile((const wchar_t*)fileEntry.nativeFilePath().utf16(),
GENERIC_READ | GENERIC_WRITE,
dwShareMode,
&securityAtts,
CREATE_NEW, // error if already exists
FILE_ATTRIBUTE_NORMAL,
NULL);
if (fh == INVALID_HANDLE_VALUE) {
const DWORD lastError = GetLastError();
switch (lastError) {
case ERROR_SHARING_VIOLATION:
case ERROR_ALREADY_EXISTS:
case ERROR_FILE_EXISTS:
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 %llu", quint64(lastError));
return QLockFile::UnknownError;
}
}
// We hold the lock, continue.
fileHandle = fh;
QByteArray fileData = current.asFileContents();
DWORD bytesWritten = 0;
QLockFile::LockError error = QLockFile::NoError;
if (!WriteFile(fh, fileData.constData(), fileData.size(), &bytesWritten, NULL) || !FlushFileBuffers(fh))
error = QLockFile::UnknownError; // partition full
return error;
}
bool QLockFilePrivate::removeStaleLock()
{
// DeleteFile fails if the other process is still using the file, so it's not stale.
return deleteFile(fileName);
}
bool QLockFilePrivate::isProcessRunning(qint64 pid, const QString &appname)
{
HANDLE procHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (!procHandle)
return false;
// We got a handle but check if process is still alive
DWORD exitCode = 0;
if (!::GetExitCodeProcess(procHandle, &exitCode))
exitCode = 0;
::CloseHandle(procHandle);
if (exitCode != STILL_ACTIVE)
return false;
const QString processName = processNameByPid(pid);
if (!processName.isEmpty() && processName != appname)
return false; // PID got reused by a different application.
return true;
}
QString QLockFilePrivate::processNameByPid(qint64 pid)
{
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, DWORD(pid));
if (!hProcess) {
return QString();
}
wchar_t buf[MAX_PATH];
const DWORD length = GetModuleFileNameExW(hProcess, NULL, buf, sizeof(buf) / sizeof(wchar_t));
CloseHandle(hProcess);
if (!length)
return QString();
QString name = QString::fromWCharArray(buf, length);
int i = name.lastIndexOf(u'\\');
if (i >= 0)
name.remove(0, i + 1);
i = name.lastIndexOf(u'.');
if (i >= 0)
name.truncate(i);
return name;
}
int QLockFilePrivate::openNewFileDescriptor(const QString &fileName)
{
// We currently open with FILE_SHARE_DELETE, which would allow deletion to
// be requested even while other processes have the file open. We mostly
// want to do this so we can later open the file with the DELETE permission
// to delete the file using SetFileInformationByHandle, avoiding the need
// to close the handle first, where e.g. search indexer or antivirus may
// see their chance to open the file before we can delete it.
// We can't make this change immediately because currently-deployed
// applications will not be using FILE_SHARE_DELETE, so they would suddenly
// be unable to read the lockfile information.
HANDLE handle = CreateFile(reinterpret_cast<const wchar_t *>(fileName.utf16()), GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (handle == INVALID_HANDLE_VALUE)
return -1;
int fd = _open_osfhandle(intptr_t(handle), _O_RDONLY);
if (fd == -1) {
CloseHandle(handle);
return -1;
}
return fd;
}
void QLockFile::unlock()
{
Q_D(QLockFile);
if (!d->isLocked)
return;
CloseHandle(d->fileHandle);
int attempts = 0;
static const int maxAttempts = 500; // 500ms
while (!deleteFile(d->fileName) && ++attempts < maxAttempts) {
// Someone is reading the lock file right now (on Windows this prevents deleting it).
QThread::msleep(1);
}
if (attempts == maxAttempts) {
qWarning() << "Could not remove our own lock file" << d->fileName
<< ". Either other users of the lock file are reading it constantly for 500 ms, "
"or we (no longer) have permissions to delete the file";
// This is bad because other users of this lock file will now have to wait for the
// stale-lock-timeout...
}
d->lockError = QLockFile::NoError;
d->isLocked = false;
}
QT_END_NAMESPACE