Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qlockfile_win.cpp
Go to the documentation of this file.
1// Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
2// Copyright (C) 2016 The Qt Company Ltd.
3// Copyright (C) 2017 Intel Corporation.
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5// Qt-Security score:significant reason:default
6
7#include "private/qlockfile_p.h"
8#include "private/qfilesystementry_p.h"
10
11#include "QtCore/qdatetime.h"
12#include "QtCore/qdir.h"
13#include "QtCore/qdebug.h"
14#include "QtCore/qfileinfo.h"
15#include "QtCore/qthread.h"
16
17#include <qt_windows.h>
18#include <psapi.h>
19#include <io.h>
20#include <fcntl.h>
21
23
24static inline bool fileExists(const wchar_t *fileName)
25{
26 WIN32_FILE_ATTRIBUTE_DATA data;
27 return GetFileAttributesEx(fileName, GetFileExInfoStandard, &data);
28}
29
30static bool deleteFile(const QString &fileName)
31{
32 const DWORD dwShareMode = 0; // no sharing
33 SECURITY_ATTRIBUTES securityAtts = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };
34 HANDLE fh = CreateFile(qt_castToWchar(QDir::toNativeSeparators(fileName)),
35 GENERIC_READ | GENERIC_WRITE,
36 dwShareMode,
37 &securityAtts,
38 OPEN_EXISTING, // error if it doesn't exist
39 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
40 NULL);
41 bool success = (fh != INVALID_HANDLE_VALUE);
42 if (success) {
43 CloseHandle(fh);
44 // the file is now deleted
45 } else {
46 const DWORD lastError = GetLastError();
47 if (lastError == ERROR_FILE_NOT_FOUND)
48 success = true;
49 }
50 return success;
51}
52
53QLockFile::LockError QLockFilePrivate::tryLock_sys()
54{
55 const QFileSystemEntry fileEntry(fileName);
56 // When writing, allow others to read.
57 // When reading, QFile will allow others to read and write, all good.
58 // ### Open the file with DELETE permission and use
59 // SetFileInformationByHandle to delete the file without needing to close
60 // the handle first, to avoid someone opening the handle again without the
61 // FILE_SHARE_DELETE flag in-between closure and deletion.
62 const DWORD dwShareMode = FILE_SHARE_READ;
63 SECURITY_ATTRIBUTES securityAtts = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };
64 HANDLE fh = CreateFile((const wchar_t*)fileEntry.nativeFilePath().utf16(),
65 GENERIC_READ | GENERIC_WRITE,
66 dwShareMode,
67 &securityAtts,
68 CREATE_NEW, // error if already exists
69 FILE_ATTRIBUTE_NORMAL,
70 NULL);
71 if (fh == INVALID_HANDLE_VALUE) {
72 const DWORD lastError = GetLastError();
73 switch (lastError) {
74 case ERROR_SHARING_VIOLATION:
75 case ERROR_ALREADY_EXISTS:
76 case ERROR_FILE_EXISTS:
77 return QLockFile::LockFailedError;
78 case ERROR_ACCESS_DENIED:
79 // readonly file, or file still in use by another process.
80 // Assume the latter if the file exists, since we don't create it readonly.
81 return fileExists((const wchar_t*)fileEntry.nativeFilePath().utf16())
82 ? QLockFile::LockFailedError
83 : QLockFile::PermissionError;
84 default:
85 qWarning("Got unexpected locking error %llu", quint64(lastError));
86 return QLockFile::UnknownError;
87 }
88 }
89
90 // We hold the lock, continue.
91 fileHandle = fh;
92 QByteArray fileData = lockFileContents();
93 DWORD bytesWritten = 0;
94 QLockFile::LockError error = QLockFile::NoError;
95 if (!WriteFile(fh, fileData.constData(), fileData.size(), &bytesWritten, NULL) || !FlushFileBuffers(fh))
96 error = QLockFile::UnknownError; // partition full
97 return error;
98}
99
100bool QLockFilePrivate::removeStaleLock()
101{
102 // DeleteFile fails if the other process is still using the file, so it's not stale.
103 return deleteFile(fileName);
104}
105
106bool QLockFilePrivate::isProcessRunning(qint64 pid, const QString &appname)
107{
108 HANDLE procHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
109 if (!procHandle)
110 return false;
111
112 // We got a handle but check if process is still alive
113 DWORD exitCode = 0;
114 if (!::GetExitCodeProcess(procHandle, &exitCode))
115 exitCode = 0;
116 ::CloseHandle(procHandle);
117 if (exitCode != STILL_ACTIVE)
118 return false;
119
120 const QString processName = processNameByPid(pid);
121 if (!processName.isEmpty() && processName != appname)
122 return false; // PID got reused by a different application.
123
124 return true;
125}
126
127QString QLockFilePrivate::processNameByPid(qint64 pid)
128{
129 HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, DWORD(pid));
130 if (!hProcess) {
131 return QString();
132 }
133 wchar_t buf[MAX_PATH];
134 const DWORD length = GetModuleFileNameExW(hProcess, NULL, buf, sizeof(buf) / sizeof(wchar_t));
135 CloseHandle(hProcess);
136 if (!length)
137 return QString();
138 QString name = QString::fromWCharArray(buf, length);
139 int i = name.lastIndexOf(u'\\');
140 if (i >= 0)
141 name.remove(0, i + 1);
142 i = name.lastIndexOf(u'.');
143 if (i >= 0)
144 name.truncate(i);
145 return name;
146}
147
148int QLockFilePrivate::openNewFileDescriptor(const QString &fileName)
149{
150 // We currently open with FILE_SHARE_DELETE, which would allow deletion to
151 // be requested even while other processes have the file open. We mostly
152 // want to do this so we can later open the file with the DELETE permission
153 // to delete the file using SetFileInformationByHandle, avoiding the need
154 // to close the handle first, where e.g. search indexer or antivirus may
155 // see their chance to open the file before we can delete it.
156 // We can't make this change immediately because currently-deployed
157 // applications will not be using FILE_SHARE_DELETE, so they would suddenly
158 // be unable to read the lockfile information.
159 HANDLE handle = CreateFile(reinterpret_cast<const wchar_t *>(fileName.utf16()), GENERIC_READ,
160 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
161 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
162 if (handle == INVALID_HANDLE_VALUE)
163 return -1;
164 int fd = _open_osfhandle(intptr_t(handle), _O_RDONLY);
165 if (fd == -1) {
166 CloseHandle(handle);
167 return -1;
168 }
169 return fd;
170}
171
172void QLockFile::unlock()
173{
174 Q_D(QLockFile);
175 if (!d->isLocked)
176 return;
177 CloseHandle(d->fileHandle);
178 int attempts = 0;
179 static const int maxAttempts = 500; // 500ms
180 while (!deleteFile(d->fileName) && ++attempts < maxAttempts) {
181 // Someone is reading the lock file right now (on Windows this prevents deleting it).
182 QThread::msleep(1);
183 }
184 if (attempts == maxAttempts) {
185 qWarning() << "Could not remove our own lock file" << d->fileName
186 << ". Either other users of the lock file are reading it constantly for 500 ms, "
187 "or we (no longer) have permissions to delete the file";
188 // This is bad because other users of this lock file will now have to wait for the
189 // stale-lock-timeout...
190 }
191 d->lockError = QLockFile::NoError;
192 d->isLocked = false;
193}
194
195QT_END_NAMESPACE
static bool deleteFile(const QString &fileName)
static QT_BEGIN_NAMESPACE bool fileExists(const wchar_t *fileName)