linux-file-extents-0.1.0.0: System/Linux/FileExtents.hsc
------------------------------------------------------------------------------
-- |
-- Module : System.Linux.FileExtents
--
-- Stability : provisional
-- Portability : non-portable (requires Linux)
--
-- This module can be used to retrieve information about how a
-- particular file is stored on disk (i.e. the file fragmentation).
-- It accomplishes that by directly calling the FIEMAP ioctl provided by
-- recent versions of the Linux kernel. This ioctl is specific to Linux
-- and therefore this module is not portable.
--
-- For more information about the FIEMAP ioctl see @filesystems/fiemap.txt@
-- in the kernel documentation.
--
------------------------------------------------------------------------------
module System.Linux.FileExtents
( -- * Extent flags
-- |See @filesystems/fiemap.txt@ in the kernel documentation for a more
-- detailed description of each of these flags.
ExtentFlags
, efLast
, efUnknown
, efDelalloc
, efEncoded
, efDataEncrypted
, efNotAligned
, efDataInline
, efDataTail
, efUnwritten
, efMerged
, efShared
-- * Extents
, Extent(..)
-- * Request flags
, Flags(..)
, defaultFlags
-- * Getting extent information
, getExtentsFd
, getExtents
, getExtentCountFd
, getExtentCount
) where
import Control.Monad
import Control.Exception
import Data.Maybe
import Foreign hiding (void)
import Foreign.C
import System.Posix.Types
import System.Posix.IO
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <linux/fiemap.h>
--------------------------------------------------------------------------------
-- extent flags
type ExtentFlags = Word32
-- |Last extent in file.
efLast :: ExtentFlags
efLast = #const FIEMAP_EXTENT_LAST
-- |Data location unknown.
efUnknown :: ExtentFlags
efUnknown = #const FIEMAP_EXTENT_UNKNOWN
-- |Location still pending.
efDelalloc :: ExtentFlags
efDelalloc = #const FIEMAP_EXTENT_DELALLOC
-- |Data cannot be read while fs is unmounted.
efEncoded :: ExtentFlags
efEncoded = #const FIEMAP_EXTENT_ENCODED
-- |Data is encrypted by fs.
efDataEncrypted :: ExtentFlags
efDataEncrypted = #const FIEMAP_EXTENT_DATA_ENCRYPTED
-- |Extent offsets may not be block aligned.
efNotAligned :: ExtentFlags
efNotAligned = #const FIEMAP_EXTENT_NOT_ALIGNED
-- |Data mixed with metadata.
efDataInline :: ExtentFlags
efDataInline = #const FIEMAP_EXTENT_DATA_INLINE
-- |Multiple files in block.
efDataTail :: ExtentFlags
efDataTail = #const FIEMAP_EXTENT_DATA_TAIL
-- |Space allocated, but no data (i.e. zero).
efUnwritten :: ExtentFlags
efUnwritten = #const FIEMAP_EXTENT_UNWRITTEN
-- |File does not natively support extents. Result merged for efficiency.
efMerged :: ExtentFlags
efMerged = #const FIEMAP_EXTENT_MERGED
-- |Space shared with other files.
efShared :: ExtentFlags
efShared = #const FIEMAP_EXTENT_SHARED
--------------------------------------------------------------------------------
-- extent type
-- |Description of a single extent. All offsets and lengths are in bytes.
data Extent = Extent
{ extLogical :: Word64 -- ^ Offset relative to the beginning of the file.
, extPhysical :: Word64 -- ^ Offset relative to the beginning of the underlying block device.
, extLength :: Word64 -- ^ The length of the extent.
, extFlags :: ExtentFlags -- ^ Flags for this extent.
}
deriving (Show, Eq)
instance Storable Extent where
sizeOf _ = #size struct fiemap_extent
alignment _ = alignment (undefined :: Int)
peek ptr = do
extLogical_ <- (#peek struct fiemap_extent, fe_logical ) ptr
extPhysical_ <- (#peek struct fiemap_extent, fe_physical) ptr
extLength_ <- (#peek struct fiemap_extent, fe_length ) ptr
extFlags_ <- (#peek struct fiemap_extent, fe_flags ) ptr
return (Extent extLogical_ extPhysical_ extLength_ extFlags_)
poke ptr ext = do
memset (castPtr ptr) 0 (#size struct fiemap_extent)
(#poke struct fiemap_extent, fe_logical ) ptr (extLogical ext)
(#poke struct fiemap_extent, fe_physical) ptr (extPhysical ext)
(#poke struct fiemap_extent, fe_length ) ptr (extLength ext)
(#poke struct fiemap_extent, fe_flags ) ptr (extFlags ext)
--------------------------------------------------------------------------------
-- request flags
-- |Request flags.
data Flags = Flags
{ fSync :: Bool -- ^ Sync the file before requesting its extents.
, fXattr :: Bool -- ^ Retrieve the extents of the inode's extended attribute lookup tree, instead of its data tree.
}
deriving (Show, Eq)
-- |Default values for the request flags. Both 'fSync' and 'fXattr' are set
-- to False.
defaultFlags :: Flags
defaultFlags = Flags False False
encodeFlags :: Flags -> Word32
encodeFlags f =
(if fSync f then (#const FIEMAP_FLAG_SYNC) else 0)
.|.
(if fXattr f then (#const FIEMAP_FLAG_XATTR) else 0)
--------------------------------------------------------------------------------
-- get extents
-- | Retrieve the list of all extents associated with the file
-- referenced by the file descriptor. Extents returned mirror those on disk
-- - that is, the logical offset of the first returned extent may start
-- before the requested range, and the last returned extent may end after
-- the end of the requested range.
--
-- Note: 'getExtentsFd' might call the FIEMAP ioctl multiple times in order to
-- retrieve all the extents of the file. This is necessary when the file
-- has too many fragments. If the file is modified in the meantime, the
-- returned list might be inconsistent.
getExtentsFd
:: Flags
-> Fd
-> Maybe (Word64, Word64) -- ^ The range (offset and length) within the file to look extents for. Use 'Nothing' for the entire file.
-> IO [Extent]
getExtentsFd flags (Fd fd) range =
allocaBytes allocSize $ \fiemap -> do
let (start, len) = fromMaybe (0, maxBound) range
memset (castPtr fiemap) 0 (#size struct fiemap)
l <- getExtentsFd' start len fiemap
return (concat l)
where
getExtentsFd' start len fiemap = do
(#poke struct fiemap, fm_start ) fiemap start
(#poke struct fiemap, fm_length ) fiemap len
(#poke struct fiemap, fm_flags ) fiemap flags'
(#poke struct fiemap, fm_extent_count) fiemap maxExtentCount
throwErrnoIfMinus1_ "getExtentsFd" $ ioctl fd (#const FS_IOC_FIEMAP) fiemap
mappedExtents <- (#peek struct fiemap, fm_mapped_extents) fiemap :: IO Word32
let extentsPtr = fiemap `plusPtr` (#offset struct fiemap, fm_extents)
extents <- peekArray (fromIntegral mappedExtents) extentsPtr
case extents of
(_ : _) | mappedExtents == maxExtentCount
, lExt <- last extents
, lExtEnd <- extLogical lExt + extLength lExt
, bytesLeft <- start + len - lExtEnd
, bytesLeft > 0 -> do
more <- getExtentsFd' lExtEnd bytesLeft fiemap
return (extents : more)
_ -> return [extents]
flags' = encodeFlags flags
maxExtentCount :: Word32
maxExtentCount = (fromIntegral allocSize - (#size struct fiemap)) `quot` (#size struct fiemap_extent);
allocSize = 16 * 1024
-- |Like 'getExtentsFd' except that it operates on file paths instead of
-- file descriptors.
getExtents :: Flags -> FilePath -> Maybe (Word64, Word64) -> IO [Extent]
getExtents flags path range = do
bracket (openFd path ReadOnly Nothing defaultFileFlags) closeFd $ \fd ->
getExtentsFd flags fd range
--------------------------------------------------------------------------------
-- get extent count
-- |Like 'getExtentsFd' except that it returns the number of extents
-- instead of a list.
getExtentCountFd :: Flags -> Fd -> Maybe (Word64, Word64) -> IO Word32
getExtentCountFd flags (Fd fd) range = do
let (start, len) = fromMaybe (0, maxBound) range
allocaBytes (#size struct fiemap) $ \fiemap -> do
memset (castPtr fiemap) 0 (#size struct fiemap)
(#poke struct fiemap, fm_start ) fiemap start
(#poke struct fiemap, fm_length ) fiemap len
(#poke struct fiemap, fm_flags ) fiemap flags'
(#poke struct fiemap, fm_extent_count) fiemap (0 :: Word32)
throwErrnoIfMinus1_ "getExtentCountFd" $ ioctl fd (#const FS_IOC_FIEMAP) fiemap
#{peek struct fiemap, fm_mapped_extents} fiemap
where
flags' = encodeFlags flags
-- |Like 'getExtents' except that it returns the number of extents
-- instead of a list.
getExtentCount :: Flags -> FilePath -> Maybe (Word64, Word64) -> IO Word32
getExtentCount flags path range = do
bracket (openFd path ReadOnly Nothing defaultFileFlags) closeFd $ \fd ->
getExtentCountFd flags fd range
--------------------------------------------------------------------------------
-- auxiliary stuff
foreign import ccall unsafe ioctl :: CInt -> CULong -> Ptr a -> IO CInt
foreign import ccall unsafe "string.h memset"
c_memset :: Ptr a -> CInt -> CSize -> IO (Ptr a)
memset :: Ptr a -> Word8 -> CSize -> IO ()
memset p b l = void $ c_memset p (fromIntegral b) l