/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2008-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ // An inotify wrapper #include "C4Include.h" #include "platform/C4FileMonitor.h" #include "game/C4Application.h" #ifdef HAVE_SYS_INOTIFY_H #include C4FileMonitor::C4FileMonitor(ChangeNotify pCallback): fStarted(false), pCallback(pCallback) { fd = inotify_init1(IN_CLOEXEC); if (fd == -1) fd = inotify_init(); if (fd == -1) LogF("inotify_init %s", strerror(errno)); } C4FileMonitor::~C4FileMonitor() { if (fStarted) { C4InteractiveThread &Thread = Application.InteractiveThread; Thread.RemoveProc(this); Thread.ClearCallback(Ev_FileChange, this); } while (close(fd) == -1 && errno == EINTR) { } } void C4FileMonitor::StartMonitoring() { if (fStarted) return; C4InteractiveThread &Thread = Application.InteractiveThread; Thread.AddProc(this); Thread.SetCallback(Ev_FileChange, this); fStarted = true; } void C4FileMonitor::AddDirectory(const char * file) { // Add IN_CLOSE_WRITE? int wd = inotify_add_watch(fd, file, IN_CREATE | IN_MODIFY | IN_MOVED_TO | IN_MOVE_SELF | IN_ONLYDIR); if (wd == -1) LogF("inotify_add_watch %s", strerror(errno)); watch_descriptors[wd] = file; } bool C4FileMonitor::Execute(int iTimeout, pollfd * pfd) // some other thread { if ((pfd->revents & pfd->events) != POLLIN || pfd->fd != fd) LogF("C4FileMonitor::Execute unexpectedly called %d %d %hd %hd", fd, pfd->fd, pfd->events, pfd->revents); char buf[sizeof(inotify_event) + _MAX_FNAME + 1]; inotify_event* event = new (buf) inotify_event; if (read(fd, buf, sizeof(buf)) > 0) { const char * file = watch_descriptors[event->wd]; uint32_t mask = event->mask; C4InteractiveThread &Thread = Application.InteractiveThread; if (mask & IN_CREATE) Thread.PushEvent(Ev_FileChange, const_cast(file)); if (mask & IN_MODIFY) Thread.PushEvent(Ev_FileChange, const_cast(file)); if (mask & IN_MOVED_TO) Thread.PushEvent(Ev_FileChange, const_cast(file)); if (mask & IN_MOVE_SELF) Thread.PushEvent(Ev_FileChange, const_cast(file)); // FIXME: (*(inotify_event*)buf).name); } else { Log("inotify buffer too small"); } return true; } void C4FileMonitor::OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData) // main thread { if (eEvent != Ev_FileChange) return; pCallback((const char *)pEventData, nullptr); } void C4FileMonitor::GetFDs(std::vector & fds) { pollfd pfd = { fd, POLLIN, 0 }; fds.push_back(pfd); } #elif defined(_WIN32) #include "platform/C4windowswrapper.h" C4FileMonitor::C4FileMonitor(ChangeNotify pCallback) : fStarted(false), pCallback(pCallback), pWatches(nullptr) { hEvent = CreateEvent(nullptr, true, false, nullptr); } C4FileMonitor::~C4FileMonitor() { if (fStarted) { C4InteractiveThread &Thread = Application.InteractiveThread; Thread.RemoveProc(this); Thread.ClearCallback(Ev_FileChange, this); } while (pWatches) { CloseHandle(pWatches->hDir); TreeWatch *pDelete = pWatches; pWatches = pWatches->Next; delete pDelete; } CloseHandle(hEvent); } void C4FileMonitor::StartMonitoring() { C4InteractiveThread &Thread = Application.InteractiveThread; Thread.AddProc(this); Thread.SetCallback(Ev_FileChange, this); fStarted = true; } const DWORD C4FileMonitorNotifies = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE; void C4FileMonitor::AddDirectory(const char *szDir) { // Create file handle HANDLE hDir = CreateFileW(GetWideChar(szDir), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr); if (hDir == INVALID_HANDLE_VALUE) return; // Create tree watch structure TreeWatch *pWatch = new TreeWatch(); pWatch->hDir = hDir; pWatch->DirName = szDir; // Build description of async operation ZeroMem(&pWatch->ov, sizeof(pWatch->ov)); pWatch->ov.hEvent = hEvent; // Add to list pWatch->Next = pWatches; pWatches = pWatch; // Start async directory change notification if (!ReadDirectoryChangesW(hDir, pWatch->Buffer, sizeof(pWatch->Buffer), false, C4FileMonitorNotifies, nullptr, &pWatch->ov, nullptr)) if (GetLastError() != ERROR_IO_PENDING) { delete pWatch; return; } } bool C4FileMonitor::Execute(int iTimeout, pollfd *) { // Check event if (WaitForSingleObject(hEvent, iTimeout) != WAIT_OBJECT_0) return true; // Check handles for (TreeWatch *pWatch = pWatches; pWatch; pWatch = pWatch->Next) { DWORD dwBytes = 0; // Has a notification? if (GetOverlappedResult(pWatch->hDir, &pWatch->ov, &dwBytes, false)) { // Read notifications const char *pPos = pWatch->Buffer; for (;;) { const _FILE_NOTIFY_INFORMATION *pNotify = reinterpret_cast(pPos); // Handle HandleNotify(pWatch->DirName.getData(), pNotify); // Get next entry if (!pNotify->NextEntryOffset) break; pPos += pNotify->NextEntryOffset; if (pPos >= pWatch->Buffer + std::min(sizeof(pWatch->Buffer), dwBytes)) break; break; } // Restart directory change notification (flush queue) ReadDirectoryChangesW(pWatch->hDir, pWatch->Buffer, sizeof(pWatch->Buffer), false, C4FileMonitorNotifies, nullptr, &pWatch->ov, nullptr); dwBytes = 0; while (GetOverlappedResult(pWatch->hDir, &pWatch->ov, &dwBytes, false)) { ReadDirectoryChangesW(pWatch->hDir, pWatch->Buffer, sizeof(pWatch->Buffer), false, C4FileMonitorNotifies, nullptr, &pWatch->ov, nullptr); dwBytes = 0; } } } ResetEvent(hEvent); return true; } void C4FileMonitor::OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData) // main thread { if (eEvent != Ev_FileChange) return; pCallback((const char *)pEventData, nullptr); StdBuf::DeletePointer(pEventData); } HANDLE C4FileMonitor::GetEvent() { return hEvent; } void C4FileMonitor::HandleNotify(const char *szDir, const _FILE_NOTIFY_INFORMATION *pNotify) { // Get filename length UINT iCodePage = CP_UTF8; int iFileNameBytes = WideCharToMultiByte(iCodePage, 0, pNotify->FileName, pNotify->FileNameLength / 2, nullptr, 0, nullptr, nullptr); // Set up filename buffer StdCopyStrBuf Path(szDir); Path.AppendChar(DirectorySeparator); Path.Grow(iFileNameBytes); char *pFilename = Path.getMPtr(SLen(Path.getData())); // Convert filename int iWritten = WideCharToMultiByte(iCodePage, 0, pNotify->FileName, pNotify->FileNameLength / 2, pFilename, iFileNameBytes, nullptr, nullptr); if (iWritten != iFileNameBytes) Path.Shrink(iFileNameBytes+1); // Send notification Application.InteractiveThread.PushEvent(Ev_FileChange, Path.GrabPointer()); } #elif not defined(__APPLE__) // Stubs C4FileMonitor::C4FileMonitor(ChangeNotify pCallback) { #ifdef HAVE_SYS_INOTIFY_H C4FileMonitor::pCallback = pCallback; #endif } C4FileMonitor::~C4FileMonitor() { } bool C4FileMonitor::Execute(int iTimeout, pollfd *) { return false; /* blarg... function must return a value */ } void C4FileMonitor::StartMonitoring() {} void C4FileMonitor::OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData) {} void C4FileMonitor::AddDirectory(const char *szDir) {} // Signal for calling Execute() #ifdef STDSCHEDULER_USE_EVENTS HANDLE C4FileMonitor::GetEvent() { return 0; } #else void C4FileMonitor::GetFDs(std::vector & FDs) { } #endif #endif // HAVE_SYS_INOTIFY_H