/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set et sw=2 ts=2: */ /*************************************************************************** * inotify.cc * * Wed Jan 6 09:58:47 CET 2010 * Copyright 2010 Bent Bisballe Nyeng * deva@aasimon.org ****************************************************************************/ /* * This file is part of Pracro. * * Pracro is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Pracro is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Pracro; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include "inotify.h" #include #include #include #include #include #include #define TEST(x, m) ((x & m) == m) #define STEST(x, m) (TEST(x, m)?#m" ":"") static std::string mask2asc(uint32_t mask) { std::string str; str += STEST(mask, IN_ACCESS); str += STEST(mask, IN_ATTRIB); str += STEST(mask, IN_CLOSE_WRITE); str += STEST(mask, IN_CLOSE_NOWRITE); str += STEST(mask, IN_CREATE); str += STEST(mask, IN_DELETE); str += STEST(mask, IN_DELETE_SELF); str += STEST(mask, IN_MODIFY); str += STEST(mask, IN_MOVE_SELF); 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); str += STEST(mask, IN_UNMOUNT); return str; } static inline bool isdir(const char *name) { struct stat s; stat(name, &s); return S_ISDIR(s.st_mode); } INotify::Event::Event(std::string name, uint32_t mask) { this->_name = name; this->_mask = mask; printf("[%s]\n", mask2asc(_mask).c_str()); } bool INotify::Event::isAttributeChangeEvent() { return TEST(_mask, IN_ATTRIB); } bool INotify::Event::isCloseEvent() { return TEST(_mask, IN_CLOSE_WRITE) || TEST(_mask, IN_CLOSE_NOWRITE); } bool INotify::Event::isOpenEvent() { return TEST(_mask, IN_OPEN) || TEST(_mask, IN_CREATE); } bool INotify::Event::isModifyEvent() { return TEST(_mask, IN_MODIFY); } bool INotify::Event::isDeleteEvent() { return TEST(_mask, IN_DELETE) || TEST(_mask, IN_DELETE_SELF); } std::string INotify::Event::name() { return _name; } uint32_t INotify::Event::mask() { return _mask; } INotify::INotify() { fd = inotify_init1(O_NONBLOCK); if(fd == -1) { perror("Inotify init failed.\n"); return; } } INotify::~INotify() { if(fd != -1) close(fd); } void INotify::add(std::string name, uint32_t mask) { // Extra mask bitflags: //IN_DONT_FOLLOW (since Linux 2.6.15) // Don't dereference pathname if it is a symbolic link. //IN_MASK_ADD // Add (OR) events to watch mask for this pathname if it already // exists (instead of replacing mask). //IN_ONESHOT // Monitor pathname for one event, then remove from watch list. //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) { perror("INotify: Add watch failed!"); return; } // dirmap[fd] = name; } void INotify::addRecursive(std::string name, uint32_t mask) { /* int fd = this->add(name, mask); return fd; */ } bool INotify::hasEvents() { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; fd_set fset; int ret; FD_ZERO(&fset); FD_SET(fd, &fset); ret = select (fd+1, &fset, NULL, NULL, &tv); switch(ret) { case -1: if(errno == EINTR) { // Interrupt } break; case 0: // Timeout break; default: if(FD_ISSET(fd, &fset)) { // Ready read return true; } } return false; } INotify::Event INotify::getNextEvent() { char buf[1024]; ssize_t r = read(fd, buf, sizeof(struct inotify_event)); 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) { case IN_IGNORED: //Watch was removed explicitly (inotify_rm_watch(2)) or automatically // (file was deleted, or file system was unmounted). case IN_ISDIR: //Subject of this event is a directory. case IN_Q_OVERFLOW: //Event queue overflowed (wd is -1 for this event). case IN_UNMOUNT: //File system containing watched object was unmounted. break; default: break; } return Event(event->name, event->mask); } 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)) } #ifdef TEST_INOTIFY //deps: debug.cc //cflags: -I.. //libs: #include "test.h" #include #define _DIR "/tmp" #define _FILE _DIR"/inotify_test" TEST_BEGIN; INotify inotify; // Create file FILE *fp = fopen(_FILE, "w"); TEST_NOTEQUAL(fp, NULL, "Testing if able to write file"); fprintf(fp, "something"); fclose(fp); inotify.add(_FILE); // Append to file fp = fopen(_FILE, "a"); TEST_NOTEQUAL(fp, NULL, "Testing if able to write 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."); fprintf(fp, "else"); fflush(fp); TEST_TRUE(inotify.hasEvents(), "Test if the append event was triggered."); TEST_TRUE(inotify.getNextEvent().isModifyEvent(), "Test if the event was a modified event."); 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."); // Overwrite file fp = fopen(_FILE, "w"); TEST_NOTEQUAL(fp, NULL, "Testing if able to write 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."); TEST_TRUE(inotify.getNextEvent().isModifyEvent(), "Test if the event was a modified event."); TEST_TRUE(inotify.hasEvents(), "Test if the open event was triggered."); TEST_TRUE(inotify.getNextEvent().isOpenEvent(), "Test if the event was an open event."); fprintf(fp, "else"); fflush(fp); TEST_TRUE(inotify.hasEvents(), "Test if the write event was triggered."); TEST_TRUE(inotify.getNextEvent().isModifyEvent(), "Test if the event was a modified event."); 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."); // Delete file unlink(_FILE); // 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."); // 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_END; #endif/*TEST_INOTIFY*/