From d34d398bc624521d969efab8eb1150c4a6cdeef0 Mon Sep 17 00:00:00 2001 From: deva Date: Thu, 14 Jan 2010 10:59:24 +0000 Subject: Almost finished the inotify wrapper. It still needs more tests. Made MacroList work recursive, added watch through inotify (also recursive). --- server/src/inotify.cc | 372 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 322 insertions(+), 50 deletions(-) (limited to 'server/src/inotify.cc') diff --git a/server/src/inotify.cc b/server/src/inotify.cc index e1d47d1..7834327 100644 --- a/server/src/inotify.cc +++ b/server/src/inotify.cc @@ -27,15 +27,21 @@ */ #include "inotify.h" +#include "debug.h" + #include #include #include #include #include #include +#include +#include +#include #define TEST(x, m) ((x & m) == m) +#ifdef WITH_DEBUG #define STEST(x, m) (TEST(x, m)?#m" ":"") static std::string mask2asc(uint32_t mask) @@ -54,13 +60,16 @@ static std::string mask2asc(uint32_t mask) str += STEST(mask, IN_MOVED_FROM); str += STEST(mask, IN_MOVED_TO); str += STEST(mask, IN_OPEN); + str += STEST(mask, IN_ALL_EVENTS); str += STEST(mask, IN_CLOSE); str += STEST(mask, IN_MOVE); + str += STEST(mask, IN_DONT_FOLLOW); str += STEST(mask, IN_MASK_ADD); str += STEST(mask, IN_ONESHOT); str += STEST(mask, IN_ONLYDIR); + str += STEST(mask, IN_IGNORED); str += STEST(mask, IN_ISDIR); str += STEST(mask, IN_Q_OVERFLOW); @@ -68,6 +77,7 @@ static std::string mask2asc(uint32_t mask) return str; } +#endif static inline bool isdir(const char *name) { @@ -76,13 +86,23 @@ static inline bool isdir(const char *name) return S_ISDIR(s.st_mode); } -INotify::Event::Event(std::string name, uint32_t mask) +INotify::Event::Event(struct inotify_event *event, std::string name) { this->_name = name; - this->_mask = mask; + if(event) { + this->_mask = event->mask; + this->_file = event->name; + } else { + this->_mask = 0; + this->_file = ""; + } - printf("[%s]\n", mask2asc(_mask).c_str()); + PRACRO_DEBUG(inotify, "Event [%s] %s (%s)\n", + mask2asc(_mask).c_str(), + _name.c_str(), + _file.c_str()); } + bool INotify::Event::isAttributeChangeEvent() { return TEST(_mask, IN_ATTRIB); @@ -90,12 +110,27 @@ bool INotify::Event::isAttributeChangeEvent() bool INotify::Event::isCloseEvent() { - return TEST(_mask, IN_CLOSE_WRITE) || TEST(_mask, IN_CLOSE_NOWRITE); + return isCloseWriteEvent() || isCloseNoWriteEvent(); +} + +bool INotify::Event::isCloseWriteEvent() +{ + return TEST(_mask, IN_CLOSE_WRITE); +} + +bool INotify::Event::isCloseNoWriteEvent() +{ + return TEST(_mask, IN_CLOSE_NOWRITE); +} + +bool INotify::Event::isCreateEvent() +{ + return TEST(_mask, IN_CREATE); } bool INotify::Event::isOpenEvent() { - return TEST(_mask, IN_OPEN) || TEST(_mask, IN_CREATE); + return TEST(_mask, IN_OPEN); } bool INotify::Event::isModifyEvent() @@ -103,9 +138,39 @@ bool INotify::Event::isModifyEvent() return TEST(_mask, IN_MODIFY); } +bool INotify::Event::isAccessEvent() +{ + return TEST(_mask, IN_ACCESS); +} + bool INotify::Event::isDeleteEvent() { - return TEST(_mask, IN_DELETE) || TEST(_mask, IN_DELETE_SELF); + return TEST(_mask, IN_DELETE); +} + +bool INotify::Event::isDeleteSelfEvent() +{ + return TEST(_mask, IN_DELETE_SELF); +} + +bool INotify::Event::isIgnoredEvent() +{ + return TEST(_mask, IN_IGNORED); +} + +bool INotify::Event::isMoveSelfEvent() +{ + return TEST(_mask, IN_MOVE_SELF); +} + +bool INotify::Event::isMovedFromEvent() +{ + return TEST(_mask, IN_MOVED_FROM); +} + +bool INotify::Event::isMovedToEvent() +{ + return TEST(_mask, IN_MOVED_TO); } std::string INotify::Event::name() @@ -113,6 +178,11 @@ std::string INotify::Event::name() return _name; } +std::string INotify::Event::file() +{ + return _file; +} + uint32_t INotify::Event::mask() { return _mask; @@ -120,8 +190,8 @@ uint32_t INotify::Event::mask() INotify::INotify() { - fd = inotify_init1(O_NONBLOCK); - if(fd == -1) { + ifd = inotify_init1(O_NONBLOCK); + if(ifd == -1) { perror("Inotify init failed.\n"); return; } @@ -129,10 +199,10 @@ INotify::INotify() INotify::~INotify() { - if(fd != -1) close(fd); + if(ifd != -1) close(ifd); } -void INotify::add(std::string name, uint32_t mask) +void INotify::addFile(std::string name, uint32_t mask) { // Extra mask bitflags: //IN_DONT_FOLLOW (since Linux 2.6.15) @@ -145,24 +215,132 @@ void INotify::add(std::string name, uint32_t mask) //IN_ONLYDIR (since Linux 2.6.15) // Only watch pathname if it is a directory. - int _fd = inotify_add_watch(fd, name.c_str(), mask); - if(_fd == -1) { + int wd = inotify_add_watch(ifd, name.c_str(), mask); + if(wd == -1) { perror("INotify: Add watch failed!"); return; } - // dirmap[fd] = name; + + Watch w; + w.mask = mask; + w.name = name; + w.depth = WATCH_SINGLE; + dirmap[wd] = w; } -void INotify::addRecursive(std::string name, uint32_t mask) +static inline bool isdir(std::string name) { - /* - int fd = this->add(name, mask); - return fd; - */ + struct stat s; + stat(name.c_str(), &s); + return S_ISDIR(s.st_mode); +} + +void INotify::addDirectory(std::string name, depth_t depth, uint32_t mask) +{ + PRACRO_DEBUG(inotify, "Adding dir: %s\n", name.c_str()); + + int depth_mask = 0; + if(depth == WATCH_DEEP || depth == WATCH_DEEP_FOLLOW) { + depth_mask = IN_CREATE; // We need to watch for create in order to catch + // creation of new subdirs. + + DIR *dir = opendir(name.c_str()); + if(!dir) { + PRACRO_ERR(inotify, "Could not open directory: %s - %s\n", + name.c_str(), strerror(errno)); + return; + } + + struct dirent *dirent; + while( (dirent = readdir(dir)) != NULL ) { + + if(std::string(dirent->d_name) == "." || std::string(dirent->d_name) == "..") + continue; + + PRACRO_DEBUG(inotify, "Add entry?: %s\n", dirent->d_name); + + if(isdir(name+"/"+dirent->d_name)) addDirectory(name+"/"+dirent->d_name, depth, mask); + } + + closedir(dir); + } + + int wd = inotify_add_watch(ifd, name.c_str(), mask | IN_ONLYDIR | depth_mask); + if(wd == -1) { + perror("INotify: Add watch failed!"); + return; + } + + Watch w; + w.mask = mask; + w.name = name; + w.depth = depth; + dirmap[wd] = w; +} + +void INotify::remove(int wd) +{ + if(inotify_rm_watch(ifd, wd) == -1) { + perror("inotify_rm_watch failed"); + return; + } + dirmap.erase(wd); +} + +void INotify::remove(std::string name) +{ + std::map::iterator i = dirmap.begin(); + while(i != dirmap.end()) { + Watch w = i->second; + if(w.name == name) this->remove(i->first); + i++; + } +} + +void INotify::readEvents() +{ + size_t size = 64; + char *buf = (char*)malloc(size); + + ssize_t r; + while( ((r = read(ifd, buf, size)) == -1 && errno == EINVAL) || r == 0 ) { + fprintf(stderr, "Doubling buffer size: %d\n", size); + size *= 2; + buf = (char*)realloc(buf, size); + } + + int p = 0; + while(p < r) { + struct inotify_event *event = (struct inotify_event*)(buf + p); + p += sizeof(struct inotify_event) + event->len; + + // TODO: We need to figure out what the new filename/destination is... + if(TEST(event->mask, IN_MOVE_SELF)) dirmap[event->wd].name = "????????"; + + if(dirmap[event->wd].depth == WATCH_DEEP_FOLLOW && + TEST(event->mask, IN_CREATE) && + TEST(event->mask, IN_ISDIR)) + addDirectory(dirmap[event->wd].name + "/" + event->name, + dirmap[event->wd].depth, dirmap[event->wd].mask); + + if(TEST(event->mask, IN_CREATE) && !TEST(dirmap[event->wd].mask, IN_CREATE)) { + // Ignore this event, it was not requested by the user. + } else { + eventlist.push_back(INotify::Event(event, dirmap[event->wd].name)); + } + + if(TEST(event->mask, IN_IGNORED)) dirmap.erase(event->wd); + } + + free(buf); } bool INotify::hasEvents() { + readEvents(); + return eventlist.size() > 0; + +#if 0 struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; @@ -170,8 +348,8 @@ bool INotify::hasEvents() fd_set fset; int ret; FD_ZERO(&fset); - FD_SET(fd, &fset); - ret = select (fd+1, &fset, NULL, NULL, &tv); + FD_SET(ifd, &fset); + ret = select (ifd+1, &fset, NULL, NULL, &tv); switch(ret) { case -1: if(errno == EINTR) { @@ -184,23 +362,36 @@ bool INotify::hasEvents() break; default: - if(FD_ISSET(fd, &fset)) { + if(FD_ISSET(ifd, &fset)) { // Ready read return true; } } return false; +#endif } INotify::Event INotify::getNextEvent() { - char buf[1024]; - ssize_t r = read(fd, buf, sizeof(struct inotify_event)); + readEvents(); + if(eventlist.size() == 0) return Event(NULL, ""); + Event e = eventlist.front(); + eventlist.pop_front(); + return e; + +#if 0 + struct inotify_event event; + ssize_t r = read(ifd, &event, sizeof(struct inotify_event)); + if(r != sizeof(struct inotify_event) && r != -1) if(r == sizeof(struct inotify_event)) { - struct inotify_event *event = (struct inotify_event *)buf; - if(event->len) read(fd, buf + sizeof(struct inotify_event), event->len); - switch(event->mask) { + char *name = NULL; + if(event.len) { + name = (char*)malloc(event.len); + if(read(ifd, name, event.len) != (int)event.len) printf("\n\nFUXK!\n\n"); + } + + switch(event.mask) { case IN_IGNORED: //Watch was removed explicitly (inotify_rm_watch(2)) or automatically // (file was deleted, or file system was unmounted). @@ -215,19 +406,13 @@ INotify::Event INotify::getNextEvent() break; } - return Event(event->name, event->mask); + if(TEST(event.mask, IN_IGNORED)) dirmap.erase(event.wd); + Event e((name!=NULL)?name:"", event.mask); + if(name) free(name); + return e; } return Event("", 0); -} - -void INotify::remove(int fd) -{ - inotify_rm_watch(this->fd, fd); -} - -void INotify::remove(std::string name) -{ - // this->remove(rlookup(map, name)) +#endif } #ifdef TEST_INOTIFY @@ -238,25 +423,45 @@ void INotify::remove(std::string name) #include -#define _DIR "/tmp" -#define _FILE _DIR"/inotify_test" +#define _BASEDIR "/tmp" +#define _DIR _BASEDIR"/inotify_test_dir" +#define _FILE _BASEDIR"/inotify_test" TEST_BEGIN; +pracro_debug_parse("+all"); + INotify inotify; // Create file FILE *fp = fopen(_FILE, "w"); -TEST_NOTEQUAL(fp, NULL, "Testing if able to write file"); +if(!fp) TEST_FATAL("Unable to write to the file"); fprintf(fp, "something"); fclose(fp); -inotify.add(_FILE); +inotify.addFile(_FILE); + +TEST_MSG("Positive tests on file watch."); + +// Append to file +fp = fopen(_FILE, "r"); +if(!fp) TEST_FATAL("Unable to read from the file"); +TEST_TRUE(inotify.hasEvents(), "Test if the open event was triggered."); +TEST_TRUE(inotify.getNextEvent().isOpenEvent(), "Test if the event was an open event."); + +char buf[32]; +fread(buf, sizeof(buf), 1, fp); +TEST_TRUE(inotify.hasEvents(), "Test if the read event was triggered."); +TEST_TRUE(inotify.getNextEvent().isAccessEvent(), "Test if the event was a access event."); + +fclose(fp); +TEST_TRUE(inotify.hasEvents(), "Test if the close event was triggered."); +TEST_TRUE(inotify.getNextEvent().isCloseNoWriteEvent(), "Test if the event was a close-nowrite event."); // Append to file fp = fopen(_FILE, "a"); -TEST_NOTEQUAL(fp, NULL, "Testing if able to write file"); +if(!fp) TEST_FATAL("Unable to write to the file"); TEST_TRUE(inotify.hasEvents(), "Test if the open event was triggered."); TEST_TRUE(inotify.getNextEvent().isOpenEvent(), "Test if the event was an open event."); @@ -266,12 +471,12 @@ TEST_TRUE(inotify.getNextEvent().isModifyEvent(), "Test if the event was a modif fclose(fp); TEST_TRUE(inotify.hasEvents(), "Test if the close event was triggered."); -TEST_TRUE(inotify.getNextEvent().isCloseEvent(), "Test if the event was a close event."); +TEST_TRUE(inotify.getNextEvent().isCloseWriteEvent(), "Test if the event was a close event."); // Overwrite file fp = fopen(_FILE, "w"); -TEST_NOTEQUAL(fp, NULL, "Testing if able to write file"); +if(!fp) TEST_FATAL("Unable to write to the file"); // Open for write initially empties the file content, thus provoking a changed event. TEST_TRUE(inotify.hasEvents(), "Test if the modified event was triggered."); @@ -286,19 +491,86 @@ TEST_TRUE(inotify.getNextEvent().isModifyEvent(), "Test if the event was a modif fclose(fp); TEST_TRUE(inotify.hasEvents(), "Test if the close event was triggered."); -TEST_TRUE(inotify.getNextEvent().isCloseEvent(), "Test if the event was a close event."); +TEST_TRUE(inotify.getNextEvent().isCloseWriteEvent(), "Test if the event was a close event."); +// Rename file +rename(_FILE, _FILE"2"); +TEST_TRUE(inotify.hasEvents(), "Test if the rename event was triggered."); +TEST_TRUE(inotify.getNextEvent().isMoveSelfEvent(), "Test if the event was a move self event."); // Delete file -unlink(_FILE); +unlink(_FILE"2"); // Unlink initially counts down the link counter, which provokes an attributes changed event. -TEST_EQUAL(inotify.hasEvents(), true, "Test if the delete event was triggered."); -TEST_EQUAL(inotify.getNextEvent().isAttributeChangeEvent(), true, "Test if the event was an attribute change event."); +TEST_TRUE(inotify.hasEvents(), "Test if the delete event was triggered."); +TEST_TRUE(inotify.getNextEvent().isAttributeChangeEvent(), "Test if the event was an attribute change event."); // Since the linkcount should now be zero, the file should be deleted. -TEST_EQUAL(inotify.hasEvents(), true, "Test if the delete event was triggered."); -TEST_EQUAL(inotify.getNextEvent().isDeleteEvent(), true, "Test if the event was a delete event."); +TEST_TRUE(inotify.hasEvents(), "Test if the delete event was triggered."); +TEST_TRUE(inotify.getNextEvent().isDeleteSelfEvent(), "Test if the event was a delete self event."); + +// Watch is removed upon delete. +//inotify.remove(_FILE); + +TEST_TRUE(inotify.hasEvents(), "Test if the delete event triggered an ignored event."); +TEST_TRUE(inotify.getNextEvent().isIgnoredEvent(), "Test if the event was an ignored event."); + +// Create file again +fp = fopen(_FILE, "w"); +if(!fp) TEST_FATAL("Unable to write to the file"); +fprintf(fp, "something"); +fclose(fp); + +inotify.addFile(_FILE); +inotify.remove(_FILE); + +TEST_TRUE(inotify.hasEvents(), "Test if the call to remove triggered an ignored event."); +TEST_TRUE(inotify.getNextEvent().isIgnoredEvent(), "Test if the event was an ignored event."); + +// Delete file +unlink(_FILE); +inotify.getNextEvent(); +TEST_FALSE(inotify.hasEvents(), "Test if the delete event was ignored."); + +TEST_FALSE(inotify.hasEvents(), "Test if the event queue is now empty."); + +TEST_MSG("Positive tests on directory watch."); + +if(mkdir(_DIR, 0777) == -1) TEST_FATAL("Could not create test dir."); +inotify.addDirectory(_DIR, WATCH_DEEP_FOLLOW); + +// Create file again +fp = fopen(_DIR"/file1", "w"); +if(!fp) TEST_FATAL("Unable to write to the file"); +fprintf(fp, "something"); +fclose(fp); + +TEST_TRUE(inotify.hasEvents(), "Test if the file creation triggered events."); +TEST_TRUE(inotify.getNextEvent().isCreateEvent(), "..."); +TEST_TRUE(inotify.getNextEvent().isOpenEvent(), "..."); +TEST_TRUE(inotify.getNextEvent().isModifyEvent(), "..."); +TEST_TRUE(inotify.getNextEvent().isCloseWriteEvent(), "..."); + +rename(_DIR"/file1", _DIR"/file2"); +TEST_TRUE(inotify.hasEvents(), "Test if the file renaming triggered events."); +TEST_TRUE(inotify.getNextEvent().isMovedFromEvent(), "..."); +TEST_TRUE(inotify.getNextEvent().isMovedToEvent(), "..."); + +unlink(_DIR"/file2"); + +if(mkdir(_DIR"/dir", 0777) == -1) TEST_FATAL("Could not create test dir."); + +if(mkdir(_DIR"/dir/anotherdir", 0777) == -1) TEST_FATAL("Could not create test dir."); + +while(inotify.hasEvents()) inotify.getNextEvent(); + +rmdir(_DIR"/dir/anotherdir"); + +rmdir(_DIR"/dir"); + +rmdir(_DIR); + +while(inotify.hasEvents()) inotify.getNextEvent(); TEST_END; -- cgit v1.2.3