MemoryFile_Win32.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. /*
  2. * Tencent is pleased to support the open source community by making
  3. * MMKV available.
  4. *
  5. * Copyright (C) 2019 THL A29 Limited, a Tencent company.
  6. * All rights reserved.
  7. *
  8. * Licensed under the BSD 3-Clause License (the "License"); you may not use
  9. * this file except in compliance with the License. You may obtain a copy of
  10. * the License at
  11. *
  12. * https://opensource.org/licenses/BSD-3-Clause
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. #include "MemoryFile.h"
  21. #ifdef MMKV_WIN32
  22. # include "InterProcessLock.h"
  23. # include "MMBuffer.h"
  24. # include "MMKVLog.h"
  25. # include "ScopedLock.hpp"
  26. # include "ThreadLock.h"
  27. # include <cassert>
  28. # include <strsafe.h>
  29. using namespace std;
  30. namespace mmkv {
  31. static bool getFileSize(MMKVFileHandle_t fd, size_t &size);
  32. static bool ftruncate(MMKVFileHandle_t file, size_t size);
  33. File::File(MMKVPath_t path, OpenFlag flag) : m_path(std::move(path)), m_fd(INVALID_HANDLE_VALUE), m_flag(flag) {
  34. open();
  35. }
  36. static pair<int, int> OpenFlag2NativeFlag(OpenFlag flag) {
  37. int access = 0, create = OPEN_EXISTING;
  38. if (flag & OpenFlag::ReadWrite) {
  39. access = (GENERIC_READ | GENERIC_WRITE);
  40. } else if (flag & OpenFlag::ReadOnly) {
  41. access |= GENERIC_READ;
  42. } else if (flag & OpenFlag::WriteOnly) {
  43. access |= GENERIC_WRITE;
  44. }
  45. if (flag & OpenFlag::Create) {
  46. create = OPEN_ALWAYS;
  47. }
  48. if (flag & OpenFlag::Excel) {
  49. access = CREATE_NEW;
  50. }
  51. if (flag & OpenFlag::Truncate) {
  52. access = CREATE_ALWAYS;
  53. }
  54. return {access, create};
  55. }
  56. bool File::open() {
  57. if (isFileValid()) {
  58. return true;
  59. }
  60. auto pair = OpenFlag2NativeFlag(m_flag);
  61. m_fd = CreateFile(m_path.c_str(), pair.first, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
  62. pair.second, FILE_ATTRIBUTE_NORMAL, nullptr);
  63. if (!isFileValid()) {
  64. MMKVError("fail to open:[%ws], %d", m_path.c_str(), GetLastError());
  65. return false;
  66. }
  67. MMKVInfo("open fd[%p], %ws", m_fd, m_path.c_str());
  68. return true;
  69. }
  70. void File::close() {
  71. if (isFileValid()) {
  72. MMKVInfo("closing fd[%p], %ws", m_fd, m_path.c_str());
  73. if (CloseHandle(m_fd)) {
  74. m_fd = INVALID_HANDLE_VALUE;
  75. } else {
  76. MMKVError("fail to close [%ws], %d", m_path.c_str(), GetLastError());
  77. }
  78. }
  79. }
  80. size_t File::getActualFileSize() const {
  81. size_t size = 0;
  82. mmkv::getFileSize(m_fd, size);
  83. return size;
  84. }
  85. MemoryFile::MemoryFile(MMKVPath_t path)
  86. : m_diskFile(std::move(path), OpenFlag::ReadWrite | OpenFlag::Create)
  87. , m_fileMapping(nullptr)
  88. , m_ptr(nullptr)
  89. , m_size(0) {
  90. reloadFromFile();
  91. }
  92. bool MemoryFile::truncate(size_t size) {
  93. if (!m_diskFile.isFileValid()) {
  94. return false;
  95. }
  96. if (size == m_size) {
  97. return true;
  98. }
  99. auto oldSize = m_size;
  100. m_size = size;
  101. // round up to (n * pagesize)
  102. if (m_size < DEFAULT_MMAP_SIZE || (m_size % DEFAULT_MMAP_SIZE != 0)) {
  103. m_size = ((m_size / DEFAULT_MMAP_SIZE) + 1) * DEFAULT_MMAP_SIZE;
  104. }
  105. if (!ftruncate(m_diskFile.getFd(), m_size)) {
  106. MMKVError("fail to truncate [%ws] to size %zu", m_diskFile.m_path.c_str(), m_size);
  107. m_size = oldSize;
  108. return false;
  109. }
  110. if (m_size > oldSize) {
  111. if (!zeroFillFile(m_diskFile.getFd(), oldSize, m_size - oldSize)) {
  112. MMKVError("fail to zeroFile [%ws] to size %zu", m_diskFile.m_path.c_str(), m_size);
  113. m_size = oldSize;
  114. return false;
  115. }
  116. }
  117. if (m_ptr) {
  118. if (!UnmapViewOfFile(m_ptr)) {
  119. MMKVError("fail to munmap [%ws], %d", m_diskFile.m_path.c_str(), GetLastError());
  120. }
  121. m_ptr = nullptr;
  122. }
  123. if (m_fileMapping) {
  124. CloseHandle(m_fileMapping);
  125. m_fileMapping = nullptr;
  126. }
  127. auto ret = mmap();
  128. if (!ret) {
  129. doCleanMemoryCache(true);
  130. }
  131. return ret;
  132. }
  133. bool MemoryFile::msync(SyncFlag syncFlag) {
  134. if (m_ptr) {
  135. if (FlushViewOfFile(m_ptr, m_size)) {
  136. if (syncFlag == MMKV_SYNC) {
  137. if (!FlushFileBuffers(m_diskFile.getFd())) {
  138. MMKVError("fail to FlushFileBuffers [%ws]:%d", m_diskFile.m_path.c_str(), GetLastError());
  139. return false;
  140. }
  141. }
  142. return true;
  143. }
  144. MMKVError("fail to FlushViewOfFile [%ws]:%d", m_diskFile.m_path.c_str(), GetLastError());
  145. return false;
  146. }
  147. return false;
  148. }
  149. bool MemoryFile::mmap() {
  150. m_fileMapping = CreateFileMapping(m_diskFile.getFd(), nullptr, PAGE_READWRITE, 0, 0, nullptr);
  151. if (!m_fileMapping) {
  152. MMKVError("fail to CreateFileMapping [%ws], %d", m_diskFile.m_path.c_str(), GetLastError());
  153. return false;
  154. } else {
  155. m_ptr = (char *) MapViewOfFile(m_fileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
  156. if (!m_ptr) {
  157. MMKVError("fail to mmap [%ws], %d", m_diskFile.m_path.c_str(), GetLastError());
  158. return false;
  159. }
  160. }
  161. return true;
  162. }
  163. void MemoryFile::reloadFromFile() {
  164. if (isFileValid()) {
  165. MMKVWarning("calling reloadFromFile while the cache [%ws] is still valid", m_diskFile.m_path.c_str());
  166. assert(0);
  167. clearMemoryCache();
  168. }
  169. m_diskFile.open();
  170. if (m_diskFile.isFileValid()) {
  171. FileLock fileLock(m_diskFile.getFd());
  172. InterProcessLock lock(&fileLock, ExclusiveLockType);
  173. SCOPED_LOCK(&lock);
  174. mmkv::getFileSize(m_diskFile.getFd(), m_size);
  175. // round up to (n * pagesize)
  176. if (m_size < DEFAULT_MMAP_SIZE || (m_size % DEFAULT_MMAP_SIZE != 0)) {
  177. size_t roundSize = ((m_size / DEFAULT_MMAP_SIZE) + 1) * DEFAULT_MMAP_SIZE;
  178. truncate(roundSize);
  179. } else {
  180. auto ret = mmap();
  181. if (!ret) {
  182. doCleanMemoryCache(true);
  183. }
  184. }
  185. }
  186. }
  187. void MemoryFile::doCleanMemoryCache(bool forceClean) {
  188. if (m_ptr) {
  189. UnmapViewOfFile(m_ptr);
  190. m_ptr = nullptr;
  191. }
  192. if (m_fileMapping) {
  193. CloseHandle(m_fileMapping);
  194. m_fileMapping = nullptr;
  195. }
  196. m_diskFile.close();
  197. }
  198. size_t getPageSize() {
  199. SYSTEM_INFO system_info;
  200. GetSystemInfo(&system_info);
  201. return system_info.dwPageSize;
  202. }
  203. bool isFileExist(const MMKVPath_t &nsFilePath) {
  204. if (nsFilePath.empty()) {
  205. return false;
  206. }
  207. auto attribute = GetFileAttributes(nsFilePath.c_str());
  208. return (attribute != INVALID_FILE_ATTRIBUTES);
  209. }
  210. bool mkPath(const MMKVPath_t &str) {
  211. wchar_t *path = _wcsdup(str.c_str());
  212. bool done = false;
  213. wchar_t *slash = path;
  214. while (!done) {
  215. slash += wcsspn(slash, L"\\");
  216. slash += wcscspn(slash, L"\\");
  217. done = (*slash == L'\0');
  218. *slash = L'\0';
  219. auto attribute = GetFileAttributes(path);
  220. if (attribute == INVALID_FILE_ATTRIBUTES) {
  221. if (!CreateDirectory(path, nullptr)) {
  222. MMKVError("fail to create dir:%ws, %d", str.c_str(), GetLastError());
  223. free(path);
  224. return false;
  225. }
  226. } else if (!(attribute & FILE_ATTRIBUTE_DIRECTORY)) {
  227. MMKVError("%ws attribute:%d not a directry", str.c_str(), attribute);
  228. free(path);
  229. return false;
  230. }
  231. *slash = L'\\';
  232. }
  233. free(path);
  234. return true;
  235. }
  236. MMBuffer *readWholeFile(const MMKVPath_t &nsFilePath) {
  237. MMBuffer *buffer = nullptr;
  238. auto fd = CreateFile(nsFilePath.c_str(), GENERIC_READ | GENERIC_WRITE,
  239. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
  240. FILE_ATTRIBUTE_NORMAL, nullptr);
  241. if (fd != INVALID_HANDLE_VALUE) {
  242. size_t fileLength = 0;
  243. getFileSize(fd, fileLength);
  244. if (fileLength > 0) {
  245. buffer = new MMBuffer(static_cast<size_t>(fileLength));
  246. SetFilePointer(fd, 0, 0, FILE_BEGIN);
  247. DWORD readSize = 0;
  248. if (ReadFile(fd, buffer->getPtr(), fileLength, &readSize, nullptr)) {
  249. //fileSize = readSize;
  250. } else {
  251. MMKVWarning("fail to read %ws: %d", nsFilePath.c_str(), GetLastError());
  252. delete buffer;
  253. buffer = nullptr;
  254. }
  255. }
  256. CloseHandle(fd);
  257. } else {
  258. MMKVWarning("fail to open %ws: %d", nsFilePath.c_str(), GetLastError());
  259. }
  260. return buffer;
  261. }
  262. bool zeroFillFile(MMKVFileHandle_t file, size_t startPos, size_t size) {
  263. if (file == INVALID_HANDLE_VALUE) {
  264. return false;
  265. }
  266. if (size == 0) {
  267. return true;
  268. }
  269. LARGE_INTEGER position;
  270. position.QuadPart = startPos;
  271. if (!SetFilePointerEx(file, position, nullptr, FILE_BEGIN)) {
  272. MMKVError("fail to lseek fd[%p], error:%d", file, GetLastError());
  273. return false;
  274. }
  275. static const char zeros[4096] = {0};
  276. while (size >= sizeof(zeros)) {
  277. DWORD bytesWritten = 0;
  278. if (!WriteFile(file, zeros, sizeof(zeros), &bytesWritten, nullptr)) {
  279. MMKVError("fail to write fd[%p], error:%d", file, GetLastError());
  280. return false;
  281. }
  282. size -= bytesWritten;
  283. }
  284. if (size > 0) {
  285. DWORD bytesWritten = 0;
  286. if (!WriteFile(file, zeros, size, &bytesWritten, nullptr)) {
  287. MMKVError("fail to write fd[%p], error:%d", file, GetLastError());
  288. return false;
  289. }
  290. }
  291. return true;
  292. }
  293. static bool ftruncate(MMKVFileHandle_t file, size_t size) {
  294. LARGE_INTEGER large;
  295. large.QuadPart = size;
  296. if (SetFilePointerEx(file, large, 0, FILE_BEGIN)) {
  297. if (SetEndOfFile(file)) {
  298. return true;
  299. }
  300. MMKVError("fail to SetEndOfFile:%d", GetLastError());
  301. return false;
  302. } else {
  303. MMKVError("fail to SetFilePointer:%d", GetLastError());
  304. return false;
  305. }
  306. }
  307. static bool getFileSize(MMKVFileHandle_t fd, size_t &size) {
  308. LARGE_INTEGER filesize = {0};
  309. if (GetFileSizeEx(fd, &filesize)) {
  310. size = static_cast<size_t>(filesize.QuadPart);
  311. return true;
  312. }
  313. return false;
  314. }
  315. static pair<MMKVPath_t, MMKVFileHandle_t> createUniqueTempFile(const wchar_t *prefix) {
  316. wchar_t lpTempPathBuffer[MAX_PATH];
  317. // Gets the temp path env string (no guarantee it's a valid path).
  318. auto dwRetVal = GetTempPath(MAX_PATH, lpTempPathBuffer);
  319. if (dwRetVal > MAX_PATH || (dwRetVal == 0)) {
  320. MMKVError("GetTempPath failed %d", GetLastError());
  321. return {L"", INVALID_HANDLE_VALUE};
  322. }
  323. // Generates a temporary file name.
  324. wchar_t szTempFileName[MAX_PATH];
  325. if (!GetTempFileName(lpTempPathBuffer, prefix, 0, szTempFileName)) {
  326. MMKVError("GetTempFileName failed %d", GetLastError());
  327. return {L"", INVALID_HANDLE_VALUE};
  328. }
  329. auto hTempFile = CreateFile(szTempFileName, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
  330. if (hTempFile == INVALID_HANDLE_VALUE) {
  331. MMKVError("fail to create unique temp file [%ws], %d", szTempFileName, GetLastError());
  332. return {L"", INVALID_HANDLE_VALUE};
  333. }
  334. MMKVDebug("create unique temp file [%ws] with fd[%p]", szTempFileName, hTempFile);
  335. return {MMKVPath_t(szTempFileName), hTempFile};
  336. }
  337. bool tryAtomicRename(const MMKVPath_t &srcPath, const MMKVPath_t &dstPath) {
  338. if (MoveFileEx(srcPath.c_str(), dstPath.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) == 0) {
  339. MMKVError("MoveFileEx [%ws] to [%ws] failed %d", srcPath.c_str(), dstPath.c_str(), GetLastError());
  340. return false;
  341. }
  342. return true;
  343. }
  344. bool copyFileContent(const MMKVPath_t &srcPath, MMKVFileHandle_t dstFD, bool needTruncate) {
  345. if (dstFD == INVALID_HANDLE_VALUE) {
  346. return false;
  347. }
  348. bool ret = false;
  349. File srcFile(srcPath, OpenFlag::ReadOnly);
  350. if (!srcFile.isFileValid()) {
  351. return false;
  352. }
  353. auto bufferSize = getPageSize();
  354. auto buffer = (char *) malloc(bufferSize);
  355. if (!buffer) {
  356. MMKVError("fail to malloc size %zu, %d(%s)", bufferSize, errno, strerror(errno));
  357. goto errorOut;
  358. }
  359. SetFilePointer(dstFD, 0, 0, FILE_BEGIN);
  360. // the Win32 platform don't have sendfile()/fcopyfile() equivalent, do it the hard way
  361. while (true) {
  362. DWORD sizeRead = 0;
  363. if (!ReadFile(srcFile.getFd(), buffer, bufferSize, &sizeRead, nullptr)) {
  364. MMKVError("fail to read %ws: %d", srcPath.c_str(), GetLastError());
  365. goto errorOut;
  366. }
  367. DWORD sizeWrite = 0;
  368. if (!WriteFile(dstFD, buffer, sizeRead, &sizeWrite, nullptr)) {
  369. MMKVError("fail to write fd [%d], %d", dstFD, GetLastError());
  370. goto errorOut;
  371. }
  372. if (sizeRead < bufferSize) {
  373. break;
  374. }
  375. }
  376. if (needTruncate) {
  377. size_t dstFileSize = 0;
  378. getFileSize(dstFD, dstFileSize);
  379. auto srcFileSize = srcFile.getActualFileSize();
  380. if ((dstFileSize != srcFileSize) && !ftruncate(dstFD, static_cast<off_t>(srcFileSize))) {
  381. MMKVError("fail to truncate [%d] to size [%zu]", dstFD, srcFileSize);
  382. goto errorOut;
  383. }
  384. }
  385. ret = true;
  386. MMKVInfo("copy content from %ws to fd[%d] finish", srcPath.c_str(), dstFD);
  387. errorOut:
  388. free(buffer);
  389. return ret;
  390. }
  391. // copy to a temp file then rename it
  392. // this is the best we can do on Win32
  393. bool copyFile(const MMKVPath_t &srcPath, const MMKVPath_t &dstPath) {
  394. auto pair = createUniqueTempFile(L"MMKV");
  395. auto tmpFD = pair.second;
  396. auto &tmpPath = pair.first;
  397. if (tmpFD == INVALID_HANDLE_VALUE) {
  398. return false;
  399. }
  400. bool renamed = false;
  401. if (copyFileContent(srcPath, tmpFD, false)) {
  402. MMKVInfo("copyed file [%ws] to [%ws]", srcPath.c_str(), tmpPath.c_str());
  403. CloseHandle(tmpFD);
  404. renamed = tryAtomicRename(tmpPath.c_str(), dstPath.c_str());
  405. if (renamed) {
  406. MMKVInfo("copyfile [%ws] to [%ws] finish.", srcPath.c_str(), dstPath.c_str());
  407. }
  408. } else {
  409. CloseHandle(tmpFD);
  410. }
  411. if (!renamed) {
  412. DeleteFile(tmpPath.c_str());
  413. }
  414. return renamed;
  415. }
  416. bool copyFileContent(const MMKVPath_t &srcPath, const MMKVPath_t &dstPath) {
  417. File dstFile(dstPath, OpenFlag::WriteOnly | OpenFlag::Create | OpenFlag::Truncate);
  418. if (!dstFile.isFileValid()) {
  419. return false;
  420. }
  421. auto ret = copyFileContent(srcPath, dstFile.getFd(), false);
  422. if (!ret) {
  423. MMKVError("fail to copyfile(): target file %ws", dstPath.c_str());
  424. } else {
  425. MMKVInfo("copy content from %ws to [%ws] finish", srcPath.c_str(), dstPath.c_str());
  426. }
  427. return ret;
  428. }
  429. bool copyFileContent(const MMKVPath_t &srcPath, MMKVFileHandle_t dstFD) {
  430. return copyFileContent(srcPath, dstFD, true);
  431. }
  432. void walkInDir(const MMKVPath_t &dirPath,
  433. WalkType type,
  434. const std::function<void(const MMKVPath_t &, WalkType)> &walker) {
  435. wchar_t szDir[MAX_PATH];
  436. StringCchCopy(szDir, MAX_PATH, dirPath.c_str());
  437. StringCchCat(szDir, MAX_PATH, L"\\*");
  438. WIN32_FIND_DATA ffd;
  439. auto hFind = FindFirstFile(szDir, &ffd);
  440. if (hFind == INVALID_HANDLE_VALUE) {
  441. return;
  442. }
  443. do {
  444. if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  445. if (type & WalkFolder) {
  446. if (wcscmp(ffd.cFileName, L".") == 0 || wcscmp(ffd.cFileName, L"..") == 0) {
  447. continue;
  448. }
  449. walker(dirPath + L"\\" + ffd.cFileName, WalkFolder);
  450. }
  451. } else if (type & WalkFile) {
  452. walker(dirPath + L"\\" + ffd.cFileName, WalkFile);
  453. }
  454. } while (FindNextFile(hFind, &ffd) != 0);
  455. auto dwError = GetLastError();
  456. if (dwError != ERROR_NO_MORE_FILES) {
  457. MMKVError("WalkInDir fail %d", dwError);
  458. }
  459. FindClose(hFind);
  460. }
  461. } // namespace mmkv
  462. std::wstring string2MMKVPath_t(const std::string &str) {
  463. auto length = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
  464. auto buffer = new wchar_t[length];
  465. MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, buffer, length);
  466. wstring result(buffer);
  467. delete[] buffer;
  468. return result;
  469. }
  470. std::string MMKVPath_t2String(const MMKVPath_t &str) {
  471. auto length = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, nullptr, 0, 0, 0);
  472. auto buffer = new char[length];
  473. WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, buffer, length, 0, 0);
  474. string result(buffer);
  475. delete[] buffer;
  476. return result;
  477. }
  478. #endif // MMKV_WIN32