linux-blkid-0.1.0.0: System/Linux/Blkid/Cache.hsc
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE ForeignFunctionInterface #-}
-- | Basic routines to work with @libblkid@ cache.
module System.Linux.Blkid.Cache
( CacheT
, withCache
, gcCache
, probeAll
, probeAllRemovable
, probeAllNew
, verify
, evaluateTagUsingCache
, evaluateSpecUsingCache
, Device
, getDevices
, getDevicesWithTag
, deviceGetTags
, deviceHasTag
, findDeviceWithTag
, DevFlags(..)
, getDevice
, getDevname
, getTagValue
) where
import Control.Exception (bracket)
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
import Foreign
import Foreign.C
import System.Linux.Blkid.Evaluate (Tag(..))
#include <blkid/blkid.h>
foreign import ccall "stdlib.h free"
c_free :: Ptr a -> IO ()
type BlkidCache = Ptr BlkidStructCache
data BlkidStructCache
-- | An operation using the @blkid.tab@ cache file, and returning a
-- value of type @a@. The computation is not performed until
-- @'withCache'@ is called.
newtype CacheT m a = CacheT {runCacheT :: StateT BlkidCache m a}
instance MonadTrans CacheT where
lift = CacheT . lift
instance Functor f => Functor (CacheT f) where
fmap f = CacheT . (fmap f) . runCacheT
instance Monad m => Monad (CacheT m) where
return = CacheT . return
(CacheT m) >>= f = CacheT (m >>= runCacheT . f)
fail = CacheT . fail
instance MonadIO m => MonadIO (CacheT m) where
liftIO = CacheT . liftIO
-- | Run a @'CacheT'@ operation using the given cache file, or the
-- default (@\/run\/blkid\/blkid.tab@) if @'Nothing'@ is passed. After
-- completing the computation, the new cache is written to the file if
-- the user has sufficent privileges.
withCache :: MonadIO m => Maybe FilePath -> CacheT m a -> m a
withCache mpath (CacheT (StateT f)) =
do c <- liftIO $
maybeWith withCString mpath $ \cstr ->
alloca $ \p ->
do e <- get_cache p cstr
if e < 0
then fail ("blkid_get_cache returned " ++ show e)
else peek p
(a,c') <- f c
liftIO $ put_cache c'
return a
--getDevices :: MonadIO m => CacheT m [Device]
--getDevice :: MonadIO m => String -> DevFlags -> CacheT m (Maybe Device)
type BlkidDev = Ptr Device
-- | A device as represented in the @blkid.tab@ cache file.
data Device = Device (ForeignPtr Device) String
withDevice :: Device -> (BlkidDev -> IO a) -> IO a
withDevice (Device p _) = withForeignPtr p
toDevice :: BlkidDev -> IO Device
toDevice p = do fp <- newForeignPtr_ p
name <- dev_devname p >>= peekCString
return (Device fp name)
instance Show Device where
show (Device _ name) = name
-- | Removes garbage (non-existing devices) from the cache.
gcCache :: MonadIO m => CacheT m ()
gcCache = CacheT $ StateT $ \c -> do liftIO $ gc_cache c
return ((),c)
-- | Probes all block devices.
probeAll :: MonadIO m => CacheT m ()
probeAll =
CacheT $ StateT $ \c ->
do liftIO $ throwIfNeg_ (\e -> "blkid_probe_all returned " ++ show e)
(probe_all c)
return ((),c)
-- | The libblkid probing is based on devices from
-- @\/proc\/partitions@ by default. This file usually does not contain
-- removable devices (e.g. CDROMs) and this kind of devices are
-- invisible for libblkid.
--
-- This function adds removable block devices to cache (probing is
-- based on information from the @\/sys@ directory). Don't forget that
-- removable devices (floppies, CDROMs, ...) could be pretty
-- slow. It's very bad idea to call this function by default.
--
-- Note that devices which were detected by this function won't be
-- written to @blkid.tab@ cache file.
probeAllRemovable :: MonadIO m => CacheT m ()
probeAllRemovable =
CacheT $ StateT $ \c ->
do liftIO $ throwIfNeg_ (\e -> "blkid_probe_all_removable returned " ++
show e) (probe_all_removable c)
return ((),c)
-- | Probes all new block devices.
probeAllNew :: MonadIO m => CacheT m ()
probeAllNew =
CacheT $ StateT $ \c ->
do liftIO $ throwIfNeg_ (\e -> "blkid_probe_all_new returned " ++
show e) (probe_all_new c)
return ((),c)
-- | Verify that the data in @'Device'@ is consistent with what is on
-- the actual block device (using the devname field only). Normally
-- this will be called when finding items in the cache, but for long
-- running processes is also desirable to revalidate an item before
-- use.
verify :: MonadIO m => Device -> CacheT m Device
verify dev =
CacheT $ StateT $ \c ->
do dev' <- liftIO $ withDevice dev $ \d -> blkid_verify c d >>= toDevice
return (dev',c)
-- | Get the list of tags and values for the given @'Device'@.
deviceGetTags :: MonadIO m => Device -> m [(String, String)]
deviceGetTags dev =
liftIO $ withDevice dev $ \d -> bracket (begin d) tag_iterate_end getTag
where begin ptr = do it <- tag_iterate_begin ptr
if it == nullPtr
then fail "blkid_tag_iterate_begin returned NULL"
else return it
getTag iter = alloca $ \t ->
alloca $ \v ->
do e <- tag_next iter t v
if e < 0
then return []
else do typ <- peek t >>= peekCString
val <- peek v >>= peekCString
xs <- getTag iter
return ((typ,val):xs)
-- | Check if @'Device'@ has the give tag and value.
deviceHasTag :: MonadIO m => Device -> String -> String -> m Bool
deviceHasTag dev typ val =
liftIO $ withDevice dev $ \d ->
withCString typ $ \t ->
withCString val $ \v ->
dev_has_tag d t v >>= return . toBool
-- | Find a @'Device'@ in cache matching the given tag and value.
findDeviceWithTag :: MonadIO m => String -> String -> CacheT m (Maybe Device)
findDeviceWithTag typ val =
CacheT $ StateT $ \c ->
do mdev <- liftIO $ withCString typ $ \t ->
withCString val $ \v ->
find_dev_with_tag c t v >>= maybePeek toDevice
return (mdev,c)
-- | Flags for @'getDevice'@.
data DevFlags = Find
| Create
| Verify
| Normal
deriving (Eq, Show)
fromDevFlags :: Num a => DevFlags -> a
fromDevFlags Find = #{const BLKID_DEV_FIND}
fromDevFlags Create = #{const BLKID_DEV_CREATE}
fromDevFlags Verify = #{const BLKID_DEV_VERIFY}
fromDevFlags Normal = #{const BLKID_DEV_NORMAL}
-- | Get the device in cache with the given name.
getDevice :: MonadIO m => String -> DevFlags -> CacheT m (Maybe Device)
getDevice nam fl =
CacheT $ StateT $ \c ->
do mdev <- liftIO $ withCString nam $ \n ->
get_dev c n (fromDevFlags fl) >>= maybePeek toDevice
return (mdev,c)
-- | Get the device name of the device in cache matching the given tag
-- and value.
getDevname :: MonadIO m => String -> String -> CacheT m (Maybe String)
getDevname tok val =
CacheT $ StateT $ \c ->
do mstr <- liftIO $ withCString tok $ \t ->
withCString val $ \v ->
bracket (get_devname c t v) c_free (maybePeek peekCString)
return (mstr,c)
-- | Get the value of a tag given the tag and device name.
getTagValue :: MonadIO m => String -> String -> CacheT m (Maybe String)
getTagValue tok nam =
CacheT $ StateT $ \c ->
do mstr <- liftIO $ withCString tok $ \t ->
withCString nam $ \n ->
bracket (get_tag_value c t n) c_free (maybePeek peekCString)
return (mstr,c)
devIterate :: MonadIO m => Maybe String -> String -> CacheT m [Device]
devIterate mtok val =
CacheT $ StateT $ \c ->
do devs <- liftIO $ bracket (getIter c) dev_iterate_end getDev
return (devs,c)
where getIter p = do it <- dev_iterate_begin p
if it == nullPtr
then fail "blkid_dev_iterate_begin returned NULL"
else case mtok of
Nothing -> return it
Just tok ->
withCString tok $ \t ->
withCString val $ \v ->
do throwIfNeg_ (\e -> "blkid_dev_set_search returned " ++ show e)
(dev_set_search it t v)
return it
getDev it = alloca $ \pd -> do e <- dev_next it pd
if e < 0
then return []
else do d <- peek pd >>= toDevice
ds <- getDev it
return (d:ds)
-- | Get the list of devices in cache.
getDevices :: MonadIO m => CacheT m [Device]
getDevices = devIterate Nothing []
-- | Get the list of devices in cache matching the given tag and value.
getDevicesWithTag :: MonadIO m => String -> String -> CacheT m [Device]
getDevicesWithTag tok val = devIterate (Just tok) val
-- | Get the partition or filesystem device with the given @'Tag'@,
-- using the cache file.
evaluateTagUsingCache :: MonadIO m => Tag -> CacheT m (Maybe String)
evaluateTagUsingCache (Label str) = evalTag "LABEL" str
evaluateTagUsingCache (PartLabel str) = evalTag "PARTLABEL" str
evaluateTagUsingCache (UUID str) = evalTag "UUID" str
evaluateTagUsingCache (PartUUID str) = evalTag "PARTUUID" str
evalTag :: MonadIO m => String -> String -> CacheT m (Maybe String)
evalTag tok val =
CacheT $ StateT $ \c ->
liftIO $ alloca $ \p ->
do poke p c
mstr <- bracket (withCString tok $ \t ->
withCString val $ \v ->
evaluate_tag t v p)
c_free
(maybePeek peekCString)
c' <- peek p
return (mstr,c')
-- | Get the desired partition or filesystem device, using the cache
-- file.
evaluateSpecUsingCache :: MonadIO m => String -> CacheT m (Maybe String)
evaluateSpecUsingCache spec =
CacheT $ StateT $ \c ->
liftIO $ alloca $ \p ->
do poke p c
mstr <- bracket (withCString spec $ \s -> evaluate_spec s p)
c_free
(maybePeek peekCString)
c' <- peek p
return (mstr,c')
type BlkidDevIterate = Ptr DevIterate
data DevIterate
type BlkidTagIterate = Ptr TagIterate
data TagIterate
foreign import ccall "blkid_put_cache" put_cache :: BlkidCache -> IO ()
foreign import ccall "blkid_get_cache" get_cache :: Ptr BlkidCache
-> CString
-> IO CInt
foreign import ccall "blkid_gc_cache" gc_cache :: BlkidCache -> IO ()
foreign import ccall "blkid_dev_devname" dev_devname :: BlkidDev -> IO CString
foreign import ccall "blkid_dev_iterate_begin"
dev_iterate_begin :: BlkidCache -> IO BlkidDevIterate
foreign import ccall "blkid_dev_set_search" dev_set_search :: BlkidDevIterate
-> CString
-> CString
-> IO CInt
foreign import ccall "blkid_dev_next" dev_next :: BlkidDevIterate
-> Ptr BlkidDev
-> IO CInt
foreign import ccall "blkid_dev_iterate_end" dev_iterate_end :: BlkidDevIterate
-> IO ()
foreign import ccall "blkid_probe_all" probe_all :: BlkidCache -> IO CInt
foreign import ccall "blkid_probe_all_new" probe_all_new :: BlkidCache
-> IO CInt
foreign import ccall "blkid_probe_all_removable"
probe_all_removable :: BlkidCache -> IO CInt
foreign import ccall "blkid_get_dev"
get_dev :: BlkidCache -> CString -> CInt -> IO BlkidDev
foreign import ccall "blkid_verify"
blkid_verify :: BlkidCache -> BlkidDev -> IO BlkidDev
foreign import ccall "blkid_get_tag_value"
get_tag_value :: BlkidCache -> CString -> CString -> IO CString
foreign import ccall "blkid_get_devname"
get_devname :: BlkidCache -> CString -> CString -> IO CString
foreign import ccall "blkid_tag_iterate_begin"
tag_iterate_begin :: BlkidDev -> IO BlkidTagIterate
foreign import ccall "blkid_tag_next"
tag_next :: BlkidTagIterate -> Ptr CString -> Ptr CString -> IO CInt
foreign import ccall "blkid_tag_iterate_end"
tag_iterate_end :: BlkidTagIterate -> IO ()
foreign import ccall "blkid_dev_has_tag"
dev_has_tag :: BlkidDev -> CString -> CString -> IO CInt
foreign import ccall "blkid_find_dev_with_tag"
find_dev_with_tag :: BlkidCache -> CString -> CString -> IO BlkidDev
foreign import ccall "blkid_evaluate_tag" evaluate_tag :: CString
-> CString
-> Ptr BlkidCache
-> IO CString
foreign import ccall "blkid_evaluate_spec" evaluate_spec :: CString
-> Ptr BlkidCache
-> IO CString