MemoryFile_Win32.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  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:[%ls], %d", m_path.c_str(), GetLastError());
  65. return false;
  66. }
  67. MMKVInfo("open fd[%p], %ls", m_fd, m_path.c_str());
  68. return true;
  69. }
  70. void File::close() {
  71. if (isFileValid()) {
  72. MMKVInfo("closing fd[%p], %ls", m_fd, m_path.c_str());
  73. if (CloseHandle(m_fd)) {
  74. m_fd = INVALID_HANDLE_VALUE;
  75. } else {
  76. MMKVError("fail to close [%ls], %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 (m_ptr) {
  106. if (!UnmapViewOfFile(m_ptr)) {
  107. MMKVError("fail to munmap [%ls], %d", m_diskFile.m_path.c_str(), GetLastError());
  108. }
  109. m_ptr = nullptr;
  110. }
  111. if (m_fileMapping) {
  112. CloseHandle(m_fileMapping);
  113. m_fileMapping = nullptr;
  114. }
  115. if (!ftruncate(m_diskFile.getFd(), m_size)) {
  116. MMKVError("fail to truncate [%ls] to size %zu", m_diskFile.m_path.c_str(), m_size);
  117. m_size = oldSize;
  118. mmap();
  119. return false;
  120. }
  121. if (m_size > oldSize) {
  122. if (!zeroFillFile(m_diskFile.getFd(), oldSize, m_size - oldSize)) {
  123. MMKVError("fail to zeroFile [%ls] to size %zu", m_diskFile.m_path.c_str(), m_size);
  124. m_size = oldSize;
  125. mmap();
  126. return false;
  127. }
  128. }
  129. auto ret = mmap();
  130. if (!ret) {
  131. doCleanMemoryCache(true);
  132. }
  133. return ret;
  134. }
  135. bool MemoryFile::msync(SyncFlag syncFlag) {
  136. if (m_ptr) {
  137. if (FlushViewOfFile(m_ptr, m_size)) {
  138. if (syncFlag == MMKV_SYNC) {
  139. if (!FlushFileBuffers(m_diskFile.getFd())) {
  140. MMKVError("fail to FlushFileBuffers [%ls]:%d", m_diskFile.m_path.c_str(), GetLastError());
  141. return false;
  142. }
  143. }
  144. return true;
  145. }
  146. MMKVError("fail to FlushViewOfFile [%ls]:%d", m_diskFile.m_path.c_str(), GetLastError());
  147. return false;
  148. }
  149. return false;
  150. }
  151. bool MemoryFile::mmap() {
  152. m_fileMapping = CreateFileMapping(m_diskFile.getFd(), nullptr, PAGE_READWRITE, 0, 0, nullptr);
  153. if (!m_fileMapping) {
  154. MMKVError("fail to CreateFileMapping [%ls], %d", m_diskFile.m_path.c_str(), GetLastError());
  155. return false;
  156. } else {
  157. m_ptr = (char *) MapViewOfFile(m_fileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
  158. if (!m_ptr) {
  159. MMKVError("fail to mmap [%ls], %d", m_diskFile.m_path.c_str(), GetLastError());
  160. return false;
  161. }
  162. }
  163. return true;
  164. }
  165. void MemoryFile::reloadFromFile() {
  166. if (isFileValid()) {
  167. MMKVWarning("calling reloadFromFile while the cache [%ls] is still valid", m_diskFile.m_path.c_str());
  168. assert(0);
  169. clearMemoryCache();
  170. }
  171. m_diskFile.open();
  172. if (m_diskFile.isFileValid()) {
  173. FileLock fileLock(m_diskFile.getFd());
  174. InterProcessLock lock(&fileLock, ExclusiveLockType);
  175. SCOPED_LOCK(&lock);
  176. mmkv::getFileSize(m_diskFile.getFd(), m_size);
  177. // round up to (n * pagesize)
  178. if (m_size < DEFAULT_MMAP_SIZE || (m_size % DEFAULT_MMAP_SIZE != 0)) {
  179. size_t roundSize = ((m_size / DEFAULT_MMAP_SIZE) + 1) * DEFAULT_MMAP_SIZE;
  180. truncate(roundSize);
  181. } else {
  182. auto ret = mmap();
  183. if (!ret) {
  184. doCleanMemoryCache(true);
  185. }
  186. }
  187. }
  188. }
  189. void MemoryFile::doCleanMemoryCache(bool forceClean) {
  190. if (m_ptr) {
  191. UnmapViewOfFile(m_ptr);
  192. m_ptr = nullptr;
  193. }
  194. if (m_fileMapping) {
  195. CloseHandle(m_fileMapping);
  196. m_fileMapping = nullptr;
  197. }
  198. m_diskFile.close();
  199. }
  200. size_t getPageSize() {
  201. SYSTEM_INFO system_info;
  202. GetSystemInfo(&system_info);
  203. return system_info.dwPageSize;
  204. }
  205. bool isFileExist(const MMKVPath_t &nsFilePath) {
  206. if (nsFilePath.empty()) {
  207. return false;
  208. }
  209. auto attribute = GetFileAttributes(nsFilePath.c_str());
  210. return (attribute != INVALID_FILE_ATTRIBUTES);
  211. }
  212. bool mkPath(const MMKVPath_t &str) {
  213. wchar_t *path = _wcsdup(str.c_str());
  214. bool done = false;
  215. wchar_t *slash = path;
  216. while (!done) {
  217. slash += wcsspn(slash, L"\\");
  218. slash += wcscspn(slash, L"\\");
  219. done = (*slash == L'\0');
  220. *slash = L'\0';
  221. auto attribute = GetFileAttributes(path);
  222. if (attribute == INVALID_FILE_ATTRIBUTES) {
  223. if (!CreateDirectory(path, nullptr)) {
  224. MMKVError("fail to create dir:%ls, %d", str.c_str(), GetLastError());
  225. free(path);
  226. return false;
  227. }
  228. } else if (!(attribute & FILE_ATTRIBUTE_DIRECTORY)) {
  229. MMKVError("%ls attribute:%d not a directry", str.c_str(), attribute);
  230. free(path);
  231. return false;
  232. }
  233. *slash = L'\\';
  234. }
  235. free(path);
  236. return true;
  237. }
  238. MMBuffer *readWholeFile(const MMKVPath_t &nsFilePath) {
  239. MMBuffer *buffer = nullptr;
  240. auto fd = CreateFile(nsFilePath.c_str(), GENERIC_READ | GENERIC_WRITE,
  241. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
  242. FILE_ATTRIBUTE_NORMAL, nullptr);
  243. if (fd != INVALID_HANDLE_VALUE) {
  244. size_t fileLength = 0;
  245. getFileSize(fd, fileLength);
  246. if (fileLength > 0) {
  247. buffer = new MMBuffer(static_cast<size_t>(fileLength));
  248. SetFilePointer(fd, 0, 0, FILE_BEGIN);
  249. DWORD readSize = 0;
  250. if (ReadFile(fd, buffer->getPtr(), fileLength, &readSize, nullptr)) {
  251. //fileSize = readSize;
  252. } else {
  253. MMKVWarning("fail to read %ls: %d", nsFilePath.c_str(), GetLastError());
  254. delete buffer;
  255. buffer = nullptr;
  256. }
  257. }
  258. CloseHandle(fd);
  259. } else {
  260. MMKVWarning("fail to open %ls: %d", nsFilePath.c_str(), GetLastError());
  261. }
  262. return buffer;
  263. }
  264. bool zeroFillFile(MMKVFileHandle_t file, size_t startPos, size_t size) {
  265. if (file == INVALID_HANDLE_VALUE) {
  266. return false;
  267. }
  268. if (size == 0) {
  269. return true;
  270. }
  271. LARGE_INTEGER position;
  272. position.QuadPart = startPos;
  273. if (!SetFilePointerEx(file, position, nullptr, FILE_BEGIN)) {
  274. MMKVError("fail to lseek fd[%p], error:%d", file, GetLastError());
  275. return false;
  276. }
  277. static const char zeros[4096] = {0};
  278. while (size >= sizeof(zeros)) {
  279. DWORD bytesWritten = 0;
  280. if (!WriteFile(file, zeros, sizeof(zeros), &bytesWritten, nullptr)) {
  281. MMKVError("fail to write fd[%p], error:%d", file, GetLastError());
  282. return false;
  283. }
  284. size -= bytesWritten;
  285. }
  286. if (size > 0) {
  287. DWORD bytesWritten = 0;
  288. if (!WriteFile(file, zeros, size, &bytesWritten, nullptr)) {
  289. MMKVError("fail to write fd[%p], error:%d", file, GetLastError());
  290. return false;
  291. }
  292. }
  293. return true;
  294. }
  295. static bool ftruncate(MMKVFileHandle_t file, size_t size) {
  296. LARGE_INTEGER large;
  297. large.QuadPart = size;
  298. if (SetFilePointerEx(file, large, 0, FILE_BEGIN)) {
  299. if (SetEndOfFile(file)) {
  300. return true;
  301. }
  302. MMKVError("fail to SetEndOfFile:%d", GetLastError());
  303. return false;
  304. } else {
  305. MMKVError("fail to SetFilePointer:%d", GetLastError());
  306. return false;
  307. }
  308. }
  309. static bool getFileSize(MMKVFileHandle_t fd, size_t &size) {
  310. LARGE_INTEGER filesize = {0};
  311. if (GetFileSizeEx(fd, &filesize)) {
  312. size = static_cast<size_t>(filesize.QuadPart);
  313. return true;
  314. }
  315. return false;
  316. }
  317. static pair<MMKVPath_t, MMKVFileHandle_t> createUniqueTempFile(const wchar_t *prefix) {
  318. wchar_t lpTempPathBuffer[MAX_PATH];
  319. // Gets the temp path env string (no guarantee it's a valid path).
  320. auto dwRetVal = GetTempPath(MAX_PATH, lpTempPathBuffer);
  321. if (dwRetVal > MAX_PATH || (dwRetVal == 0)) {
  322. MMKVError("GetTempPath failed %d", GetLastError());
  323. return {L"", INVALID_HANDLE_VALUE};
  324. }
  325. // Generates a temporary file name.
  326. wchar_t szTempFileName[MAX_PATH];
  327. if (!GetTempFileName(lpTempPathBuffer, prefix, 0, szTempFileName)) {
  328. MMKVError("GetTempFileName failed %d", GetLastError());
  329. return {L"", INVALID_HANDLE_VALUE};
  330. }
  331. auto hTempFile =
  332. CreateFile(szTempFileName, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
  333. if (hTempFile == INVALID_HANDLE_VALUE) {
  334. MMKVError("fail to create unique temp file [%ls], %d", szTempFileName, GetLastError());
  335. return {L"", INVALID_HANDLE_VALUE};
  336. }
  337. MMKVDebug("create unique temp file [%ls] with fd[%p]", szTempFileName, hTempFile);
  338. return {MMKVPath_t(szTempFileName), hTempFile};
  339. }
  340. bool tryAtomicRename(const MMKVPath_t &srcPath, const MMKVPath_t &dstPath) {
  341. if (MoveFileEx(srcPath.c_str(), dstPath.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) == 0) {
  342. MMKVError("MoveFileEx [%ls] to [%ls] failed %d", srcPath.c_str(), dstPath.c_str(), GetLastError());
  343. return false;
  344. }
  345. return true;
  346. }
  347. bool copyFileContent(const MMKVPath_t &srcPath, MMKVFileHandle_t dstFD, bool needTruncate) {
  348. if (dstFD == INVALID_HANDLE_VALUE) {
  349. return false;
  350. }
  351. bool ret = false;
  352. File srcFile(srcPath, OpenFlag::ReadOnly);
  353. if (!srcFile.isFileValid()) {
  354. return false;
  355. }
  356. auto bufferSize = getPageSize();
  357. auto buffer = (char *) malloc(bufferSize);
  358. if (!buffer) {
  359. MMKVError("fail to malloc size %zu, %d(%s)", bufferSize, errno, strerror(errno));
  360. goto errorOut;
  361. }
  362. SetFilePointer(dstFD, 0, 0, FILE_BEGIN);
  363. // the Win32 platform don't have sendfile()/fcopyfile() equivalent, do it the hard way
  364. while (true) {
  365. DWORD sizeRead = 0;
  366. if (!ReadFile(srcFile.getFd(), buffer, bufferSize, &sizeRead, nullptr)) {
  367. MMKVError("fail to read %ls: %d", srcPath.c_str(), GetLastError());
  368. goto errorOut;
  369. }
  370. DWORD sizeWrite = 0;
  371. if (!WriteFile(dstFD, buffer, sizeRead, &sizeWrite, nullptr)) {
  372. MMKVError("fail to write fd [%d], %d", dstFD, GetLastError());
  373. goto errorOut;
  374. }
  375. if (sizeRead < bufferSize) {
  376. break;
  377. }
  378. }
  379. if (needTruncate) {
  380. size_t dstFileSize = 0;
  381. getFileSize(dstFD, dstFileSize);
  382. auto srcFileSize = srcFile.getActualFileSize();
  383. if ((dstFileSize != srcFileSize) && !ftruncate(dstFD, static_cast<off_t>(srcFileSize))) {
  384. MMKVError("fail to truncate [%d] to size [%zu]", dstFD, srcFileSize);
  385. goto errorOut;
  386. }
  387. }
  388. ret = true;
  389. MMKVInfo("copy content from %ls to fd[%d] finish", srcPath.c_str(), dstFD);
  390. errorOut:
  391. free(buffer);
  392. return ret;
  393. }
  394. // copy to a temp file then rename it
  395. // this is the best we can do on Win32
  396. bool copyFile(const MMKVPath_t &srcPath, const MMKVPath_t &dstPath) {
  397. auto pair = createUniqueTempFile(L"MMKV");
  398. auto tmpFD = pair.second;
  399. auto &tmpPath = pair.first;
  400. if (tmpFD == INVALID_HANDLE_VALUE) {
  401. return false;
  402. }
  403. bool renamed = false;
  404. if (copyFileContent(srcPath, tmpFD, false)) {
  405. MMKVInfo("copyed file [%ls] to [%ls]", srcPath.c_str(), tmpPath.c_str());
  406. CloseHandle(tmpFD);
  407. renamed = tryAtomicRename(tmpPath.c_str(), dstPath.c_str());
  408. if (renamed) {
  409. MMKVInfo("copyfile [%ls] to [%ls] finish.", srcPath.c_str(), dstPath.c_str());
  410. }
  411. } else {
  412. CloseHandle(tmpFD);
  413. }
  414. if (!renamed) {
  415. DeleteFile(tmpPath.c_str());
  416. }
  417. return renamed;
  418. }
  419. bool copyFileContent(const MMKVPath_t &srcPath, const MMKVPath_t &dstPath) {
  420. File dstFile(dstPath, OpenFlag::WriteOnly | OpenFlag::Create | OpenFlag::Truncate);
  421. if (!dstFile.isFileValid()) {
  422. return false;
  423. }
  424. auto ret = copyFileContent(srcPath, dstFile.getFd(), false);
  425. if (!ret) {
  426. MMKVError("fail to copyfile(): target file %ls", dstPath.c_str());
  427. } else {
  428. MMKVInfo("copy content from %ls to [%ls] finish", srcPath.c_str(), dstPath.c_str());
  429. }
  430. return ret;
  431. }
  432. bool copyFileContent(const MMKVPath_t &srcPath, MMKVFileHandle_t dstFD) {
  433. return copyFileContent(srcPath, dstFD, true);
  434. }
  435. void walkInDir(const MMKVPath_t &dirPath,
  436. WalkType type,
  437. const std::function<void(const MMKVPath_t &, WalkType)> &walker) {
  438. wchar_t szDir[MAX_PATH];
  439. StringCchCopy(szDir, MAX_PATH, dirPath.c_str());
  440. StringCchCat(szDir, MAX_PATH, L"\\*");
  441. WIN32_FIND_DATA ffd;
  442. auto hFind = FindFirstFile(szDir, &ffd);
  443. if (hFind == INVALID_HANDLE_VALUE) {
  444. return;
  445. }
  446. do {
  447. if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  448. if (type & WalkFolder) {
  449. if (wcscmp(ffd.cFileName, L".") == 0 || wcscmp(ffd.cFileName, L"..") == 0) {
  450. continue;
  451. }
  452. walker(dirPath + L"\\" + ffd.cFileName, WalkFolder);
  453. }
  454. } else if (type & WalkFile) {
  455. walker(dirPath + L"\\" + ffd.cFileName, WalkFile);
  456. }
  457. } while (FindNextFile(hFind, &ffd) != 0);
  458. auto dwError = GetLastError();
  459. if (dwError != ERROR_NO_MORE_FILES) {
  460. MMKVError("WalkInDir fail %d", dwError);
  461. }
  462. FindClose(hFind);
  463. }
  464. } // namespace mmkv
  465. std::wstring string2MMKVPath_t(const std::string &str) {
  466. auto length = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
  467. auto buffer = new wchar_t[length];
  468. MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, buffer, length);
  469. wstring result(buffer);
  470. delete[] buffer;
  471. return result;
  472. }
  473. std::string MMKVPath_t2String(const MMKVPath_t &str) {
  474. auto length = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, nullptr, 0, 0, 0);
  475. auto buffer = new char[length];
  476. WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, buffer, length, 0, 0);
  477. string result(buffer);
  478. delete[] buffer;
  479. return result;
  480. }
  481. #endif // MMKV_WIN32