{-# LINE 1 "src/System/Linux/Epoll/Base.hsc" #-}
{-# LANGUAGE CPP, ForeignFunctionInterface #-}
{-# LINE 2 "src/System/Linux/Epoll/Base.hsc" #-}
-- |
-- Module      : System.Linux.Epoll.Base
-- Copyright   : (c) 2009 Toralf Wittner
-- License     : LGPL
-- Maintainer  : toralf.wittner@gmail.com
-- Stability   : experimental
-- Portability : non-portable
-- 
-- Low level interface to Linux' epoll, a high performance polling mechanism
-- which handles high numbers of file descriptors efficiently. See man epoll(7)
-- for details.

module System.Linux.Epoll.Base (

    EventType,
    Size,
    toSize,
    Duration,
    toDuration,
    Descriptor,
    Device,
    Event (eventFd, eventType, eventRef, eventDesc),
    (=~),

    create,
    close,
    wait,

    add,
    modify,
    delete,
    freeDesc,

    inEvent,
    outEvent,
    peerCloseEvent,
    urgentEvent,
    errorEvent,
    hangupEvent,
    edgeTriggeredEvent,
    oneShotEvent,
    combineEvents

) where

import Util
import Foreign
import Foreign.C.Types (CInt)
import Foreign.C.Error (throwErrnoIfMinus1,
                        throwErrnoIfMinus1_,
                        throwErrnoIfMinus1Retry)
import Foreign.Marshal.Array (peekArray, mallocArray)
import Foreign.Marshal.Utils (with)
import System.Posix.Types (Fd (Fd))
import System.Posix.Signals (installHandler, sigPIPE, Handler (Ignore))


{-# LINE 59 "src/System/Linux/Epoll/Base.hsc" #-}

-- | EventType corresponds to epoll's event type defines, e.g. EPOLLIN,
-- EPOLLOUT, EPOLLET, etc.
newtype EventType = EventType { fromEventType :: Int } deriving (Eq, Ord)

-- | Operation corresponds to epoll's EPOLL_CTL_ADD et al. It is the operation
-- designation in epoll_ctl.
newtype Operation = Operation { fromOp :: Int } deriving (Eq, Ord)

-- | Unsigned type used for length specifications.
newtype Size = Size { fromSize :: Word32 } deriving (Eq, Ord, Show)

-- | Unsigned type used for timeout specifications.
newtype Duration = Duration { fromDuration :: Word32 } deriving (Eq, Ord, Show)

-- | Event descriptor. Will be returned from 'add' and must be passed to
-- 'delete' exactly once.
newtype Descriptor a = Descriptor { descrPtr :: StablePtr (Fd, a) }

-- | Abstract epoll device. Holds internal data. Returned from 'create' and used
-- in almost every other API function. Must be closed explicitely with 'close'.
data Device = Device {
        deviceFd      :: !Fd,                -- ^ The epoll file descriptor.
        eventArray    :: !(Ptr EventStruct), -- ^ The event array used in 'wait'.
        eventArrayLen :: !Size               -- ^ The size of the event array.
    } deriving (Eq, Show)

-- | A single event ocurrence.
data Event a = Event {
        eventFd   :: !Fd,        -- ^ The file descriptor.
        eventType :: !EventType, -- ^ The event type.
        eventRef  :: !a,         -- ^ The user data as given in 'add'.
        eventDesc :: !(Descriptor a)
    }

-- | Representation of struct epoll_event.
data EventStruct = EventStruct {
        epollEvents :: !Word32,
        epollData   :: !(Ptr ())
    }

instance Storable EventStruct where
    alignment _ = 1
{-# LINE 102 "src/System/Linux/Epoll/Base.hsc" #-}
    sizeOf _ = (12)
{-# LINE 103 "src/System/Linux/Epoll/Base.hsc" #-}
    peek p = do
        evts <- (\hsc_ptr -> peekByteOff hsc_ptr 0) p
{-# LINE 105 "src/System/Linux/Epoll/Base.hsc" #-}
        dat  <- (\hsc_ptr -> peekByteOff hsc_ptr 4) p
{-# LINE 106 "src/System/Linux/Epoll/Base.hsc" #-}
        return (EventStruct evts dat)
    poke p (EventStruct evts dat) = do
        (\hsc_ptr -> pokeByteOff hsc_ptr 0) p evts
{-# LINE 109 "src/System/Linux/Epoll/Base.hsc" #-}
        (\hsc_ptr -> pokeByteOff hsc_ptr 4) p dat
{-# LINE 110 "src/System/Linux/Epoll/Base.hsc" #-}


{-# LINE 112 "src/System/Linux/Epoll/Base.hsc" #-}

inEvent  :: EventType
inEvent  = EventType 1
outEvent  :: EventType
outEvent  = EventType 4
peerCloseEvent  :: EventType
peerCloseEvent  = EventType 8192
urgentEvent  :: EventType
urgentEvent  = EventType 2
errorEvent  :: EventType
errorEvent  = EventType 8
hangupEvent  :: EventType
hangupEvent  = EventType 16
edgeTriggeredEvent  :: EventType
edgeTriggeredEvent  = EventType (-2147483648)
oneShotEvent  :: EventType
oneShotEvent  = EventType 1073741824

{-# LINE 123 "src/System/Linux/Epoll/Base.hsc" #-}

instance Show EventType where
    show e | e == inEvent = "EPOLLIN (0x001)"
           | e == outEvent = "EPOLLOUT (0x004)"
           | e == peerCloseEvent = "EPOLLRDHUP (0x2000)"
           | e == urgentEvent = "EPOLLPRI (0x002)"
           | e == errorEvent = "EPOLLERR (0x008)"
           | e == hangupEvent = "EPOLLHUP (0x010)"
           | e == edgeTriggeredEvent = "EPOLLET (1 << 31)"
           | e == oneShotEvent = "EPOLLONESHOT (1 << 30)"
           | otherwise = show $ fromEventType e

addOp  :: Operation
addOp  = Operation 1
modifyOp  :: Operation
modifyOp  = Operation 3
deleteOp  :: Operation
deleteOp  = Operation 2

{-# LINE 140 "src/System/Linux/Epoll/Base.hsc" #-}

instance Show Operation where
    show op | op == addOp    = "EPOLL_CTL_ADD"
            | op == modifyOp = "EPOLL_CTL_MOD"
            | op == deleteOp = "EPOLL_CTL_DEL"
            | otherwise = show $ fromOp op

-- | Creates an epoll device. Must be closed with 'close'.
-- The parameter 'Size' specifies the number of events that can be reported by a
-- single call to 'wait'.
create :: Size -> IO Device
create s = do
    dev <- throwErrnoIfMinus1 "create: c_epoll_create" (c_epoll_create 0)
    buf <- mallocArray (fromIntegral $ fromSize s)
    installHandler sigPIPE Ignore Nothing
    return $ Device (Fd dev) buf s

-- | Closes epoll device.
close :: Device -> IO ()
close = free . eventArray

-- | Adds a filedescriptor to the epoll watch set using the specified
-- EventTypes. User data might be passed in as well which will be returned on
-- event occurence as part of the 'Event' type. Returns an event descriptor
-- which must be deleted from the watch set with 'delete' and passed to
-- 'freeDesc' exactly once.
add :: Device -> a -> [EventType] -> Fd -> IO (Descriptor a)
add dev dat evts fd = do
    p <- newStablePtr (fd, dat)
    control dev addOp evts p
    return $ Descriptor p

-- | Modified the event types of the event descriptor.
modify :: Device -> [EventType] -> Descriptor a -> IO ()
modify dev evts des = control dev modifyOp evts (descrPtr des)

-- | Removes the event descriptor from the epoll watch set.
-- and frees descriptor which must not be used afterwards.
delete :: Device -> Descriptor a -> IO ()
delete dev des = control dev deleteOp [] (descrPtr des)

-- | Frees the resources associated with this descriptor.
-- Must be called exactly once.
freeDesc :: Descriptor a -> IO ()
freeDesc = freeStablePtr . descrPtr

-- | Representation of epoll_ctl.
control :: Device -> Operation -> [EventType] -> StablePtr (Fd, a) -> IO ()
control dev op evts ptr = do
    (fd, _) <- deRefStablePtr ptr
    throwErrnoIfMinus1_ (errMsg fd) $
        with (EventStruct (fromIntegral . fromEventType $ combineEvents evts)
                           (castStablePtrToPtr ptr))
             (c_epoll_ctl (intToNum $ deviceFd dev)
                          (fromIntegral $ fromOp op)
                          (intToNum fd))
 where
    errMsg fd = "control: c_epoll_ctl: fd=" ++ (show fd)
             ++ ", op=" ++ (show op)
             ++ ", events=" ++ (show evts)

-- | Waits for the specified duration on all event descriptors. Returns the
-- list of events that occured.
wait :: Duration -> Device -> IO [Event a]
wait timeout dev = do
    r <- throwErrnoIfMinus1Retry "wait: c_epoll_wait"
         (c_epoll_wait (intToNum $ deviceFd dev)
                       (eventArray dev)
                       (fromIntegral . fromSize $ eventArrayLen dev)
                       (fromIntegral $ fromDuration timeout))
    evts <- peekArray (fromIntegral r) (eventArray dev)
    mapM createEvent evts
 where
     createEvent e = do
        let ptr = castPtrToStablePtr $ epollData e
            ety = EventType (fromIntegral $ epollEvents e)
        (fd, ref) <- deRefStablePtr ptr
        return (Event fd ety ref (Descriptor ptr))

-- | Match operator. Useful to test whether an 'EventType' returned from
-- 'wait' contains one of the defined event types because EventTypes returned by
-- wait might be the bitwise OR of several EventTypes.
(=~) :: EventType -> EventType -> Bool
e1 =~ e2 = fromEventType e1 .&. fromEventType e2 /= 0

infix 4 =~

toSize :: Int -> Maybe Size
toSize i = toWord32 i >>= Just . Size

toDuration :: Int -> Maybe Duration
toDuration i = toWord32 i >>= Just . Duration

-- | Bitwise OR of the list of 'EventType's.
combineEvents :: [EventType] -> EventType
combineEvents = EventType . foldr ((.|.) . fromEventType) 0

-- Foreign Imports --

foreign import ccall unsafe "epoll.h epoll_create"
    c_epoll_create :: CInt -> IO CInt

foreign import ccall unsafe "epoll.h epoll_ctl"
    c_epoll_ctl :: CInt -> CInt -> CInt -> Ptr EventStruct -> IO CInt

foreign import ccall safe "epoll.h epoll_wait"
    c_epoll_wait :: CInt -> Ptr EventStruct -> CInt -> CInt -> IO CInt