packages feed

kqueue-0.2.1: src/System/KQueue.chs

{-# LANGUAGE DeriveDataTypeable
           , EmptyDataDecls
           , ForeignFunctionInterface
           #-}
-- | This module contains a low-level binding to the kqueue interface.
-- It stays close to the C API, changing the types to more native
-- Haskell types, but not significantly changing it.
-- See the kqueue man page or the examples in @examples/@ for usage
-- information.
-- For a higher-level binding, see "System.KQueue.HighLevel".
module System.KQueue
  ( KQueue
  , kqueue
  , KEvent (..)
  , Filter (..)
  , Flag (..)
  , FFlag (..)
  , kevent
  , KQueueException
  ) where

#include <sys/time.h>
#include <sys/event.h>

#if __GLASGOW_HASKELL__ <= 708
import Control.Applicative ( (<$>), (<*>) )
#endif
import Control.Exception   ( Exception, bracket_, throwIO )
import Data.List           ( foldl' )
import Data.Maybe          ( mapMaybe )
import Data.Time.Clock     ( NominalDiffTime )
import Data.Typeable       ( Typeable )
import Foreign             ( (.|.)
                           , Ptr
                           , Storable (..)
                           , allocaArray
                           , bit
                           , finiteBitSize
                           , maybeWith
                           , testBit
                           , peekArray
                           , with
                           , withArray
                           )

#if __GLASGOW_HASKELL__ >= 710
import Foreign.C           ( CInt (..) )
#elif __GLASGOW_HASKELL__ >= 704
import Foreign.C           ( CInt (..)
                           , CShort
                           , CUInt
                           , CUShort
                           )
#else
import Foreign.C           ( CInt )
#endif
import Foreign.C           ( CLong
                           , CTime
                           , CULong
                           )
import System.Posix.Signals ( blockSignals
                            , reservedSignals
                            , unblockSignals
                            )

-- | A kernel event queue.
newtype KQueue = KQueue CInt -- The descriptor

-- | Create a new KQueue.
kqueue :: IO KQueue
kqueue = KQueue <$> {#call kqueue as kqueue_ #}

-- | A kernel event.
data KEvent = KEvent
  { ident    :: CULong  -- ^ The identifier for the event, often a file descriptor.
  , evfilter :: Filter  -- ^ The kernel filter (type of event).
  , flags    :: [Flag]  -- ^ Actions to perform on the event.
  , fflags   :: [FFlag] -- ^ Filter-specific flags.
  , data_    :: CLong   -- ^ Filter-specific data value.
  , udata    :: Ptr ()  -- ^ User-defined data, passed through unchanged.
  } deriving (Show, Eq)

-- TODO: nicer types for ident, data_ and udata.

#c
enum Filter
  { EvfiltRead = EVFILT_READ
  , EvfiltWrite = EVFILT_WRITE
  , EvfiltAio = EVFILT_AIO
  , EvfiltVnode = EVFILT_VNODE
  , EvfiltProc = EVFILT_PROC
  , EvfiltSignal = EVFILT_SIGNAL
  , EvfiltTimer = EVFILT_TIMER
// Not on Mac OS X
// , EvfiltUser = EVFILT_USER
  };
#endc

-- | The types of kernel events.
{#enum Filter {} deriving (Show, Eq) #}

#c
enum Flag
  { EvAdd      = EV_ADD
  , EvEnable   = EV_ENABLE
  , EvDisable  = EV_DISABLE
// Not on Mac OS X
//  , EvDispatch = EV_DISPATCH
  , EvDelete   = EV_DELETE
  , EvReceipt  = EV_RECEIPT
  , EvOneshot  = EV_ONESHOT
  , EvClear    = EV_CLEAR
  , EvEof      = EV_EOF
  , EvError    = EV_ERROR
  };
#endc

-- | The actions to perform on the event.
{#enum Flag {} deriving (Show, Eq) #}

#c
enum FFlag
  { NoteDelete = NOTE_DELETE
  , NoteWrite  = NOTE_WRITE
  , NoteExtend = NOTE_EXTEND
  , NoteAttrib = NOTE_ATTRIB
  , NoteLink   = NOTE_LINK
  , NoteRename = NOTE_RENAME
  , NoteRevoke = NOTE_REVOKE
// Seems to have the same value as NoteDelete
//  , NoteLowat  = NOTE_LOWAT
  , NoteExit   = NOTE_EXIT
  , NoteFork   = NOTE_FORK
  , NoteExec   = NOTE_EXEC
  , NoteSignal = NOTE_SIGNAL
//  , NoteReap   = NOTE_REAP
  };
#endc

-- | The filter specific flags.
{#enum FFlag {} deriving (Show, Eq) #}

-- | Convert a list of enumeration values to an integer by combining
-- them with bitwise 'or'.
enumToBitmask :: Enum a => [a] -> Int
enumToBitmask = foldl' (.|.) 0 . map fromEnum

-- | Convert an integer to a list of enumeration values by testing
-- each bit, and if set, convert it to an enumeration member.
bitmaskToEnum :: Enum a => Int -> [a]
bitmaskToEnum bm = mapMaybe maybeBit [0 .. finiteBitSize bm - 1]
  where
    maybeBit b | testBit bm b = Just . toEnum . bit $ b
               | otherwise    = Nothing

#c
typedef struct kevent kevent_t;
#endc

instance Storable KEvent where
  sizeOf _ = {#sizeof kevent_t #}
  alignment _ = 24
  peek e = KEvent <$>                                     ({#get kevent_t->ident  #} e)
                  <*> fmap (toEnum . fromIntegral)        ({#get kevent_t->filter #} e)
                  <*> fmap (bitmaskToEnum . fromIntegral) ({#get kevent_t->flags  #} e)
                  <*> fmap (bitmaskToEnum . fromIntegral) ({#get kevent_t->fflags #} e)
                  <*>                                     ({#get kevent_t->data   #} e)
                  <*>                                     ({#get kevent_t->udata  #} e)
  poke e ev =
    do {#set kevent_t->ident  #} e (ident                                   ev)
       {#set kevent_t->filter #} e (fromIntegral . fromEnum . evfilter    $ ev)
       {#set kevent_t->flags  #} e (fromIntegral . enumToBitmask . flags  $ ev)
       {#set kevent_t->fflags #} e (fromIntegral . enumToBitmask . fflags $ ev)
       {#set kevent_t->data   #} e (data_                                   ev)
       {#set kevent_t->udata  #} e (udata                                   ev)

newtype TimeSpec = TimeSpec NominalDiffTime
  deriving (Show, Eq)

#c
typedef struct timespec timespec_t;
#endc

-- TODO: waarom krijg ik geen CTime maar een CLong als seconds bij gebruik van #get/#set?
instance Storable TimeSpec where
  sizeOf _ = {#sizeof timespec_t #}
  alignment _ = 8
  peek t =  mkTimeSpec
        <$> (\ptr -> peekByteOff ptr 0 :: IO CTime)  t
        <*> {#get timespec_t->tv_nsec #} t
    where
      mkTimeSpec s ns = TimeSpec $ realToFrac s + realToFrac ns/1000000000
  poke t (TimeSpec dt) =
    do (\ptr val -> pokeByteOff ptr 0 (val :: CTime)) t (fromInteger s)
       {#set timespec_t->tv_nsec #} t (floor . (* 1000000000) $ ns)
    where
      (s, ns) = properFraction dt

foreign import ccall "kevent" kevent_ :: CInt -> Ptr KEvent -> CInt -> Ptr KEvent -> CInt -> Ptr TimeSpec -> IO CInt

data KQueueException = KQueueException
  deriving (Show, Typeable)

instance Exception KQueueException

-- | Add events to monitor, or retrieve events from the kqueue. If an
-- error occurs, will throw a 'KQueueException' if there is no room in
-- the returned event list. Otherwise, will set 'EvError' on the event
-- and add it to the returned event list.
kevent ::  KQueue               -- ^ The kernel queue to operate on.
       -> [KEvent]              -- ^ The list of events to start monitoring, or changes to retrieve.
       -> Int                   -- ^ The maximum number of events to retrieve.
       -> Maybe NominalDiffTime -- ^ Timeout. When nothing, blocks until an event has occurred.
       -> IO [KEvent]           -- ^ A list of events that have occurred.
kevent (KQueue kq) changelist nevents mtimeout =
  withArray changelist $ \chArray ->
  allocaArray nevents  $ \evArray ->
  maybeWith with (TimeSpec <$> mtimeout) $ \timeout -> do
    ret <- bracket_
      (blockSignals reservedSignals)
      (unblockSignals reservedSignals)
      (kevent_ kq chArray (fromIntegral . length $ changelist) evArray (fromIntegral nevents) timeout)
    case ret of
      -- Error while processing changelist, and no room in return array.
      -1 -> throwIO KQueueException
      -- Timeout.
      0  -> return []
      -- Returned n events. Can contain errors. The change that
      -- failed will be in the event list. EV_ERROR will be set on the
      -- event.
      n  -> peekArray (fromIntegral n) evArray