2-- Module : Streamly.Internal.FileSystem.Event.Linux
3-- Copyright : (c) 2020 Composewell Technologies
4-- License : BSD-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
11-- Use 'watchPaths' with a list of file system paths you want to watch as
12-- argument. It returns a stream of 'Event' representing the file system events
13-- occurring under the watched paths.
16-- Stream.mapM_ (putStrLn . 'showEvent') $ 'watchPaths' [Array.fromCString\# "dir"#]
19-- 'Event' is an opaque type. Accessor functions (e.g. 'showEvent' above)
20-- provided in this module are used to determine the attributes of the event.
22-- Identical successive events may be coalesced into a single event.
26-- For reference documentation see:
28-- * <https://man7.org/linux/man-pages/man7/inotify.7.html inotify man page>
30-- We try to keep the macOS\/Linux/Windows event handling APIs and defaults
31-- semantically and syntactically as close as possible.
35-- When testing on Linux Kernel version @5.3.0-53-generic #47-Ubuntu@, the last
36-- event for the root path seems to be delayed until one more event occurs.
38-- = Differences between macOS and Linux APIs:
40-- 1. macOS watch is based on the path provided to it, if the path is
41-- deleted and recreated it will still be watched, if the path moves to another
42-- path it won't be watched anymore. Whereas Linux watch is based on a handle
43-- to the path, if the path is deleted and recreated it won't be watched, if
44-- the path moves to another it can still be watched (though this is
47-- 2. macOS watches the directory hierarchy recursively, Linux watches only one
48-- level of dir, recursive watch has to be built in user space by watching for
49-- create events and adding the new directories to the watch. Not sure how this
50-- will scale for too many paths.
52-- 3. In macOS the path of the subject of the event is absolute, in Linux the
53-- path is the name of the object inside the dir being watched.
55-- 4. On Linux 'watchPaths' fails if a path does not exist, on macOS it does
60#if HAVE_DECL_IN_EXCL_UNLINK
61module Streamly.Internal.FileSystem.Event.Linux
63 -- * Subscribing to events
65 -- ** Default configuration
78 -- ** Events of Interest
79 -- *** Root Level Events
83 -- *** Item Level Metadata change
86 -- *** Item Level Access
92 -- *** Item CRUD events
113 -- ** Exception Conditions
116 -- ** Root Level Events
122 -- ** Item Level Metadata change
125 -- ** Item Level Access
131 -- ** Item CRUD events
146import Control.Monad (void, when)
147import Control.Monad.IO.Class (liftIO)
148import Data.Bits ((.|.), (.&.), complement)
149import Data.Foldable (foldlM)
150import Data.Functor.Identity (runIdentity)
151import Data.IntMap.Lazy (IntMap)
152import Data.List.NonEmpty (NonEmpty)
153import Data.Word (Word8, Word32)
154import Foreign.C.Error (throwErrnoIfMinus1)
155import Foreign.C.String (CString)
156import Foreign.C.Types (CInt(..), CUInt(..))
157import Foreign.Ptr (Ptr)
158import Foreign.Storable (peek, peekByteOff, sizeOf)
159import GHC.IO.Device (IODeviceType(Stream))
160import GHC.IO.FD (fdFD, mkFD)
161import GHC.IO.Handle.FD (mkHandleFromFD)
162import Streamly.Prelude (SerialT)
163import Streamly.Internal.Data.Parser (Parser)
164import Streamly.Internal.Data.Array.Storable.Foreign.Types (Array(..))
165import System.IO (Handle, hClose, IOMode(ReadMode))
166#if !MIN_VERSION_base(4,10,0)
167import Control.Concurrent.MVar (readMVar)
168import Data.Typeable (cast)
169import GHC.IO.Exception (IOException(..), IOErrorType(..), ioException)
171import GHC.IO.Handle.Types (Handle__(..), Handle(FileHandle, DuplexHandle))
173import GHC.IO.Handle.FD (handleToFd)
176import qualified Data.IntMap.Lazy as Map
177import qualified Data.List.NonEmpty as NonEmpty
178import qualified Streamly.Internal.Data.Fold as FL
179import qualified Streamly.Internal.Data.Parser as PR
180import qualified Streamly.Internal.Data.Stream.IsStream as S
181import qualified Streamly.Internal.Unicode.Stream as U
182import qualified Streamly.Internal.FileSystem.Handle as FH
183import qualified Streamly.Internal.Data.Array.Storable.Foreign as A
185-------------------------------------------------------------------------------
186-- Subscription to events
187-------------------------------------------------------------------------------
189-- | Watch configuration, used to specify the events of interest and the
190-- behavior of the watch.
195 { createFlags :: Word32 }
197-------------------------------------------------------------------------------
199-------------------------------------------------------------------------------
201-- | Whether a setting is 'On' or 'Off'.
205data Toggle = On | Off
207toggle :: Toggle -> Toggle
211setFlag :: Word32 -> Toggle -> Config -> Config
212setFlag mask status cfg@Config{..} =
215 On -> createFlags .|. mask
216 Off -> createFlags .&. complement mask
217 in cfg {createFlags = flags}
219-------------------------------------------------------------------------------
221-------------------------------------------------------------------------------
225 "sys/inotify.h value IN_DONT_FOLLOW" iN_DONT_FOLLOW :: Word32
227-- | If the pathname to be watched is a symbolic link then watch the target of
228-- the symbolic link instead of the symbolic link itself.
234setFollowSymLinks :: Toggle -> Config -> Config
235setFollowSymLinks s = setFlag iN_DONT_FOLLOW (toggle s)
238 "sys/inotify.h value IN_EXCL_UNLINK" iN_EXCL_UNLINK :: Word32
240-- | If an object moves out of the directory being watched then stop watching
247setUnwatchMoved :: Toggle -> Config -> Config
248setUnwatchMoved = setFlag iN_EXCL_UNLINK
250#if HAVE_DECL_IN_MASK_CREATE
252 "sys/inotify.h value IN_MASK_CREATE" iN_MASK_CREATE :: Word32
256 "sys/inotify.h value IN_MASK_ADD" iN_MASK_ADD :: Word32
258-- | What to do if a watch already exists when 'openWatch' or 'addToWatch' is
264 AddIfExists -- ^ Do not set an existing setting to 'Off' only set to 'On'
265 | ReplaceIfExists -- ^ Replace the existing settings with new settings
266#if HAVE_DECL_IN_MASK_CREATE
267 | FailIfExists -- ^ Fail the API
270-- | When adding a new path to the watch, specify what to do if a watch already
271-- exists on that path.
273-- /default: FailIfExists/
277setWhenExists :: WhenExists -> Config -> Config
278setWhenExists val cfg =
280 AddIfExists -> setFlag iN_MASK_ADD On cfg
281 ReplaceIfExists -> setFlag iN_MASK_ADD Off cfg
282#if HAVE_DECL_IN_MASK_CREATE
283 FailIfExists -> setFlag iN_MASK_CREATE On cfg
287 "sys/inotify.h value IN_ONESHOT" iN_ONESHOT :: Word32
289-- | Watch the object only for one event and then remove it from the watch.
295setOneShot :: Toggle -> Config -> Config
296setOneShot = setFlag iN_ONESHOT
299 "sys/inotify.h value IN_ONLYDIR" iN_ONLYDIR :: Word32
301-- | Watch the object only if it is a directory. This provides a race-free way
302-- to ensure that the watched object is a directory.
308setOnlyDir :: Toggle -> Config -> Config
309setOnlyDir = setFlag iN_ONLYDIR
311-------------------------------------------------------------------------------
312-- Event types that can occur
313-------------------------------------------------------------------------------
316 "sys/inotify.h value IN_DELETE_SELF" iN_DELETE_SELF :: Word32
318-- | Report when the watched path itself gets deleted.
324setRootDeleted :: Toggle -> Config -> Config
325setRootDeleted = setFlag iN_DELETE_SELF
328 "sys/inotify.h value IN_MOVE_SELF" iN_MOVE_SELF :: Word32
330-- | Report when the watched root path itself gets renamed.
336setRootMoved :: Toggle -> Config -> Config
337setRootMoved = setFlag iN_MOVE_SELF
340 "sys/inotify.h value IN_ATTRIB" iN_ATTRIB :: Word32
342-- | Report when the metadata e.g. owner, permission modes, modifications times
343-- of an object changes.
349setMetadataChanged :: Toggle -> Config -> Config
350setMetadataChanged = setFlag iN_ATTRIB
353 "sys/inotify.h value IN_ACCESS" iN_ACCESS :: Word32
355-- | Report when a file is accessed.
361setAccessed :: Toggle -> Config -> Config
362setAccessed = setFlag iN_ACCESS
365 "sys/inotify.h value IN_OPEN" iN_OPEN :: Word32
367-- | Report when a file is opened.
373setOpened :: Toggle -> Config -> Config
374setOpened = setFlag iN_OPEN
377 "sys/inotify.h value IN_CLOSE_WRITE" iN_CLOSE_WRITE :: Word32
379-- | Report when a file that was opened for writes is closed.
385setWriteClosed :: Toggle -> Config -> Config
386setWriteClosed = setFlag iN_CLOSE_WRITE
389 "sys/inotify.h value IN_CLOSE_NOWRITE" iN_CLOSE_NOWRITE :: Word32
391-- | Report when a file that was opened for not writing is closed.
397setNonWriteClosed :: Toggle -> Config -> Config
398setNonWriteClosed = setFlag iN_CLOSE_NOWRITE
401 "sys/inotify.h value IN_CREATE" iN_CREATE :: Word32
403-- | Report when a file is created.
409setCreated :: Toggle -> Config -> Config
410setCreated = setFlag iN_CREATE
413 "sys/inotify.h value IN_DELETE" iN_DELETE :: Word32
415-- | Report when a file is deleted.
421setDeleted :: Toggle -> Config -> Config
422setDeleted = setFlag iN_DELETE
425 "sys/inotify.h value IN_MOVED_FROM" iN_MOVED_FROM :: Word32
427-- | Report the source of a move.
433setMovedFrom :: Toggle -> Config -> Config
434setMovedFrom = setFlag iN_MOVED_FROM
437 "sys/inotify.h value IN_MOVED_TO" iN_MOVED_TO :: Word32
439-- | Report the target of a move.
445setMovedTo :: Toggle -> Config -> Config
446setMovedTo = setFlag iN_MOVED_TO
449 "sys/inotify.h value IN_MODIFY" iN_MODIFY :: Word32
451-- | Report when a file is modified.
457setModified :: Toggle -> Config -> Config
458setModified = setFlag iN_MODIFY
460-- | Set all events 'On' or 'Off'.
466setAllEvents :: Toggle -> Config -> Config
470 . setMetadataChanged s
474 . setNonWriteClosed s
482-------------------------------------------------------------------------------
484-------------------------------------------------------------------------------
486-- The defaults are set in such a way that the behavior on macOS and Linux is
487-- as much compatible as possible.
491-- * 'setFollowSymLinks' 'On'
492-- * 'setUnwatchMoved' 'On'
493-- * 'setOneShot' 'Off'
494-- * 'setOnlyDir' 'Off'
495-- * 'setWhenExists' 'AddIfExists'
496-- * 'setAllEvents' 'On'
500defaultConfig :: Config
502 setWhenExists AddIfExists
504 $ Config { createFlags = 0 }
506-------------------------------------------------------------------------------
507-- Open an event stream
508-------------------------------------------------------------------------------
510-- | A handle for a watch.
511data Watch = Watch Handle (IntMap (Array Word8))
513-- Instead of using the watch descriptor we can provide APIs that use the path
514-- itself to identify the watch. That will require us to maintain a map from wd
515-- to path in the Watch handle.
517newtype WD = WD CInt deriving Show
519foreign import ccall unsafe
520 "sys/inotify.h inotify_init" c_inotify_init :: IO CInt
522-- | Create a 'Watch' handle. 'addToWatch' can be used to add paths being
523-- monitored by this watch.
527createWatch :: IO Watch
529 rawfd <- throwErrnoIfMinus1 "createWatch" c_inotify_init
530 -- we could use fdToHandle but it cannot determine the fd type
531 -- automatically for the inotify fd
536 (Just (Stream, 0, 0)) -- (IODeviceType, CDev, CIno)
537 False -- not a socket
538 False -- non-blocking is false
539 let fdString = "<createWatch file descriptor: " ++ show fd ++ ">"
546 True -- use non-blocking IO
547 Nothing -- TextEncoding (binary)
548 return $ Watch h Map.empty
550foreign import ccall unsafe
551 "sys/inotify.h inotify_add_watch" c_inotify_add_watch
552 :: CInt -> CString -> CUInt -> IO CInt
554utf8ToString :: Array Word8 -> String
555utf8ToString = runIdentity . S.toList . U.decodeUtf8' . A.toStream
557#if !MIN_VERSION_base(4,10,0)
558-- | Turn an existing Handle into a file descriptor. This function throws an
559-- IOError if the Handle does not reference a file descriptor.
560handleToFd :: Handle -> IO FD
561handleToFd h = case h of
562 FileHandle _ mv -> do
563 Handle__{haDevice = dev} <- readMVar mv
566 Nothing -> throwErr "not a file descriptor"
567 DuplexHandle{} -> throwErr "not a file handle"
571 throwErr msg = ioException $ IOError (Just h)
572 InappropriateType "handleToFd" msg Nothing Nothing
575-- | Given a 'Config' and a @path@ supplied as a UTF-8 encoded byte array, add
576-- the path to the list of paths being monitored by the given watch 'Handle'.
577-- Returns a "watch descriptor" which is used as an idenitfier in the events
578-- generated for this particular path and also to remove the path from this
583addToWatch :: Config -> Watch -> Array Word8 -> IO Watch
584addToWatch Config{..} (Watch handle wdMap) path = do
585 fd <- handleToFd handle
586 wd <- A.asCString path $ \pathPtr ->
587 throwErrnoIfMinus1 ("addToWatch " ++ utf8ToString path) $
588 c_inotify_add_watch (fdFD fd) pathPtr (CUInt createFlags)
589 return $ Watch handle (Map.insert (fromIntegral wd) path wdMap)
591foreign import ccall unsafe
592 "sys/inotify.h inotify_rm_watch" c_inotify_rm_watch
593 :: CInt -> CInt -> IO CInt
595-- | Remove a path from a 'Watch', if a path was moved after adding you need to
596-- need to provide the original path which was used to add the Watch.
600removeFromWatch :: Watch -> Array Word8 -> IO Watch
601removeFromWatch (Watch handle wdMap) path = do
602 fd <- handleToFd handle
603 wdMap1 <- foldlM (step fd) Map.empty (Map.toList wdMap)
604 return $ Watch handle wdMap1
608 step fd newMap (wd, v) = do
611 let err = "removeFromWatch " ++ show (utf8ToString path)
612 rm = c_inotify_rm_watch (fdFD fd) (fromIntegral wd)
613 void $ throwErrnoIfMinus1 err rm
615 else return $ Map.insert wd v newMap
617-- | Given a 'Config' and list of @paths@ (UTF-8 encoded byte arrays) start
618-- monitoring the paths for file system events. Returns a 'Watch' handle which
619-- can then be used to read the event stream or to close the watch.
623openWatch :: Config -> NonEmpty (Array Word8) -> IO Watch
624openWatch cfg paths = do
626 foldlM (\w1 pth -> addToWatch cfg w1 pth) w (NonEmpty.toList paths)
628-- | Close a 'Watch' handle.
632closeWatch :: Watch -> IO ()
633closeWatch (Watch h _) = hClose h
635-------------------------------------------------------------------------------
636-- Raw events read from the watch file handle
637-------------------------------------------------------------------------------
639newtype Cookie = Cookie Word32 deriving (Show, Eq)
641-- | An Event generated by the file system. Use the accessor functions to
648 , eventFlags :: Word32
649 , eventCookie :: Word32
650 , eventRelPath :: Array Word8
651 , eventMap :: IntMap (Array Word8)
652 } deriving (Show, Ord, Eq)
654-- The inotify event struct from the man page/header file:
656-- struct inotify_event {
657-- int wd; /* Watch descriptor */
658-- uint32_t mask; /* Mask describing event */
659-- uint32_t cookie; /* Unique cookie associating related
660-- events (for rename(2)) */
661-- uint32_t len; /* Size of name field */
662-- char name[]; /* Optional null-terminated name */
665-- XXX We can perhaps use parseD monad instance for fusing with parseMany? Need
666-- to measure the perf.
668readOneEvent :: IntMap (Array Word8) -> Parser IO Word8 Event
669readOneEvent wdMap = do
670 let headerLen = (sizeOf (undefined :: CInt)) + 12
671 arr <- PR.takeEQ headerLen (A.writeN headerLen)
672 (ewd, eflags, cookie, pathLen) <- PR.yieldM $ A.asPtr arr readHeader
673 -- XXX need the "initial" in parsers to return a step type so that "take 0"
674 -- can return without an input. otherwise if pathLen is 0 we will keep
675 -- waiting to read one more char before we return this event.
679 -- XXX sliceSepByMax drops the separator so assumes a null
680 -- terminated path, we should use a takeWhile nested inside a
682 pth <- PR.sliceSepByMax (== 0) pathLen (A.writeN pathLen)
683 let remaining = pathLen - A.length pth - 1
684 when (remaining /= 0) $ PR.takeEQ remaining FL.drain
686 else return $ A.fromList []
688 { eventWd = (fromIntegral ewd)
689 , eventFlags = eflags
690 , eventCookie = cookie
691 , eventRelPath = path
697 readHeader (ptr :: Ptr Word8) = do
698 let len = sizeOf (undefined :: CInt)
700 eflags <- peekByteOff ptr len
701 cookie <- peekByteOff ptr (len + 4)
702 pathLen :: Word32 <- peekByteOff ptr (len + 8)
703 return (ewd, eflags, cookie, fromIntegral pathLen)
705watchToStream :: Watch -> SerialT IO Event
706watchToStream (Watch handle wdMap) =
707 -- Do not use too small a buffer. As per inotify man page:
709 -- The behavior when the buffer given to read(2) is too small to return
710 -- information about the next event depends on the kernel version: in
711 -- kernels before 2.6.21, read(2) returns 0; since kernel 2.6.21, read(2)
712 -- fails with the error EINVAL. Specifying a buffer of size
714 -- sizeof(struct inotify_event) + NAME_MAX + 1
716 -- will be sufficient to read at least one event.
717 S.parseMany (readOneEvent wdMap) $ S.unfold FH.read handle
719-- | Start monitoring a list of file system paths for file system events with
720-- the supplied configuration operation over the 'defaultConfig'. The
721-- paths could be files or directories. When the path is a directory, only the
722-- files and directories directly under the watched directory are monitored,
723-- contents of subdirectories are not monitored. Monitoring starts from the
724-- current time onwards. The paths are specified as UTF-8 encoded 'Array' of
729-- ('setFollowSymLinks' On . 'setUnwatchMoved' Off)
730-- [Array.fromCString\# "dir"#]
736 (Config -> Config) -> NonEmpty (Array Word8) -> SerialT IO Event
737watchPathsWith f paths = S.bracket before after watchToStream
741 before = liftIO $ openWatch (f defaultConfig) paths
742 after = liftIO . closeWatch
744-- | Like 'watchPathsWith' but uses the 'defaultConfig' options.
747-- watchPaths = watchPathsWith id
752watchPaths :: NonEmpty (Array Word8) -> SerialT IO Event
753watchPaths = watchPathsWith id
755-------------------------------------------------------------------------------
756-- Examine event stream
757-------------------------------------------------------------------------------
759-- | Get the watch descriptor corresponding to the 'Event'. A watch descriptor
760-- identifies which of the paths being monitored has generated this event.
762-- Note that if a path was moved after adding to the watch, this will give the
763-- original path and not the new path after moving.
767getRoot :: Event -> Array Word8
771 case Map.lookup (fromIntegral eventWd) eventMap of
774 error $ "Bug: getRoot: No path found corresponding to the "
775 ++ "watch descriptor " ++ show eventWd
779-- XXX should we use a Maybe here?
780-- | Get the file system object path for which the event is generated, relative
781-- to the watched root. The path is a UTF-8 encoded array of bytes.
785getRelPath :: Event -> Array Word8
786getRelPath Event{..} = eventRelPath
788-- XXX should we use a Maybe?
789-- | Cookie is set when a rename occurs. The cookie value can be used to
790-- connect the 'isMovedFrom' and 'isMovedTo' events, if both the events belong
791-- to the same move operation then they will have the same cookie value.
795getCookie :: Event -> Cookie
796getCookie Event{..} = Cookie eventCookie
798-------------------------------------------------------------------------------
800-------------------------------------------------------------------------------
802getFlag :: Word32 -> Event -> Bool
803getFlag mask Event{..} = eventFlags .&. mask /= 0
805-------------------------------------------------------------------------------
807-------------------------------------------------------------------------------
810 "sys/inotify.h value IN_Q_OVERFLOW" iN_Q_OVERFLOW :: Word32
812-- XXX rename to isQOverflowed or hasOverflowed?
814-- macOS overflow API is more specific, it tells which paths have lost the
815-- events due to overflow.
817-- | Event queue overflowed (WD is invalid for this event) and we may have lost
818-- some events.. The user application must scan everything under the watched
819-- paths to know the current state.
823isOverflow :: Event -> Bool
824isOverflow = getFlag iN_Q_OVERFLOW
826-------------------------------------------------------------------------------
827-- Events affecting the watched path only
828-------------------------------------------------------------------------------
831 "sys/inotify.h value IN_IGNORED" iN_IGNORED :: Word32
833-- Compare with isRootChanged on macOS. isRootChanged includes all these cases.
835-- | A path was removed from the watch explicitly using 'removeFromWatch' or
836-- automatically (file was deleted, or filesystem was unmounted).
838-- /Occurs only for a watched path/
842isRootUnwatched :: Event -> Bool
843isRootUnwatched = getFlag iN_IGNORED
845-- | Watched file/directory was itself deleted. (This event also occurs if an
846-- object is moved to another filesystem, since mv(1) in effect copies the file
847-- to the other filesystem and then deletes it from the original filesystem.)
848-- In addition, an 'isRootUnwatched' event will subsequently be generated
849-- for the watch descriptor.
851-- /Occurs only for a watched path/
855isRootDeleted :: Event -> Bool
856isRootDeleted = getFlag iN_DELETE_SELF
858-- | Watched file/directory was itself moved within the file system.
860-- /Occurs only for a watched path/
864isRootMoved :: Event -> Bool
865isRootMoved = getFlag iN_MOVE_SELF
868 "sys/inotify.h value IN_UNMOUNT" iN_UNMOUNT :: Word32
870-- | Filesystem containing watched object was unmounted. In addition, an
871-- 'isRootUnwatched' event will subsequently be generated for the watch
874-- /Occurs only for a watched path/
878isRootUnmounted :: Event -> Bool
879isRootUnmounted = getFlag iN_UNMOUNT
881-------------------------------------------------------------------------------
882-- Metadata change Events
883-------------------------------------------------------------------------------
885-- macOS has multiple APIs for metadata change for different metadata.
887-- | Determine whether the event indicates inode metadata change for an object
888-- contained within the monitored path.
890-- Metadata change may include, permissions (e.g., chmod(2)), timestamps
891-- (e.g., utimensat(2)), extended attributes (setxattr(2)), link count (since
892-- Linux 2.6.25; e.g., for the target of link(2) and for unlink(2)), and
893-- user/group ID (e.g., chown(2)).
895-- /Can occur for watched path or a file inside it/
899isMetadataChanged :: Event -> Bool
900isMetadataChanged = getFlag iN_ATTRIB
902-------------------------------------------------------------------------------
904-------------------------------------------------------------------------------
906-- | File was accessed (e.g. read, execve).
908-- /Occurs only for a file inside the watched directory/
912isAccessed :: Event -> Bool
913isAccessed = getFlag iN_ACCESS
915-- | File or directory was opened.
917-- /Occurs only for a file inside the watched directory/
921isOpened :: Event -> Bool
922isOpened = getFlag iN_OPEN
924-- | File opened for writing was closed.
926-- /Occurs only for a file inside the watched directory/
930isWriteClosed :: Event -> Bool
931isWriteClosed = getFlag iN_CLOSE_WRITE
933-- XXX what if it was opened for append? Does NOWRITE mean all cases where the
934-- mode was not write? A dir open comes in this category?
936-- | File or directory opened for read but not write was closed.
938-- /Can occur for watched path or a file inside it/
942isNonWriteClosed :: Event -> Bool
943isNonWriteClosed = getFlag iN_CLOSE_NOWRITE
945-------------------------------------------------------------------------------
947-------------------------------------------------------------------------------
949-- On macOS this is not generated on hard linking but on Linux it is.
951-- | File/directory created in watched directory (e.g., open(2) O_CREAT,
952-- mkdir(2), link(2), symlink(2), bind(2) on a UNIX domain socket).
954-- /Occurs only for an object inside the watched directory/
958isCreated :: Event -> Bool
959isCreated = getFlag iN_CREATE
961-- | File/directory deleted from watched directory.
963-- /Occurs only for an object inside the watched directory/
967isDeleted :: Event -> Bool
968isDeleted = getFlag iN_DELETE
970-- XXX what if an object is moved in from outside or moved out of the monitored
973-- | Generated for the original path when an object is moved from under a
974-- monitored directory.
976-- /Occurs only for an object inside the watched directory/
980isMovedFrom :: Event -> Bool
981isMovedFrom = getFlag iN_MOVED_FROM
983-- | Generated for the new path when an object is moved under a monitored
986-- /Occurs only for an object inside the watched directory/
990isMovedTo :: Event -> Bool
991isMovedTo = getFlag iN_MOVED_TO
993-- | Determine whether the event indicates modification of an object within the
994-- monitored path. This event is generated only for files and not directories.
996-- /Occurs only for an object inside the watched directory/
1000isModified :: Event -> Bool
1001isModified = getFlag iN_MODIFY
1003-------------------------------------------------------------------------------
1004-- Information about path type (applicable only when 'setFileEvents' is 'On')
1005-------------------------------------------------------------------------------
1008 "sys/inotify.h value IN_ISDIR" iN_ISDIR :: Word32
1010-- | Determine whether the event is for a directory path.
1014isDir :: Event -> Bool
1015isDir = getFlag iN_ISDIR
1017-------------------------------------------------------------------------------
1019-------------------------------------------------------------------------------
1021-- | Convert an 'Event' record to a String representation.
1022showEvent :: Event -> String
1023showEvent ev@Event{..} =
1024 "--------------------------"
1025 ++ "\nWd = " ++ show eventWd
1026 ++ "\nRoot = " ++ show (utf8ToString $ getRoot ev)
1027 ++ "\nPath = " ++ show (utf8ToString $ getRelPath ev)
1028 ++ "\nCookie = " ++ show (getCookie ev)
1029 ++ "\nFlags " ++ show eventFlags
1031 ++ showev isOverflow "Overflow"
1033 ++ showev isRootUnwatched "RootUnwatched"
1034 ++ showev isRootDeleted "RootDeleted"
1035 ++ showev isRootMoved "RootMoved"
1036 ++ showev isRootUnmounted "RootUnmounted"
1038 ++ showev isMetadataChanged "MetadataChanged"
1040 ++ showev isAccessed "Accessed"
1041 ++ showev isOpened "Opened"
1042 ++ showev isWriteClosed "WriteClosed"
1043 ++ showev isNonWriteClosed "NonWriteClosed"
1045 ++ showev isCreated "Created"
1046 ++ showev isDeleted "Deleted"
1047 ++ showev isModified "Modified"
1048 ++ showev isMovedFrom "MovedFrom"
1049 ++ showev isMovedTo "MovedTo"
1051 ++ showev isDir "Dir"
1054 where showev f str = if f ev then "\n" ++ str else ""
1056#warning "Disabling module Streamly.Internal.FileSystem.Event.Linux. Does not support kernels older than 2.6.36."
1057module Streamly.Internal.FileSystem.Event.Linux () where
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list