LinuxCommon.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. #define BFP_HAS_EXECINFO
  2. #define BFP_HAS_PTHREAD_TIMEDJOIN_NP
  3. #define BFP_HAS_PTHREAD_GETATTR_NP
  4. #define BFP_HAS_DLINFO
  5. #define BFP_HAS_FILEWATCHER
  6. #include "../posix/PosixCommon.cpp"
  7. #ifdef BFP_HAS_FILEWATCHER
  8. #include <sys/inotify.h>
  9. struct BfpFileWatcher
  10. {
  11. String mPath;
  12. BfpDirectoryChangeFunc mDirectoryChangeFunc;
  13. int mHandle;
  14. BfpFileWatcherFlags mFlags;
  15. void* mUserData;
  16. };
  17. class InotifyFileWatchManager : public FileWatchManager
  18. {
  19. struct SubdirInfo
  20. {
  21. BfpFileWatcher* mWatcher;
  22. int mHandle;
  23. String mRelativePath;
  24. };
  25. static constexpr size_t MAX_NOTIFY_EVENTS = 1024;
  26. static constexpr size_t NOTIFY_BUFFER_SIZE = (sizeof(inotify_event) * (MAX_NOTIFY_EVENTS + PATH_MAX));
  27. bool mIsClosing;
  28. int mInotifyHandle;
  29. pthread_t mWorkerThread;
  30. Dictionary<int, BfpFileWatcher*> mWatchers;
  31. Dictionary<int, SubdirInfo> mSubdirs;
  32. CritSect mCritSect;
  33. char mEventBuffer[NOTIFY_BUFFER_SIZE];
  34. private:
  35. void WorkerProc()
  36. {
  37. char pathBuffer[PATH_MAX];
  38. Array<inotify_event*> unhandledEvents;
  39. while (!mIsClosing)
  40. {
  41. int length = read(mInotifyHandle, &mEventBuffer, NOTIFY_BUFFER_SIZE);
  42. if (mIsClosing)
  43. break;
  44. if (length < 0)
  45. {
  46. BFP_ERRPRINTF("Failed to read inotify event data!\n");
  47. return;
  48. }
  49. int i = 0;
  50. while(i < length)
  51. {
  52. inotify_event* event = (inotify_event*) &mEventBuffer[i];
  53. if(event->len != 0)
  54. {
  55. BfpFileWatcher* w;
  56. SubdirInfo* subdir;
  57. {
  58. AutoCrit autoCrit(mCritSect);
  59. if (!mWatchers.TryGetValue(event->wd, &w))
  60. continue;
  61. if (!mSubdirs.TryGetValue(event->wd, &subdir))
  62. subdir = NULL;
  63. }
  64. bool handleDir = (event->mask & IN_ISDIR) && (w->mFlags & BfpFileWatcherFlag_IncludeSubdirectories);
  65. if (GetRelativePath(pathBuffer, sizeof(pathBuffer), event->name, event->len, w, subdir) == 0)
  66. {
  67. // our buffer was too small, we can't handle this event
  68. i += sizeof(inotify_event) + event->len;
  69. continue;
  70. }
  71. if (event->mask & IN_MOVED_FROM)
  72. {
  73. unhandledEvents.Add(event);
  74. }
  75. if ((event->mask & IN_MOVED_TO))
  76. {
  77. bool handled = false;
  78. for (int i = 0; i < unhandledEvents.size(); i++)
  79. {
  80. // Only handle as rename if src and dst directory is the same
  81. if ((event->cookie == unhandledEvents[i]->cookie) && (event->wd == unhandledEvents[i]->wd))
  82. {
  83. char renameBuffer[PATH_MAX];
  84. if (GetRelativePath(renameBuffer, sizeof(renameBuffer), unhandledEvents[i]->name, unhandledEvents[i]->len, w, subdir) == 0)
  85. {
  86. break;
  87. }
  88. w->mDirectoryChangeFunc(w, w->mUserData, BfpFileChangeKind_Renamed, w->mPath.c_str(), renameBuffer, pathBuffer);
  89. unhandledEvents.RemoveAtFast(i);
  90. handled = true;
  91. break;
  92. }
  93. }
  94. if (!handled)
  95. {
  96. unhandledEvents.Add(event);
  97. }
  98. }
  99. if (event->mask & IN_CREATE)
  100. {
  101. w->mDirectoryChangeFunc(w, w->mUserData, BfpFileChangeKind_Added, w->mPath.c_str(), pathBuffer, NULL);
  102. HandleDirAdd(event, w, subdir, false);
  103. }
  104. if (event->mask & IN_DELETE)
  105. {
  106. w->mDirectoryChangeFunc(w, w->mUserData, BfpFileChangeKind_Removed, w->mPath.c_str(), pathBuffer, NULL);
  107. HandleDirRemove(event, w, subdir);
  108. }
  109. if ((event->mask & IN_CLOSE_WRITE) || (event->mask & IN_ATTRIB))
  110. {
  111. w->mDirectoryChangeFunc(w, w->mUserData, BfpFileChangeKind_Modified, w->mPath.c_str(), pathBuffer, NULL);
  112. }
  113. }
  114. i += sizeof(inotify_event) + event->len;
  115. }
  116. for (auto event : unhandledEvents)
  117. {
  118. BfpFileWatcher* w;
  119. SubdirInfo* subdir;
  120. {
  121. AutoCrit autoCrit(mCritSect);
  122. if (!mWatchers.TryGetValue(event->wd, &w))
  123. continue;
  124. if (!mSubdirs.TryGetValue(event->wd, &subdir))
  125. subdir = NULL;
  126. }
  127. if (GetRelativePath(pathBuffer, sizeof(pathBuffer), event->name, event->len, w, subdir) == 0)
  128. {
  129. continue;
  130. }
  131. if (event->mask & IN_MOVED_FROM)
  132. {
  133. w->mDirectoryChangeFunc(w, w->mUserData, BfpFileChangeKind_Removed, w->mPath.c_str(), pathBuffer, NULL);
  134. HandleDirRemove(event, w, subdir);
  135. }
  136. if (event->mask & IN_MOVED_TO)
  137. {
  138. w->mDirectoryChangeFunc(w, w->mUserData, BfpFileChangeKind_Added, w->mPath.c_str(), pathBuffer, NULL);
  139. HandleDirAdd(event, w, subdir, true);
  140. }
  141. }
  142. unhandledEvents.Clear();
  143. }
  144. }
  145. static void* WorkerProcThunk(void* _this)
  146. {
  147. BfpThread_SetName(NULL, "InotifyFileWatcher", NULL);
  148. ((InotifyFileWatchManager*)_this)->WorkerProc();
  149. return NULL;
  150. }
  151. void HandleDirRemove(const inotify_event* event, const BfpFileWatcher* fileWatch, const SubdirInfo* subdir)
  152. {
  153. const bool shouldHandle = (event->mask & IN_ISDIR) && (fileWatch->mFlags & BfpFileWatcherFlag_IncludeSubdirectories);
  154. if (!shouldHandle)
  155. return;
  156. AutoCrit autoCrit(mCritSect);
  157. Array<int> toRemove;
  158. String removedDir;
  159. if (subdir != NULL)
  160. {
  161. removedDir = subdir->mRelativePath;
  162. removedDir.Append('/');
  163. removedDir.Append(event->name);
  164. }
  165. for (const auto& kv : mSubdirs)
  166. {
  167. if (subdir == NULL)
  168. {
  169. if (kv.mValue.mWatcher == fileWatch)
  170. {
  171. toRemove.Add(kv.mKey);
  172. if (kv.mValue.mRelativePath == event->name)
  173. mWatchers.Remove(kv.mKey); // Watch is already destroyed by OS
  174. }
  175. }
  176. else
  177. {
  178. if ((kv.mValue.mRelativePath.StartsWith(removedDir)))
  179. {
  180. //BFP_ERRPRINTF("REMOVING: %s %s\n", kv.mValue.mRelativePath.c_str(), subdir->mRelativePath.c_str());
  181. toRemove.Add(kv.mKey);
  182. }
  183. }
  184. }
  185. for (const auto val : toRemove)
  186. {
  187. mSubdirs.Remove(val);
  188. if (mWatchers.Remove(val))
  189. InotifyRemoveWatch(val);
  190. }
  191. }
  192. void HandleDirAdd(const inotify_event* event, BfpFileWatcher* fileWatch, const SubdirInfo* subdir, bool wasMoved)
  193. {
  194. const bool shouldHandle = (event->mask & IN_ISDIR) && (fileWatch->mFlags & BfpFileWatcherFlag_IncludeSubdirectories);
  195. if (!shouldHandle)
  196. return;
  197. String dirPath = fileWatch->mPath;
  198. if (subdir != NULL)
  199. {
  200. dirPath.Append('/');
  201. dirPath.Append(subdir->mRelativePath);
  202. }
  203. dirPath.Append('/');
  204. dirPath.Append(event->name);
  205. int watchHandle = InotifyWatchPath(dirPath.c_str());
  206. if (watchHandle == -1)
  207. {
  208. BFP_ERRPRINTF("Failed to add watch for subdirectory '%s' (%d)\n", dirPath.c_str(), errno);
  209. return;
  210. }
  211. AddWatchEntry(watchHandle, fileWatch);
  212. AddSubdirEntry(watchHandle, dirPath, fileWatch);
  213. WatchSubdirectories(dirPath.c_str(), fileWatch, !wasMoved);
  214. }
  215. void AddWatchEntry(int handle, BfpFileWatcher* fileWatcher)
  216. {
  217. AutoCrit autoCrit(mCritSect);
  218. mWatchers[handle] = fileWatcher;
  219. }
  220. void AddSubdirEntry(int handle, const String& currentPath, BfpFileWatcher* fileWatcher)
  221. {
  222. AutoCrit autoCrit(mCritSect);
  223. SubdirInfo info;
  224. info.mHandle = handle;
  225. info.mWatcher = fileWatcher;
  226. auto substringLength = std::min(currentPath.length(), fileWatcher->mPath.length() + 1);
  227. info.mRelativePath = currentPath.Substring(substringLength);
  228. mSubdirs[handle] = info;
  229. }
  230. int InotifyWatchPath(const char* path)
  231. {
  232. return inotify_add_watch(mInotifyHandle, path, IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_ATTRIB | IN_MOVE);
  233. }
  234. void InotifyRemoveWatch(int handle)
  235. {
  236. if (inotify_rm_watch(mInotifyHandle, handle) == -1)
  237. {
  238. BFP_ERRPRINTF("Failed to remove watch handle(%d) err(%d)\n", handle, errno);
  239. }
  240. }
  241. void HandleDirectory(DIR* dirp, String& o_path, Array<DIR*>& o_workList, BfpFileWatcher* fileWatcher, bool sendEvents)
  242. {
  243. struct dirent* dp;
  244. while ((dp = readdir(dirp)) != NULL)
  245. {
  246. if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
  247. continue;
  248. // Send events for files/dirs inside the directory as don't receive events in newly created directories
  249. if (sendEvents)
  250. {
  251. String localPath = o_path.Substring(std::min(o_path.length(), fileWatcher->mPath.length()+1));
  252. localPath.Append('/');
  253. localPath.Append(dp->d_name);
  254. fileWatcher->mDirectoryChangeFunc(fileWatcher, fileWatcher->mUserData, BfpFileChangeKind_Added, fileWatcher->mPath.c_str(), localPath.c_str(), NULL);
  255. fileWatcher->mDirectoryChangeFunc(fileWatcher, fileWatcher->mUserData, BfpFileChangeKind_Modified, fileWatcher->mPath.c_str(), localPath.c_str(), NULL);
  256. }
  257. if (dp->d_type != DT_DIR)
  258. continue;
  259. const auto length = o_path.length();
  260. o_path.Append('/');
  261. o_path.Append(dp->d_name);
  262. int watchHandle = InotifyWatchPath(o_path.c_str());
  263. if (watchHandle == -1)
  264. {
  265. o_path.RemoveToEnd(length);
  266. BFP_ERRPRINTF("Failed to add watch for subdirectory '%s' (%d)\n", o_path.c_str(), errno);
  267. continue;
  268. }
  269. AddWatchEntry(watchHandle, fileWatcher);
  270. AddSubdirEntry(watchHandle, o_path, fileWatcher);
  271. DIR* todo = opendir(o_path.c_str());
  272. if (todo == NULL)
  273. {
  274. o_path.RemoveToEnd(length);
  275. continue;
  276. }
  277. o_workList.Add(dirp);
  278. o_workList.Add(todo);
  279. return;
  280. }
  281. o_workList.Add(NULL);
  282. closedir(dirp);
  283. }
  284. void WatchSubdirectories(const char* path, BfpFileWatcher* fileWatcher, bool sendEvents)
  285. {
  286. DIR* dirp = opendir(path);
  287. if (dirp == NULL)
  288. return;
  289. Array<DIR*> workList;
  290. String currentPath(path);
  291. HandleDirectory(dirp, currentPath, workList, fileWatcher, sendEvents);
  292. while (workList.size() > 0)
  293. {
  294. dirp = workList.back();
  295. workList.pop_back();
  296. if (dirp == NULL)
  297. {
  298. auto dirSeparator = currentPath.LastIndexOf('/');
  299. if (dirSeparator == -1)
  300. {
  301. BF_ASSERT(workList.IsEmpty());
  302. break;
  303. }
  304. currentPath.RemoveToEnd(dirSeparator);
  305. continue;
  306. }
  307. HandleDirectory(dirp, currentPath, workList, fileWatcher, sendEvents);
  308. }
  309. }
  310. int GetRelativePath(char* buffer, int bufferSize, const char* fileName, int fileNameLength, const BfpFileWatcher* fileWatcher, const SubdirInfo* subdir)
  311. {
  312. if (subdir == NULL)
  313. {
  314. memcpy(buffer, fileName, fileNameLength);
  315. return fileNameLength;
  316. }
  317. const auto subdirLength = subdir->mRelativePath.length();
  318. if (bufferSize < (subdirLength + fileNameLength + 2))
  319. return 0;
  320. memcpy(buffer, subdir->mRelativePath.GetPtr(), subdirLength);
  321. buffer[subdirLength] = '/';
  322. buffer += subdirLength + 1;
  323. memcpy(buffer, fileName, fileNameLength);
  324. buffer[fileNameLength] = '\0';
  325. return subdirLength + fileNameLength + 1;
  326. }
  327. public:
  328. virtual bool Init() override
  329. {
  330. mIsClosing = false;
  331. mInotifyHandle = inotify_init();
  332. if (mInotifyHandle == -1)
  333. {
  334. BFP_ERRPRINTF("Failed to initialize inotify (%d)\n", errno);
  335. return false;
  336. }
  337. int err = pthread_create(&mWorkerThread, NULL, &WorkerProcThunk, this);
  338. if (err != 0)
  339. {
  340. BFP_ERRPRINTF("Failed to create worker thread for inotify FileWatcher!\n");
  341. return false;
  342. }
  343. return true;
  344. }
  345. virtual void Shutdown() override
  346. {
  347. mIsClosing = true;
  348. close(mInotifyHandle);
  349. }
  350. virtual BfpFileWatcher* WatchDirectory(const char* path, BfpDirectoryChangeFunc callback, BfpFileWatcherFlags flags, void* userData, BfpFileResult* outResult) override
  351. {
  352. int watchHandle = InotifyWatchPath(path);
  353. if (watchHandle == -1)
  354. {
  355. BFP_ERRPRINTF("Failed to add watch for directory '%s' (%d)\n", path, errno);
  356. OUTRESULT(BfpFileResult_UnknownError);
  357. return NULL;
  358. }
  359. BfpFileWatcher* fileWatcher = new BfpFileWatcher();
  360. fileWatcher->mPath = path;
  361. fileWatcher->mDirectoryChangeFunc = callback;
  362. fileWatcher->mHandle = watchHandle;
  363. fileWatcher->mFlags = flags;
  364. fileWatcher->mUserData = userData;
  365. AddWatchEntry(watchHandle, fileWatcher);
  366. if (flags & BfpFileWatcherFlag_IncludeSubdirectories)
  367. {
  368. WatchSubdirectories(path, fileWatcher, false);
  369. }
  370. return fileWatcher;
  371. }
  372. virtual void Remove(BfpFileWatcher* watcher) override
  373. {
  374. AutoCrit autoCrit(mCritSect);
  375. if ((watcher->mFlags & BfpFileWatcherFlag_IncludeSubdirectories))
  376. {
  377. Array<int> toRemove;
  378. for (const auto& subdir : mSubdirs)
  379. {
  380. if (subdir.mValue.mWatcher == watcher)
  381. {
  382. toRemove.Add(subdir.mKey);
  383. }
  384. }
  385. for (auto handle : toRemove)
  386. {
  387. mSubdirs.Remove(handle);
  388. if (mWatchers.Remove(handle))
  389. InotifyRemoveWatch(handle);
  390. }
  391. }
  392. // Check if watched directory exists so we don't error/remove other watch
  393. if ((DirectoryExists(watcher->mPath)) && (mWatchers.Remove(watcher->mHandle)))
  394. {
  395. InotifyRemoveWatch(watcher->mHandle);
  396. }
  397. delete watcher;
  398. }
  399. };
  400. FileWatchManager* FileWatchManager::Get()
  401. {
  402. if (gFileWatchManager == NULL)
  403. {
  404. gFileWatchManager = new InotifyFileWatchManager();
  405. gFileWatchManager->Init();
  406. }
  407. return gFileWatchManager;
  408. }
  409. #endif // BFP_HAS_FILEWATCHER