{-# LINE 1 "System/BSD/Sysctl.hsc" #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LINE 2 "System/BSD/Sysctl.hsc" #-}
{-# 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.
--
-- Nodes may be queried either by their name, their OID as a list of 'Int's, or
-- by an OID returned by 'sysctlNameToOid' if speed is a concern.
-------------------------------------------------------------------------------


{-# LINE 28 "System/BSD/Sysctl.hsc" #-}

{-# LINE 29 "System/BSD/Sysctl.hsc" #-}
module System.BSD.Sysctl (
  -- * The data types
  SysctlKey,		-- The class of types that can be used to identify a node
  OID,			-- The @OID@ datatype identifies a sysctl node

  -- * Name to OID conversion
  sysctlNameToOid,	-- :: String -> IO OID

  -- * 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

data OID = OID {-# UNPACK #-} !(ForeignPtr CInt)
               {-# UNPACK #-} !CUInt

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 String where
  withKey name f = sysctlNameToOid name >>= flip withKey f

instance SysctlKey [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

foreign import ccall unsafe "sysctlnametomib"
  c_sysctlnametomib :: CString -> Ptr CInt -> Ptr CSize -> IO CInt

-- 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)

-- Get the OID corresponding to a sysctl name.
sysctlNameToOid :: String -> IO OID
sysctlNameToOid name =
  withCString name $ \cname -> do
    fp  <- mallocForeignPtrArray (fromIntegral maxlen)
    len <- withForeignPtr fp $ \oid ->
             alloca $ \sizePtr -> do
               poke sizePtr maxlen
               throwErrnoIfMinus1_ "sysctlnametomib"
                 (c_sysctlnametomib cname oid sizePtr)
               peek sizePtr
    return (OID fp (fromIntegral len))
  where maxlen = 10
{-# LINE 112 "System/BSD/Sysctl.hsc" #-}

{-
-- 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' ->
      allocaBytes defaultBufSize $ \buf ->
        alloca $ \sizePtr ->
          do poke oid' 0
             poke (oid' `advancePtr` 1) 4
             copyArray (oid' `advancePtr` 2) oid (fromIntegral len)
             poke sizePtr (fromIntegral defaultBufSize)
             throwErrnoIfMinus1_ "sysctl"
               (c_sysctl oid' len' buf sizePtr nullPtr 0)
             kind <- peek buf
             fmt  <- peekCString (buf `plusPtr` (sizeOf kind))
             return (kind, fmt)
  where defaultBufSize = 1024 -- as in FreeBSD's libc
-}

-- 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

-- Read a sysctl value that is an instance of Storable.
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)

sysctlReadInt :: SysctlKey k => k -> IO Int32
{-# LINE 154 "System/BSD/Sysctl.hsc" #-}
sysctlReadInt = sysctlPeek

sysctlReadUInt :: SysctlKey k => k -> IO Word32
{-# LINE 157 "System/BSD/Sysctl.hsc" #-}
sysctlReadUInt = sysctlPeek

sysctlReadLong :: SysctlKey k => k -> IO Int32
{-# LINE 160 "System/BSD/Sysctl.hsc" #-}
sysctlReadLong = sysctlPeek

sysctlReadULong :: SysctlKey k => k -> IO Word32
{-# LINE 163 "System/BSD/Sysctl.hsc" #-}
sysctlReadULong = sysctlPeek

sysctlReadQuad :: SysctlKey k => k -> IO Int64
sysctlReadQuad = sysctlPeek

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

-- Retrieve a variable number of elements from a sysctl.
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 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)

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))

sysctlWriteInt :: SysctlKey k => k -> Int32 -> IO ()
{-# LINE 204 "System/BSD/Sysctl.hsc" #-}
sysctlWriteInt = sysctlPoke

sysctlWriteUInt :: SysctlKey k => k -> Word32 -> IO ()
{-# LINE 207 "System/BSD/Sysctl.hsc" #-}
sysctlWriteUInt = sysctlPoke

sysctlWriteLong :: SysctlKey k => k -> Int32 -> IO ()
{-# LINE 210 "System/BSD/Sysctl.hsc" #-}
sysctlWriteLong = sysctlPoke

sysctlWriteULong :: SysctlKey k => k -> Word32 -> IO ()
{-# LINE 213 "System/BSD/Sysctl.hsc" #-}
sysctlWriteULong = sysctlPoke

sysctlWriteQuad :: SysctlKey k => k -> Int64 -> IO ()
sysctlWriteQuad = sysctlPoke

sysctlWriteUQuad :: SysctlKey k => k -> Word64 -> IO ()
sysctlWriteUQuad = sysctlPoke

sysctlWriteString :: SysctlKey k => k -> String -> IO ()
sysctlWriteString key s =
  withKey key $ \oid len ->
    withCStringLen s $ \(cs,slen) -> sysctlWrite oid len cs (fromIntegral slen)