-- |
--   Copyright   :  (c) Sam Truzjan 2013
--   License     :  BSD3
--   Maintainer  :  pxqr.sta@gmail.com
--   Stability   :  stable
--   Portability :  portable
--
--   Lookup devices in the sys filesystem, filter devices by
--   properties, and return a sorted list of devices.
--
{-# LANGUAGE ForeignFunctionInterface #-}
module System.UDev.Enumerate
       ( Enumerate

       , newEnumerate

         -- * Match
       , Subsystem
       , addMatchSubsystem
       , addNoMatchSubsystem

       , SysAttr
       , SysValue
       , addMatchSysattr
       , addNoMatchSysattr

       , addMatchProperty
       , addMatchTag
       , addMatchParent
       , addMatchIsInitialized
       , addMatchSysname
       , addSyspath

         -- * Scan
       , scanDevices
       , scanSubsystems

         -- * Query
       , getListEntry
       ) where

import Data.ByteString as BS
import Foreign
import Foreign.C.Error
import Foreign.C.String
import Foreign.C.Types
import System.Posix.FilePath

import System.UDev.Context
import System.UDev.Device
import System.UDev.List
import System.UDev.Types


foreign import ccall unsafe "udev_enumerate_new"
  c_new :: UDev -> IO Enumerate

-- | Create an enumeration context to scan /sys.
newEnumerate :: UDev -> IO Enumerate
newEnumerate :: UDev -> IO Enumerate
newEnumerate = UDev -> IO Enumerate
c_new
{-# INLINE newEnumerate #-}

{-----------------------------------------------------------------------
--  Match
-----------------------------------------------------------------------}

foreign import ccall unsafe "udev_enumerate_add_match_subsystem"
  c_addMatchSubsystem :: Enumerate -> CString -> IO CInt

-- | Kernel subsystem string.
type Subsystem = ByteString

-- | Match only devices belonging to a certain kernel subsystem.
addMatchSubsystem :: Enumerate -- ^ context
                  -> Subsystem -- ^ filter for a subsystem of the
                               -- device to include in the list
                  -> IO ()     -- ^ can throw exception
addMatchSubsystem :: Enumerate -> Subsystem -> IO ()
addMatchSubsystem Enumerate
enumerate Subsystem
subsystem =
  String -> IO CInt -> IO ()
forall a. (Eq a, Num a) => String -> IO a -> IO ()
throwErrnoIfMinus1_ String
"addMatchSubsystem" (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$
    Subsystem -> (CString -> IO CInt) -> IO CInt
forall a. Subsystem -> (CString -> IO a) -> IO a
useAsCString Subsystem
subsystem ((CString -> IO CInt) -> IO CInt)
-> (CString -> IO CInt) -> IO CInt
forall a b. (a -> b) -> a -> b
$
      Enumerate -> CString -> IO CInt
c_addMatchSubsystem Enumerate
enumerate

foreign import ccall unsafe "udev_enumerate_add_nomatch_subsystem"
  c_addNoMatchSubsystem :: Enumerate -> CString -> IO CInt

-- | Match only devices not belonging to a certain kernel subsystem.
addNoMatchSubsystem :: Enumerate -- ^ context
                    -> Subsystem -- ^ filter for a subsystem of the
                                 -- device to exclude from the list
                    -> IO ()     -- ^ can throw exception
addNoMatchSubsystem :: Enumerate -> Subsystem -> IO ()
addNoMatchSubsystem Enumerate
enumerate Subsystem
subsystem =
  String -> IO CInt -> IO ()
forall a. (Eq a, Num a) => String -> IO a -> IO ()
throwErrnoIfMinus1_ String
"addNoMatchSubsystem" (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$
    Subsystem -> (CString -> IO CInt) -> IO CInt
forall a. Subsystem -> (CString -> IO a) -> IO a
useAsCString Subsystem
subsystem ((CString -> IO CInt) -> IO CInt)
-> (CString -> IO CInt) -> IO CInt
forall a b. (a -> b) -> a -> b
$
      Enumerate -> CString -> IO CInt
c_addNoMatchSubsystem Enumerate
enumerate

-- | \/sys attribute string.
type SysAttr  = ByteString

-- | Attribute specific \/sys value string. Can be an int or
-- identifier depending on attribute.
type SysValue = ByteString

foreign import ccall unsafe "udev_enumerate_add_match_sysattr"
  c_addMatchSysattr :: Enumerate -> CString -> CString -> IO CInt

-- | Match only devices with a certain \/sys device attribute.
addMatchSysattr :: Enumerate      -- ^ context
                -> SysAttr        -- ^ filter for a sys attribute at
                                  -- the device to include in the list
                -> Maybe SysValue -- ^ optional value of the sys attribute
                -> IO ()          -- ^ can throw exception
addMatchSysattr :: Enumerate -> Subsystem -> Maybe Subsystem -> IO ()
addMatchSysattr Enumerate
enumerate Subsystem
sysattr Maybe Subsystem
mvalue =
  (CInt -> Bool) -> String -> IO CInt -> IO ()
forall a. (a -> Bool) -> String -> IO a -> IO ()
throwErrnoIf_ (CInt -> CInt -> Bool
forall a. Ord a => a -> a -> Bool
< CInt
0) String
"addMatchSysattr" (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$
    Subsystem -> (CString -> IO CInt) -> IO CInt
forall a. Subsystem -> (CString -> IO a) -> IO a
useAsCString Subsystem
sysattr ((CString -> IO CInt) -> IO CInt)
-> (CString -> IO CInt) -> IO CInt
forall a b. (a -> b) -> a -> b
$ \ CString
c_sysattr ->
      case Maybe Subsystem
mvalue of
        Maybe Subsystem
Nothing    -> Enumerate -> CString -> CString -> IO CInt
c_addMatchSysattr Enumerate
enumerate CString
c_sysattr CString
forall a. Ptr a
nullPtr
        Just Subsystem
value ->
          Subsystem -> (CString -> IO CInt) -> IO CInt
forall a. Subsystem -> (CString -> IO a) -> IO a
useAsCString Subsystem
value ((CString -> IO CInt) -> IO CInt)
-> (CString -> IO CInt) -> IO CInt
forall a b. (a -> b) -> a -> b
$ \ CString
c_value ->
            Enumerate -> CString -> CString -> IO CInt
c_addMatchSysattr Enumerate
enumerate CString
c_sysattr CString
c_value

foreign import ccall unsafe  "udev_enumerate_add_nomatch_sysattr"
  c_addNoMatchSysattr :: Enumerate -> CString -> CString -> IO CInt

-- | Match only devices not having a certain /sys device attribute.
addNoMatchSysattr :: Enumerate  -- ^ context
                  -> ByteString -- ^ filter for a sys attribute at the
                                -- device to exclude from the list
                  -> Maybe ByteString -- ^ optional value of the sys
                                      -- attribute
                  -> IO ()
addNoMatchSysattr :: Enumerate -> Subsystem -> Maybe Subsystem -> IO ()
addNoMatchSysattr Enumerate
enumerate Subsystem
sysattr Maybe Subsystem
mvalue =
  (CInt -> Bool) -> String -> IO CInt -> IO ()
forall a. (a -> Bool) -> String -> IO a -> IO ()
throwErrnoIf_ (CInt -> CInt -> Bool
forall a. Ord a => a -> a -> Bool
< CInt
0) String
"addNoMatchSysattr" (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$
    Subsystem -> (CString -> IO CInt) -> IO CInt
forall a. Subsystem -> (CString -> IO a) -> IO a
useAsCString Subsystem
sysattr ((CString -> IO CInt) -> IO CInt)
-> (CString -> IO CInt) -> IO CInt
forall a b. (a -> b) -> a -> b
$ \ CString
c_sysattr ->
      case Maybe Subsystem
mvalue of
        Maybe Subsystem
Nothing    -> Enumerate -> CString -> CString -> IO CInt
c_addNoMatchSysattr Enumerate
enumerate CString
c_sysattr CString
forall a. Ptr a
nullPtr
        Just Subsystem
value ->
          Subsystem -> (CString -> IO CInt) -> IO CInt
forall a. Subsystem -> (CString -> IO a) -> IO a
useAsCString Subsystem
value ((CString -> IO CInt) -> IO CInt)
-> (CString -> IO CInt) -> IO CInt
forall a b. (a -> b) -> a -> b
$ \ CString
c_value ->
            Enumerate -> CString -> CString -> IO CInt
c_addNoMatchSysattr Enumerate
enumerate CString
c_sysattr CString
c_value

foreign import ccall unsafe "udev_enumerate_add_match_property"
  c_addMatchProperty :: Enumerate -> CString -> CString -> IO CInt

-- | Match only devices with a certain property.
addMatchProperty :: Enumerate  -- ^ context
                 -> ByteString -- ^ filter for a property of the
                               -- device to include in the list
                 -> ByteString -- ^ value of the property
                 -> IO ()
addMatchProperty :: Enumerate -> Subsystem -> Subsystem -> IO ()
addMatchProperty Enumerate
enumerate Subsystem
prop Subsystem
value =
  (CInt -> Bool) -> String -> IO CInt -> IO ()
forall a. (a -> Bool) -> String -> IO a -> IO ()
throwErrnoIf_ (CInt -> CInt -> Bool
forall a. Ord a => a -> a -> Bool
< CInt
0) String
"addMatchProperty" (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$
    Subsystem -> (CString -> IO CInt) -> IO CInt
forall a. Subsystem -> (CString -> IO a) -> IO a
useAsCString Subsystem
prop ((CString -> IO CInt) -> IO CInt)
-> (CString -> IO CInt) -> IO CInt
forall a b. (a -> b) -> a -> b
$ \ CString
c_prop ->
      Subsystem -> (CString -> IO CInt) -> IO CInt
forall a. Subsystem -> (CString -> IO a) -> IO a
useAsCString Subsystem
value ((CString -> IO CInt) -> IO CInt)
-> (CString -> IO CInt) -> IO CInt
forall a b. (a -> b) -> a -> b
$ \ CString
c_value ->
        Enumerate -> CString -> CString -> IO CInt
c_addMatchProperty Enumerate
enumerate CString
c_prop CString
c_value

foreign import ccall unsafe "udev_enumerate_add_match_tag"
  c_addMatchTag :: Enumerate -> CString -> IO CInt


-- | Match only devices with a certain tag.
addMatchTag :: Enumerate  -- ^ context
            -> ByteString -- ^ filter for a tag of the device to
                          -- include in the list
            -> IO ()
addMatchTag :: Enumerate -> Subsystem -> IO ()
addMatchTag Enumerate
enumerate Subsystem
tag =
  (CInt -> Bool) -> String -> IO CInt -> IO ()
forall a. (a -> Bool) -> String -> IO a -> IO ()
throwErrnoIf_ (CInt -> CInt -> Bool
forall a. Ord a => a -> a -> Bool
< CInt
0) String
"addMatchTag" (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$
    Subsystem -> (CString -> IO CInt) -> IO CInt
forall a. Subsystem -> (CString -> IO a) -> IO a
useAsCString Subsystem
tag ((CString -> IO CInt) -> IO CInt)
-> (CString -> IO CInt) -> IO CInt
forall a b. (a -> b) -> a -> b
$ \ CString
c_tag ->
      Enumerate -> CString -> IO CInt
c_addMatchTag Enumerate
enumerate CString
c_tag

foreign import ccall unsafe "udev_enumerate_add_match_parent"
  c_addMatchParent :: Enumerate -> Device -> IO CInt

-- | Return the devices on the subtree of one given device. The parent
-- itself is included in the list.
--
-- A reference for the device is held until the udev_enumerate context
-- is cleaned up.
--
addMatchParent :: Enumerate -- ^ context
               -> Device    -- ^ parent device where to start searching
               -> IO ()     -- ^ can throw exception
addMatchParent :: Enumerate -> Device -> IO ()
addMatchParent Enumerate
enumerate Device
dev =
  (CInt -> Bool) -> String -> IO CInt -> IO ()
forall a. (a -> Bool) -> String -> IO a -> IO ()
throwErrnoIf_ (CInt -> CInt -> Bool
forall a. Ord a => a -> a -> Bool
< CInt
0) String
"addMatchParent" (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$
    Enumerate -> Device -> IO CInt
c_addMatchParent Enumerate
enumerate Device
dev

foreign import ccall unsafe "udev_enumerate_add_match_is_initialized"
  c_addMatchIsInitialized :: Enumerate -> IO CInt

-- | Match only devices which udev has set up already.
addMatchIsInitialized :: Enumerate -> IO ()
addMatchIsInitialized :: Enumerate -> IO ()
addMatchIsInitialized Enumerate
enumerate =
  String -> IO CInt -> IO ()
forall a. (Eq a, Num a) => String -> IO a -> IO ()
throwErrnoIfMinus1_ String
"addMatchIsInitialized" (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$
    Enumerate -> IO CInt
c_addMatchIsInitialized Enumerate
enumerate

foreign import ccall unsafe "udev_enumerate_add_match_sysname"
  c_addMatchSysname :: Enumerate -> CString -> IO CInt

-- | Match only devices with a given \/sys device name.
addMatchSysname :: Enumerate  -- ^ context
                -> ByteString -- ^ filter for the name of the device
                              -- to include in the list
                -> IO ()      -- ^ can throw exception
addMatchSysname :: Enumerate -> Subsystem -> IO ()
addMatchSysname Enumerate
enumerate Subsystem
sysName =
  String -> IO CInt -> IO ()
forall a. (Eq a, Num a) => String -> IO a -> IO ()
throwErrnoIfMinus1_ String
"addMatchSysname" (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$
    Subsystem -> (CString -> IO CInt) -> IO CInt
forall a. Subsystem -> (CString -> IO a) -> IO a
useAsCString Subsystem
sysName ((CString -> IO CInt) -> IO CInt)
-> (CString -> IO CInt) -> IO CInt
forall a b. (a -> b) -> a -> b
$
      Enumerate -> CString -> IO CInt
c_addMatchSysname Enumerate
enumerate

foreign import ccall unsafe "udev_enumerate_add_syspath"
  c_addSyspath :: Enumerate -> CString -> IO CInt

-- | Add a device to the list of devices, to retrieve it back sorted
-- in dependency order.
--
addSyspath :: Enumerate   -- ^ context
           -> RawFilePath -- ^ path of a device
           -> IO ()       -- ^ can throw exception
addSyspath :: Enumerate -> Subsystem -> IO ()
addSyspath Enumerate
enumerate Subsystem
syspath =
  (CInt -> Bool) -> String -> IO CInt -> IO ()
forall a. (a -> Bool) -> String -> IO a -> IO ()
throwErrnoIf_ (CInt -> CInt -> Bool
forall a. Ord a => a -> a -> Bool
< CInt
0) String
"addSyspath" (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$
    Subsystem -> (CString -> IO CInt) -> IO CInt
forall a. Subsystem -> (CString -> IO a) -> IO a
useAsCString Subsystem
syspath ((CString -> IO CInt) -> IO CInt)
-> (CString -> IO CInt) -> IO CInt
forall a b. (a -> b) -> a -> b
$ \ CString
c_syspath ->
      Enumerate -> CString -> IO CInt
c_addSyspath Enumerate
enumerate CString
c_syspath

{-----------------------------------------------------------------------
--  Scan
-----------------------------------------------------------------------}

foreign import ccall unsafe "udev_enumerate_scan_devices"
  c_scanDevices :: Enumerate -> IO CInt

-- | Scan \/sys for all devices which match the given filters.
scanDevices :: Enumerate -> IO ()
scanDevices :: Enumerate -> IO ()
scanDevices = String -> IO CInt -> IO ()
forall a. (Eq a, Num a) => String -> IO a -> IO ()
throwErrnoIfMinus1_ String
"scanDevices" (IO CInt -> IO ()) -> (Enumerate -> IO CInt) -> Enumerate -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Enumerate -> IO CInt
c_scanDevices

foreign import ccall unsafe "udev_enumerate_scan_subsystems"
  c_scanSubsystems :: Enumerate -> IO CInt

-- | Scan \/sys for all devices which match the given filters.
scanSubsystems :: Enumerate -> IO ()
scanSubsystems :: Enumerate -> IO ()
scanSubsystems = String -> IO CInt -> IO ()
forall a. (Eq a, Num a) => String -> IO a -> IO ()
throwErrnoIfMinus1_ String
"scanSubsystems" (IO CInt -> IO ()) -> (Enumerate -> IO CInt) -> Enumerate -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Enumerate -> IO CInt
c_scanSubsystems

{-----------------------------------------------------------------------
--  Query
-----------------------------------------------------------------------}

foreign import ccall unsafe "udev_enumerate_get_list_entry"
  c_getListEntry :: Enumerate -> IO List

-- | Get the first entry of the sorted list of device paths.
getListEntry :: Enumerate -> IO (Maybe List)
getListEntry :: Enumerate -> IO (Maybe List)
getListEntry Enumerate
enumerate = do
  List
xs <- Enumerate -> IO List
c_getListEntry Enumerate
enumerate
  Maybe List -> IO (Maybe List)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe List -> IO (Maybe List)) -> Maybe List -> IO (Maybe List)
forall a b. (a -> b) -> a -> b
$ if List
xs List -> List -> Bool
forall a. Eq a => a -> a -> Bool
== List
nil then Maybe List
forall a. Maybe a
Nothing else List -> Maybe List
forall a. a -> Maybe a
Just List
xs
{-# INLINE getListEntry #-}