-- | Interface to @fd_set@. See @select(2)@. -- -- The type 'I.FdSet' is opaque, but is implemented internally as a -- pointer to an @fd_set@. All operations on 'I.FdSet's must adhere to -- the requirements of @FD_CLR@, @FD_ISSET@, @FD_SET@ and @FD_ZERO@ -- (see @select(2)@). This includes requiring /valid/ file descriptors -- for all operations. Most functions in this module are kept in the -- 'IO' monad to make it easier to guarantee validity of the file -- descriptors, but since invalid ones seem to work fine in practice -- (at least on Linux), the module -- "System.Posix.IO.Select.FdSet.Unsafe" provides a non-'IO' -- interface. -- -- Functions that return an 'I.FdSet', such as 'insert', copy the -- underlying @fd_set@ in order to be referentially transparent. -- -- In the documentation that follows, a file descriptor is said to be -- /in range/ if it is non-negative and strictly smaller than the -- system-defined @FD_SETSIZE@. Many functions silently ignore file -- descriptors that are not in range. module System.Posix.IO.Select.FdSet (I.FdSet(), fromList, insert, insertList, empty, elem, remove, removeList, inList, inRange, bound, duplicate) where import Prelude hiding (elem) import qualified System.Posix.IO.Select.FdSet.Internal as I import Foreign import System.Posix.Types import Misc import Control.Monad -- | Create an 'FdSet' from a list of file descriptors. File -- descriptors not in range (see above) are silently ignored. fromList :: [Fd] -> IO I.FdSet fromList fds = I.allocate' >>= \ptr -> withForeignPtr ptr I.c_fd_zero_wrapper >> mapM_ ((flip I.insert') ptr) (filter inRange fds) >> return (I.FdSet ptr (maximum (0:fds))) -- | Insert a file descriptor. insert :: Fd -> I.FdSet -> IO I.FdSet insert fd = insertList [fd] -- | Insert multiple file descriptors. This is more efficient than -- multiple 'insert's (only a single copy of the set is made). insertList :: [Fd] -> I.FdSet -> IO I.FdSet insertList fds (I.FdSet ptr l) = I.duplicate' ptr >>= \newPtr -> mapM_ ((flip I.insert') newPtr) (filter inRange fds) >> return (I.FdSet newPtr (max l (maximum (0:fds)))) -- | An empty 'FdSet'. empty :: IO I.FdSet empty = I.allocate' >>= \ptr -> withForeignPtr ptr I.c_fd_zero_wrapper >> return (I.FdSet ptr 0) -- | Test for membership. Recall that POSIX allows undefined behavior -- if the file descriptor is not valid (it does, however, seem to work -- fine on Linux). elem :: Fd -> I.FdSet -> IO Bool elem fd (I.FdSet ptr _) = I.elem' fd ptr >>= (return . cTrue) -- | Remove a file descriptor. remove :: Fd -> I.FdSet -> IO I.FdSet remove fd = removeList [fd] -- | Remove multiple file descriptors. This is more efficient than -- multiple 'remove's (only a single copy of the set is made). removeList :: [Fd] -> I.FdSet -> IO I.FdSet removeList fds (I.FdSet ptr l) = I.duplicate' ptr >>= \newPtr -> mapM_ ((flip I.remove') newPtr) (filter inRange fds) >> return (I.FdSet newPtr l) -- We don't actually shrink the maximum fd here. Should be ok! -- | @'inList' fds fdset@ gives a list of all file descriptors in -- @fd@ that are in @fdset@. inList :: [Fd] -> I.FdSet -> IO [Fd] inList fds (I.FdSet ptr l) = filterM (\fd -> I.elem' fd ptr >>= (return . cTrue)) fds' where fds' = filter (\fd -> fd <= l && inRange fd) fds -- | Test if a file descriptor is in range (see introduction). inRange :: Fd -> Bool inRange fd = fd >= 0 && fd < I.c_FD_SETSIZE -- | This file descriptor is at least as large as the largest in the -- set. If no file descriptors have ever been removed from the set, -- the value is /the largest/ in the set, but this assumption may not -- hold after removals or other operations. bound :: I.FdSet -> Fd bound (I.FdSet _ l) = l -- | Copy an 'FdSet'. duplicate :: I.FdSet -> IO I.FdSet duplicate (I.FdSet ptr l) = I.duplicate' ptr >>= \newPtr -> return (I.FdSet newPtr l)