Unixutils-1.54.3: System/Unix/SpecialDevice.hs
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, TypeSynonymInstances #-}
-- | Construct an ADT representing block and character devices
-- (but mostly block devices) by interpreting the contents of
-- the Linux sysfs filesystem.
module System.Unix.SpecialDevice
(SpecialDevice,
sysMountPoint, -- IO String
ofNode, -- FilePath -> IO (Maybe SpecialDevice)
ofNodeStatus, -- FileStatus -> Maybe SpecialDevice
ofPath, -- FilePath -> IO (Maybe SpecialDevice)
rootPart, -- IO (Maybe SpecialDevice)
ofDevNo, -- (DeviceID -> SpecialDevice) -> Int -> SpecialDevice
ofSysName, -- String -> IO (Maybe SpecialDevice)
ofSysPath, -- (DeviceID -> SpecialDevice) -> FilePath -> IO (Maybe SpecialDevice)
toDevno, -- SpecialDevice -> Int
--major, -- SpecialDevice -> Int
--minor, -- SpecialDevice -> Int
ofMajorMinor, -- (DeviceID -> SpecialDevice) -> Int -> Int -> SpecialDevice
node, -- SpecialDevice -> IO (Maybe FilePath)
nodes, -- SpecialDevice -> IO [FilePath]
sysName, -- SpecialDevice -> IO (Maybe String)
splitPart, -- String -> (String, Int)
sysDir, -- SpecialDevice -> IO (Maybe FilePath)
diskOfPart, -- SpecialDevice -> IO (Maybe SpecialDevice)
getAllDisks, -- IO [SpecialDevice]
getAllPartitions, -- IO [SpecialDevice]
getAllCdroms, -- IO [SpecialDevice]
getAllRemovable, -- IO [SpecialDevice]
-- toDevName,
-- getBlkidAlist,
-- getBlkidInfo,
-- deviceOfUuid,
-- devicesOfLabel,
-- updateBlkidFns,
-- update
)
where
import Control.Exception
import System.IO
import System.Directory
import Data.Char
import Data.List
import Data.Maybe
import System.FilePath
import System.Posix.Types
import System.Posix.Files
import System.Posix.User
import Text.Regex.TDFA
data SpecialDevice =
BlockDevice DeviceID | CharacterDevice DeviceID
deriving (Show, Ord, Eq)
-- | FIXME: We should really get this value from the mount table.
sysMountPoint :: FilePath
sysMountPoint = "/sys"
ofPath :: FilePath -> IO (Maybe SpecialDevice)
ofPath path =
-- Catch the exception thrown on an invalid symlink
(try $ getFileStatus path) >>=
return . either (\ (_ :: SomeException) -> Nothing) (Just . BlockDevice . deviceID)
rootPart :: IO (Maybe SpecialDevice)
rootPart = ofPath "/"
-- | Return the device represented by a device node, such as \/dev\/sda2.
-- Returns Nothing if there is an exception trying to stat the node, or
-- if the node turns out not to be a special device.
ofNode :: FilePath -> IO (Maybe SpecialDevice)
ofNode "/dev/root" = ofPath "/"
ofNode node = (try $ getFileStatus node) >>= return . either (\ (_ :: SomeException) -> Nothing) ofNodeStatus
ofNodeStatus :: FileStatus -> Maybe SpecialDevice
ofNodeStatus status =
if isBlockDevice status then
(Just . BlockDevice . specialDeviceID $ status) else
if isCharacterDevice status then
(Just . CharacterDevice . specialDeviceID $ status) else
Nothing
ofSysName :: String -> IO (Maybe SpecialDevice)
ofSysName name =
do
paths <- directory_find False (sysMountPoint ++ "/block") >>= return . map fst . filter isDev
case filter (\ x -> basename (dirname x) == name) paths of
[path] -> ofSysPath BlockDevice (dirname path)
where
isDev (path, status) = basename path == "dev"
ofSysPath :: (DeviceID -> SpecialDevice) -> FilePath -> IO (Maybe SpecialDevice)
ofSysPath typ path = readFile (path ++ "/dev") >>= return . parseSysDevFile typ
parseSysDevFile :: (DeviceID -> SpecialDevice) -> String -> Maybe SpecialDevice
parseSysDevFile typ text =
case filter (all isDigit) . groupBy (\ a b -> isDigit a && isDigit b) $ text of
[major, minor] -> Just (ofMajorMinor typ (read major) (read minor))
_ -> Nothing
ofMajorMinor :: (DeviceID -> SpecialDevice) -> Int -> Int -> SpecialDevice
ofMajorMinor typ major minor = ofDevNo typ $ major * 256 + minor
ofDevNo :: (DeviceID -> SpecialDevice) -> Int -> SpecialDevice
ofDevNo typ n = typ . fromInteger . toInteger $ n
{-
major :: SpecialDevice -> Integer
major dev = toInteger (toDevno dev)
minor :: SpecialDevice -> Int
minor dev = mod (fromInteger (toInteger (toDevno dev))) 256
-}
toDevno :: SpecialDevice -> DeviceID
toDevno (BlockDevice n) = n
toDevno (CharacterDevice n) = n
node :: SpecialDevice -> IO (Maybe FilePath)
node dev@(BlockDevice _) = nodes dev >>= return . listToMaybe
nodes :: SpecialDevice -> IO [FilePath]
nodes dev@(BlockDevice _) =
do
pairs <- directory_find True "/dev" >>=
return .
filter (not . isPrefixOf "/dev/.static/" . fst) .
filter (not . isPrefixOf "/dev/.udevdb/" . fst)
let pairs' = filter (\ (node, status) -> (ofNodeStatus status) == Just dev) pairs
return . map fst $ pairs'
where
mapSnd f (a, b) = (a, f b)
splitPart :: String -> (String, Int)
splitPart name =
mapSnd read (break isDigit name)
where mapSnd f (a, b) = (a, f b)
diskOfPart :: SpecialDevice -> IO (Maybe SpecialDevice)
diskOfPart part =
sysName part >>=
return . maybe Nothing (Just . fst . splitPart) >>=
maybe (return Nothing) ofSysName
sysName :: SpecialDevice -> IO (Maybe String)
sysName dev = sysDir dev >>= return . maybe Nothing (Just . basename)
sysDir :: SpecialDevice -> IO (Maybe FilePath)
sysDir dev@(BlockDevice _) =
do
(pairs' :: [(FilePath, FileStatus)]) <- directory_find False (sysMountPoint ++ "/block")
let (paths :: [FilePath]) = map fst . filter isDev $ pairs'
devs <- mapM readFile paths >>= return . map (parseSysDevFile BlockDevice)
let pairs = zip devs (map dirname paths)
return . lookup (Just dev) $ pairs
where
isDev (path, status) = basename path == "dev"
diskGroup :: IO GroupID
diskGroup = getGroupEntryForName "disk" >>= return . groupID
cdromGroup :: IO GroupID
cdromGroup = getGroupEntryForName "cdrom" >>= return . groupID
-- | Removable devices, such as USB keys, are in this group.
floppyGroup :: IO GroupID
floppyGroup = getGroupEntryForName "floppy" >>= return . groupID
getDisksInGroup :: GroupID -> IO [SpecialDevice]
getDisksInGroup group =
directory_find True "/dev/disk/by-path" >>=
return . filter (inGroup group) >>=
return . catMaybes . map (ofNodeStatus . snd)
where
inGroup group (_, status) = fileGroup status == group
getAllDisks :: IO [SpecialDevice]
getAllDisks =
do
group <- diskGroup
devs <- directory_find True "/dev/disk/by-path" >>=
return . filter (not . isPart) . filter (inGroup group) >>=
return . map (ofNodeStatus . snd)
return (catMaybes devs)
where
inGroup group (_, status) = fileGroup status == group
getAllPartitions :: IO [SpecialDevice]
getAllPartitions =
directory_find True "/dev/disk/by-path" >>= return . filter isPart >>= return . catMaybes . map (ofNodeStatus . snd)
isPart :: (FilePath, FileStatus) -> Bool
isPart (path, _) =
case path =~ "-part[0-9]+$" of
x | mrMatch x == "" -> False
x -> True
getAllCdroms :: IO [SpecialDevice]
getAllCdroms = cdromGroup >>= getDisksInGroup
getAllRemovable :: IO [SpecialDevice]
getAllRemovable = floppyGroup >>= getDisksInGroup
-- ofNode "/dev/sda1" >>= maybe (return Nothing) sysDir >>= putStrLn . show
-- -> Just "/sys/block/sda/sda1/dev"
-- | Traverse a directory and return a list of all the (path,
-- fileStatus) pairs.
directory_find :: Bool -> FilePath -> IO [(FilePath, FileStatus)]
directory_find follow path =
if follow then fileStatus else linkStatus
where
fileStatus = try (getFileStatus path) >>= either (\ (_ :: SomeException) -> return []) useStatus
linkStatus = getSymbolicLinkStatus path >>= useStatus
useStatus status
| isDirectory status =
do -- Catch the exception thrown if we lack read permission
subs <- (try $ getDirectoryContents path) >>=
return . either (\ (_ :: SomeException) -> []) id >>=
return . map (path </>) . filter (not . flip elem [".", ".."]) >>=
mapM (directory_find follow) >>=
return . concat
return $ (path, status) : subs
| True = return [(path, status)]
dirname path = reverse . tail . snd . break (== '/') . reverse $ path
basename path = reverse . fst . break (== '/') . reverse $ path