OSDN Git Service

CPU flag CPU_TYPE_X64_AVX now means AVX+AVX2 support is available, instead of just...
[lamexp/LameXP.git] / src / LockedFile.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2022 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!
10 //
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.
15 //
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.
19 //
20 // http://www.gnu.org/licenses/gpl-2.0.txt
21 ///////////////////////////////////////////////////////////////////////////////
22
23 #include "LockedFile.h"
24
25 //Internal
26 #include "Global.h"
27 #include "FileHash.h"
28
29 //MUtils
30 #include <MUtils/OSSupport.h>
31 #include <MUtils/Exception.h>
32
33 //Qt
34 #include <QResource>
35 #include <QFile>
36 #include <QFileInfo>
37 #include <QDir>
38 #include <QCryptographicHash>
39
40 //CRT
41 #include <stdio.h>
42 #include <io.h>
43 #include <fcntl.h>
44 #include <stdexcept>
45
46 //Windows includes
47 #define NOMINMAX
48 #define WIN32_LEAN_AND_MEAN
49 #include <Windows.h>
50
51 //Const
52 static const quint32 MAX_LOCK_DELAY = 16384U;
53
54 ///////////////////////////////////////////////////////////////////////////////
55
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
62
63 #define VALID_HANDLE(H) (((H) != NULL) && ((H) != INVALID_HANDLE_VALUE))
64
65 static __forceinline quint32 NEXT_DELAY(const quint32 delay)
66 {
67         return (delay > 0U) ? (delay * 2U) : 1U;
68 }
69
70 static bool PROTECT_HANDLE(const HANDLE &h, const bool &lock)
71 {
72         if (SetHandleInformation(h, HANDLE_FLAG_PROTECT_FROM_CLOSE, (lock ? HANDLE_FLAG_PROTECT_FROM_CLOSE : 0U)))
73         {
74                 return true;
75         }
76         return false;
77 }
78
79 static void CLOSE_HANDLE(HANDLE &h)
80 {
81         if(VALID_HANDLE(h))
82         {
83                 PROTECT_HANDLE(h, false);
84                 CloseHandle(h);
85         }
86         h = NULL;
87 }
88
89 static void CLOSE_FILE(int &fd)
90 {
91         if (fd >= 0)
92         {
93                 PROTECT_HANDLE((HANDLE)_get_osfhandle(fd), false);
94                 _close(fd);
95         }
96         fd = -1;
97 }
98
99 ///////////////////////////////////////////////////////////////////////////////
100
101 static __forceinline void doWriteOutput(QFile &outFile, const QResource *const resource)
102 {
103         for (quint32 delay = 0U; delay <= MAX_LOCK_DELAY; delay = NEXT_DELAY(delay))
104         {
105                 if (delay > 0U)
106                 {
107                         if (delay <= 1U)
108                         {
109                                 qWarning("Failed to open file on first attempt, retrying...");
110                         }
111                         MUtils::OS::sleep_ms(delay);
112                 }
113                 if(outFile.open(QIODevice::WriteOnly))
114                 {
115                         break; /*file opened successfully*/
116                 }
117         }
118         
119         //Write data to file
120         if(outFile.isOpen() && outFile.isWritable())
121         {
122                 if(outFile.write(reinterpret_cast<const char*>(resource->data()), resource->size()) != resource->size())
123                 {
124                         QFile::remove(QFileInfo(outFile).canonicalFilePath());
125                         MUTILS_THROW_FMT("File '%s' could not be written!", MUTILS_UTF8(QFileInfo(outFile).fileName()));
126                 }
127         }
128         else
129         {
130                 MUTILS_THROW_FMT("File '%s' could not be created!", MUTILS_UTF8(QFileInfo(outFile).fileName()));
131         }
132
133         //Close file after it has been written
134         outFile.close();
135 }
136
137 static __forceinline void doValidateFileExists(const QString &filePath)
138 {
139         QFileInfo existingFileInfo(filePath);
140         existingFileInfo.setCaching(false);
141         
142         //Make sure the file exists, before we try to lock it
143         if((!existingFileInfo.exists()) || (!existingFileInfo.isFile()) || filePath.isEmpty())
144         {
145                 MUTILS_THROW_FMT("File '%s' does not exist!", MUTILS_UTF8(filePath));
146         }
147 }
148
149 static __forceinline void doLockFile(HANDLE &fileHandle, const QString &filePath, QFile *const outFile)
150 {
151         bool success = false;
152         fileHandle = INVALID_HANDLE_VALUE;
153
154         //Try to open the file!
155         for(quint32 delay = 0U; delay <= MAX_LOCK_DELAY; delay = NEXT_DELAY(delay))
156         {
157                 if (delay > 0U)
158                 {
159                         if (delay <= 1U)
160                         {
161                                 qWarning("Failed to open file on first attempt, retrying...");
162                         }
163                         MUtils::OS::sleep_ms(delay);
164                 }
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))
167                 {
168                         PROTECT_HANDLE(fileHandle = hTemp, true);
169                         break; /*file opened successfully*/
170                 }
171         }
172         
173         //Now try to actually lock the file!
174         if (VALID_HANDLE(fileHandle))
175         {
176                 for (quint32 delay = 0U; delay <= MAX_LOCK_DELAY; delay = NEXT_DELAY(delay))
177                 {
178                         LARGE_INTEGER fileSize;
179                         if (delay > 0U)
180                         {
181                                 if (delay <= 1U)
182                                 {
183                                         qWarning("Failed to lock file on first attempt, retrying...");
184                                 }
185                                 MUtils::OS::sleep_ms(delay);
186                         }
187                         if (GetFileSizeEx(fileHandle, &fileSize))
188                         {
189                                 OVERLAPPED overlapped = { 0U, 0U, 0U, 0U, 0U };
190                                 if (LockFileEx(fileHandle, LOCKFILE_FAIL_IMMEDIATELY, 0, fileSize.LowPart, fileSize.HighPart, &overlapped))
191                                 {
192                                         success = true;
193                                         break; /*file locked successfully*/
194                                 }
195                         }
196                 }
197         }
198
199         //Locked successfully?
200         if(!success)
201         {
202                 CLOSE_HANDLE(fileHandle);
203                 if(outFile)
204                 {
205                         QFile::remove(QFileInfo(*outFile).canonicalFilePath());
206                 }
207                 MUTILS_THROW_FMT("File '%s' could not be locked!", MUTILS_UTF8(QFileInfo(filePath).fileName()));
208         }
209 }
210
211 static __forceinline void doInitFileDescriptor(const HANDLE &fileHandle, int &fileDescriptor)
212 {
213         fileDescriptor = _open_osfhandle(reinterpret_cast<intptr_t>(fileHandle), _O_RDONLY | _O_BINARY);
214         if(fileDescriptor < 0)
215         {
216                 MUTILS_THROW_FMT("Failed to obtain C Runtime file descriptor!");
217         }
218 }
219
220 static __forceinline void doValidateHash(HANDLE &fileHandle, const int &fileDescriptor, const QByteArray &expectedHash, const QString &filePath)
221 {
222         QFile checkFile;
223
224         //Now re-open the file for reading
225         if(g_useFileDescrForQFile)
226         {
227                 checkFile.open(fileDescriptor, QIODevice::ReadOnly);
228         }
229         else
230         {
231                 checkFile.setFileName(filePath);
232                 for (quint32 delay = 0U; delay <= MAX_LOCK_DELAY; delay = NEXT_DELAY(delay))
233                 {
234                         if (delay > 0U)
235                         {
236                                 if (delay <= 1U)
237                                 {
238                                         qWarning("Failed to open file on first attempt, retrying...");
239                                 }
240                                 MUtils::OS::sleep_ms(delay);
241                         }
242                         if (checkFile.open(QIODevice::ReadOnly))
243                         {
244                                 break; /*file locked successfully*/
245                         }
246                 }
247         }
248
249         //Opened successfully
250         if((!checkFile.isOpen()) || checkFile.peek(1).isEmpty())
251         {
252                 QFile::remove(filePath);
253                 MUTILS_THROW_FMT("File '%s' could not be read!", MUTILS_UTF8(QFileInfo(filePath).fileName()));
254         }
255
256         //Verify file contents
257         const QByteArray hash = FileHash::computeHash(checkFile);
258         checkFile.close();
259
260         //Compare hashes
261         if(hash.isNull() || _stricmp(hash.constData(), expectedHash.constData()))
262         {
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()));
267         }
268 }
269
270 static __forceinline bool doRemoveFile(const QString &filePath)
271 {
272         for (quint32 delay = 0U; delay <= MAX_LOCK_DELAY / 4; delay = NEXT_DELAY(delay))
273         {
274                 if (delay > 0U)
275                 {
276                         if (delay <= 1U)
277                         {
278                                 qWarning("Failed to delete file on first attempt, retrying...");
279                         }
280                         MUtils::OS::sleep_ms(delay);
281                 }
282                 if(MUtils::remove_file(filePath))
283                 {
284                         return true; /*file removed successfully*/
285                 }
286         }
287         return false;
288 }
289
290 ///////////////////////////////////////////////////////////////////////////////
291
292 LockedFile::LockedFile(QResource *const resource, const QString &outPath, const QByteArray &expectedHash, const bool bOwnsFile)
293 :
294         m_bOwnsFile(bOwnsFile),
295         m_filePath(QFileInfo(outPath).absoluteFilePath())
296 {
297         m_fileDescriptor = -1;
298         HANDLE fileHandle = NULL;
299                 
300         //Make sure the resource is valid
301         if(!(resource->isValid() && resource->data()))
302         {
303                 MUTILS_THROW_FMT("The resource at %p is invalid!", resource);
304         }
305
306         //Write data to output file
307         QFile outFile(m_filePath);
308         doWriteOutput(outFile, resource);
309
310         //Now lock the file!
311         doLockFile(fileHandle, m_filePath, &outFile);
312
313         //Get file descriptor
314         doInitFileDescriptor(fileHandle, m_fileDescriptor);
315
316         //Validate file hash
317         doValidateHash(fileHandle, m_fileDescriptor, expectedHash, m_filePath);
318 }
319
320 LockedFile::LockedFile(const QString &filePath, const QByteArray &expectedHash, const bool bOwnsFile)
321 :
322         m_bOwnsFile(bOwnsFile),
323         m_filePath(QFileInfo(filePath).absoluteFilePath())
324 {
325         m_fileDescriptor = -1;
326         HANDLE fileHandle = NULL;
327         
328         //Make sure the file exists, before we try to lock it
329         doValidateFileExists(m_filePath);
330
331         //Now lock the file!
332         doLockFile(fileHandle, m_filePath, NULL);
333
334         //Get file descriptor
335         doInitFileDescriptor(fileHandle, m_fileDescriptor);
336
337         //Validate file hash
338         doValidateHash(fileHandle, m_fileDescriptor, expectedHash, m_filePath);
339 }
340
341 LockedFile::LockedFile(const QString &filePath, const bool bOwnsFile)
342 :
343         m_bOwnsFile(bOwnsFile),
344         m_filePath(QFileInfo(filePath).canonicalFilePath())
345 {
346         m_fileDescriptor = -1;
347         HANDLE fileHandle = NULL;
348         
349         //Make sure the file exists, before we try to lock it
350         doValidateFileExists(m_filePath);
351
352         //Now lock the file!
353         doLockFile(fileHandle, m_filePath, NULL);
354
355         //Get file descriptor
356         doInitFileDescriptor(fileHandle, m_fileDescriptor);
357 }
358
359 LockedFile::~LockedFile(void)
360 {
361         CLOSE_FILE(m_fileDescriptor);
362         if(m_bOwnsFile)
363         {
364                 doRemoveFile(m_filePath);
365         }
366 }
367
368 const QString LockedFile::filePath(void)
369 {
370         if (m_fileDescriptor >= 0)
371         {
372                 if (GetFileType((HANDLE)_get_osfhandle(m_fileDescriptor)) == FILE_TYPE_UNKNOWN)
373                 {
374                         MUTILS_THROW_FMT("Failed to validate file handle!");
375                 }
376         }
377         return m_filePath;
378 }