MMKV_Android.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  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 "MMKV.h"
  21. // #include <bits/alltypes.h>
  22. #ifdef MMKV_ANDROID
  23. # include "InterProcessLock.h"
  24. # include "KeyValueHolder.h"
  25. # include "MMKVLog.h"
  26. # include "MMKVMetaInfo.hpp"
  27. # include "MemoryFile.h"
  28. # include "ScopedLock.hpp"
  29. # include "ThreadLock.h"
  30. # include <unistd.h>
  31. # include "MMKV_IO.h"
  32. using namespace std;
  33. using namespace mmkv;
  34. extern unordered_map<string, MMKV *> *g_instanceDic;
  35. extern ThreadLock *g_instanceLock;
  36. #ifndef MMKV_OHOS
  37. static bool g_enableProcessModeCheck = false;
  38. #endif
  39. MMKV::MMKV(const string &mmapID, int size, MMKVMode mode, const string *cryptKey, const string *rootPath, size_t expectedCapacity)
  40. : m_mmapID(mmapID)
  41. , m_mode(mode)
  42. , m_path(mappedKVPathWithID(m_mmapID, rootPath, mode))
  43. , m_crcPath(crcPathWithPath(m_path))
  44. , m_dic(nullptr)
  45. , m_dicCrypt(nullptr)
  46. , m_expectedCapacity(std::max<size_t>(DEFAULT_MMAP_SIZE, roundUp<size_t>(expectedCapacity, DEFAULT_MMAP_SIZE)))
  47. , m_file(new MemoryFile(m_path, size, isAshmem() ? MMFILE_TYPE_ASHMEM : MMFILE_TYPE_FILE, m_expectedCapacity, isReadOnly(), !isAshmem()))
  48. , m_metaFile(new MemoryFile(m_crcPath, DEFAULT_MMAP_SIZE, m_file->m_fileType, 0, isReadOnly()))
  49. , m_metaInfo(new MMKVMetaInfo())
  50. , m_crypter(nullptr)
  51. , m_lock(new ThreadLock())
  52. , m_fileLock(new FileLock(m_metaFile->getFd(), isAshmem(), 0, 1))
  53. , m_sharedProcessLock(new InterProcessLock(m_fileLock, SharedLockType))
  54. , m_exclusiveProcessLock(new InterProcessLock(m_fileLock, ExclusiveLockType)) {
  55. m_actualSize = 0;
  56. m_output = nullptr;
  57. #ifndef MMKV_OHOS
  58. if (g_enableProcessModeCheck) {
  59. // force use fcntl(), otherwise will conflict with MemoryFile::reloadFromFile()
  60. m_fileModeLock = new FileLock(m_metaFile->getFd(), true, 1, 2);
  61. m_sharedProcessModeLock = new InterProcessLock(m_fileModeLock, SharedLockType);
  62. } else {
  63. m_fileModeLock = nullptr;
  64. m_sharedProcessModeLock = nullptr;
  65. }
  66. m_exclusiveProcessModeLock = nullptr;
  67. #endif
  68. m_fileMigrationLock = new FileLock(m_metaFile->getFd(), true, 2, 3);
  69. m_sharedMigrationLock = new InterProcessLock(m_fileMigrationLock, SharedLockType);
  70. m_sharedMigrationLock->try_lock();
  71. # ifndef MMKV_DISABLE_CRYPT
  72. if (cryptKey && cryptKey->length() > 0) {
  73. m_dicCrypt = new MMKVMapCrypt();
  74. m_crypter = new AESCrypt(cryptKey->data(), cryptKey->length());
  75. } else
  76. # endif
  77. {
  78. m_dic = new MMKVMap();
  79. }
  80. m_needLoadFromFile = true;
  81. m_hasFullWriteback = false;
  82. m_crcDigest = 0;
  83. m_sharedProcessLock->m_enable = isMultiProcess();
  84. m_exclusiveProcessLock->m_enable = isMultiProcess();
  85. // sensitive zone
  86. /*{
  87. SCOPED_LOCK(m_sharedProcessLock);
  88. loadFromFile();
  89. }*/
  90. }
  91. MMKV::MMKV(const string &mmapID, int ashmemFD, int ashmemMetaFD, const string *cryptKey)
  92. : m_mmapID(mmapID)
  93. , m_mode(MMKV_ASHMEM)
  94. , m_path(mappedKVPathWithID(m_mmapID, nullptr, MMKV_ASHMEM))
  95. , m_crcPath(crcPathWithPath(m_path))
  96. , m_dic(nullptr)
  97. , m_dicCrypt(nullptr)
  98. , m_expectedCapacity(DEFAULT_MMAP_SIZE)
  99. , m_file(new MemoryFile(ashmemFD))
  100. , m_metaFile(new MemoryFile(ashmemMetaFD))
  101. , m_metaInfo(new MMKVMetaInfo())
  102. , m_crypter(nullptr)
  103. , m_lock(new ThreadLock())
  104. , m_fileLock(new FileLock(m_metaFile->getFd(), true, 0, 1))
  105. , m_sharedProcessLock(new InterProcessLock(m_fileLock, SharedLockType))
  106. , m_exclusiveProcessLock(new InterProcessLock(m_fileLock, ExclusiveLockType)) {
  107. m_actualSize = 0;
  108. m_output = nullptr;
  109. #ifndef MMKV_OHOS
  110. if (g_enableProcessModeCheck) {
  111. // force use fcntl(), otherwise will conflict with MemoryFile::reloadFromFile()
  112. m_fileModeLock = new FileLock(m_metaFile->getFd(), true, 1, 2);
  113. m_sharedProcessModeLock = new InterProcessLock(m_fileModeLock, SharedLockType);
  114. } else {
  115. m_fileModeLock = nullptr;
  116. m_sharedProcessModeLock = nullptr;
  117. }
  118. m_exclusiveProcessModeLock = nullptr;
  119. #endif
  120. m_fileMigrationLock = new FileLock(m_metaFile->getFd(), true, 2, 3);
  121. m_sharedMigrationLock = new InterProcessLock(m_fileMigrationLock, SharedLockType);
  122. m_sharedMigrationLock->try_lock();
  123. # ifndef MMKV_DISABLE_CRYPT
  124. if (cryptKey && cryptKey->length() > 0) {
  125. m_dicCrypt = new MMKVMapCrypt();
  126. m_crypter = new AESCrypt(cryptKey->data(), cryptKey->length());
  127. } else
  128. # endif
  129. {
  130. m_dic = new MMKVMap();
  131. }
  132. m_needLoadFromFile = true;
  133. m_hasFullWriteback = false;
  134. m_crcDigest = 0;
  135. m_sharedProcessLock->m_enable = true;
  136. m_exclusiveProcessLock->m_enable = true;
  137. // sensitive zone
  138. /*{
  139. SCOPED_LOCK(m_sharedProcessLock);
  140. loadFromFile();
  141. }*/
  142. }
  143. // historically Android mistakenly use mmapKey as mmapID, we try migrate back to normal when possible
  144. MigrateStatus tryMigrateLegacyMMKVFile(const string &mmapID, const string *rootPath) {
  145. auto legacyID = legacyMmapedKVKey(mmapID, rootPath);
  146. if (legacyID == mmapID) {
  147. // it's not specially encoded
  148. return MigrateStatus::NotSpecial;
  149. }
  150. auto path = mappedKVPathWithID(legacyID, rootPath);
  151. auto targetPath = mappedKVPathWithID(mmapID, rootPath);
  152. bool oldExit = isFileExist(path);
  153. bool newExist = isFileExist(targetPath);
  154. if (oldExit) {
  155. if (newExist) {
  156. MMKVWarning("both legacy file [%s] modify: %lld ms, and new file [%s] modify: %lld ms exist",
  157. path.c_str(), getFileModifyTimeInMS(path.c_str()),
  158. targetPath.c_str(), getFileModifyTimeInMS(targetPath.c_str()));
  159. return MigrateStatus::OldAndNewExist;
  160. }
  161. auto file = File(path, OpenFlag::ReadWrite);
  162. if (file.isFileValid()) {
  163. // check if it's opened by other process
  164. auto fileMigrationLock = FileLock(file.getFd(), true, 2, 3);
  165. auto exclusiveMigrationLock = InterProcessLock(&fileMigrationLock, ExclusiveLockType);
  166. // works even if it's opened by us
  167. if (exclusiveMigrationLock.try_lock()) {
  168. if (tryAtomicRename(path, targetPath)) {
  169. if (tryAtomicRename(crcPathWithPath(path), crcPathWithPath(targetPath))) {
  170. MMKVInfo("Migrated legacy MMKV [%s] to [%s] in path %s", legacyID.c_str(), mmapID.c_str(), rootPath->c_str());
  171. return MigrateStatus::OldToNewMigrated;
  172. }
  173. }
  174. } else {
  175. MMKVInfo("Can't migrate legacy MMKV [%s] to [%s] in path %s, try next time.", legacyID.c_str(), mmapID.c_str(), rootPath->c_str());
  176. }
  177. }
  178. return MigrateStatus::OldToNewMigrateFail;
  179. }
  180. return newExist ? MigrateStatus::NewExist : MigrateStatus::NoneExist;
  181. }
  182. MMKV *MMKV::mmkvWithID(const string &mmapID, int size, MMKVMode mode, const string *cryptKey, const string *rootPath, size_t expectedCapacity) {
  183. if (mmapID.empty() || !g_instanceLock) {
  184. return nullptr;
  185. }
  186. SCOPED_LOCK(g_instanceLock);
  187. auto mmapKey = mmapedKVKey(mmapID, rootPath);
  188. auto itr = g_instanceDic->find(mmapKey);
  189. if (itr != g_instanceDic->end()) {
  190. MMKV *kv = itr->second;
  191. return kv;
  192. }
  193. if (rootPath) {
  194. if (!isFileExist(*rootPath)) {
  195. if (!mkPath(*rootPath)) {
  196. return nullptr;
  197. }
  198. }
  199. MMKVInfo("prepare to load %s (id %s) from rootPath %s", mmapID.c_str(), mmapKey.c_str(), rootPath->c_str());
  200. }
  201. MMKV *kv = nullptr;
  202. auto migrateStatus = (mode & MMKV_ASHMEM) ? MigrateStatus::NoneExist : tryMigrateLegacyMMKVFile(mmapID, rootPath);
  203. switch (migrateStatus) {
  204. case MigrateStatus::NotSpecial:
  205. case MigrateStatus::NoneExist:
  206. case MigrateStatus::NewExist:
  207. case MigrateStatus::OldToNewMigrated:
  208. case MigrateStatus::OldAndNewExist: // TODO: shell we compare and use the latest one?
  209. kv = new MMKV(mmapID, size, mode, cryptKey, rootPath, expectedCapacity);
  210. break;
  211. case MigrateStatus::OldToNewMigrateFail: {
  212. auto legacyID = legacyMmapedKVKey(mmapID, rootPath);
  213. kv = new MMKV(legacyID, size, mode, cryptKey, rootPath, expectedCapacity);
  214. break;
  215. }
  216. }
  217. kv->m_mmapKey = mmapKey;
  218. (*g_instanceDic)[mmapKey] = kv;
  219. return kv;
  220. }
  221. MMKV *MMKV::mmkvWithAshmemFD(const string &mmapID, int fd, int metaFD, const string *cryptKey) {
  222. if (fd < 0 || !g_instanceLock) {
  223. return nullptr;
  224. }
  225. SCOPED_LOCK(g_instanceLock);
  226. auto itr = g_instanceDic->find(mmapID);
  227. if (itr != g_instanceDic->end()) {
  228. MMKV *kv = itr->second;
  229. # ifndef MMKV_DISABLE_CRYPT
  230. kv->checkReSetCryptKey(fd, metaFD, cryptKey);
  231. # endif
  232. return kv;
  233. }
  234. auto kv = new MMKV(mmapID, fd, metaFD, cryptKey);
  235. kv->m_mmapKey = mmapID;
  236. (*g_instanceDic)[mmapID] = kv;
  237. return kv;
  238. }
  239. int MMKV::ashmemFD() {
  240. return (m_file->m_fileType & mmkv::MMFILE_TYPE_ASHMEM) ? m_file->getFd() : -1;
  241. }
  242. int MMKV::ashmemMetaFD() {
  243. return (m_file->m_fileType & mmkv::MMFILE_TYPE_ASHMEM) ? m_metaFile->getFd() : -1;
  244. }
  245. # ifndef MMKV_DISABLE_CRYPT
  246. void MMKV::checkReSetCryptKey(int fd, int metaFD, const string *cryptKey) {
  247. SCOPED_LOCK(m_lock);
  248. checkReSetCryptKey(cryptKey);
  249. if (m_file->m_fileType & MMFILE_TYPE_ASHMEM) {
  250. if (m_file->getFd() != fd) {
  251. ::close(fd);
  252. }
  253. if (m_metaFile->getFd() != metaFD) {
  254. ::close(metaFD);
  255. }
  256. }
  257. }
  258. # endif // MMKV_DISABLE_CRYPT
  259. #ifndef MMKV_OHOS
  260. bool MMKV::checkProcessMode() {
  261. // avoid exception on open() error
  262. if (!m_file->isFileValid()) {
  263. return true;
  264. }
  265. // avoid invalid status
  266. if (!g_enableProcessModeCheck || !m_fileModeLock || !m_sharedProcessModeLock) {
  267. return true;
  268. }
  269. if (isMultiProcess()) {
  270. if (!m_exclusiveProcessModeLock) {
  271. m_exclusiveProcessModeLock = new InterProcessLock(m_fileModeLock, ExclusiveLockType);
  272. }
  273. // avoid multiple processes get shared lock at the same time, https://github.com/Tencent/MMKV/issues/523
  274. auto tryAgain = false;
  275. auto exclusiveLocked = m_exclusiveProcessModeLock->try_lock(&tryAgain);
  276. if (exclusiveLocked) {
  277. return true;
  278. }
  279. auto shareLocked = m_sharedProcessModeLock->try_lock();
  280. if (!shareLocked) {
  281. // this call will fail on most case, just do it to make sure
  282. m_exclusiveProcessModeLock->try_lock();
  283. return true;
  284. } else {
  285. if (!tryAgain) {
  286. // something wrong with the OS/filesystem, let's try again
  287. exclusiveLocked = m_exclusiveProcessModeLock->try_lock(&tryAgain);
  288. if (!exclusiveLocked && !tryAgain) {
  289. // still something wrong, we have to give up and assume it passed the test
  290. MMKVWarning("Got a shared lock, but fail to exclusive lock [%s], assume it's ok", m_mmapID.c_str());
  291. exclusiveLocked = true;
  292. }
  293. }
  294. if (!exclusiveLocked) {
  295. MMKVError("Got a shared lock, but fail to exclusive lock [%s]", m_mmapID.c_str());
  296. }
  297. return exclusiveLocked;
  298. }
  299. } else {
  300. auto tryAgain = false;
  301. auto shareLocked = m_sharedProcessModeLock->try_lock(&tryAgain);
  302. if (!shareLocked && !tryAgain) {
  303. // something wrong with the OS/filesystem, we have to give up and assume it passed the test
  304. MMKVWarning("Fail to shared lock [%s], assume it's ok", m_mmapID.c_str());
  305. shareLocked = true;
  306. }
  307. if (!shareLocked) {
  308. MMKVError("Fail to share lock [%s]", m_mmapID.c_str());
  309. }
  310. return shareLocked;
  311. }
  312. }
  313. void MMKV::enableDisableProcessMode(bool enable) {
  314. MMKVInfo("process mode check enable/disable: %d", enable);
  315. g_enableProcessModeCheck = enable;
  316. }
  317. #endif // !MMKV_OHOS
  318. MMKV *NameSpace::mmkvWithID(const string &mmapID, int size, MMKVMode mode, const string *cryptKey, size_t expectedCapacity) {
  319. return MMKV::mmkvWithID(mmapID, size, mode, cryptKey, &m_rootDir, expectedCapacity);
  320. }
  321. #endif // MMKV_ANDROID