/// inotify C++ interface header /** * \file inotify-cxx.h * * inotify C++ interface * * Copyright (C) 2006, 2007 Lukas Jelinek, * * This program is free software; you can redistribute it and/or * modify it under the terms of one of the following licenses: * * \li 1. X11-style license (see LICENSE-X11) * \li 2. GNU Lesser General Public License, version 2.1 (see LICENSE-LGPL) * \li 3. GNU General Public License, version 2 (see LICENSE-GPL) * * If you want to help with choosing the best license for you, * please visit http://www.gnu.org/licenses/license-list.html. * */ #ifndef _INOTIFYCXX_H_ #define _INOTIFYCXX_H_ #include #include #include #include // Please ensure that the following headers take the right place #include #include // Use this if syscalls not defined #ifndef __NR_inotify_init #include #endif // __NR_inotify_init /// Event struct size #define INOTIFY_EVENT_SIZE (sizeof(struct inotify_event)) /// Event buffer length #define INOTIFY_BUFLEN (1024 * (INOTIFY_EVENT_SIZE + 16)) /// Helper macro for creating exception messages. /** * It prepends the message by the function name. */ #define IN_EXC_MSG(msg) (std::string(__PRETTY_FUNCTION__) + ": " + msg) /// inotify capability/limit identifiers typedef enum { IN_MAX_EVENTS = 0, ///< max. events in the kernel queue IN_MAX_INSTANCES = 1, ///< max. inotify file descriptors per process IN_MAX_WATCHES = 2 ///< max. watches per file descriptor } InotifyCapability_t; /// inotify-cxx thread safety /** * If this symbol is defined you can use this interface safely * threaded applications. Remember that it slightly degrades * performance. * * Even if INOTIFY_THREAD_SAFE is defined some classes stay * unsafe. If you must use them (must you?) in more than one * thread concurrently you need to implement explicite locking. * * You need not to define INOTIFY_THREAD_SAFE in that cases * where the application is multithreaded but all the inotify * infrastructure will be managed only in one thread. This is * the recommended way. * * Locking may fail (it is very rare but not impossible). In this * case an exception is thrown. But if unlocking fails in case * of an error it does nothing (this failure is ignored). */ #ifdef INOTIFY_THREAD_SAFE #include #define IN_LOCK_DECL mutable pthread_rwlock_t __m_lock; #define IN_LOCK_INIT \ { \ pthread_rwlockattr_t attr; \ int res = 0; \ if ((res = pthread_rwlockattr_init(&attr)) != 0) \ throw InotifyException(IN_EXC_MSG("cannot initialize lock attributes"), res, this); \ if ((res = pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NP)) != 0) \ throw InotifyException(IN_EXC_MSG("cannot set lock kind"), res, this); \ if ((res = pthread_rwlock_init(&__m_lock, &attr)) != 0) \ throw InotifyException(IN_EXC_MSG("cannot initialize lock"), res, this); \ pthread_rwlockattr_destroy(&attr); \ } #define IN_LOCK_DONE pthread_rwlock_destroy(&__m_lock); #define IN_READ_BEGIN \ { \ int res = pthread_rwlock_rdlock(&__m_lock); \ if (res != 0) \ throw InotifyException(IN_EXC_MSG("locking for reading failed"), res, (void*) this); \ } #define IN_READ_END \ { \ int res = pthread_rwlock_unlock(&__m_lock); \ if (res != 0) \ throw InotifyException(IN_EXC_MSG("unlocking failed"), res, (void*) this); \ } #define IN_READ_END_NOTHROW pthread_rwlock_unlock(&__m_lock); #define IN_WRITE_BEGIN \ { \ int res = pthread_rwlock_wrlock(&__m_lock); \ if (res != 0) \ throw InotifyException(IN_EXC_MSG("locking for writing failed"), res, (void*) this); \ } #define IN_WRITE_END IN_READ_END #define IN_WRITE_END_NOTHROW IN_READ_END_NOTHROW #else // INOTIFY_THREAD_SAFE #define IN_LOCK_DECL #define IN_LOCK_INIT #define IN_LOCK_DONE #define IN_READ_BEGIN #define IN_READ_END #define IN_READ_END_NOTHROW #define IN_WRITE_BEGIN #define IN_WRITE_END #define IN_WRITE_END_NOTHROW #endif // INOTIFY_THREAD_SAFE // forward declaration class InotifyWatch; class Inotify; /// Class for inotify exceptions /** * This class allows to acquire information about exceptional * events. It makes easier to log or display error messages * and to identify problematic code locations. * * Although this class is basically thread-safe it is not intended * to be shared between threads. */ class InotifyException { public: /// Constructor /** * \param[in] rMsg message * \param[in] iErr error number (see errno.h) * \param[in] pSrc source */ InotifyException(const std::string& rMsg = "", int iErr = 0, void* pSrc = NULL) : m_msg(rMsg), m_err(iErr) { m_pSrc = pSrc; } /// Returns the exception message. /** * \return message */ inline const std::string& GetMessage() const { return m_msg; } /// Returns the exception error number. /** * If not applicable this value is 0 (zero). * * \return error number (standardized; see errno.h) */ inline int GetErrorNumber() const { return m_err; } /// Returns the exception source. /** * \return source */ inline void* GetSource() const { return m_pSrc; } protected: std::string m_msg; ///< message int m_err; ///< error number mutable void* m_pSrc; ///< source }; /// inotify event class /** * It holds all information about inotify event and provides * access to its particular values. * * This class is not (and is not intended to be) thread-safe * and therefore it must not be used concurrently in multiple * threads. */ class InotifyEvent { public: /// Constructor. /** * Creates a plain event. */ InotifyEvent() : m_uMask(0), m_uCookie(0) { m_pWatch = NULL; } /// Constructor. /** * Creates an event based on inotify event data. * For NULL pointers it works the same way as InotifyEvent(). * * \param[in] pEvt event data * \param[in] pWatch inotify watch */ InotifyEvent(const struct inotify_event* pEvt, InotifyWatch* pWatch) : m_uMask(0), m_uCookie(0) { if (pEvt != NULL) { m_uMask = (uint32_t) pEvt->mask; m_uCookie = (uint32_t) pEvt->cookie; if (pEvt->name != NULL) { m_name = pEvt->len > 0 ? pEvt->name : ""; } m_pWatch = pWatch; } else { m_pWatch = NULL; } } /// Destructor. ~InotifyEvent() {} /// Returns the event watch descriptor. /** * \return watch descriptor * * \sa InotifyWatch::GetDescriptor() */ int32_t GetDescriptor() const; /// Returns the event mask. /** * \return event mask * * \sa InotifyWatch::GetMask() */ inline uint32_t GetMask() const { return m_uMask; } /// Checks a value for the event type. /** * \param[in] uValue checked value * \param[in] uType type which is checked for * \return true = the value contains the given type, false = otherwise */ inline static bool IsType(uint32_t uValue, uint32_t uType) { return ((uValue & uType) != 0) && ((~uValue & uType) == 0); } /// Checks for the event type. /** * \param[in] uType type which is checked for * \return true = event mask contains the given type, false = otherwise */ inline bool IsType(uint32_t uType) const { return IsType(m_uMask, uType); } /// Returns the event cookie. /** * \return event cookie */ inline uint32_t GetCookie() const { return m_uCookie; } /// Returns the event name length. /** * \return event name length */ inline uint32_t GetLength() const { return (uint32_t) m_name.length(); } /// Returns the event name. /** * \return event name */ inline const std::string& GetName() const { return m_name; } /// Extracts the event name. /** * \param[out] rName event name */ inline void GetName(std::string& rName) const { rName = GetName(); } /// Returns the source watch. /** * \return source watch */ inline InotifyWatch* GetWatch() { return m_pWatch; } /// Finds the appropriate mask for a name. /** * \param[in] rName mask name * \return mask for name; 0 on failure */ static uint32_t GetMaskByName(const std::string& rName); /// Fills the string with all types contained in an event mask value. /** * \param[in] uValue event mask value * \param[out] rStr dumped event types */ static void DumpTypes(uint32_t uValue, std::string& rStr); /// Fills the string with all types contained in the event mask. /** * \param[out] rStr dumped event types */ void DumpTypes(std::string& rStr) const; private: uint32_t m_uMask; ///< mask uint32_t m_uCookie; ///< cookie std::string m_name; ///< name InotifyWatch* m_pWatch; ///< source watch }; /// inotify watch class /** * It holds information about the inotify watch on a particular * inode. * * If the INOTIFY_THREAD_SAFE is defined this class is thread-safe. */ class InotifyWatch { public: /// Constructor. /** * Creates an inotify watch. Because this watch is * inactive it has an invalid descriptor (-1). * * \param[in] rPath watched file path * \param[in] uMask mask for events * \param[in] fEnabled events enabled yes/no */ InotifyWatch(const std::string& rPath, int32_t uMask, bool fEnabled = true) : m_path(rPath), m_uMask(uMask), m_wd((int32_t) -1), m_fEnabled(fEnabled) { IN_LOCK_INIT } /// Destructor. ~InotifyWatch() { IN_LOCK_DONE } /// Returns the watch descriptor. /** * \return watch descriptor; -1 for inactive watch */ inline int32_t GetDescriptor() const { return m_wd; } /// Returns the watched file path. /** * \return file path */ inline const std::string& GetPath() const { return m_path; } /// Returns the watch event mask. /** * \return event mask */ inline uint32_t GetMask() const { return (uint32_t) m_uMask; } /// Sets the watch event mask. /** * If the watch is active (added to an instance of Inotify) * this method may fail due to unsuccessful re-setting * the watch in the kernel. * * \param[in] uMask event mask * * \throw InotifyException thrown if changing fails */ void SetMask(uint32_t uMask) throw (InotifyException); /// Returns the appropriate inotify class instance. /** * \return inotify instance */ inline Inotify* GetInotify() { return m_pInotify; } /// Enables/disables the watch. /** * If the watch is active (added to an instance of Inotify) * this method may fail due to unsuccessful re-setting * the watch in the kernel. * * Re-setting the current state has no effect. * * \param[in] fEnabled set enabled yes/no * * \throw InotifyException thrown if enabling/disabling fails */ void SetEnabled(bool fEnabled) throw (InotifyException); /// Checks whether the watch is enabled. /** * \return true = enables, false = disabled */ inline bool IsEnabled() const { return m_fEnabled; } /// Checks whether the watch is recursive. /** * A recursive watch monitors a directory itself and all * its subdirectories. This watch is a logical object * which may have many underlying kernel watches. * * \return currently always false (recursive watches not yet supported) * \attention Recursive watches are currently NOT supported. * They are planned for future versions. */ inline bool IsRecursive() const { return false; } private: friend class Inotify; std::string m_path; ///< watched file path uint32_t m_uMask; ///< event mask int32_t m_wd; ///< watch descriptor Inotify* m_pInotify; ///< inotify object bool m_fEnabled; ///< events enabled yes/no IN_LOCK_DECL /// Disables the watch (due to removing by the kernel). /** * This method must be called after receiving an event. * It ensures the watch object is consistent with the kernel * data. */ void __Disable(); }; /// Mapping from watch descriptors to watch objects. typedef std::map IN_WATCH_MAP; /// Mapping from paths to watch objects. typedef std::map IN_WP_MAP; /// inotify class /** * It holds information about the inotify device descriptor * and manages the event queue. * * If the INOTIFY_THREAD_SAFE is defined this class is thread-safe. */ class Inotify { public: /// Constructor. /** * Creates and initializes an instance of inotify communication * object (opens the inotify device). * * \throw InotifyException thrown if inotify isn't available */ Inotify() throw (InotifyException); /// Destructor. /** * Calls Close() due to clean-up. */ ~Inotify(); /// Removes all watches and closes the inotify device. void Close(); /// Adds a new watch. /** * \param[in] pWatch inotify watch * * \throw InotifyException thrown if adding failed */ void Add(InotifyWatch* pWatch) throw (InotifyException); /// Adds a new watch. /** * \param[in] rWatch inotify watch * * \throw InotifyException thrown if adding failed */ inline void Add(InotifyWatch& rWatch) throw (InotifyException) { Add(&rWatch); } /// Removes a watch. /** * If the given watch is not present it does nothing. * * \param[in] pWatch inotify watch * * \throw InotifyException thrown if removing failed */ void Remove(InotifyWatch* pWatch) throw (InotifyException); /// Removes a watch. /** * If the given watch is not present it does nothing. * * \param[in] rWatch inotify watch * * \throw InotifyException thrown if removing failed */ inline void Remove(InotifyWatch& rWatch) throw (InotifyException) { Remove(&rWatch); } /// Removes all watches. void RemoveAll(); /// Returns the count of watches. /** * This is the total count of all watches (regardless whether * enabled or not). * * \return count of watches * * \sa GetEnabledCount() */ inline size_t GetWatchCount() const { IN_READ_BEGIN size_t n = (size_t) m_paths.size(); IN_READ_END return n; } /// Returns the count of enabled watches. /** * \return count of enabled watches * * \sa GetWatchCount() */ inline size_t GetEnabledCount() const { IN_READ_BEGIN size_t n = (size_t) m_watches.size(); IN_READ_END return n; } /// Waits for inotify events. /** * It waits until one or more events occur. When called * in nonblocking mode it only retrieves occurred events * to the internal queue and exits. * * \param[in] fNoIntr if true it re-calls the system call after a handled signal * * \throw InotifyException thrown if reading events failed * * \sa SetNonBlock() */ void WaitForEvents(bool fNoIntr = false) throw (InotifyException); /// Returns the count of received and queued events. /** * This number is related to the events in the queue inside * this object, not to the events pending in the kernel. * * \return count of events */ inline size_t GetEventCount() { IN_READ_BEGIN size_t n = (size_t) m_events.size(); IN_READ_END return n; } /// Extracts a queued inotify event. /** * The extracted event is removed from the queue. * If the pointer is NULL it does nothing. * * \param[in,out] pEvt event object * * \throw InotifyException thrown if the provided pointer is NULL */ bool GetEvent(InotifyEvent* pEvt) throw (InotifyException); /// Extracts a queued inotify event. /** * The extracted event is removed from the queue. * * \param[in,out] rEvt event object * * \throw InotifyException thrown only in very anomalous cases */ bool GetEvent(InotifyEvent& rEvt) throw (InotifyException) { return GetEvent(&rEvt); } /// Extracts a queued inotify event (without removing). /** * The extracted event stays in the queue. * If the pointer is NULL it does nothing. * * \param[in,out] pEvt event object * * \throw InotifyException thrown if the provided pointer is NULL */ bool PeekEvent(InotifyEvent* pEvt) throw (InotifyException); /// Extracts a queued inotify event (without removing). /** * The extracted event stays in the queue. * * \param[in,out] rEvt event object * * \throw InotifyException thrown only in very anomalous cases */ bool PeekEvent(InotifyEvent& rEvt) throw (InotifyException) { return PeekEvent(&rEvt); } /// Searches for a watch by a watch descriptor. /** * It tries to find a watch by the given descriptor. * * \param[in] iDescriptor watch descriptor * \return pointer to a watch; NULL if no such watch exists */ InotifyWatch* FindWatch(int iDescriptor); /// Searches for a watch by a filesystem path. /** * It tries to find a watch by the given filesystem path. * * \param[in] rPath filesystem path * \return pointer to a watch; NULL if no such watch exists * * \attention The path must be exactly identical to the one * used for the searched watch. Be careful about * absolute/relative and case-insensitive paths. */ InotifyWatch* FindWatch(const std::string& rPath); /// Returns the file descriptor. /** * The descriptor can be used in standard low-level file * functions (poll(), select(), fcntl() etc.). * * \return valid file descriptor or -1 for inactive object * * \sa SetNonBlock() */ inline int GetDescriptor() const { return m_fd; } /// Enables/disables non-blocking mode. /** * Use this mode if you want to monitor the descriptor * (acquired thru GetDescriptor()) in functions such as * poll(), select() etc. * * Non-blocking mode is disabled by default. * * \param[in] fNonBlock enable/disable non-blocking mode * * \throw InotifyException thrown if setting mode failed * * \sa GetDescriptor(), SetCloseOnExec() */ void SetNonBlock(bool fNonBlock) throw (InotifyException); /// Enables/disables closing on exec. /** * Enable this if you want to close the descriptor when * executing another program. Otherwise, the descriptor * will be inherited. * * Closing on exec is disabled by default. * * \param[in] fClOnEx enable/disable closing on exec * * \throw InotifyException thrown if setting failed * * \sa GetDescriptor(), SetNonBlock() */ void SetCloseOnExec(bool fClOnEx) throw (InotifyException); /// Acquires a particular inotify capability/limit. /** * \param[in] cap capability/limit identifier * \return capability/limit value * \throw InotifyException thrown if the given value cannot be acquired */ static uint32_t GetCapability(InotifyCapability_t cap) throw (InotifyException); /// Modifies a particular inotify capability/limit. /** * \param[in] cap capability/limit identifier * \param[in] val new capability/limit value * \throw InotifyException thrown if the given value cannot be set * \attention Using this function requires root privileges. * Beware of setting extensive values - it may seriously * affect system performance and/or stability. */ static void SetCapability(InotifyCapability_t cap, uint32_t val) throw (InotifyException); /// Returns the maximum number of events in the kernel queue. /** * \return maximum number of events in the kernel queue * \throw InotifyException thrown if the given value cannot be acquired */ inline static uint32_t GetMaxEvents() throw (InotifyException) { return GetCapability(IN_MAX_EVENTS); } /// Sets the maximum number of events in the kernel queue. /** * \param[in] val new value * \throw InotifyException thrown if the given value cannot be set * \attention Using this function requires root privileges. * Beware of setting extensive values - the greater value * is set here the more physical memory may be used for the inotify * infrastructure. */ inline static void SetMaxEvents(uint32_t val) throw (InotifyException) { SetCapability(IN_MAX_EVENTS, val); } /// Returns the maximum number of inotify instances per process. /** * It means the maximum number of open inotify file descriptors * per running process. * * \return maximum number of inotify instances * \throw InotifyException thrown if the given value cannot be acquired */ inline static uint32_t GetMaxInstances() throw (InotifyException) { return GetCapability(IN_MAX_INSTANCES); } /// Sets the maximum number of inotify instances per process. /** * \param[in] val new value * \throw InotifyException thrown if the given value cannot be set * \attention Using this function requires root privileges. * Beware of setting extensive values - the greater value * is set here the more physical memory may be used for the inotify * infrastructure. */ inline static void SetMaxInstances(uint32_t val) throw (InotifyException) { SetCapability(IN_MAX_INSTANCES, val); } /// Returns the maximum number of inotify watches per instance. /** * It means the maximum number of inotify watches per inotify * file descriptor. * * \return maximum number of inotify watches * \throw InotifyException thrown if the given value cannot be acquired */ inline static uint32_t GetMaxWatches() throw (InotifyException) { return GetCapability(IN_MAX_WATCHES); } /// Sets the maximum number of inotify watches per instance. /** * \param[in] val new value * \throw InotifyException thrown if the given value cannot be set * \attention Using this function requires root privileges. * Beware of setting extensive values - the greater value * is set here the more physical memory may be used for the inotify * infrastructure. */ inline static void SetMaxWatches(uint32_t val) throw (InotifyException) { SetCapability(IN_MAX_WATCHES, val); } private: int m_fd; ///< file descriptor IN_WATCH_MAP m_watches; ///< watches (by descriptors) IN_WP_MAP m_paths; ///< watches (by paths) unsigned char m_buf[INOTIFY_BUFLEN]; ///< buffer for events std::deque m_events; ///< event queue IN_LOCK_DECL friend class InotifyWatch; static std::string GetCapabilityPath(InotifyCapability_t cap) throw (InotifyException); }; #endif //_INOTIFYCXX_H_