Bump version.
[LameXP.git] / src / LockedFile.cpp
blobf88af9ed126cbed8dbb3fb9cecf63daacb1dbbe8
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2021 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU GENERAL PUBLIC LICENSE as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version; always including the non-optional
9 // LAMEXP GNU GENERAL PUBLIC LICENSE ADDENDUM. See "License.txt" file!
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License along
17 // with this program; if not, write to the Free Software Foundation, Inc.,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // http://www.gnu.org/licenses/gpl-2.0.txt
21 ///////////////////////////////////////////////////////////////////////////////
23 #include "LockedFile.h"
25 //Internal
26 #include "Global.h"
27 #include "FileHash.h"
29 //MUtils
30 #include <MUtils/OSSupport.h>
31 #include <MUtils/Exception.h>
33 //Qt
34 #include <QResource>
35 #include <QFile>
36 #include <QFileInfo>
37 #include <QDir>
38 #include <QCryptographicHash>
40 //CRT
41 #include <stdio.h>
42 #include <io.h>
43 #include <fcntl.h>
44 #include <stdexcept>
46 //Windows includes
47 #define NOMINMAX
48 #define WIN32_LEAN_AND_MEAN
49 #include <Windows.h>
51 //Const
52 static const quint32 MAX_LOCK_DELAY = 16384U;
54 ///////////////////////////////////////////////////////////////////////////////
56 // WARNING: Passing file descriptors into Qt does NOT work with dynamically linked CRT!
57 #ifdef QT_NODLL
58 static const bool g_useFileDescrForQFile = true;
59 #else
60 static const bool g_useFileDescrForQFile = false;
61 #endif
63 #define VALID_HANDLE(H) (((H) != NULL) && ((H) != INVALID_HANDLE_VALUE))
65 static __forceinline quint32 NEXT_DELAY(const quint32 delay)
67 return (delay > 0U) ? (delay * 2U) : 1U;
70 static bool PROTECT_HANDLE(const HANDLE &h, const bool &lock)
72 if (SetHandleInformation(h, HANDLE_FLAG_PROTECT_FROM_CLOSE, (lock ? HANDLE_FLAG_PROTECT_FROM_CLOSE : 0U)))
74 return true;
76 return false;
79 static void CLOSE_HANDLE(HANDLE &h)
81 if(VALID_HANDLE(h))
83 PROTECT_HANDLE(h, false);
84 CloseHandle(h);
86 h = NULL;
89 static void CLOSE_FILE(int &fd)
91 if (fd >= 0)
93 PROTECT_HANDLE((HANDLE)_get_osfhandle(fd), false);
94 _close(fd);
96 fd = -1;
99 ///////////////////////////////////////////////////////////////////////////////
101 static __forceinline void doWriteOutput(QFile &outFile, const QResource *const resource)
103 for (quint32 delay = 0U; delay <= MAX_LOCK_DELAY; delay = NEXT_DELAY(delay))
105 if (delay > 0U)
107 if (delay <= 1U)
109 qWarning("Failed to open file on first attempt, retrying...");
111 MUtils::OS::sleep_ms(delay);
113 if(outFile.open(QIODevice::WriteOnly))
115 break; /*file opened successfully*/
119 //Write data to file
120 if(outFile.isOpen() && outFile.isWritable())
122 if(outFile.write(reinterpret_cast<const char*>(resource->data()), resource->size()) != resource->size())
124 QFile::remove(QFileInfo(outFile).canonicalFilePath());
125 MUTILS_THROW_FMT("File '%s' could not be written!", MUTILS_UTF8(QFileInfo(outFile).fileName()));
128 else
130 MUTILS_THROW_FMT("File '%s' could not be created!", MUTILS_UTF8(QFileInfo(outFile).fileName()));
133 //Close file after it has been written
134 outFile.close();
137 static __forceinline void doValidateFileExists(const QString &filePath)
139 QFileInfo existingFileInfo(filePath);
140 existingFileInfo.setCaching(false);
142 //Make sure the file exists, before we try to lock it
143 if((!existingFileInfo.exists()) || (!existingFileInfo.isFile()) || filePath.isEmpty())
145 MUTILS_THROW_FMT("File '%s' does not exist!", MUTILS_UTF8(filePath));
149 static __forceinline void doLockFile(HANDLE &fileHandle, const QString &filePath, QFile *const outFile)
151 bool success = false;
152 fileHandle = INVALID_HANDLE_VALUE;
154 //Try to open the file!
155 for(quint32 delay = 0U; delay <= MAX_LOCK_DELAY; delay = NEXT_DELAY(delay))
157 if (delay > 0U)
159 if (delay <= 1U)
161 qWarning("Failed to open file on first attempt, retrying...");
163 MUtils::OS::sleep_ms(delay);
165 const HANDLE hTemp = CreateFileW(MUTILS_WCHR(QDir::toNativeSeparators(filePath)), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
166 if(VALID_HANDLE(hTemp))
168 PROTECT_HANDLE(fileHandle = hTemp, true);
169 break; /*file opened successfully*/
173 //Now try to actually lock the file!
174 if (VALID_HANDLE(fileHandle))
176 for (quint32 delay = 0U; delay <= MAX_LOCK_DELAY; delay = NEXT_DELAY(delay))
178 LARGE_INTEGER fileSize;
179 if (delay > 0U)
181 if (delay <= 1U)
183 qWarning("Failed to lock file on first attempt, retrying...");
185 MUtils::OS::sleep_ms(delay);
187 if (GetFileSizeEx(fileHandle, &fileSize))
189 OVERLAPPED overlapped = { 0U, 0U, 0U, 0U, 0U };
190 if (LockFileEx(fileHandle, LOCKFILE_FAIL_IMMEDIATELY, 0, fileSize.LowPart, fileSize.HighPart, &overlapped))
192 success = true;
193 break; /*file locked successfully*/
199 //Locked successfully?
200 if(!success)
202 CLOSE_HANDLE(fileHandle);
203 if(outFile)
205 QFile::remove(QFileInfo(*outFile).canonicalFilePath());
207 MUTILS_THROW_FMT("File '%s' could not be locked!", MUTILS_UTF8(QFileInfo(filePath).fileName()));
211 static __forceinline void doInitFileDescriptor(const HANDLE &fileHandle, int &fileDescriptor)
213 fileDescriptor = _open_osfhandle(reinterpret_cast<intptr_t>(fileHandle), _O_RDONLY | _O_BINARY);
214 if(fileDescriptor < 0)
216 MUTILS_THROW_FMT("Failed to obtain C Runtime file descriptor!");
220 static __forceinline void doValidateHash(HANDLE &fileHandle, const int &fileDescriptor, const QByteArray &expectedHash, const QString &filePath)
222 QFile checkFile;
224 //Now re-open the file for reading
225 if(g_useFileDescrForQFile)
227 checkFile.open(fileDescriptor, QIODevice::ReadOnly);
229 else
231 checkFile.setFileName(filePath);
232 for (quint32 delay = 0U; delay <= MAX_LOCK_DELAY; delay = NEXT_DELAY(delay))
234 if (delay > 0U)
236 if (delay <= 1U)
238 qWarning("Failed to open file on first attempt, retrying...");
240 MUtils::OS::sleep_ms(delay);
242 if (checkFile.open(QIODevice::ReadOnly))
244 break; /*file locked successfully*/
249 //Opened successfully
250 if((!checkFile.isOpen()) || checkFile.peek(1).isEmpty())
252 QFile::remove(filePath);
253 MUTILS_THROW_FMT("File '%s' could not be read!", MUTILS_UTF8(QFileInfo(filePath).fileName()));
256 //Verify file contents
257 const QByteArray hash = FileHash::computeHash(checkFile);
258 checkFile.close();
260 //Compare hashes
261 if(hash.isNull() || _stricmp(hash.constData(), expectedHash.constData()))
263 qWarning("\nFile checksum error:\n A = %s\n B = %s\n", expectedHash.constData(), hash.constData());
264 CLOSE_HANDLE(fileHandle);
265 QFile::remove(filePath);
266 MUTILS_THROW_FMT("File '%s' is corruputed, take care!", MUTILS_UTF8(QFileInfo(filePath).fileName()));
270 static __forceinline bool doRemoveFile(const QString &filePath)
272 for (quint32 delay = 0U; delay <= MAX_LOCK_DELAY / 4; delay = NEXT_DELAY(delay))
274 if (delay > 0U)
276 if (delay <= 1U)
278 qWarning("Failed to delete file on first attempt, retrying...");
280 MUtils::OS::sleep_ms(delay);
282 if(MUtils::remove_file(filePath))
284 return true; /*file removed successfully*/
287 return false;
290 ///////////////////////////////////////////////////////////////////////////////
292 LockedFile::LockedFile(QResource *const resource, const QString &outPath, const QByteArray &expectedHash, const bool bOwnsFile)
294 m_bOwnsFile(bOwnsFile),
295 m_filePath(QFileInfo(outPath).absoluteFilePath())
297 m_fileDescriptor = -1;
298 HANDLE fileHandle = NULL;
300 //Make sure the resource is valid
301 if(!(resource->isValid() && resource->data()))
303 MUTILS_THROW_FMT("The resource at %p is invalid!", resource);
306 //Write data to output file
307 QFile outFile(m_filePath);
308 doWriteOutput(outFile, resource);
310 //Now lock the file!
311 doLockFile(fileHandle, m_filePath, &outFile);
313 //Get file descriptor
314 doInitFileDescriptor(fileHandle, m_fileDescriptor);
316 //Validate file hash
317 doValidateHash(fileHandle, m_fileDescriptor, expectedHash, m_filePath);
320 LockedFile::LockedFile(const QString &filePath, const QByteArray &expectedHash, const bool bOwnsFile)
322 m_bOwnsFile(bOwnsFile),
323 m_filePath(QFileInfo(filePath).absoluteFilePath())
325 m_fileDescriptor = -1;
326 HANDLE fileHandle = NULL;
328 //Make sure the file exists, before we try to lock it
329 doValidateFileExists(m_filePath);
331 //Now lock the file!
332 doLockFile(fileHandle, m_filePath, NULL);
334 //Get file descriptor
335 doInitFileDescriptor(fileHandle, m_fileDescriptor);
337 //Validate file hash
338 doValidateHash(fileHandle, m_fileDescriptor, expectedHash, m_filePath);
341 LockedFile::LockedFile(const QString &filePath, const bool bOwnsFile)
343 m_bOwnsFile(bOwnsFile),
344 m_filePath(QFileInfo(filePath).canonicalFilePath())
346 m_fileDescriptor = -1;
347 HANDLE fileHandle = NULL;
349 //Make sure the file exists, before we try to lock it
350 doValidateFileExists(m_filePath);
352 //Now lock the file!
353 doLockFile(fileHandle, m_filePath, NULL);
355 //Get file descriptor
356 doInitFileDescriptor(fileHandle, m_fileDescriptor);
359 LockedFile::~LockedFile(void)
361 CLOSE_FILE(m_fileDescriptor);
362 if(m_bOwnsFile)
364 doRemoveFile(m_filePath);
368 const QString LockedFile::filePath(void)
370 if (m_fileDescriptor >= 0)
372 if (GetFileType((HANDLE)_get_osfhandle(m_fileDescriptor)) == FILE_TYPE_UNKNOWN)
374 MUTILS_THROW_FMT("Failed to validate file handle!");
377 return m_filePath;