Adapt for latest MUtils changes.
[LameXP.git] / src / LockedFile.cpp
blob579d70a37c2e3800f0b06e1af5975f19a9f4b45b
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2016 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, but always including the *additional*
9 // restrictions defined in the "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 ///////////////////////////////////////////////////////////////////////////////
53 // WARNING: Passing file descriptors into Qt does NOT work with dynamically linked CRT!
54 #ifdef QT_NODLL
55 static const bool g_useFileDescrForQFile = true;
56 #else
57 static const bool g_useFileDescrForQFile = false;
58 #endif
60 #define VALID_HANDLE(H) (((H) != NULL) && ((H) != INVALID_HANDLE_VALUE))
62 static bool PROTECT_HANDLE(const HANDLE &h, const bool &lock)
64 if (SetHandleInformation(h, HANDLE_FLAG_PROTECT_FROM_CLOSE, (lock ? HANDLE_FLAG_PROTECT_FROM_CLOSE : 0U)))
66 return true;
68 return false;
71 static void CLOSE_HANDLE(HANDLE &h)
73 if(VALID_HANDLE(h))
75 PROTECT_HANDLE(h, false);
76 CloseHandle(h);
78 h = NULL;
81 static void CLOSE_FILE(int &fd)
83 if (fd >= 0)
85 PROTECT_HANDLE((HANDLE)_get_osfhandle(fd), false);
86 _close(fd);
88 fd = -1;
91 ///////////////////////////////////////////////////////////////////////////////
93 static __forceinline void doWriteOutput(QFile &outFile, const QResource *const resource)
95 for(int i = 0; i < 64; i++)
97 if(outFile.open(QIODevice::WriteOnly))
99 break;
101 if(i == 0)
103 qWarning("Failed to open file on first attemp, retrying...");
105 Sleep(25);
108 //Write data to file
109 if(outFile.isOpen() && outFile.isWritable())
111 if(outFile.write(reinterpret_cast<const char*>(resource->data()), resource->size()) != resource->size())
113 QFile::remove(QFileInfo(outFile).canonicalFilePath());
114 MUTILS_THROW_FMT("File '%s' could not be written!", MUTILS_UTF8(QFileInfo(outFile).fileName()));
117 else
119 MUTILS_THROW_FMT("File '%s' could not be created!", MUTILS_UTF8(QFileInfo(outFile).fileName()));
122 //Close file after it has been written
123 outFile.close();
126 static __forceinline void doValidateFileExists(const QString &filePath)
128 QFileInfo existingFileInfo(filePath);
129 existingFileInfo.setCaching(false);
131 //Make sure the file exists, before we try to lock it
132 if((!existingFileInfo.exists()) || (!existingFileInfo.isFile()) || filePath.isEmpty())
134 MUTILS_THROW_FMT("File '%s' does not exist!", MUTILS_UTF8(filePath));
138 static __forceinline void doLockFile(HANDLE &fileHandle, const QString &filePath, QFile *const outFile)
140 bool success = false;
141 fileHandle = INVALID_HANDLE_VALUE;
143 //Try to open the file!
144 for(int i = 0; i < 64; i++)
146 const HANDLE hTemp = CreateFileW(MUTILS_WCHR(QDir::toNativeSeparators(filePath)), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
147 if(VALID_HANDLE(hTemp))
149 PROTECT_HANDLE(fileHandle = hTemp, true);
150 break; /*file opened successfully*/
152 if(i == 0)
154 qWarning("Failed to open file on first attemp, retrying...");
156 Sleep(1);
159 //Now try to actually lock the file!
160 if (VALID_HANDLE(fileHandle))
162 for (int i = 0; i < 64; i++)
164 LARGE_INTEGER fileSize;
165 if (GetFileSizeEx(fileHandle, &fileSize))
167 OVERLAPPED overlapped = { 0U, 0U, 0U, 0U, 0U };
168 if (LockFileEx(fileHandle, LOCKFILE_FAIL_IMMEDIATELY, 0, fileSize.LowPart, fileSize.HighPart, &overlapped))
170 success = true;
171 break; /*file locked successfully*/
173 Sleep(1);
175 if (i == 0)
177 qWarning("Failed to lock file on first attemp, retrying...");
182 //Locked successfully?
183 if(!success)
185 CLOSE_HANDLE(fileHandle);
186 if(outFile)
188 QFile::remove(QFileInfo(*outFile).canonicalFilePath());
190 MUTILS_THROW_FMT("File '%s' could not be locked!", MUTILS_UTF8(QFileInfo(filePath).fileName()));
194 static __forceinline void doInitFileDescriptor(const HANDLE &fileHandle, int &fileDescriptor)
196 fileDescriptor = _open_osfhandle(reinterpret_cast<intptr_t>(fileHandle), _O_RDONLY | _O_BINARY);
197 if(fileDescriptor < 0)
199 MUTILS_THROW_FMT("Failed to obtain C Runtime file descriptor!");
203 static __forceinline void doValidateHash(HANDLE &fileHandle, const int &fileDescriptor, const QByteArray &expectedHash, const QString &filePath)
205 QFile checkFile;
207 //Now re-open the file for reading
208 if(g_useFileDescrForQFile)
210 checkFile.open(fileDescriptor, QIODevice::ReadOnly);
212 else
214 checkFile.setFileName(filePath);
215 for(int i = 0; i < 64; i++)
217 if(checkFile.open(QIODevice::ReadOnly)) break;
218 if(!i) qWarning("Failed to re-open file on first attemp, retrying...");
219 Sleep(100);
223 //Opened successfully
224 if((!checkFile.isOpen()) || checkFile.peek(1).isEmpty())
226 QFile::remove(filePath);
227 MUTILS_THROW_FMT("File '%s' could not be read!", MUTILS_UTF8(QFileInfo(filePath).fileName()));
230 //Verify file contents
231 const QByteArray hash = FileHash::computeHash(checkFile);
232 checkFile.close();
234 //Compare hashes
235 if(hash.isNull() || _stricmp(hash.constData(), expectedHash.constData()))
237 qWarning("\nFile checksum error:\n A = %s\n B = %s\n", expectedHash.constData(), hash.constData());
238 CLOSE_HANDLE(fileHandle);
239 QFile::remove(filePath);
240 MUTILS_THROW_FMT("File '%s' is corruputed, take care!", MUTILS_UTF8(QFileInfo(filePath).fileName()));
244 static __forceinline bool doRemoveFile(const QString &filePath)
246 for(int i = 0; i < 32; i++)
248 if(MUtils::remove_file(filePath))
250 return true;
252 MUtils::OS::sleep_ms(1);
254 return false;
257 ///////////////////////////////////////////////////////////////////////////////
259 LockedFile::LockedFile(QResource *const resource, const QString &outPath, const QByteArray &expectedHash, const bool bOwnsFile)
261 m_bOwnsFile(bOwnsFile),
262 m_filePath(QFileInfo(outPath).absoluteFilePath())
264 m_fileDescriptor = -1;
265 HANDLE fileHandle = NULL;
267 //Make sure the resource is valid
268 if(!(resource->isValid() && resource->data()))
270 MUTILS_THROW_FMT("The resource at %p is invalid!", resource);
273 //Write data to output file
274 QFile outFile(m_filePath);
275 doWriteOutput(outFile, resource);
277 //Now lock the file!
278 doLockFile(fileHandle, m_filePath, &outFile);
280 //Get file descriptor
281 doInitFileDescriptor(fileHandle, m_fileDescriptor);
283 //Validate file hash
284 doValidateHash(fileHandle, m_fileDescriptor, expectedHash, m_filePath);
287 LockedFile::LockedFile(const QString &filePath, const QByteArray &expectedHash, const bool bOwnsFile)
289 m_bOwnsFile(bOwnsFile),
290 m_filePath(QFileInfo(filePath).absoluteFilePath())
292 m_fileDescriptor = -1;
293 HANDLE fileHandle = NULL;
295 //Make sure the file exists, before we try to lock it
296 doValidateFileExists(m_filePath);
298 //Now lock the file!
299 doLockFile(fileHandle, m_filePath, NULL);
301 //Get file descriptor
302 doInitFileDescriptor(fileHandle, m_fileDescriptor);
304 //Validate file hash
305 doValidateHash(fileHandle, m_fileDescriptor, expectedHash, m_filePath);
308 LockedFile::LockedFile(const QString &filePath, const bool bOwnsFile)
310 m_bOwnsFile(bOwnsFile),
311 m_filePath(QFileInfo(filePath).canonicalFilePath())
313 m_fileDescriptor = -1;
314 HANDLE fileHandle = NULL;
316 //Make sure the file exists, before we try to lock it
317 doValidateFileExists(m_filePath);
319 //Now lock the file!
320 doLockFile(fileHandle, m_filePath, NULL);
322 //Get file descriptor
323 doInitFileDescriptor(fileHandle, m_fileDescriptor);
326 LockedFile::~LockedFile(void)
328 CLOSE_FILE(m_fileDescriptor);
329 if(m_bOwnsFile)
331 doRemoveFile(m_filePath);
335 const QString LockedFile::filePath(void)
337 if (m_fileDescriptor >= 0)
339 if (GetFileType((HANDLE)_get_osfhandle(m_fileDescriptor)) == FILE_TYPE_UNKNOWN)
341 MUTILS_THROW_FMT("Failed to validate file handle!");
344 return m_filePath;