123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- #define BFP_HAS_EXECINFO
- #define BFP_HAS_PTHREAD_TIMEDJOIN_NP
- #define BFP_HAS_PTHREAD_GETATTR_NP
- #define BFP_HAS_DLINFO
- #define BFP_HAS_FILEWATCHER
- #include "../posix/PosixCommon.cpp"
- #ifdef BFP_HAS_FILEWATCHER
- #include <sys/inotify.h>
- struct BfpFileWatcher
- {
- String mPath;
- BfpDirectoryChangeFunc mDirectoryChangeFunc;
- int mHandle;
- BfpFileWatcherFlags mFlags;
- void* mUserData;
- };
- class InotifyFileWatchManager : public FileWatchManager
- {
- struct SubdirInfo
- {
- BfpFileWatcher* mWatcher;
- int mHandle;
- String mRelativePath;
- };
- static constexpr size_t MAX_NOTIFY_EVENTS = 1024;
- static constexpr size_t NOTIFY_BUFFER_SIZE = (sizeof(inotify_event) * (MAX_NOTIFY_EVENTS + PATH_MAX));
- bool mIsClosing;
- int mInotifyHandle;
- pthread_t mWorkerThread;
- Dictionary<int, BfpFileWatcher*> mWatchers;
- Dictionary<int, SubdirInfo> mSubdirs;
- CritSect mCritSect;
- char mEventBuffer[NOTIFY_BUFFER_SIZE];
- private:
- void WorkerProc()
- {
- char pathBuffer[PATH_MAX];
- Array<inotify_event*> unhandledEvents;
- while (!mIsClosing)
- {
- int length = read(mInotifyHandle, &mEventBuffer, NOTIFY_BUFFER_SIZE);
- if (mIsClosing)
- break;
- if (length < 0)
- {
- BFP_ERRPRINTF("Failed to read inotify event data!\n");
- return;
- }
- int i = 0;
- while(i < length)
- {
- inotify_event* event = (inotify_event*) &mEventBuffer[i];
- if(event->len != 0)
- {
- BfpFileWatcher* w;
- SubdirInfo* subdir;
- {
- AutoCrit autoCrit(mCritSect);
- if (!mWatchers.TryGetValue(event->wd, &w))
- continue;
- if (!mSubdirs.TryGetValue(event->wd, &subdir))
- subdir = NULL;
- }
- bool handleDir = (event->mask & IN_ISDIR) && (w->mFlags & BfpFileWatcherFlag_IncludeSubdirectories);
- if (GetRelativePath(pathBuffer, sizeof(pathBuffer), event->name, event->len, w, subdir) == 0)
- {
- // our buffer was too small, we can't handle this event
- i += sizeof(inotify_event) + event->len;
- continue;
- }
- if (event->mask & IN_MOVED_FROM)
- {
- unhandledEvents.Add(event);
- }
- if ((event->mask & IN_MOVED_TO))
- {
- bool handled = false;
- for (int i = 0; i < unhandledEvents.size(); i++)
- {
- // Only handle as rename if src and dst directory is the same
- if ((event->cookie == unhandledEvents[i]->cookie) && (event->wd == unhandledEvents[i]->wd))
- {
- char renameBuffer[PATH_MAX];
- if (GetRelativePath(renameBuffer, sizeof(renameBuffer), unhandledEvents[i]->name, unhandledEvents[i]->len, w, subdir) == 0)
- {
- break;
- }
- w->mDirectoryChangeFunc(w, w->mUserData, BfpFileChangeKind_Renamed, w->mPath.c_str(), renameBuffer, pathBuffer);
- unhandledEvents.RemoveAtFast(i);
- handled = true;
- break;
- }
- }
- if (!handled)
- {
- unhandledEvents.Add(event);
- }
- }
- if (event->mask & IN_CREATE)
- {
- w->mDirectoryChangeFunc(w, w->mUserData, BfpFileChangeKind_Added, w->mPath.c_str(), pathBuffer, NULL);
- HandleDirAdd(event, w, subdir, false);
- }
- if (event->mask & IN_DELETE)
- {
- w->mDirectoryChangeFunc(w, w->mUserData, BfpFileChangeKind_Removed, w->mPath.c_str(), pathBuffer, NULL);
- HandleDirRemove(event, w, subdir);
- }
- if ((event->mask & IN_CLOSE_WRITE) || (event->mask & IN_ATTRIB))
- {
- w->mDirectoryChangeFunc(w, w->mUserData, BfpFileChangeKind_Modified, w->mPath.c_str(), pathBuffer, NULL);
- }
- }
- i += sizeof(inotify_event) + event->len;
- }
- for (auto event : unhandledEvents)
- {
- BfpFileWatcher* w;
- SubdirInfo* subdir;
- {
- AutoCrit autoCrit(mCritSect);
- if (!mWatchers.TryGetValue(event->wd, &w))
- continue;
- if (!mSubdirs.TryGetValue(event->wd, &subdir))
- subdir = NULL;
- }
- if (GetRelativePath(pathBuffer, sizeof(pathBuffer), event->name, event->len, w, subdir) == 0)
- {
- continue;
- }
- if (event->mask & IN_MOVED_FROM)
- {
- w->mDirectoryChangeFunc(w, w->mUserData, BfpFileChangeKind_Removed, w->mPath.c_str(), pathBuffer, NULL);
- HandleDirRemove(event, w, subdir);
- }
- if (event->mask & IN_MOVED_TO)
- {
- w->mDirectoryChangeFunc(w, w->mUserData, BfpFileChangeKind_Added, w->mPath.c_str(), pathBuffer, NULL);
- HandleDirAdd(event, w, subdir, true);
- }
- }
- unhandledEvents.Clear();
- }
- }
- static void* WorkerProcThunk(void* _this)
- {
- BfpThread_SetName(NULL, "InotifyFileWatcher", NULL);
- ((InotifyFileWatchManager*)_this)->WorkerProc();
- return NULL;
- }
-
- void HandleDirRemove(const inotify_event* event, const BfpFileWatcher* fileWatch, const SubdirInfo* subdir)
- {
- const bool shouldHandle = (event->mask & IN_ISDIR) && (fileWatch->mFlags & BfpFileWatcherFlag_IncludeSubdirectories);
- if (!shouldHandle)
- return;
-
- AutoCrit autoCrit(mCritSect);
- Array<int> toRemove;
- String removedDir;
- if (subdir != NULL)
- {
- removedDir = subdir->mRelativePath;
- removedDir.Append('/');
- removedDir.Append(event->name);
- }
- for (const auto& kv : mSubdirs)
- {
- if (subdir == NULL)
- {
- if (kv.mValue.mWatcher == fileWatch)
- {
- toRemove.Add(kv.mKey);
- if (kv.mValue.mRelativePath == event->name)
- mWatchers.Remove(kv.mKey); // Watch is already destroyed by OS
- }
- }
- else
- {
- if ((kv.mValue.mRelativePath.StartsWith(removedDir)))
- {
- //BFP_ERRPRINTF("REMOVING: %s %s\n", kv.mValue.mRelativePath.c_str(), subdir->mRelativePath.c_str());
- toRemove.Add(kv.mKey);
- }
- }
- }
- for (const auto val : toRemove)
- {
- mSubdirs.Remove(val);
- if (mWatchers.Remove(val))
- InotifyRemoveWatch(val);
- }
- }
- void HandleDirAdd(const inotify_event* event, BfpFileWatcher* fileWatch, const SubdirInfo* subdir, bool wasMoved)
- {
- const bool shouldHandle = (event->mask & IN_ISDIR) && (fileWatch->mFlags & BfpFileWatcherFlag_IncludeSubdirectories);
- if (!shouldHandle)
- return;
- String dirPath = fileWatch->mPath;
- if (subdir != NULL)
- {
- dirPath.Append('/');
- dirPath.Append(subdir->mRelativePath);
- }
- dirPath.Append('/');
- dirPath.Append(event->name);
- int watchHandle = InotifyWatchPath(dirPath.c_str());
- if (watchHandle == -1)
- {
- BFP_ERRPRINTF("Failed to add watch for subdirectory '%s' (%d)\n", dirPath.c_str(), errno);
- return;
- }
- AddWatchEntry(watchHandle, fileWatch);
- AddSubdirEntry(watchHandle, dirPath, fileWatch);
- WatchSubdirectories(dirPath.c_str(), fileWatch, !wasMoved);
- }
- void AddWatchEntry(int handle, BfpFileWatcher* fileWatcher)
- {
- AutoCrit autoCrit(mCritSect);
- mWatchers[handle] = fileWatcher;
- }
- void AddSubdirEntry(int handle, const String& currentPath, BfpFileWatcher* fileWatcher)
- {
- AutoCrit autoCrit(mCritSect);
- SubdirInfo info;
- info.mHandle = handle;
- info.mWatcher = fileWatcher;
- auto substringLength = std::min(currentPath.length(), fileWatcher->mPath.length() + 1);
- info.mRelativePath = currentPath.Substring(substringLength);
- mSubdirs[handle] = info;
- }
- int InotifyWatchPath(const char* path)
- {
- return inotify_add_watch(mInotifyHandle, path, IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_ATTRIB | IN_MOVE);
- }
- void InotifyRemoveWatch(int handle)
- {
- if (inotify_rm_watch(mInotifyHandle, handle) == -1)
- {
- BFP_ERRPRINTF("Failed to remove watch handle(%d) err(%d)\n", handle, errno);
- }
- }
- void HandleDirectory(DIR* dirp, String& o_path, Array<DIR*>& o_workList, BfpFileWatcher* fileWatcher, bool sendEvents)
- {
- struct dirent* dp;
- while ((dp = readdir(dirp)) != NULL)
- {
- if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
- continue;
- // Send events for files/dirs inside the directory as don't receive events in newly created directories
- if (sendEvents)
- {
- String localPath = o_path.Substring(std::min(o_path.length(), fileWatcher->mPath.length()+1));
- localPath.Append('/');
- localPath.Append(dp->d_name);
- fileWatcher->mDirectoryChangeFunc(fileWatcher, fileWatcher->mUserData, BfpFileChangeKind_Added, fileWatcher->mPath.c_str(), localPath.c_str(), NULL);
- fileWatcher->mDirectoryChangeFunc(fileWatcher, fileWatcher->mUserData, BfpFileChangeKind_Modified, fileWatcher->mPath.c_str(), localPath.c_str(), NULL);
- }
- if (dp->d_type != DT_DIR)
- continue;
- const auto length = o_path.length();
- o_path.Append('/');
- o_path.Append(dp->d_name);
- int watchHandle = InotifyWatchPath(o_path.c_str());
- if (watchHandle == -1)
- {
- o_path.RemoveToEnd(length);
- BFP_ERRPRINTF("Failed to add watch for subdirectory '%s' (%d)\n", o_path.c_str(), errno);
- continue;
- }
- AddWatchEntry(watchHandle, fileWatcher);
- AddSubdirEntry(watchHandle, o_path, fileWatcher);
- DIR* todo = opendir(o_path.c_str());
- if (todo == NULL)
- {
- o_path.RemoveToEnd(length);
- continue;
- }
- o_workList.Add(dirp);
- o_workList.Add(todo);
- return;
- }
- o_workList.Add(NULL);
- closedir(dirp);
- }
- void WatchSubdirectories(const char* path, BfpFileWatcher* fileWatcher, bool sendEvents)
- {
- DIR* dirp = opendir(path);
- if (dirp == NULL)
- return;
- Array<DIR*> workList;
- String currentPath(path);
- HandleDirectory(dirp, currentPath, workList, fileWatcher, sendEvents);
- while (workList.size() > 0)
- {
- dirp = workList.back();
- workList.pop_back();
- if (dirp == NULL)
- {
- auto dirSeparator = currentPath.LastIndexOf('/');
- if (dirSeparator == -1)
- {
- BF_ASSERT(workList.IsEmpty());
- break;
- }
- currentPath.RemoveToEnd(dirSeparator);
- continue;
- }
- HandleDirectory(dirp, currentPath, workList, fileWatcher, sendEvents);
- }
- }
- int GetRelativePath(char* buffer, int bufferSize, const char* fileName, int fileNameLength, const BfpFileWatcher* fileWatcher, const SubdirInfo* subdir)
- {
- if (subdir == NULL)
- {
- memcpy(buffer, fileName, fileNameLength);
- return fileNameLength;
- }
- const auto subdirLength = subdir->mRelativePath.length();
- if (bufferSize < (subdirLength + fileNameLength + 2))
- return 0;
- memcpy(buffer, subdir->mRelativePath.GetPtr(), subdirLength);
- buffer[subdirLength] = '/';
- buffer += subdirLength + 1;
- memcpy(buffer, fileName, fileNameLength);
- buffer[fileNameLength] = '\0';
- return subdirLength + fileNameLength + 1;
- }
- public:
- virtual bool Init() override
- {
- mIsClosing = false;
- mInotifyHandle = inotify_init();
- if (mInotifyHandle == -1)
- {
- BFP_ERRPRINTF("Failed to initialize inotify (%d)\n", errno);
- return false;
- }
- int err = pthread_create(&mWorkerThread, NULL, &WorkerProcThunk, this);
- if (err != 0)
- {
- BFP_ERRPRINTF("Failed to create worker thread for inotify FileWatcher!\n");
- return false;
- }
- return true;
- }
- virtual void Shutdown() override
- {
- mIsClosing = true;
- close(mInotifyHandle);
- }
- virtual BfpFileWatcher* WatchDirectory(const char* path, BfpDirectoryChangeFunc callback, BfpFileWatcherFlags flags, void* userData, BfpFileResult* outResult) override
- {
- int watchHandle = InotifyWatchPath(path);
- if (watchHandle == -1)
- {
- BFP_ERRPRINTF("Failed to add watch for directory '%s' (%d)\n", path, errno);
- OUTRESULT(BfpFileResult_UnknownError);
- return NULL;
- }
- BfpFileWatcher* fileWatcher = new BfpFileWatcher();
- fileWatcher->mPath = path;
- fileWatcher->mDirectoryChangeFunc = callback;
- fileWatcher->mHandle = watchHandle;
- fileWatcher->mFlags = flags;
- fileWatcher->mUserData = userData;
- AddWatchEntry(watchHandle, fileWatcher);
- if (flags & BfpFileWatcherFlag_IncludeSubdirectories)
- {
- WatchSubdirectories(path, fileWatcher, false);
- }
- return fileWatcher;
- }
- virtual void Remove(BfpFileWatcher* watcher) override
- {
- AutoCrit autoCrit(mCritSect);
- if ((watcher->mFlags & BfpFileWatcherFlag_IncludeSubdirectories))
- {
- Array<int> toRemove;
- for (const auto& subdir : mSubdirs)
- {
- if (subdir.mValue.mWatcher == watcher)
- {
- toRemove.Add(subdir.mKey);
- }
- }
-
- for (auto handle : toRemove)
- {
- mSubdirs.Remove(handle);
- if (mWatchers.Remove(handle))
- InotifyRemoveWatch(handle);
- }
- }
- // Check if watched directory exists so we don't error/remove other watch
- if ((DirectoryExists(watcher->mPath)) && (mWatchers.Remove(watcher->mHandle)))
- {
- InotifyRemoveWatch(watcher->mHandle);
- }
- delete watcher;
- }
- };
- FileWatchManager* FileWatchManager::Get()
- {
- if (gFileWatchManager == NULL)
- {
- gFileWatchManager = new InotifyFileWatchManager();
- gFileWatchManager->Init();
- }
- return gFileWatchManager;
- }
- #endif // BFP_HAS_FILEWATCHER
|