{-# LANGUAGE ForeignFunctionInterface #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} -- vim:filetype=haskell ----------------------------------------------------------------------------- -- | -- Module : System.BSD.Sysctl -- Copyright : (c) Maxime Henrion 2009 -- License : see LICENSE -- -- Maintainer : mhenrion@gmail.com -- Stability : stable -- Portability : portable -- -- This module allows access to the BSD sysctl(3) interface via the Haskell FFI. -- -- Convenience functions to read and write the usual sysctl types are provided, -- as well as more advanced functions to handle binary values given a suitable -- 'Storable' instance. It is also possible to retrieve data whose size changes -- at runtime with the 'sysctlPeekArray' function. -- -- On some platforms, there are sysctl nodes that accept parameters via -- additional components in the OID (see for instance the \"kern.proc.pid\" -- sysctl described in sysctl(3) on FreeBSD). The 'sysctlNameToOidArgs' -- function makes it easy to query such nodes as well. -- -- Nodes may be queried either by their OID as a list of integers, by their -- binary OID for maximum speed, or by their names on platforms that support it. ------------------------------------------------------------------------------- #include #include #if defined(__HADDOCK__) || (!defined(__linux__) && !defined(__OpenBSD__)) #define HAVE_SYSCTLNAMETOMIB #endif module System.BSD.Sysctl ( -- * The data types SysctlKey, OID, -- * OID creation and extraction #ifdef HAVE_SYSCTLNAMETOMIB sysctlNameToOid, -- :: String -> IO OID sysctlNameToOidArgs, -- :: String -> [#{type int}] -> IO OID #endif sysctlPrepareOid, -- :: [#{type int}] -> IO OID sysctlExtractOid, -- :: OID -> IO [#{type int}] -- * Basic reading functions sysctlReadInt, -- :: SysctlKey k => k -> IO #{type int} sysctlReadUInt, -- :: SysctlKey k => k -> IO #{type unsigned int} sysctlReadLong, -- :: SysctlKey k => k -> IO #{type long} sysctlReadULong, -- :: SysctlKey k => k -> IO #{type unsigned long} sysctlReadQuad, -- :: SysctlKey k => k -> IO Int64 sysctlReadUQuad, -- :: SysctlKey k => k -> IO Word64 sysctlReadString, -- :: SysctlKey k => k -> IO String -- * Advanced reading functions sysctlPeek, -- :: forall k a. (SysctlKey k, Storable a) => k -> IO a sysctlPeekArray, -- :: forall k a. (SysctlKey k, Storable a) => k -> IO [a] -- * Basic writing functions sysctlWriteInt, -- :: SysctlKey k => k -> #{type int} -> IO () sysctlWriteUInt, -- :: SysctlKey k => k -> #{type unsigned int} -> IO () sysctlWriteLong, -- :: SysctlKey k => k -> #{type long} -> IO () sysctlWriteULong, -- :: SysctlKey k => k -> #{type unsigned long} -> IO () sysctlWriteQuad, -- :: SysctlKey k => k -> Int64 -> IO () sysctlWriteUQuad, -- :: SysctlKey k => k -> Word64 -> IO () sysctlWriteString, -- :: SysctlKey k => k -> String -> IO () -- * Advanced writing functions sysctlPoke -- :: (SysctlKey k, Storable a) => k -> a -> IO () ) where import Control.Arrow (second) import Data.Int import Data.Word import Foreign.Ptr import Foreign.C import Foreign.Marshal import Foreign.Storable import Foreign.ForeignPtr -- | An efficient representation of a sysctl 'OID' for maximum performance. data OID = OID {-# UNPACK #-} !(ForeignPtr CInt) {-# UNPACK #-} !CUInt -- | The class of types that can be used to identify a sysctl node. class SysctlKey k where withKey :: k -> (Ptr CInt -> CUInt -> IO a) -> IO a instance SysctlKey OID where withKey (OID fp len) f = withForeignPtr fp (\ptr -> f ptr len) instance SysctlKey [#{type int}] where withKey oid f = withArrayLen (map fromIntegral oid) (\len ptr -> f ptr (fromIntegral len)) foreign import ccall unsafe "sysctl" c_sysctl :: Ptr CInt -> CUInt -> Ptr a -> Ptr CSize -> Ptr b -> CSize -> IO CInt -- | Prepare an 'OID' for later use. sysctlPrepareOid :: [#{type int}] -> IO OID sysctlPrepareOid [] = error "sysctPrepareOid: empty list" sysctlPrepareOid oid = do fp <- mallocForeignPtrArray len withForeignPtr fp (flip pokeArray (map fromIntegral oid)) return (OID fp (fromIntegral len)) where len = length oid #ifdef HAVE_SYSCTLNAMETOMIB -- Support for looking up sysctls by name. This is not supported -- on (at least) Linux and OpenBSD. foreign import ccall unsafe "sysctlnametomib" c_sysctlnametomib :: CString -> Ptr CInt -> Ptr CSize -> IO CInt -- | Get the 'OID' corresponding to a sysctl name. sysctlNameToOid :: String -> IO OID sysctlNameToOid name = sysctlNameToOidArgs name [] -- | Like 'sysctlNameToOid', but allows to provide a list of -- additional integers to append to the OID, for specific sysctl -- nodes that support parameters this way. sysctlNameToOidArgs :: String -> [#{type int}] -> IO OID sysctlNameToOidArgs name args = withCString name $ \cname -> do allocaArray (fromIntegral maxlen) $ \oid -> do alloca $ \sizePtr -> do poke sizePtr maxlen throwErrnoIfMinus1_ "sysctlnametomib" (c_sysctlnametomib cname oid sizePtr) nlen <- fromIntegral `fmap` peek sizePtr -- Copy to a new buffer to save space. let len = nlen + alen fp <- mallocForeignPtrArray len withForeignPtr fp $ \ptr -> do copyArray ptr oid nlen pokeArray (ptr `advancePtr` nlen) (map fromIntegral args) return (OID fp (fromIntegral len)) where maxlen = #{const CTL_MAXNAME} alen = length args instance SysctlKey String where withKey name f = sysctlNameToOid name >>= flip withKey f #endif {- -- This could be used to implement some form of type checking at runtime some -- day, but the interface is undocumented and probably unportable though. oidToType :: Ptr CInt -> CUInt -> IO (CUInt, String) oidToType oid len = let len' = len + 2 in allocaArray (fromIntegral len') $ \oid' -> do poke oid' 0 poke (oid' `advancePtr` 1) 4 copyArray (oid' `advancePtr` 2) oid (fromIntegral len) allocaBytes defaultBufSize $ \buf -> sysctlRead oid' len' buf (fromIntegral defaultBufSize) $ \buf' _ -> do kind <- peek buf' fmt <- peekCString (buf' `plusPtr` (sizeOf kind)) return (kind, fmt) where defaultBufSize = 1024 -- as in FreeBSD's libc -} -- | Extract the list of integers contained in an 'OID'. sysctlExtractOid :: OID -> IO [#{type int}] sysctlExtractOid (OID fp len) = map fromIntegral `fmap` withForeignPtr fp (peekArray (fromIntegral len)) -- Base primitive for all reading operations. Abstracts away the low-level C -- machinery such as using a pointer to have multiple return values. sysctlRead :: Ptr CInt -> CUInt -> Ptr a -> CSize -> (Ptr a -> CSize -> IO b) -> IO b sysctlRead oid len buf size f = alloca $ \sizePtr -> do poke sizePtr size throwErrnoIfMinus1_ "sysctl" (c_sysctl oid len buf sizePtr nullPtr 0) realSize <- peek sizePtr f buf realSize -- Call sysctl with a size set to 0 to retrieve the size of the object. sysctlGetSize :: Ptr CInt -> CUInt -> IO CSize sysctlGetSize oid len = sysctlRead oid len nullPtr 0 (const return) -- | Read a storable value from a sysctl node. -- This is useful to read binary values such as C structures, otherwise -- the ad-hoc reading functions should be used instead. sysctlPeek :: forall k a. (SysctlKey k, Storable a) => k -> IO a sysctlPeek key = withKey key $ \oid len -> alloca $ \buf -> sysctlRead oid len buf (fromIntegral (sizeOf (undefined::a))) (const . peek) -- | Read a signed integer from a sysctl (the C int type). sysctlReadInt :: SysctlKey k => k -> IO #{type int} sysctlReadInt = sysctlPeek -- | Read an unsigned integer from a sysctl (the C unsigned int type). sysctlReadUInt :: SysctlKey k => k -> IO #{type unsigned int} sysctlReadUInt = sysctlPeek -- | Read a signed long integer from a sysctl (the C long type). sysctlReadLong :: SysctlKey k => k -> IO #{type long} sysctlReadLong = sysctlPeek -- | Read an unsigned long integer from a sysctl (the C unsigned long type). sysctlReadULong :: SysctlKey k => k -> IO #{type unsigned long} sysctlReadULong = sysctlPeek -- | Read a signed 64-bit integer from a sysctl. sysctlReadQuad :: SysctlKey k => k -> IO Int64 sysctlReadQuad = sysctlPeek -- | Read an unsigned 64-bit integer from a sysctl. sysctlReadUQuad :: SysctlKey k => k -> IO Word64 sysctlReadUQuad = sysctlPeek -- Useful specialisation of sysctlRead for when the size of the data isn't -- statically known, and also potentially variable with time. sysctlReadDynamic :: SysctlKey k => k -> (CSize -> CSize) -> (Ptr a -> CSize -> IO b) -> IO b sysctlReadDynamic key scale f = withKey key $ \oid len -> do size <- sysctlGetSize oid len let bufSize = scale size -- Allows to make room for lists of variable length allocaBytes (fromIntegral bufSize) $ \buf -> sysctlRead oid len buf bufSize f -- | Like 'sysctlPeek', but allows to retrieve a list of elements whose -- length can possibly change at runtime. sysctlPeekArray :: forall k a. (SysctlKey k, Storable a) => k -> IO [a] sysctlPeekArray key = sysctlReadDynamic key (*2) $ \buf size -> peekArray (fromIntegral size `div` sizeOf (undefined::a)) buf -- | Read a string from a sysctl. If the string can possibly change with -- time, use 'sysctlPeekArray' for characters instead. sysctlReadString :: SysctlKey k => k -> IO String sysctlReadString key = sysctlReadDynamic key id (curry (peekCStringLen . second ((subtract 1) . fromIntegral))) -- Base primitive for all writing operations. sysctlWrite :: Ptr CInt -> CUInt -> Ptr a -> CSize -> IO () sysctlWrite oid len buf size = throwErrnoIfMinus1_ "sysctl" (c_sysctl oid len nullPtr nullPtr buf size) -- | Write a storable value to a sysctl node. -- This is useful to write binary values such as C structures, otherwise -- the ad-hoc writing functions should be used instead. sysctlPoke :: (SysctlKey k, Storable a) => k -> a -> IO () sysctlPoke key x = withKey key $ \oid len -> with x $ \buf -> sysctlWrite oid len buf (fromIntegral (sizeOf buf)) -- | Write a signed integer to a sysctl (the C int type). sysctlWriteInt :: SysctlKey k => k -> #{type int} -> IO () sysctlWriteInt = sysctlPoke -- | Write an unsigned integer to a sysctl (the C unsigned int type). sysctlWriteUInt :: SysctlKey k => k -> #{type unsigned int} -> IO () sysctlWriteUInt = sysctlPoke -- | Write a signed long integer to a sysctl (the C long type). sysctlWriteLong :: SysctlKey k => k -> #{type long} -> IO () sysctlWriteLong = sysctlPoke -- | Write an unsigned long integer to a sysctl (the C unsigned long type). sysctlWriteULong :: SysctlKey k => k -> #{type unsigned long} -> IO () sysctlWriteULong = sysctlPoke -- | Write a signed 64-bit integer to a sysctl. sysctlWriteQuad :: SysctlKey k => k -> Int64 -> IO () sysctlWriteQuad = sysctlPoke -- | Write an unsigned 64-bit integer to a sysctl. sysctlWriteUQuad :: SysctlKey k => k -> Word64 -> IO () sysctlWriteUQuad = sysctlPoke -- | Write a string to a sysctl. sysctlWriteString :: SysctlKey k => k -> String -> IO () sysctlWriteString key s = withKey key $ \oid len -> withCStringLen s $ \(cs,slen) -> sysctlWrite oid len cs (fromIntegral slen)