mptFileIO.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. /*
  2. * mptFileIO.cpp
  3. * -------------
  4. * Purpose: File I/O wrappers
  5. * Notes : (currently none)
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #include "stdafx.h"
  10. #include "mptFileIO.h"
  11. #if defined(MPT_ENABLE_FILEIO)
  12. #include "mpt/io/io.hpp"
  13. #include "mpt/io/io_stdstream.hpp"
  14. #if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS
  15. #include "mpt/system_error/system_error.hpp"
  16. #include "FileReader.h"
  17. #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
  18. #endif // MPT_ENABLE_FILEIO
  19. #if defined(MPT_ENABLE_FILEIO)
  20. #include <stdexcept>
  21. #endif // MPT_ENABLE_FILEIO
  22. #ifdef MODPLUG_TRACKER
  23. #if MPT_OS_WINDOWS
  24. #include <windows.h>
  25. #include <WinIoCtl.h>
  26. #include <io.h>
  27. #endif // MPT_OS_WINDOWS
  28. #endif // MODPLUG_TRACKER
  29. #if defined(MPT_ENABLE_FILEIO)
  30. #if MPT_COMPILER_MSVC
  31. #include <stdio.h>
  32. #include <tchar.h>
  33. #endif // MPT_COMPILER_MSVC
  34. #endif // MPT_ENABLE_FILEIO
  35. OPENMPT_NAMESPACE_BEGIN
  36. #if defined(MPT_ENABLE_FILEIO)
  37. #if !defined(MPT_BUILD_SILENCE_LIBOPENMPT_CONFIGURATION_WARNINGS)
  38. #if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR)
  39. #if MPT_GCC_BEFORE(9,1,0)
  40. MPT_WARNING("Warning: MinGW with GCC earlier than 9.1 detected. Standard library does neither provide std::fstream wchar_t overloads nor std::filesystem with wchar_t support. Unicode filename support is thus unavailable.")
  41. #endif // MPT_GCC_AT_LEAST(9,1,0)
  42. #endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
  43. #endif // !MPT_BUILD_SILENCE_LIBOPENMPT_CONFIGURATION_WARNINGS
  44. #ifdef MODPLUG_TRACKER
  45. #if MPT_OS_WINDOWS
  46. bool SetFilesystemCompression(HANDLE hFile)
  47. {
  48. if(hFile == INVALID_HANDLE_VALUE)
  49. {
  50. return false;
  51. }
  52. USHORT format = COMPRESSION_FORMAT_DEFAULT;
  53. DWORD dummy = 0;
  54. BOOL result = DeviceIoControl(hFile, FSCTL_SET_COMPRESSION, (LPVOID)&format, sizeof(format), NULL, 0, &dummy /*required*/ , NULL);
  55. return result != FALSE;
  56. }
  57. bool SetFilesystemCompression(int fd)
  58. {
  59. if(fd < 0)
  60. {
  61. return false;
  62. }
  63. uintptr_t fhandle = _get_osfhandle(fd);
  64. HANDLE hFile = (HANDLE)fhandle;
  65. if(hFile == INVALID_HANDLE_VALUE)
  66. {
  67. return false;
  68. }
  69. return SetFilesystemCompression(hFile);
  70. }
  71. bool SetFilesystemCompression(const mpt::PathString &filename)
  72. {
  73. DWORD attributes = GetFileAttributes(filename.AsNativePrefixed().c_str());
  74. if(attributes == INVALID_FILE_ATTRIBUTES)
  75. {
  76. return false;
  77. }
  78. if(attributes & FILE_ATTRIBUTE_COMPRESSED)
  79. {
  80. return true;
  81. }
  82. HANDLE hFile = CreateFile(filename.AsNativePrefixed().c_str(), GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
  83. if(hFile == INVALID_HANDLE_VALUE)
  84. {
  85. return false;
  86. }
  87. bool result = SetFilesystemCompression(hFile);
  88. CloseHandle(hFile);
  89. return result;
  90. }
  91. #endif // MPT_OS_WINDOWS
  92. #endif // MODPLUG_TRACKER
  93. #ifdef MODPLUG_TRACKER
  94. namespace mpt {
  95. #if MPT_COMPILER_MSVC
  96. mpt::tstring SafeOutputFile::convert_mode(std::ios_base::openmode mode, FlushMode flushMode)
  97. {
  98. mpt::tstring fopen_mode;
  99. switch(mode & ~(std::ios_base::ate | std::ios_base::binary))
  100. {
  101. case std::ios_base::in:
  102. fopen_mode = _T("r");
  103. break;
  104. case std::ios_base::out:
  105. [[fallthrough]];
  106. case std::ios_base::out | std::ios_base::trunc:
  107. fopen_mode = _T("w");
  108. break;
  109. case std::ios_base::app:
  110. [[fallthrough]];
  111. case std::ios_base::out | std::ios_base::app:
  112. fopen_mode = _T("a");
  113. break;
  114. case std::ios_base::out | std::ios_base::in:
  115. fopen_mode = _T("r+");
  116. break;
  117. case std::ios_base::out | std::ios_base::in | std::ios_base::trunc:
  118. fopen_mode = _T("w+");
  119. break;
  120. case std::ios_base::out | std::ios_base::in | std::ios_base::app:
  121. [[fallthrough]];
  122. case std::ios_base::in | std::ios_base::app:
  123. fopen_mode = _T("a+");
  124. break;
  125. }
  126. if(fopen_mode.empty())
  127. {
  128. return fopen_mode;
  129. }
  130. if(mode & std::ios_base::binary)
  131. {
  132. fopen_mode += _T("b");
  133. }
  134. if(flushMode == FlushMode::Full)
  135. {
  136. fopen_mode += _T("c"); // force commit on fflush (MSVC specific)
  137. }
  138. return fopen_mode;
  139. }
  140. std::FILE * SafeOutputFile::internal_fopen(const mpt::PathString &filename, std::ios_base::openmode mode, FlushMode flushMode)
  141. {
  142. m_f = nullptr;
  143. mpt::tstring fopen_mode = convert_mode(mode, flushMode);
  144. if(fopen_mode.empty())
  145. {
  146. return nullptr;
  147. }
  148. std::FILE *f =
  149. #ifdef UNICODE
  150. _wfopen(filename.AsNativePrefixed().c_str(), fopen_mode.c_str())
  151. #else
  152. std::fopen(filename.AsNativePrefixed().c_str(), fopen_mode.c_str())
  153. #endif
  154. ;
  155. if(!f)
  156. {
  157. return nullptr;
  158. }
  159. if(mode & std::ios_base::ate)
  160. {
  161. if(std::fseek(f, 0, SEEK_END) != 0)
  162. {
  163. std::fclose(f);
  164. f = nullptr;
  165. return nullptr;
  166. }
  167. }
  168. m_f = f;
  169. return f;
  170. }
  171. #endif // MPT_COMPILER_MSVC
  172. // cppcheck-suppress exceptThrowInDestructor
  173. SafeOutputFile::~SafeOutputFile() noexcept(false)
  174. {
  175. const bool mayThrow = (std::uncaught_exceptions() == 0);
  176. if(!stream())
  177. {
  178. #if MPT_COMPILER_MSVC
  179. if(m_f)
  180. {
  181. std::fclose(m_f);
  182. }
  183. #endif // MPT_COMPILER_MSVC
  184. if(mayThrow && (stream().exceptions() & (std::ios::badbit | std::ios::failbit)))
  185. {
  186. // cppcheck-suppress exceptThrowInDestructor
  187. throw std::ios_base::failure(std::string("Error before flushing file buffers."));
  188. }
  189. return;
  190. }
  191. if(!stream().rdbuf())
  192. {
  193. #if MPT_COMPILER_MSVC
  194. if(m_f)
  195. {
  196. std::fclose(m_f);
  197. }
  198. #endif // MPT_COMPILER_MSVC
  199. if(mayThrow && (stream().exceptions() & (std::ios::badbit | std::ios::failbit)))
  200. {
  201. // cppcheck-suppress exceptThrowInDestructor
  202. throw std::ios_base::failure(std::string("Error before flushing file buffers."));
  203. }
  204. return;
  205. }
  206. #if MPT_COMPILER_MSVC
  207. if(!m_f)
  208. {
  209. return;
  210. }
  211. #endif // MPT_COMPILER_MSVC
  212. bool errorOnFlush = false;
  213. if(m_FlushMode != FlushMode::None)
  214. {
  215. try
  216. {
  217. if(stream().rdbuf()->pubsync() != 0)
  218. {
  219. errorOnFlush = true;
  220. }
  221. } catch(const std::exception &)
  222. {
  223. errorOnFlush = true;
  224. #if MPT_COMPILER_MSVC
  225. if(m_FlushMode != FlushMode::None)
  226. {
  227. if(std::fflush(m_f) != 0)
  228. {
  229. errorOnFlush = true;
  230. }
  231. }
  232. if(std::fclose(m_f) != 0)
  233. {
  234. errorOnFlush = true;
  235. }
  236. #endif // MPT_COMPILER_MSVC
  237. if(mayThrow)
  238. {
  239. // ignore errorOnFlush here, and re-throw the earlier exception
  240. // cppcheck-suppress exceptThrowInDestructor
  241. throw;
  242. }
  243. }
  244. }
  245. #if MPT_COMPILER_MSVC
  246. if(m_FlushMode != FlushMode::None)
  247. {
  248. if(std::fflush(m_f) != 0)
  249. {
  250. errorOnFlush = true;
  251. }
  252. }
  253. if(std::fclose(m_f) != 0)
  254. {
  255. errorOnFlush = true;
  256. }
  257. #endif // MPT_COMPILER_MSVC
  258. if(mayThrow && errorOnFlush && (stream().exceptions() & (std::ios::badbit | std::ios::failbit)))
  259. {
  260. // cppcheck-suppress exceptThrowInDestructor
  261. throw std::ios_base::failure(std::string("Error flushing file buffers."));
  262. }
  263. }
  264. } // namespace mpt
  265. #endif // MODPLUG_TRACKER
  266. #ifdef MODPLUG_TRACKER
  267. namespace mpt {
  268. LazyFileRef & LazyFileRef::operator = (const std::vector<std::byte> &data)
  269. {
  270. mpt::ofstream file(m_Filename, std::ios::binary);
  271. file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
  272. mpt::IO::WriteRaw(file, mpt::as_span(data));
  273. mpt::IO::Flush(file);
  274. return *this;
  275. }
  276. LazyFileRef & LazyFileRef::operator = (const std::vector<char> &data)
  277. {
  278. mpt::ofstream file(m_Filename, std::ios::binary);
  279. file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
  280. mpt::IO::WriteRaw(file, mpt::as_span(data));
  281. mpt::IO::Flush(file);
  282. return *this;
  283. }
  284. LazyFileRef & LazyFileRef::operator = (const std::string &data)
  285. {
  286. mpt::ofstream file(m_Filename, std::ios::binary);
  287. file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
  288. mpt::IO::WriteRaw(file, mpt::as_span(data));
  289. mpt::IO::Flush(file);
  290. return *this;
  291. }
  292. LazyFileRef::operator std::vector<std::byte> () const
  293. {
  294. mpt::ifstream file(m_Filename, std::ios::binary);
  295. if(!mpt::IO::IsValid(file))
  296. {
  297. return std::vector<std::byte>();
  298. }
  299. file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
  300. mpt::IO::SeekEnd(file);
  301. std::vector<std::byte> buf(mpt::saturate_cast<std::size_t>(mpt::IO::TellRead(file)));
  302. mpt::IO::SeekBegin(file);
  303. mpt::IO::ReadRaw(file, mpt::as_span(buf));
  304. return buf;
  305. }
  306. LazyFileRef::operator std::vector<char> () const
  307. {
  308. mpt::ifstream file(m_Filename, std::ios::binary);
  309. if(!mpt::IO::IsValid(file))
  310. {
  311. return std::vector<char>();
  312. }
  313. file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
  314. mpt::IO::SeekEnd(file);
  315. std::vector<char> buf(mpt::saturate_cast<std::size_t>(mpt::IO::TellRead(file)));
  316. mpt::IO::SeekBegin(file);
  317. mpt::IO::ReadRaw(file, mpt::as_span(buf));
  318. return buf;
  319. }
  320. LazyFileRef::operator std::string () const
  321. {
  322. mpt::ifstream file(m_Filename, std::ios::binary);
  323. if(!mpt::IO::IsValid(file))
  324. {
  325. return std::string();
  326. }
  327. file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
  328. mpt::IO::SeekEnd(file);
  329. std::vector<char> buf(mpt::saturate_cast<std::size_t>(mpt::IO::TellRead(file)));
  330. mpt::IO::SeekBegin(file);
  331. mpt::IO::ReadRaw(file, mpt::as_span(buf));
  332. return mpt::buffer_cast<std::string>(buf);
  333. }
  334. } // namespace mpt
  335. #endif // MODPLUG_TRACKER
  336. InputFile::InputFile(const mpt::PathString &filename, bool allowWholeFileCaching)
  337. : m_IsValid(false)
  338. , m_IsCached(false)
  339. {
  340. MPT_ASSERT(!filename.empty());
  341. Open(filename, allowWholeFileCaching);
  342. }
  343. InputFile::~InputFile()
  344. {
  345. return;
  346. }
  347. bool InputFile::Open(const mpt::PathString &filename, bool allowWholeFileCaching)
  348. {
  349. m_IsCached = false;
  350. m_Cache.resize(0);
  351. m_Cache.shrink_to_fit();
  352. m_Filename = filename;
  353. m_File.open(m_Filename, std::ios::binary | std::ios::in);
  354. if(allowWholeFileCaching)
  355. {
  356. if(mpt::IO::IsReadSeekable(m_File))
  357. {
  358. if(!mpt::IO::SeekEnd(m_File))
  359. {
  360. m_File.close();
  361. return false;
  362. }
  363. mpt::IO::Offset filesize = mpt::IO::TellRead(m_File);
  364. if(!mpt::IO::SeekBegin(m_File))
  365. {
  366. m_File.close();
  367. return false;
  368. }
  369. if(mpt::in_range<std::size_t>(filesize))
  370. {
  371. std::size_t buffersize = mpt::saturate_cast<std::size_t>(filesize);
  372. m_Cache.resize(buffersize);
  373. if(mpt::IO::ReadRaw(m_File, mpt::as_span(m_Cache)).size() != mpt::saturate_cast<std::size_t>(filesize))
  374. {
  375. m_File.close();
  376. return false;
  377. }
  378. if(!mpt::IO::SeekBegin(m_File))
  379. {
  380. m_File.close();
  381. return false;
  382. }
  383. m_IsCached = true;
  384. m_IsValid = true;
  385. return true;
  386. }
  387. }
  388. }
  389. m_IsValid = true;
  390. return m_File.good();
  391. }
  392. bool InputFile::IsValid() const
  393. {
  394. return m_IsValid && m_File.good();
  395. }
  396. bool InputFile::IsCached() const
  397. {
  398. return m_IsCached;
  399. }
  400. mpt::PathString InputFile::GetFilename() const
  401. {
  402. return m_Filename;
  403. }
  404. std::istream& InputFile::GetStream()
  405. {
  406. MPT_ASSERT(!m_IsCached);
  407. return m_File;
  408. }
  409. mpt::const_byte_span InputFile::GetCache()
  410. {
  411. MPT_ASSERT(m_IsCached);
  412. return mpt::as_span(m_Cache);
  413. }
  414. #if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS
  415. OnDiskFileWrapper::OnDiskFileWrapper(FileCursor &file, const mpt::PathString &fileNameExtension)
  416. : m_IsTempFile(false)
  417. {
  418. try
  419. {
  420. file.Rewind();
  421. if(!file.GetOptionalFileName())
  422. {
  423. const mpt::PathString tempName = mpt::CreateTempFileName(P_("OpenMPT"), fileNameExtension);
  424. #if MPT_OS_WINDOWS && MPT_OS_WINDOWS_WINRT
  425. #if (_WIN32_WINNT < 0x0602)
  426. #define MPT_ONDISKFILEWRAPPER_NO_CREATEFILE
  427. #endif
  428. #endif
  429. #ifdef MPT_ONDISKFILEWRAPPER_NO_CREATEFILE
  430. mpt::ofstream f(tempName, std::ios::binary);
  431. if(!f)
  432. {
  433. throw std::runtime_error("Error creating temporary file.");
  434. }
  435. while(!file.EndOfFile())
  436. {
  437. FileCursor::PinnedView view = file.ReadPinnedView(mpt::IO::BUFFERSIZE_NORMAL);
  438. std::size_t towrite = view.size();
  439. std::size_t written = 0;
  440. do
  441. {
  442. std::size_t chunkSize = mpt::saturate_cast<std::size_t>(towrite);
  443. bool chunkOk = false;
  444. chunkOk = mpt::IO::WriteRaw(f, mpt::const_byte_span(view.data() + written, chunkSize));
  445. if(!chunkOk)
  446. {
  447. throw std::runtime_error("Incomplete Write.");
  448. }
  449. towrite -= chunkSize;
  450. written += chunkSize;
  451. } while(towrite > 0);
  452. }
  453. f.close();
  454. #else // !MPT_ONDISKFILEWRAPPER_NO_CREATEFILE
  455. HANDLE hFile = NULL;
  456. #if MPT_OS_WINDOWS_WINRT
  457. hFile = mpt::windows::CheckFileHANDLE(CreateFile2(tempName.AsNative().c_str(), GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS, NULL));
  458. #else
  459. hFile = mpt::windows::CheckFileHANDLE(CreateFile(tempName.AsNative().c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL));
  460. #endif
  461. while(!file.EndOfFile())
  462. {
  463. FileCursor::PinnedView view = file.ReadPinnedView(mpt::IO::BUFFERSIZE_NORMAL);
  464. std::size_t towrite = view.size();
  465. std::size_t written = 0;
  466. do
  467. {
  468. DWORD chunkSize = mpt::saturate_cast<DWORD>(towrite);
  469. DWORD chunkDone = 0;
  470. try
  471. {
  472. mpt::windows::CheckBOOL(WriteFile(hFile, view.data() + written, chunkSize, &chunkDone, NULL));
  473. } catch(...)
  474. {
  475. CloseHandle(hFile);
  476. hFile = NULL;
  477. throw;
  478. }
  479. if(chunkDone != chunkSize)
  480. {
  481. CloseHandle(hFile);
  482. hFile = NULL;
  483. throw std::runtime_error("Incomplete WriteFile().");
  484. }
  485. towrite -= chunkDone;
  486. written += chunkDone;
  487. } while(towrite > 0);
  488. }
  489. CloseHandle(hFile);
  490. hFile = NULL;
  491. #endif // MPT_ONDISKFILEWRAPPER_NO_CREATEFILE
  492. m_Filename = tempName;
  493. m_IsTempFile = true;
  494. } else
  495. {
  496. m_Filename = file.GetOptionalFileName().value();
  497. }
  498. } catch (const std::runtime_error &)
  499. {
  500. m_IsTempFile = false;
  501. m_Filename = mpt::PathString();
  502. }
  503. }
  504. OnDiskFileWrapper::~OnDiskFileWrapper()
  505. {
  506. if(m_IsTempFile)
  507. {
  508. DeleteFile(m_Filename.AsNative().c_str());
  509. m_IsTempFile = false;
  510. }
  511. m_Filename = mpt::PathString();
  512. }
  513. bool OnDiskFileWrapper::IsValid() const
  514. {
  515. return !m_Filename.empty();
  516. }
  517. mpt::PathString OnDiskFileWrapper::GetFilename() const
  518. {
  519. return m_Filename;
  520. }
  521. #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
  522. #else // !MPT_ENABLE_FILEIO
  523. MPT_MSVC_WORKAROUND_LNK4221(mptFileIO)
  524. #endif // MPT_ENABLE_FILEIO
  525. OPENMPT_NAMESPACE_END