A little toolkit for single-quad fragment shader demos
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

429 lines
11 KiB

  1. /**
  2. * @file Inotify.h
  3. * @author Erik Zenker
  4. * @date 02.11.2012
  5. * @copyright Gnu Public License
  6. **/
  7. #pragma once
  8. #include <sys/inotify.h>
  9. #include <string>
  10. #include <queue>
  11. #include <map>
  12. #include <vector>
  13. #include <assert.h>
  14. #include <unistd.h>
  15. #include <errno.h>
  16. #include <time.h>
  17. #include <poll.h>
  18. #include <errno.h>
  19. #include <string>
  20. #include <exception>
  21. #include <sstream>
  22. #include <experimental/filesystem>
  23. #include <experimental/optional>
  24. #include <FileSystemEvent.h>
  25. #define MAX_EVENTS 4096
  26. #define EVENT_SIZE (sizeof (inotify_event))
  27. #define EVENT_BUF_LEN (MAX_EVENTS * (EVENT_SIZE + 16))
  28. namespace fs = std::experimental::filesystem;
  29. /**
  30. * @brief C++ wrapper for linux inotify interface
  31. * @class Inotify
  32. * Inotify.h
  33. * "include/Inotify.h"
  34. *
  35. * folders will be watched by watchFolderRecursively or
  36. * files by watchFile. If there are changes inside this
  37. * folder or files events will be raised. This events
  38. * can be get by getNextEvent.
  39. *
  40. * @eventMask
  41. *
  42. * IN_ACCESS File was accessed (read) (*).
  43. * IN_ATTRIB Metadata changedfor example, permissions,
  44. * timestamps, extended attributes, link count
  45. * (since Linux 2.6.25), UID, or GID. (*).
  46. * IN_CLOSE_WRITE File opened for writing was closed (*).
  47. * IN_CLOSE_NOWRITE File not opened for writing was closed (*).
  48. * IN_CREATE File/directory created in watched directory(*).
  49. * IN_DELETE File/directory deleted from watched directory(*).
  50. * IN_DELETE_SELF Watched file/directory was itself deleted.
  51. * IN_MODIFY File was modified (*).
  52. * IN_MOVE_SELF Watched file/directory was itself moved.
  53. * IN_MOVED_FROM Generated for the directory containing the old
  54. * filename when a file is renamed (*).
  55. * IN_MOVED_TO Generated for the directory containing the new
  56. * filename when a file is renamed (*).
  57. * IN_OPEN File was opened (*).
  58. * IN_ALL_EVENTS macro is defined as a bit mask of all of the above
  59. * events
  60. * IN_MOVE IN_MOVED_FROM|IN_MOVED_TO
  61. * IN_CLOSE IN_CLOSE_WRITE | IN_CLOSE_NOWRITE
  62. *
  63. * See inotify manpage for more event details
  64. *
  65. */
  66. class Inotify {
  67. public:
  68. Inotify();
  69. Inotify(uint32_t eventMask);
  70. Inotify(std::vector< std::string> ignoredDirectories, unsigned eventTimeout, uint32_t eventMask);
  71. ~Inotify();
  72. void watchDirectoryRecursively(fs::path path);
  73. void watchFile(fs::path file, std::optional<fs::path> recurseRoot = std::nullopt);
  74. void ignoreFileOnce(fs::path file);
  75. FileSystemEvent getNextEvent();
  76. std::optional<FileSystemEvent> nb_getNextEvent();
  77. int getLastErrno();
  78. private:
  79. fs::path wdToPath(int wd);
  80. bool isIgnored(std::string file);
  81. bool onTimeout(time_t eventTime);
  82. void removeWatch(int wd); // TODO
  83. void init();
  84. // Member
  85. int mError;
  86. time_t mEventTimeout;
  87. time_t mLastEventTime;
  88. uint32_t mEventMask;
  89. std::vector<std::string> mIgnoredDirectories;
  90. std::vector<std::string> mOnceIgnoredDirectories;
  91. std::queue<FileSystemEvent> mEventQueue;
  92. std::map<int, fs::path> mDirectorieMap;
  93. std::map<int, fs::path> mRecurseMap;
  94. int mInotifyFd;
  95. };
  96. inline Inotify::Inotify() :
  97. mError(0),
  98. mEventTimeout(0),
  99. mLastEventTime(0),
  100. mEventMask(IN_ALL_EVENTS),
  101. mIgnoredDirectories(std::vector<std::string>()),
  102. mInotifyFd(0){
  103. // Initialize inotify
  104. init();
  105. }
  106. inline Inotify::Inotify(uint32_t eventMask) :
  107. mError(0),
  108. mEventTimeout(0),
  109. mLastEventTime(0),
  110. mEventMask(eventMask),
  111. mIgnoredDirectories(std::vector<std::string>()),
  112. mInotifyFd(0){
  113. // Initialize inotify
  114. init();
  115. }
  116. inline Inotify::Inotify(std::vector<std::string> ignoredDirectories, unsigned eventTimeout, uint32_t eventMask) :
  117. mError(0),
  118. mEventTimeout(eventTimeout),
  119. mLastEventTime(0),
  120. mEventMask(eventMask),
  121. mIgnoredDirectories(ignoredDirectories),
  122. mInotifyFd(0){
  123. // Initialize inotify
  124. init();
  125. }
  126. inline Inotify::~Inotify(){
  127. if(!close(mInotifyFd)){
  128. mError = errno;
  129. }
  130. }
  131. inline void Inotify::init(){
  132. mInotifyFd = inotify_init();
  133. if(mInotifyFd == -1){
  134. mError = errno;
  135. std::stringstream errorStream;
  136. errorStream << "Can't initialize inotify ! " << strerror(mError) << ".";
  137. throw std::runtime_error(errorStream.str());
  138. }
  139. }
  140. /**
  141. * @brief Adds the given path and all files and subdirectories
  142. * to the set of watched files/directories.
  143. * Symlinks will be followed!
  144. *
  145. * @param path that will be watched recursively
  146. *
  147. */
  148. inline void Inotify::watchDirectoryRecursively(fs::path path){
  149. if(fs::exists(path)){
  150. if(fs::is_directory(path)){
  151. fs::recursive_directory_iterator it(path, fs::directory_options::follow_directory_symlink);
  152. fs::recursive_directory_iterator end;
  153. while(it != end){
  154. fs::path currentPath = *it;
  155. if(fs::is_directory(currentPath)){
  156. watchFile(currentPath, path);
  157. }
  158. if(fs::is_symlink(currentPath)){
  159. watchFile(currentPath, path);
  160. }
  161. ++it;
  162. }
  163. }
  164. watchFile(path);
  165. }
  166. else {
  167. throw std::invalid_argument("Can´t watch Path! Path does not exist. Path: " + path.string());
  168. }
  169. }
  170. /**
  171. * @brief Adds a single file/directorie to the list of
  172. * watches. Path and corresponding watchdescriptor
  173. * will be stored in the directorieMap. This is done
  174. * because events on watches just return this
  175. * watchdescriptor.
  176. *
  177. * @param path that will be watched
  178. *
  179. */
  180. inline void Inotify::watchFile(fs::path filePath, std::optional<fs::path> recurseRoot) {
  181. if(fs::exists(filePath)){
  182. mError = 0;
  183. int wd = 0;
  184. if(!isIgnored(filePath.string())){
  185. wd = inotify_add_watch(mInotifyFd, filePath.string().c_str(), mEventMask);
  186. }
  187. if(wd == -1){
  188. mError = errno;
  189. std::stringstream errorStream;
  190. if(mError == 28){
  191. errorStream << "Failed to watch! " << strerror(mError) << ". Please increase number of watches in \"/proc/sys/fs/inotify/max_user_watches\".";
  192. throw std::runtime_error(errorStream.str());
  193. }
  194. errorStream << "Failed to watch! " << strerror(mError) << ". Path: " << filePath.string();
  195. throw std::runtime_error(errorStream.str());
  196. }
  197. mDirectorieMap[wd] = filePath;
  198. if (recurseRoot)
  199. mRecurseMap[wd] = *recurseRoot;
  200. }
  201. }
  202. inline void Inotify::ignoreFileOnce(fs::path file){
  203. mOnceIgnoredDirectories.push_back(file.string());
  204. }
  205. /**
  206. * @brief Removes watch from set of watches. This
  207. * is not done recursively!
  208. *
  209. * @param wd watchdescriptor
  210. *
  211. */
  212. inline void Inotify::removeWatch(int wd){
  213. int result = inotify_rm_watch(mInotifyFd, wd);
  214. if(result == -1){
  215. mError = errno;
  216. std::stringstream errorStream;
  217. errorStream << "Failed to remove watch! " << strerror(mError) << ".";
  218. throw std::runtime_error(errorStream.str());
  219. }
  220. mDirectorieMap.erase(wd);
  221. }
  222. inline fs::path Inotify::wdToPath(int wd){
  223. return mDirectorieMap[wd];
  224. }
  225. /**
  226. * @brief Blocking wait on new events of watched files/directories
  227. * specified on the eventmask. FileSystemEvents
  228. * will be returned one by one. Thus this
  229. * function can be called in some while(true)
  230. * loop.
  231. *
  232. * @return A new FileSystemEvent
  233. *
  234. */
  235. inline FileSystemEvent Inotify::getNextEvent(){
  236. int length = 0;
  237. char buffer[EVENT_BUF_LEN];
  238. time_t currentEventTime = time(NULL);
  239. std::vector<FileSystemEvent> events;
  240. // Read Events from fd into buffer
  241. while(mEventQueue.empty()){
  242. length = 0;
  243. memset(&buffer, 0, EVENT_BUF_LEN);
  244. while(length <= 0 ){
  245. length = read(mInotifyFd, buffer, EVENT_BUF_LEN);
  246. currentEventTime = time(NULL);
  247. if(length == -1){
  248. mError = errno;
  249. if(mError != EINTR){
  250. continue;
  251. }
  252. }
  253. }
  254. // Read events from buffer into queue
  255. currentEventTime = time(NULL);
  256. int i = 0;
  257. while(i < length){
  258. inotify_event *event = ((struct inotify_event*) &buffer[i]);
  259. fs::path path(wdToPath(event->wd) / std::string(event->name));
  260. if(fs::is_directory(path)){
  261. event->mask |= IN_ISDIR;
  262. }
  263. FileSystemEvent fsEvent(event->wd, event->mask, path);
  264. if(!fsEvent.path.empty()){
  265. events.push_back(fsEvent);
  266. }
  267. else{
  268. // Event is not complete --> ignore
  269. }
  270. i += EVENT_SIZE + event->len;
  271. }
  272. // Filter events
  273. for(auto eventIt = events.begin(); eventIt < events.end(); ++eventIt){
  274. FileSystemEvent currentEvent = *eventIt;
  275. if(onTimeout(currentEventTime)){
  276. events.erase(eventIt);
  277. }
  278. else if(isIgnored(currentEvent.path.string())){
  279. events.erase(eventIt);
  280. }
  281. else{
  282. mLastEventTime = currentEventTime;
  283. mEventQueue.push(currentEvent);
  284. }
  285. }
  286. }
  287. // Return next event
  288. FileSystemEvent event = mEventQueue.front();
  289. mEventQueue.pop();
  290. return event;
  291. }
  292. std::optional<FileSystemEvent> Inotify::nb_getNextEvent() {
  293. char buffer[EVENT_BUF_LEN]
  294. __attribute__ ((aligned(__alignof__(struct inotify_event))));
  295. std::vector<FileSystemEvent> events;
  296. struct pollfd fd = {.fd = mInotifyFd, .events = POLLIN, 0};
  297. int pn = poll(&fd, 1, 0); // nonblocking poll(2)
  298. if (pn == -1 && errno != EINTR) {
  299. perror("poll()");
  300. exit(EXIT_FAILURE);
  301. }
  302. if (pn > 0) {
  303. ssize_t len = read(mInotifyFd, buffer, sizeof buffer);
  304. if (len <= 0)
  305. return std::nullopt;
  306. struct inotify_event *event;
  307. for (char *ptr = buffer; ptr < buffer + len;
  308. ptr += sizeof(struct inotify_event) + event->len) {
  309. event = (struct inotify_event*) ptr;
  310. fs::path path(wdToPath(event->wd) / std::string(event->name));
  311. if (fs::is_directory(path))
  312. event->mask |= IN_ISDIR;
  313. FileSystemEvent fsEvent(event->wd, event->mask, path);
  314. if (mRecurseMap.find(event->wd) != mRecurseMap.end()) {
  315. fsEvent.recursive_root_path = mRecurseMap[event->wd];
  316. }
  317. if (!fsEvent.path.empty())
  318. events.push_back(fsEvent);
  319. }
  320. for (auto eventIt = events.begin(); eventIt < events.end(); ++eventIt){
  321. FileSystemEvent currentEvent = *eventIt;
  322. if (isIgnored(currentEvent.path.string())) {
  323. events.erase(eventIt);
  324. } else {
  325. mEventQueue.push(currentEvent);
  326. }
  327. }
  328. }
  329. if (mEventQueue.empty())
  330. return std::nullopt;
  331. auto event = mEventQueue.front(); mEventQueue.pop();
  332. return event;
  333. }
  334. inline int Inotify::getLastErrno(){
  335. return mError;
  336. }
  337. inline bool Inotify::isIgnored(std::string file){
  338. if(mIgnoredDirectories.empty() and mOnceIgnoredDirectories.empty()){
  339. return false;
  340. }
  341. for(unsigned i = 0; i < mOnceIgnoredDirectories.size(); ++i){
  342. size_t pos = file.find(mOnceIgnoredDirectories[i]);
  343. if(pos!= std::string::npos){
  344. mOnceIgnoredDirectories.erase(mOnceIgnoredDirectories.begin() + i);
  345. return true;
  346. }
  347. }
  348. for(unsigned i = 0; i < mIgnoredDirectories.size(); ++i){
  349. size_t pos = file.find(mIgnoredDirectories[i]);
  350. if(pos!= std::string::npos){
  351. return true;
  352. }
  353. }
  354. return false;
  355. }
  356. inline bool Inotify::onTimeout(time_t eventTime){
  357. return (mLastEventTime + mEventTimeout) > eventTime;
  358. }