bsd-sysctl-1.0.4: System/BSD/Sysctl.hsc
{-# 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 <sys/param.h>
#include <sys/sysctl.h>
#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)