linux-file-extents (empty) → 0.1.0.0
raw patch · 4 files changed
+306/−0 lines, 4 filesdep +basedep +unixsetup-changed
Dependencies added: base, unix
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- System/Linux/FileExtents.hsc +249/−0
- linux-file-extents.cabal +25/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, Marios Titas++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Marios Titas nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ System/Linux/FileExtents.hsc view
@@ -0,0 +1,249 @@+------------------------------------------------------------------------------+-- |+-- 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
+ linux-file-extents.cabal view
@@ -0,0 +1,25 @@+name: linux-file-extents+version: 0.1.0.0+synopsis: Retrieve file fragmentation information under Linux+description:+ This package provides a simple wrapper around the Linux FIEMAP ioctl.+ It can be used to retrieve a complete list of all the extents of any given+ file, i.e. the area of the disk where the file is actually stored. This is+ similar to the @filefrag@ command line tool provided by the @e2fsprogs@+ package.+homepage: https://github.com/redneb/linux-file-extents+bug-reports: https://github.com/redneb/linux-file-extents/issues+license: BSD3+license-file: LICENSE+author: Marios Titas <rednebΑΤgmxDΟΤcom>+maintainer: Marios Titas <rednebΑΤgmxDΟΤcom>+category: System, Filesystem, FFI+build-type: Simple+cabal-version: >=1.10++library+ exposed-modules: System.Linux.FileExtents+ build-depends: base >=4.6 && <5, unix >=2.6+ build-tools: hsc2hs+ default-language: Haskell2010+ ghc-options: -Wall