libH/FileEventWatcher.cpp

Go to the documentation of this file.
00001 
00012 /*
00013   
00014   Copyright (c) 2007, Tim Burrell
00015   Licensed under the Apache License, Version 2.0 (the "License");
00016   you may not use this file except in compliance with the License.
00017   You may obtain a copy of the License at 
00018 
00019         http://www.apache.org/licenses/LICENSE-2.0
00020 
00021   Unless required by applicable law or agreed to in writing, software
00022   distributed under the License is distributed on an "AS IS" BASIS,
00023   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00024   See the License for the specific language governing permissions and 
00025   limitations under the License. 
00026   
00027 */
00028 
00029 #include "FileEventWatcher.hpp"
00030 #include "Debug.hpp"
00031 #include "Exception.hpp"
00032 #include "Util.hpp"
00033 #include "UtilTime.hpp"
00034 #include <boost/bind.hpp>
00035 #include <boost/filesystem/operations.hpp>
00036 #include <sys/time.h>
00037 #include <sys/types.h>
00038 #include <sys/stat.h>
00039 #include <fcntl.h>
00040 #include <linux/input.h>
00041 #include <unistd.h>
00042 #include <sys/inotify.h>
00043 #include <unistd.h>
00044 #include <errno.h>
00045 #include <sys/socket.h>
00046 #include <sys/un.h>
00047 
00048 using namespace std;
00049 using namespace boost;
00050 using namespace H;
00051 
00053 // Type Defs / Defines
00055 
00060 #define READ_BUF_SIZE           65536
00061 
00066 #define NOTIFY_EVENT_SIZE       (sizeof(struct inotify_event))
00067 
00072 #define NOTIFY_READ_BUF_SIZE    1024 * (NOTIFY_EVENT_SIZE + 16)
00073 
00078 #define DEVICE_NAME_BUF_SIZE    1024
00079 
00084 #define RETRY_FAIL_WAIT_NSECS   100000000
00085 
00090 #define MAX_RETRIES             5
00091 
00096 #define POLL_TIMEOUT    1000
00097 
00099 // Construction
00101 
00105 DeviceInfo::DeviceInfo() {
00106         DeviceIDBusType = -1;
00107         DeviceIDProduct = -1;
00108         DeviceIDVendor = -1;
00109         DeviceIDVersion = -1;   
00110 }
00111 
00115 DeviceInfo::DeviceInfo(std::string deviceName, std::string fileName, int deviceIDBusType, int deviceIDVendor, int deviceIDProduct, int deviceIDVersion, int fileDescriptor) {
00116         DeviceName = deviceName;
00117         FileName = fileName;
00118         DeviceIDBusType = deviceIDBusType;
00119         DeviceIDVendor = deviceIDVendor;
00120         DeviceIDProduct = deviceIDProduct;
00121         DeviceIDVersion = deviceIDVersion;
00122         FileDescriptor = fileDescriptor;
00123 }
00124 
00128 DeviceInfo::DeviceInfo(const DeviceInfo & DeviceInformation) {
00129         DeviceName = DeviceInformation.DeviceName;
00130         FileName = DeviceInformation.FileName;
00131         DeviceIDBusType = DeviceInformation.DeviceIDBusType;
00132         DeviceIDVendor = DeviceInformation.DeviceIDVendor;
00133         DeviceIDProduct = DeviceInformation.DeviceIDProduct;
00134         DeviceIDVersion = DeviceInformation.DeviceIDVersion;
00135         FileDescriptor = DeviceInformation.FileDescriptor;
00136 }
00137 
00141 DeviceInfo::~DeviceInfo() {
00142 }
00143 
00147 FileEventWatcher::FileEventWatcher() {
00148         mPolling = false;
00149         if ((mInotifyFD = inotify_init()) < 0)
00150                 throw H::Exception("Failed to Initialize Inotify!\n    - Inotify must be compiled into the kernel, or installed as a kernel module\n    - Set kernel options CONFIG_INOTIFY, and CONFIG_INOTIFY_USER to yes", __FILE__, __FUNCTION__, __LINE__);
00151 }
00152 
00156 FileEventWatcher::~FileEventWatcher() {
00157         removeAllWatchDescriptors();
00158         if (mInotifyFD != -1)
00159                 close(mInotifyFD);
00160 }
00161 
00165 FileWatchee::FileWatchee() {
00166         WatchType = WATCH_IN;
00167         fd = -1;
00168         Events = POLLIN;
00169 }
00170 
00174 FileWatchee::FileWatchee(std::string fileName, FileWatchType watchType, short events, int fileDescriptor, int watchDescriptor, std::string deviceName, int deviceIDBusType, int deviceIDVendor, int deviceIDProduct, int deviceIDVersion) :
00175         DeviceInfo(deviceName, fileName, deviceIDBusType, deviceIDVendor, deviceIDProduct, deviceIDVersion, fileDescriptor)
00176 {
00177         WatchType = watchType;
00178         Events = events;
00179         fd = fileDescriptor;
00180         wd = watchDescriptor;
00181         if (fd < 0)
00182                 DeviceType = WATCH_INOTIFY;
00183         else
00184                 DeviceType = WATCH_POLL;
00185 }
00186 
00190 FileWatchee::~FileWatchee() {
00191         if (fd > -1)
00192                 close(fd);
00193 }
00194 
00196 // Class Body
00198 
00205 boost::shared_ptr<FileWatchee> FileEventWatcher::addFileToWatch(std::string FileName, FileWatchType WatchType, std::string DefaultDeviceName) {
00206         // make sure we're not already watching this file
00207         shared_ptr<FileWatchee> pDupWatchee = getWatcheeByPath(FileName);
00208         if (pDupWatchee) {
00209                 //if (H::Debug::testPrint(dbg0))
00210                 //      H::dbg0 << "Already Watching File [" << FileName << "]" << endl;
00211                 cdbg << "Already Watching File [" << FileName << "]" << endl;
00212                 return shared_ptr<FileWatchee>();
00213         }
00214         pDupWatchee.reset();
00215         cdbg1 << "Adding File [" << FileName << "] to Watch List with Mode [" << WatchType << "]" << endl;
00216         
00217         // get mode mask
00218         int     flags = 0;
00219         string  ModeString;
00220         short   events = 0;
00221         switch (WatchType) {
00222         case WATCH_IN:
00223                 flags = O_RDONLY;
00224                 events = POLLIN;
00225                 ModeString = "Read";
00226                 break;
00227         case WATCH_OUT:
00228                 flags = O_WRONLY;
00229                 events = POLLOUT;
00230                 ModeString = "Write";
00231                 break;
00232         case WATCH_INOUT:
00233                 flags = O_RDWR;
00234                 events = POLLIN | POLLOUT;
00235                 ModeString = "Read / Write";
00236                 break;
00237         case WATCH_INVALID:
00238                 throw H::Exception("Invalid Watch Type specified on [" + FileName + "]", __FILE__, __FUNCTION__, __LINE__);
00239         }
00240         
00241         // make sure it exists
00242         filesystem::path FilePath(FileName);
00243         if (!filesystem::exists(FilePath))
00244                 throw H::Exception("Path [" + FileName + "] does not exist, or cannot open (perms?) with Mode [" + ModeString + "]", __FILE__, __FUNCTION__, __LINE__);
00245         
00246         // if it's a directory, then watch with inotify, otherwise use poll
00247         int fd = -1;
00248         int wd = -1;
00249         char DeviceName[DEVICE_NAME_BUF_SIZE] = {'\0'};
00250         unsigned short DeviceIDs[4] = {-1, -1, -1, -1};
00251         if (filesystem::is_directory(FilePath)) {
00252                 // directory, add watch
00253                 if ((wd = inotify_add_watch(mInotifyFD, FileName.c_str(), IN_CREATE | IN_DELETE | IN_DELETE_SELF)) == -1)
00254                         throw H::Exception("Failed to add Watch on Directory [" + FileName + "]", __FILE__, __FUNCTION__, __LINE__);
00255                 fd = -wd;
00256                 mInotifyWDs.push_back(wd);
00257                 strcpy(DeviceName, "Directory");
00258         } else {        
00259                 // not a directory, open the device
00260                 int retry;
00261                 for (retry = 0; retry < MAX_RETRIES; retry ++) {
00262                         if ((fd = open(FileName.c_str(), flags)) == -1) {
00263                                 cdbg1 << "New Device [" << FileName << "] Open Failed -- Retrying" << endl;
00264                                 UtilTime::nanoSleep(RETRY_FAIL_WAIT_NSECS);
00265                                 continue;
00266                         }
00267                         break;
00268                 }
00269                 if (retry == MAX_RETRIES) {
00270                         //throw H::Exception("Failed to Open file [" + FileName + "] with Mode [" + ModeString + "]", __FILE__, __FUNCTION__, __LINE__);
00271                         cerr << "Failed to Open [" << FileName << "] for [" << ModeString + "] -- Check Permissions!" << endl;
00272                         return shared_ptr<FileWatchee>();
00273                 }
00274                 
00275                 // get the device name
00276                 if (ioctl(fd, EVIOCGNAME(sizeof(DeviceName)), DeviceName) < 0) {
00277                         cdbg3 << "Failed to Get Device Name for [" + FileName + "]" << endl;
00278                         strcpy(DeviceName, DefaultDeviceName.c_str());
00279                 }
00280                 
00281                 // get the device id information
00282                 if (ioctl(fd, EVIOCGID, DeviceIDs) < 0)
00283                         cdbg3 << "Failed to Get Device IDs for [" + FileName + "]" << endl;                     
00284         }
00285         
00286         cdbg1 << "Watching Device [" << FileName << "]: " << DeviceName << endl;
00287         shared_ptr<FileWatchee> pWatchee(new FileWatchee(FileName, WatchType, events, fd, wd, DeviceName, DeviceIDs[0], DeviceIDs[1], DeviceIDs[2], DeviceIDs[3]));
00288         mWatchees.insert(make_pair(fd, pWatchee));
00289         buildPollFDArrayFromWatchees();
00290         onFileEventRegister(pWatchee);
00291                         
00292         return pWatchee;
00293 }
00294 
00300 boost::shared_ptr<FileWatchee> FileEventWatcher::addUnixSocketToWatch(std::string FileName, std::string DeviceName) {
00301         // get mode mask
00302         string          ModeString = "Read";
00303         short           events = POLLIN;
00304         FileWatchType   WatchType = WATCH_IN;
00305         
00306         // make sure it exists
00307         filesystem::path FilePath(FileName);
00308         if (!filesystem::exists(FilePath))
00309                 throw H::Exception("Path [" + FileName + "] does not exist, or cannot open (perms?) with Mode [" + ModeString + "]", __FILE__, __FUNCTION__, __LINE__);
00310         
00311         // set up the datastructure
00312         struct sockaddr_un Addr;
00313         Addr.sun_family = AF_UNIX;
00314         strcpy(Addr.sun_path, FileName.c_str());
00315         
00316         // open the unix socket
00317         int fd;
00318         if ((fd = socket(AF_UNIX,SOCK_STREAM, 0)) == -1) {
00319                 cdbg1 << "Failed to Creat Socket for [" << FileName << "] for [" << ModeString + "] -- Check Permissions!" << endl;
00320                 return shared_ptr<FileWatchee>();
00321         }
00322         
00323         // connect to the socket
00324         if (connect(fd,(struct sockaddr *) &Addr,sizeof(Addr)) == -1)  {
00325                 cdbg1 << "Failed to Connect to [" << FileName << "] for [" << ModeString + "] -- Check Permissions!" << endl;
00326                 close(fd);
00327                 return shared_ptr<FileWatchee>();
00328         }
00329         
00330         cdbg1 << "Watching Unix Socket [" << FileName << "]: " << DeviceName << endl;
00331         shared_ptr<FileWatchee> pWatchee(new FileWatchee(FileName, WatchType, events, fd, -1, DeviceName, -1, -1, -1, -1));
00332         mWatchees.insert(make_pair(fd, pWatchee));
00333         buildPollFDArrayFromWatchees();
00334         onFileEventRegister(pWatchee);
00335                         
00336         return pWatchee;
00337 }
00338 
00342 void FileEventWatcher::buildPollFDArrayFromWatchees() {
00343         // clear out the pollfsd
00344         mPollFDs.clear();
00345         
00346         // put in the inotify pollfd
00347         struct pollfd PollFD;
00348         PollFD.fd = mInotifyFD;
00349         PollFD.events = POLLIN | POLLOUT;
00350         PollFD.revents = 0;
00351         mPollFDs.push_back(PollFD);
00352         
00353         // build the rest of the pollfds via the functor
00354         apply_func(mWatchees, &FileEventWatcher::buildPollFDArrayFunctor, this);
00355 }
00356 
00361 void FileEventWatcher::buildPollFDArrayFunctor(std::pair< int, boost::shared_ptr<FileWatchee> > WatcheePair) {
00362         boost::shared_ptr<FileWatchee> pWatchee = WatcheePair.second;
00363         if ( (!pWatchee) || (pWatchee->fd < 0) )
00364                 return;
00365         
00366         // Add the new watchee to the list
00367         struct pollfd PollFD;
00368         PollFD.fd = pWatchee->fd;
00369         PollFD.events = pWatchee->Events;
00370         PollFD.revents = 0;
00371         mPollFDs.push_back(PollFD);
00372 }
00373 
00379 FileWatchType FileEventWatcher::getType(int Index) {
00380         if (mPollFDs[Index].revents & POLLIN)
00381                 return WATCH_IN;
00382         else if (mPollFDs[Index].revents & POLLOUT)
00383                 return WATCH_OUT;
00384         else if (mPollFDs[Index].revents & POLLOUT & POLLIN)
00385                 return WATCH_INOUT;
00386         else
00387                 return WATCH_INVALID;
00388 }
00389 
00395 boost::shared_ptr<FileWatchee> FileEventWatcher::getWatcheeByFileDescriptor(int fd) {
00396         return mWatchees[fd];
00397 }
00398 
00404 boost::shared_ptr<FileWatchee> FileEventWatcher::getWatcheeByPath(std::string FileName) {
00405         map< int, shared_ptr<FileWatchee> >::iterator iter;
00406         for (iter = mWatchees.begin(); iter != mWatchees.end(); iter ++) {
00407                 shared_ptr<FileWatchee> pWatchee = iter->second;
00408                 if (!pWatchee) {
00409                         mWatchees.erase(iter);
00410                         continue;
00411                 }
00412                 if (pWatchee->FileName == FileName)
00413                         return pWatchee;
00414         }
00415         
00416         // not found, return null shared pointer
00417         return shared_ptr<FileWatchee>();
00418 }
00419 
00425 boost::shared_ptr<FileWatchee> FileEventWatcher::getWatcheeByWatchDescriptor(int wd) {
00426         return mWatchees[-wd];
00427 }
00428 
00434 void FileEventWatcher::readFromFile(int fd, DynamicBuffer<char> & Buffer) {
00435         char ReadBuffer[READ_BUF_SIZE];
00436         ssize_t BytesRead;
00437         do {
00438                 if ((BytesRead = read(fd, ReadBuffer, READ_BUF_SIZE)) <= 0)
00439                         throw H::DeviceDisconnectException();
00440                 Buffer.addToBuffer(ReadBuffer, BytesRead);
00441         } while (BytesRead == READ_BUF_SIZE);
00442 }
00443 
00448 void FileEventWatcher::handleEventsOnFile(struct pollfd & item) {
00449         if (item.fd == mInotifyFD) {
00450                 if (item.revents & POLLERR) {
00451                         cdbg << "Error detected on inotify device" << endl;
00452                 } else if ( (item.revents & POLLIN) || (item.revents & POLLPRI) ) {
00453                         // read from the inotify device
00454                         char ReadBuffer[NOTIFY_READ_BUF_SIZE];
00455                         int BytesRead = read(mInotifyFD, ReadBuffer, NOTIFY_READ_BUF_SIZE);
00456                         if (BytesRead < 0)
00457                                 if (errno == EINTR)
00458                                         return;
00459                                 else
00460                                         throw H::Exception("Failed to Read from Inotify Device!", __FILE__, __FUNCTION__, __LINE__);
00461                                 
00462                         // loop through all of the returned inotify_event structures and produce events
00463                         int BytesHandled = 0;
00464                         while (BytesHandled < BytesRead) {
00465                                 struct inotify_event * event;
00466                                 event = (struct inotify_event *) &ReadBuffer[BytesHandled];
00467                                 BytesHandled += NOTIFY_EVENT_SIZE + event->len;
00468                                 
00469                                 shared_ptr<FileWatchee> pWatchee = getWatcheeByWatchDescriptor(event->wd);
00470                                 if (!pWatchee) {
00471                                         cerr << "Unhandled inotify event: " << event->name << endl;
00472                                         continue;
00473                                 }
00474                                 
00475                                 if (event->mask & IN_ACCESS)
00476                                         cout << "Access" << endl;
00477                                 if (event->mask & IN_ATTRIB)
00478                                         cout << "Attrib" << endl;
00479                                 if (event->mask & IN_CLOSE_WRITE)
00480                                         cout << "CloseWrite" << endl;
00481                                 if (event->mask & IN_CLOSE_NOWRITE)
00482                                         cout << "CloseNoWrite" << endl;
00483                                 if (event->mask & IN_CREATE)
00484                                         onFileEventCreate(pWatchee, pWatchee->FileName + "/" + event->name, event->name);
00485                                 if ( (event->mask & IN_DELETE) || (event->mask & IN_DELETE_SELF) ) {
00486                                         // pass in the actual watchee rather than the directory watchee
00487                                         shared_ptr<FileWatchee> pActualWatchee = getWatcheeByPath(pWatchee->FileName + "/" + event->name);
00488                                         if (!pActualWatchee) {
00489                                                 cdbg2 << "Delete inotify event on unhandled file: " << event->name << endl;
00490                                                 continue;
00491                                         }
00492                                         onFileEventDelete(pActualWatchee, pActualWatchee->FileName, event->name);
00493                                 }
00494                                 if (event->mask & IN_MODIFY)
00495                                         cout << "Modify" << endl;
00496                                 if (event->mask & IN_MOVE_SELF)
00497                                         cout << "MoveSelf" << endl;
00498                                 if (event->mask & IN_MOVED_FROM)
00499                                         cout << "MovedFrom" << endl;
00500                                 if (event->mask & IN_MOVED_TO)
00501                                         cout << "MovedTo" << endl;
00502                                 if (event->mask & IN_OPEN)
00503                                         cout << "Open" << endl;
00504                                 if (event->mask & IN_UNMOUNT)
00505                                         cout << "Unmount" << endl;
00506                         }
00507                 }
00508         } else if (item.fd >= 0) {
00509                 if (item.revents & POLLERR) {
00510                         shared_ptr<FileWatchee> pWatchee = getWatcheeByFileDescriptor(item.fd);
00511                         if (pWatchee) {
00512                                 cdbg5 << "Error detected on poll device: " << item.fd << " -- " << pWatchee->FileName << endl;
00513                                 onFileEventDisconnect(pWatchee);
00514                                 removeWatchee(pWatchee);                        
00515                         } else {
00516                                 cdbg5 << "Error detected on poll device: " << item.fd << endl;
00517                                 buildPollFDArrayFromWatchees();
00518                         }
00519                 } else if ( (item.revents & POLLIN) || (item.revents & POLLPRI) ) {
00520                         // poll device, try to read from it
00521                         shared_ptr<FileWatchee> pWatchee = getWatcheeByFileDescriptor(item.fd);
00522                         if (!pWatchee) {
00523                                 cdbg5 << "Unhandled file event on fd [" << item.fd << "]" << endl;
00524                                 buildPollFDArrayFromWatchees();
00525                                 return;
00526                         }
00527                         try {
00528                                 DynamicBuffer<char> Buffer;
00529                                 readFromFile(item.fd, Buffer);
00530                                 if (pWatchee)
00531                                         onFileEventRead(pWatchee, Buffer);
00532                         } catch (H::DeviceDisconnectException & e) {
00533                                 if ( (pWatchee) && (pWatchee->DeviceType == WATCH_POLL) ) {
00534                                         onFileEventDisconnect(pWatchee);
00535                                         removeWatchee(pWatchee);
00536                                 } else
00537                                         cout << "Disconnect from Unknown Device" << endl;
00538                         }                       
00539                 }
00540         }
00541 }
00542 
00549 void FileEventWatcher::onFileEventCreate(boost::shared_ptr<FileWatchee> pWatchee, std::string FullPath, std::string FileName) {
00550         // override me
00551 }
00552 
00559 void FileEventWatcher::onFileEventDelete(boost::shared_ptr<FileWatchee> pWatchee, std::string FullPath, std::string FileName) {
00560         // override me
00561 }
00562 
00567 void FileEventWatcher::onFileEventDisconnect(boost::shared_ptr<FileWatchee> pWatchee) {
00568         // override me
00569 }
00570 
00576 void FileEventWatcher::onFileEventRead(boost::shared_ptr<FileWatchee> pWatchee, DynamicBuffer<char> const & ReadBuffer) {
00577         // override me
00578 }
00579 
00584 void FileEventWatcher::onFileEventRegister(boost::shared_ptr<FileWatchee> pWatchee) {
00585         // override me
00586 }
00587 
00591 void FileEventWatcher::onFileEventWatchBegin() {
00592         // override me
00593 }
00594 
00598 void FileEventWatcher::onFileEventWatchEnd() {
00599         // override me
00600 }
00601 
00606 void FileEventWatcher::removeWatchDescriptor(int wd) {
00607         inotify_rm_watch(mInotifyFD, wd);
00608 }
00609 
00613 void FileEventWatcher::removeAllWatchDescriptors() {
00614         apply_func(mInotifyWDs, &FileEventWatcher::removeWatchDescriptor, this);
00615 }
00616 
00621 void FileEventWatcher::removeWatchee(boost::shared_ptr<FileWatchee> pWatchee) {
00622         if (!pWatchee)
00623                 return;
00624         map< int, shared_ptr<FileWatchee> >::iterator iter;
00625         bool removed = false;
00626         for (iter = mWatchees.begin(); iter != mWatchees.end(); iter ++) {
00627                 if (!iter->second)
00628                         continue;
00629                 if (iter->second->fd == pWatchee->fd) {
00630                         cdbg4 << "Removed Watchee [" << pWatchee->FileName << "]" << endl;
00631                         mWatchees.erase(iter);
00632                         removed = true;
00633                         break;
00634                 }
00635         }
00636         if (removed)
00637                 buildPollFDArrayFromWatchees();
00638 }
00639 
00646 void FileEventWatcher::shutdown() {
00647         mPolling = false;
00648 }
00649 
00655 void FileEventWatcher::watchForFileEvents() {
00656         if (mPollFDs.size() == 0) {
00657                 cdbg << "FileEventWatcher :: watchForFileEvents -- No file to watch!" << endl;
00658                 return;
00659         }
00660                 
00661         // fire notification of blocking
00662         onFileEventWatchBegin();
00663         
00664         cdbg1 << "FileEventWatcher :: Watching [" << (int) mPollFDs.size() << " Files] for Events..." << endl;
00665         mPolling = true;
00666         int ret;
00667         do {
00668                 // poll the open files
00669                 if ((ret = poll(&mPollFDs[0], mPollFDs.size(), POLL_TIMEOUT)) == -1) {
00670                         // error
00671                         cdbg1 << "Poll error: " << strerror(errno) << endl;
00672                         //continue; // <-- for debugging, since the debugger fires signals and causes poll to abort
00673                         
00674                         // fire notification of end of blocking
00675                         onFileEventWatchEnd();
00676                         return;
00677                 }
00678 
00679                 if (!ret)
00680                         // timeout
00681                         continue;
00682                 
00683                 // file events have happened, check for them and dispatch
00684                 cdbg5 << "Processing File Events..." << endl;
00685                 apply_func(mPollFDs, &FileEventWatcher::handleEventsOnFile, this);
00686         } while (mPolling);
00687         cdbg1 << "FileEventWatcher :: Done Watching for File Events" << endl;
00688         
00689         // fire notification of end of blocking
00690         onFileEventWatchEnd();
00691 }

Generated on Wed Nov 7 10:04:16 2007 for gizmod by  doxygen 1.5.3