linux-inotify (empty) → 0.1.0.0
raw patch · 4 files changed
+678/−0 lines, 4 filesdep +basedep +bytestringdep +unixsetup-changed
Dependencies added: base, bytestring, unix
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- linux-inotify.cabal +59/−0
- src/System/Linux/Inotify.hsc +587/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2013, Leon P Smith++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Leon P Smith nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ linux-inotify.cabal view
@@ -0,0 +1,59 @@+name: linux-inotify+version: 0.1.0.0+synopsis: Thinner binding to the Linux Kernel's inotify interface+description:+ This is a binding for GHC 7 to the Linux Kernel's inotify interface,+ which provides notifications to applications regarding file system+ events, such as file creation, modification, deletion, etc.+ .+ Some of the advantages over hinotify are:+ .+ 1. linux-inotify provides a plain getEvent operator that blocks,+ instead of implementing a callback API.+ .+ 2. linux-inotify avoids most of GHC's standard IO handling code,+ relying on plain system calls with minimal overhead in Haskell-land.+ (However, it still does make good use of GHC's IO manager via+ nonblocking inotify sockets and threadWaitRead, so getEvent is+ still efficient.)+ .+ 3. linux-inotify does not call forkIO, which means less context+ switching and scheduling overhead, especially in contexts where+ hinotify's particular event router isn't a very good fit for+ your application; e.g. you are implementing a following log file+ processor.+ .+ Some of the disadvantages compared to hinotify are:+ .+ 1. Due to the use of `inotify_init1`, `linux-inotify` currently+ requires linux 2.6.27 or later, even though `inotify` support+ debuted in linux 2.6.13. You can check which version of linux is+ on a machine via `uname -a`. I would like to fix this at some point,+ but it isn't a personal priority.+ .+ 2. `linux-inotify` requires GHC 7.0.2 or later, whereas `hinotify`+ works with many versions of GHC 6. I have no plans to fix this.+license: BSD3+license-file: LICENSE+author: Leon P Smith+maintainer: leon@melding-monads.com+copyright: (c) 2013-2014 Leon P Smith+category: System+build-type: Simple+cabal-version: >=1.8++library+ hs-source-dirs: src+ exposed-modules: System.Linux.Inotify+ build-depends: base >= 4 && < 5, unix, bytestring++ ghc-options: -Wall++source-repository head+ type: git+ location: http://github.com/lpsmith/linux-inotify++source-repository this+ type: git+ location: http://github.com/lpsmith/linux-inotify+ tag: v0.1.0.0
+ src/System/Linux/Inotify.hsc view
@@ -0,0 +1,587 @@+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE RecordWildCards, NamedFieldPuns #-}+{-# LANGUAGE BangPatterns, DoAndIfThenElse #-}+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE DeriveDataTypeable #-}++module System.Linux.Inotify+ ( Inotify+ , Event(..)+ , Watch(..)+ , Mask(..)+ , isect+ , isSubset+ , hasOverlap+ , WatchFlag+ , EventFlag+ , Cookie(..)+ , init+ , initWith+ , InotifyOptions(..)+ , defaultInotifyOptions+ , addWatch+ , addWatch_+ , rmWatch+ , getEvent+ , getEventNonBlocking+ , getEventFromBuffer+ , peekEvent+ , peekEventNonBlocking+ , peekEventFromBuffer+ , close+ , in_ACCESS+ , in_ATTRIB+ , in_CLOSE+ , in_CLOSE_WRITE+ , in_CLOSE_NOWRITE+ , in_CREATE+ , in_DELETE+ , in_DELETE_SELF+ , in_MODIFY+ , in_MOVE_SELF+ , in_MOVE+ , in_MOVED_FROM+ , in_MOVED_TO+ , in_OPEN+ , in_ALL_EVENTS+ , in_DONT_FOLLOW+ , in_EXCL_UNLINK+ , in_MASK_ADD+ , in_ONESHOT+ , in_ONLYDIR+ , in_IGNORED+ , in_ISDIR+ , in_Q_OVERFLOW+ , in_UNMOUNT+ ) where++#include "unistd.h"+#include "sys/inotify.h"++import Prelude hiding (init)++import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as B8+import Control.Applicative+import Data.Monoid+import Data.Typeable+import Data.Function ( on )+import Control.Concurrent ( threadWaitRead )+import GHC.Conc ( closeFdWith )+#if __GLASGOW_HASKELL__ < 706+import Control.Concurrent.MVar+#endif+import Control.Monad+import System.Posix+import Data.IORef+import Foreign+import Foreign.C+import qualified Foreign.Concurrent as FC+import System.Posix.ByteString.FilePath (RawFilePath)++data Inotify = Inotify+ { fd :: {-# UNPACK #-} !Fd+ , buffer :: {-# UNPACK #-} !(ForeignPtr CChar)+ , bufSize :: {-# UNPACK #-} !Int+ , startRef :: {-# UNPACK #-} !(IORef Int)+ , endRef :: {-# UNPACK #-} !(IORef Int)+ } deriving (Eq, Typeable)++instance Show Inotify where+ show Inotify{fd} = "Inotify { fd = " ++ show fd ++ " }"++instance Ord Inotify where+ compare = compare `on` fd++{-+-- I'm tempted to define 'Watch' as++data Watch = Watch+ { fd :: {-# UNPACK #-} !Fd+ , wd :: {-# UNPACK #-} !CInt+ }++-- and then give rmWatch the type++rmWatch :: Watch -> IO ()++-- An advantage would be that it would make the API possibly+-- easier to use, and harder to misuse. A disadvantage is+-- that this is a slightly thicker wrapper around the system calls,+-- and that storing Watches in Maps would be less efficient, at least+-- somewhat.+-}++newtype Watch = Watch CInt deriving (Eq, Ord, Show, Typeable)++-- | Represents the mask, which in inotify terminology is a union+-- of flags that are used when setting up watches and receiving+-- event notifications.+--+-- The type parameter is a phantom type that tracks whether+-- a particular flag is used to set up a watch ('WatchFlag') or+-- when receiving an event. ('EventFlag') Polymorphic+-- parameters mean that the flag may appear in either context.++newtype Mask a = Mask CUInt deriving (Eq, Show, Typeable)++-- | Computes the union of two 'Mask's.+instance Monoid (Mask a) where+ mempty = Mask 0+ mappend (Mask a) (Mask b) = Mask (a .|. b)++-- | An empty type used to denote 'Mask' values that can be received+-- from the kernel in an inotify event message.+data EventFlag++-- | An empty type used to denote 'Mask' values that can be sent to+-- the kernel when setting up an inotify watch.+data WatchFlag++isect :: Mask a -> Mask a -> Mask a+isect (Mask a) (Mask b) = Mask (a .&. b)++hasOverlap :: Mask a -> Mask a -> Bool+hasOverlap a b = isect a b /= Mask 0++isSubset :: Mask a -> Mask a -> Bool+isSubset a b = isect a b == a+++-- | File was accessed. Includes the files of a watched directory.+in_ACCESS :: Mask a+in_ACCESS = Mask (#const IN_ACCESS)++-- | Metadata changed, e.g., permissions, timestamps, extended attributes,+-- link count (since Linux 2.6.25), UID, GID, etc. Includes the files of+-- a watched directory.+in_ATTRIB :: Mask a+in_ATTRIB = Mask (#const IN_ATTRIB)++-- | File was closed. This is not a separate flag, but a convenience definition+-- such that 'in_CLOSE' '==' 'in_CLOSE_WRITE' '<>' 'in_CLOSE_NOWRITE'+in_CLOSE :: Mask a+in_CLOSE = Mask (#const IN_CLOSE)++-- | File opened for writing was closed. Includes the files of a watched+-- directory.+in_CLOSE_WRITE :: Mask a+in_CLOSE_WRITE = Mask (#const IN_CLOSE_WRITE)++-- | File not opened for writing was closed. Includes the files of a watched+-- directory.+in_CLOSE_NOWRITE :: Mask a+in_CLOSE_NOWRITE = Mask (#const IN_CLOSE_NOWRITE)++-- | File/directory created in watched directory.+in_CREATE :: Mask a+in_CREATE = Mask (#const IN_CREATE)++-- | File/directory deleted from watched directory.+in_DELETE :: Mask a+in_DELETE = Mask (#const IN_DELETE)++-- | Watched file/directory was itself deleted.+in_DELETE_SELF :: Mask a+in_DELETE_SELF = Mask (#const IN_DELETE_SELF)++-- | File was modified. Includes the files of a watched+-- directory.+in_MODIFY :: Mask a+in_MODIFY = Mask (#const IN_MODIFY)++-- | Watched file/directory was itself moved.+in_MOVE_SELF :: Mask a+in_MOVE_SELF = Mask (#const IN_MOVE_SELF)++-- | File was moved. This is not a separate flag, but a convenience definition+-- such that 'in_MOVE' '==' 'in_MOVED_FROM' '<>' 'in_MOVED_TO'.+in_MOVE :: Mask a+in_MOVE = Mask (#const IN_MOVE)++-- | File moved out of watched directory. Includes the files of a watched+-- directory.+in_MOVED_FROM :: Mask a+in_MOVED_FROM = Mask (#const IN_MOVED_FROM)++-- | File moved into watched directory. Includes the files of a watched+-- directory.+in_MOVED_TO :: Mask a+in_MOVED_TO = Mask (#const IN_MOVED_TO)++-- | File was opened. Includes the files of a watched+-- directory.+in_OPEN :: Mask a+in_OPEN = Mask (#const IN_OPEN)++-- | A union of all flags above; this is not a separate flag but a convenience+-- definition.+in_ALL_EVENTS :: Mask a+in_ALL_EVENTS = Mask (#const IN_OPEN)++-- | (since Linux 2.6.15) Don't dereference pathname if it is a symbolic link.+in_DONT_FOLLOW :: Mask WatchFlag+in_DONT_FOLLOW = Mask (#const IN_DONT_FOLLOW)++-- | (since Linux 2.6.36)+-- By default, when watching events on the children+-- of a directory, events are generated for children+-- even after they have been unlinked from the+-- directory. This can result in large numbers of+-- uninteresting events for some applications (e.g.,+-- if watching /tmp, in which many applications create+-- temporary files whose names are immediately+-- unlinked). Specifying IN_EXCL_UNLINK changes the+-- default behavior, so that events are not generated+-- for children after they have been unlinked+-- from the watched directory.+in_EXCL_UNLINK :: Mask WatchFlag+in_EXCL_UNLINK = Mask (#const IN_EXCL_UNLINK)++-- | Add (OR) events to watch mask for this pathname+-- if it already exists (instead of replacing mask).+in_MASK_ADD :: Mask WatchFlag+in_MASK_ADD = Mask (#const IN_MASK_ADD)++-- | Monitor pathname for one event, then remove from watch list.+in_ONESHOT :: Mask WatchFlag+in_ONESHOT = Mask (#const IN_ONESHOT)++-- | (since Linux 2.6.15) Only watch pathname if it is a directory.+in_ONLYDIR :: Mask WatchFlag+in_ONLYDIR = Mask (#const IN_ONLYDIR)++-- | Watch was removed explicitly ('rmWatch') or automatically+-- (file was deleted, or file system was unmounted).+in_IGNORED :: Mask EventFlag+in_IGNORED = Mask (#const IN_IGNORED)++-- | Subject of this event is a directory.+in_ISDIR :: Mask EventFlag+in_ISDIR = Mask (#const IN_ISDIR)++-- | Event queue overflowed (wd is -1 for this event).+in_Q_OVERFLOW :: Mask EventFlag+in_Q_OVERFLOW = Mask (#const IN_Q_OVERFLOW)++-- | File system containing watched object was unmounted.+in_UNMOUNT :: Mask EventFlag+in_UNMOUNT = Mask (#const IN_UNMOUNT)+++newtype Cookie = Cookie CUInt deriving (Eq, Ord, Show, Typeable)++data Event = Event+ { wd :: {-# UNPACK #-} !Watch+ , mask :: {-# UNPACK #-} !(Mask EventFlag)+ , cookie :: {-# UNPACK #-} !Cookie+ , name :: {-# UNPACK #-} !B.ByteString+ -- ^ The proper interpretation of this seems to be to use+ -- 'GHC.IO.getForeignEncoding' and then unpack it to a String+ -- or decode it using the text package.+ } deriving (Eq, Show, Typeable)++#if __GLASGOW_HASKELL__ < 706+-- | Workaround for bug in 'FC.newForeignPtr' before base 4.6. Ensure the+-- finalizer is only run once. See GHC ticket #7170+addFinalizerOnce :: ForeignPtr a -> IO () -> IO ()+addFinalizerOnce ptr fin = do+ mv <- newMVar fin+ FC.addForeignPtrFinalizer ptr $ tryTakeMVar mv >>= maybe (return ()) id+#else+addFinalizerOnce :: ForeignPtr a -> IO () -> IO ()+addFinalizerOnce = FC.addForeignPtrFinalizer+#endif++-- | Creates an inotify socket descriptor that watches can be+-- added to and events can be read from.++init :: IO Inotify+init = initWith defaultInotifyOptions++newtype InotifyOptions = InotifyOptions { bufferSize :: Int }++defaultInotifyOptions :: InotifyOptions+defaultInotifyOptions = InotifyOptions { bufferSize = 2048 }++initWith :: InotifyOptions -> IO Inotify+initWith InotifyOptions{..} = do+ fd <- Fd <$> throwErrnoIfMinus1 "System.Linux.Inotify.initWith"+ (c_inotify_init1 flags)+ let bufSize = bufferSize+ buffer <- mallocForeignPtrBytes bufSize+ addFinalizerOnce buffer (closeFdWith closeFd fd)+ startRef <- newIORef 0+ endRef <- newIORef 0+ return $! Inotify{..}+ where flags = (#const IN_NONBLOCK) .|. (#const IN_CLOEXEC)++-- | Adds a watch on the inotify descriptor, returns a watch descriptor.+-- This function is thread safe.++addWatch :: Inotify -> FilePath -> Mask WatchFlag -> IO Watch+addWatch Inotify{fd} path !mask =+ withCString path $ \cpath -> do+ Watch <$> throwErrnoPathIfMinus1 "System.Linux.Inotify.addWatch" path+ (c_inotify_add_watch fd cpath mask)++-- | A variant of 'addWatch' that operates on a 'RawFilePath', which is+-- a file path represented as strict 'ByteString'. One weakness of the+-- current implementation is that if 'addWatch_' throws an 'IOException',+-- then any unicode paths will be mangled in the error message.++addWatch_ :: Inotify -> RawFilePath -> Mask WatchFlag -> IO Watch+addWatch_ Inotify{fd} path !mask =+ B.useAsCString path $ \cpath -> do+ Watch <$> throwErrnoPathIfMinus1 "System.Linux.Inotify.addWatch_"+ (B8.unpack path)+ (c_inotify_add_watch fd cpath mask)+++-- | Stops watching a path for changes. This watch descriptor must be+-- associated with the particular inotify port, otherwise undefined+-- behavior can happen.+--+-- This function is thread safe. This binding ignores @inotify_rm_watch@'s+-- errno when it is @EINVAL@, so it is ok to delete a previously+-- removed or non-existent watch descriptor.+--+-- However long lived applications that set and remove many watches+-- should still endeavor to avoid calling `rmWatch` on removed+-- watch descriptors, due to possible wrap-around bugs.++-- The (small) downside to this behavior is that it also ignores the+-- case when, for some slightly strange reason, 'rmWatch' is called+-- on a file descriptor that is not an inotify descriptor.+-- Unfortunately @inotify_rm_watch@ does not provide any way to+-- distinguish these cases.+--+-- Haskell's type system should prevent this from happening in almost+-- all cases, but it could be possible in wrap-around situations+-- when you use an inotify descriptor after you have closed it. But+-- then you deserve (at least some of) what you get anyway.++rmWatch :: Inotify -> Watch -> IO ()+rmWatch Inotify{fd} !wd = do+ res <- c_inotify_rm_watch fd wd+ when (res == -1) $ do+ err <- getErrno+ if err == eINVAL+ then return ()+ else throwErrno "System.Linux.Inotify.rmWatch"++{--+-- | Stops watching a path for changes. This version throws an exception+-- on @EINVAL@, so it is not ok to delete a non-existant watch+-- descriptor. Therefore this function is not thread safe, even+-- if it's only ever called from a single thread.+--+-- The problem is that in some cases the kernel will automatically+-- delete a watch descriptor. Although the kernel generates an+-- @IN_IGNORED@ event whenever a descriptor is deleted, it's+-- possible that using @rmWatch'@ and @getEvent@ in different threads+-- would delete the descriptor after the kernel has delivered the+-- @IN_IGNORED@ event but before your application has acted on the+-- message.+--+-- It may not even be safe to call this function from the thread+-- that is calling @getEvent@. I need to investigate whether or+-- not the kernel would return @EINVAL@ on a descriptor before+-- the @IN_IGNORED@ message has been delivered to your application.+-- This would make rmWatch' somewhat safer in the presence of threads,+-- but the application would still have to ensure that all delivered+-- events are processed before rmWatch' is called.++rmWatch' :: Inotify -> Watch -> IO ()+rmWatch' (Inotify (Fd !fd)) (Watch !wd) = do+ throwErrnoIfMinus1_ "System.Linux.Inotify.rmWatch'" $+ c_inotify_rm_watch fd wd+--}++-- | Returns an inotify event, blocking until one is available.+--+-- It is not safe to call this function from multiple threads at the same+-- time. Though this could be fixed, I do not see why it would be useful.++getEvent :: Inotify -> IO Event+getEvent inotify@Inotify{..} = do+ fillBufferBlocking inotify "System.Linux.Inotify.getEvent"+ evt <- peekMessage inotify+ consumeMessage inotify+ return evt++-- | Returns an inotify event, blocking until one is available.+--+-- If this returns an event, then the next read from the inotify+-- descriptor will return the same event and the second read will not+-- result in a system call.+--+-- It is not safe to call this function from multiple threads at the same+-- time. Though this could be fixed, I do not see why it would be useful.++peekEvent :: Inotify -> IO Event+peekEvent inotify@Inotify{..} = do+ fillBufferBlocking inotify "System.Linux.Inotify.peekEvent"+ peekMessage inotify++hasEmptyBuffer :: Inotify -> IO Bool+hasEmptyBuffer Inotify{..} = do+ start <- readIORef startRef+ end <- readIORef endRef+ return $! (start >= end)+{-# INLINE hasEmptyBuffer #-}++fillBuffer :: Inotify -> a -> (Errno -> IO a) -> IO a+fillBuffer Inotify{..} val errorHandler = do+ numBytes <- withForeignPtr buffer $ \ptr -> do+ c_unsafe_read fd ptr (fromIntegral bufSize)+ if numBytes == -1+ then getErrno >>= errorHandler+ else do+ writeIORef startRef 0+ writeIORef endRef (fromIntegral numBytes)+ return val+{-# INLINE fillBuffer #-}++fillBufferBlocking :: Inotify -> String -> IO ()+fillBufferBlocking inotify@Inotify{..} funcName = do+ isEmpty <- hasEmptyBuffer inotify+ when isEmpty loop+ where+ loop = do+ threadWaitRead fd+ fillBuffer inotify () $ \err -> do+ if err == eINTR || err == eAGAIN || err == eWOULDBLOCK+ then loop+ else throwErrno funcName++fillBufferNonBlocking :: Inotify -> String -> IO Bool+fillBufferNonBlocking inotify@Inotify{..} funcName = do+ isEmpty <- hasEmptyBuffer inotify+ if isEmpty+ then loop+ else return False+ where+ loop = do+ fillBuffer inotify False $ \err -> do+ if err == eAGAIN || err == eWOULDBLOCK+ then return True+ else if err == eINTR+ then loop+ else throwErrno funcName++peekMessage :: Inotify -> IO Event+peekMessage Inotify{..} = withForeignPtr buffer $ \ptr0 -> do+ start <- readIORef startRef+ let ptr = ptr0 `plusPtr` start+ wd <- Watch <$> ((#peek struct inotify_event, wd ) ptr :: IO CInt)+ mask <- Mask <$> ((#peek struct inotify_event, mask ) ptr :: IO CUInt)+ cookie <- Cookie <$> ((#peek struct inotify_event, cookie) ptr :: IO CUInt)+ len <- ((#peek struct inotify_event, len ) ptr :: IO CUInt)+ name <- if len == 0+ then return B.empty+ else B.packCString ((#ptr struct inotify_event, name) ptr)+ return $! Event{..}+{-# INLINE peekMessage #-}++consumeMessage :: Inotify -> IO ()+consumeMessage Inotify{..} = do+ start <- readIORef startRef+ len <- withForeignPtr buffer $ \ptr0 -> do+ let ptr = ptr0 `plusPtr` start+ (#peek struct inotify_event, len ) ptr :: IO CUInt+ writeIORef endRef $! start + (#size struct inotify_event) + fromIntegral len+{-# INLINE consumeMessage #-}++-- | Returns an inotify event only if one is immediately available.+--+-- One possible downside of the current implementation is that+-- returning 'Nothing' necessarily results in a system call.++getEventNonBlocking :: Inotify -> IO (Maybe Event)+getEventNonBlocking inotify@Inotify{..} = do+ isEmpty <- fillBufferNonBlocking inotify funcName+ if isEmpty+ then return Nothing+ else do+ evt <- peekMessage inotify+ consumeMessage inotify+ return $! Just evt+ where+ funcName = "System.Linux.Inotify.getEventNonBlocking"++-- | Returns an inotify event only if one is immediately available.+--+-- If this returns an event, then the next read from the inotify+-- descriptor will return the same event and the second read will+-- not result in a system call.+--+-- One possible downside of the current implementation is that+-- returning 'Nothing' necessarily results in a system call.++peekEventNonBlocking :: Inotify -> IO (Maybe Event)+peekEventNonBlocking inotify@Inotify{..} = do+ isEmpty <- fillBufferNonBlocking inotify funcName+ if isEmpty+ then return Nothing+ else do+ evt <- peekMessage inotify+ return $! Just evt+ where+ funcName = "System.Linux.Inotify.peekEventNonBlocking"++-- | Returns an inotify event only if one is available in 'Inotify's+-- buffer. This won't ever make a system call.++getEventFromBuffer :: Inotify -> IO (Maybe Event)+getEventFromBuffer inotify = do+ isEmpty <- hasEmptyBuffer inotify+ if isEmpty+ then return Nothing+ else do+ evt <- peekMessage inotify+ consumeMessage inotify+ return $! Just evt++-- | Returns an inotify event only if one is available in 'Inotify's+-- buffer. This won't ever make a system call.+--+-- If this returns an event, then the next read from the inotify+-- descriptor will return the same event and the second read will not+-- result in a system call.++peekEventFromBuffer :: Inotify -> IO (Maybe Event)+peekEventFromBuffer inotify@Inotify{..} = do+ isEmpty <- hasEmptyBuffer inotify+ if isEmpty+ then return Nothing+ else do+ evt <- peekMessage inotify+ return $! Just evt++-- | Closes an inotify descriptor, freeing the resources associated+-- with it. This will also raise an 'IOException' in any threads that+-- are blocked on 'getEvent'.+--+-- Although using a descriptor after it is closed is likely to raise+-- an exception, it is not safe to use the descriptor after it is closed.+-- However, it is safe to call 'close' multiple times; this binding+-- ensures that only one system call will be made.+--+-- Descriptors will be closed after they are garbage collected, via+-- a finalizer, although it is often preferable to call 'close' yourself.++close :: Inotify -> IO ()+close Inotify{buffer} = finalizeForeignPtr buffer++foreign import ccall unsafe "sys/inotify.h inotify_init1"+ c_inotify_init1 :: CInt -> IO CInt++foreign import ccall unsafe "sys/inotify.h inotify_add_watch"+ c_inotify_add_watch :: Fd -> CString -> Mask WatchFlag -> IO CInt++foreign import ccall unsafe "sys/inotify.h inotify_rm_watch"+ c_inotify_rm_watch :: Fd -> Watch -> IO CInt++foreign import ccall unsafe "unistd.h read"+ c_unsafe_read :: Fd -> Ptr CChar -> CSize -> IO CSsize