packages feed

halfs-2.0: Halfs/BlockMap.hs

{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances, BangPatterns #-}
module Halfs.BlockMap
  (
  -- * Types
    BlockGroup(..)
  , BlockMap(..)
  , Extent(..)
  -- * Block Map creation, de/serialization, and query functions
  , newBlockMap
  , readBlockMap
  , writeBlockMap
  , numFreeBlocks
  -- * Block Map allocation/unallocation functions
  , alloc1
  , allocBlocks
  , unalloc1
  , unallocBlocks
  -- * Utility functions
  , blkGroupExts
  , blkGroupSz
  , blkRange
  , blkRangeExt
  , blkRangeBG
  -- * Internal use only
  , blockMapSizeBlks
  , newUsedBitmap
  , writeUsedBitmap
  )
 where

import Control.Exception (assert)
import Data.Bits hiding (setBit, clearBit)
import qualified Data.Bits as B 
import qualified Data.ByteString as BS
import Data.FingerTree
import qualified Data.Foldable as DF
import Data.Monoid
import Data.Word
import Prelude hiding (null)

import Halfs.Classes
import Halfs.Monad
import Halfs.Utils
import System.Device.BlockDevice

-- ----------------------------------------------------------------------------
--
-- Important block format diagram for a ficticious block device w/ 36 blocks;
-- note that the superblock always consumes exactly one block, while the
-- blockmap itself may span multiple blocks as needed, depending on device
-- geometry.
--
--                      1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3
--  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- |S|M| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
--
--

{-

TODO: 

-}

data BlockGroup = Contig Extent | Discontig [Extent]
  deriving (Show, Eq)

data Extent = Extent { extBase :: Word64, extSz :: Word64 }
  deriving (Show, Eq)

newtype ExtentSize = ES Word64
type    FreeTree   = FingerTree ExtentSize Extent

instance Monoid ExtentSize where
  mempty                = ES minBound
  mappend (ES a) (ES b) = ES (max a b)

instance Measured ExtentSize Extent where
  measure (Extent _ s)  = ES s

splitBlockSz :: Word64 -> FreeTree -> (FreeTree, FreeTree)
splitBlockSz sz = split $ \(ES y) -> y >= sz

insert :: Extent -> FreeTree -> FreeTree
insert ext tr = treeL >< (ext <| treeR)
 where (treeL, treeR) = splitBlockSz (extSz ext) tr

-- ----------------------------------------------------------------------------

data BlockMap b r l = BM {
    bmFreeTree :: r FreeTree
  , bmUsedMap  :: b          -- ^ Is the given block free?
  , bmNumFree  :: r Word64   -- ^ Number of available free blocks; the blockmap
                             -- never counts blocks required for storing
                             -- blockmap itself nor the superblock as 'free'
  , bmLock     :: l
  }

-- | Calculate the number of bytes required to store a block map for the
-- given number of blocks
blockMapSizeBytes :: Word64 -> Word64
blockMapSizeBytes numBlks = numBlks `divCeil` 8

-- | Calculate the number of blocks required to store a block map for
-- the given number of blocks.
blockMapSizeBlks :: Word64 -> Word64 -> Word64
blockMapSizeBlks numBlks blkSzBytes = bytes `divCeil` blkSzBytes
  where bytes = blockMapSizeBytes numBlks

-- | Create a new block map for the given device geometry
newBlockMap :: (Monad m, Reffable r m, Bitmapped b m, Lockable l m) =>
               BlockDevice m
            -> m (BlockMap b r l)
newBlockMap dev = do
  when (numFree == 0) $ fail "Block device is too small for block map creation"

  bArr     <- newUsedBitmap dev
  treeR    <- newRef $ singleton $ Extent baseFreeIdx numFree
  numFreeR <- newRef numFree
  lk       <- newLock 
  return $ assert (baseFreeIdx + numFree == numBlks) $
    BM treeR bArr numFreeR lk
 where
  numBlks        = bdNumBlocks dev
  blockMapSzBlks = blockMapSizeBlks numBlks (bdBlockSize dev)
  baseFreeIdx    = blockMapSzBlks + 1
  numFree        = numBlks - blockMapSzBlks - 1 {- -1 for superblock -}

newUsedBitmap :: (Monad m, Reffable r m, Bitmapped b m, Lockable l m) =>
               BlockDevice m
            -> m b
newUsedBitmap dev = do
  -- We overallocate the bitmap up to the entire size of the block(s)
  -- needed for the block map region so that de/serialization in the
  -- {read,write}BlockMap functions is straightforward
  bArr <- newBitmap totalBits False
  let markUsed (l,h) = forM_ [l..h] (setBit bArr)
  mapM_ markUsed
    [ (0, 0)                   -- superblock
    , (1, blockMapSzBlks)      -- blocks for storing the block map
    , (numBlks, totalBits - 1) -- overallocated region
    ]
  return bArr
  where
    numBlks        = bdNumBlocks dev
    totalBits      = blockMapSzBlks * bdBlockSize dev * 8
    blockMapSzBlks = blockMapSizeBlks numBlks (bdBlockSize dev)

-- | Read in the block map from the disk
readBlockMap :: (Monad m, Reffable r m, Bitmapped b m, Lockable l m) =>
                BlockDevice m
             -> m (BlockMap b r l)
readBlockMap dev = do
  bArr  <- newBitmap totalBits False
  freeR <- newRef 0

  -- Unpack the block map's block region into the empty bitmap
  forM_ [0..blockMapSzBlks - 1] $ \blkIdx -> do
    blockBS <- bdReadBlock dev (blkIdx + 1 {- +1 for superblock -})
    forM_ [0..bdBlockSize dev - 1] $ \byteIdx -> do
      let byte = BS.index blockBS (fromIntegral byteIdx)
      forM_ [0..7] $ \bitIdx -> do
        if (testBit byte bitIdx)
         then do let baseByte = blkIdx * bdBlockSize dev
                     idx      = (baseByte + byteIdx) * 8 + fromIntegral bitIdx 
                 setBit bArr idx
         else do cur <- readRef freeR
                 writeRef freeR $ cur + 1
  
  baseTreeR <- newRef empty
  getFreeBlocks bArr baseTreeR Nothing 0
  lk        <- newLock
  return $ BM baseTreeR bArr freeR lk
 where
  numBlks        = bdNumBlocks dev
  totalBits      = blockMapSzBlks * bdBlockSize dev * 8
  blockMapSzBlks = blockMapSizeBlks numBlks (bdBlockSize dev)
  -- 
  writeExtent treeR ext = do
    t <- readRef treeR
    writeRef treeR $ insert ext t
  -- 
  -- getFreeBlocks recurses over each used bit in the used bitmap and
  -- finds runs of free block regions, inserting representative Extents
  -- into the "free tree" as it does so.  The third parameter tracks the
  -- block address of start of the current free region.
  getFreeBlocks _bmap treeR mb cur | cur == totalBits =
    maybe (return ()) (writeExtent treeR . \b -> Extent b (cur - b)) mb

  getFreeBlocks bmap treeR Nothing cur = do
    used <- checkBit bmap cur
    getFreeBlocks bmap treeR (if used then Nothing else Just cur) (cur + 1)

  getFreeBlocks bmap treeR b@(Just base) cur = do
    used <- checkBit bmap cur
    when used $ writeExtent treeR (Extent base $ cur - base)
    getFreeBlocks bmap treeR (if used then Nothing else b) (cur + 1)

-- | Write the block map to the disk
writeBlockMap ::
  (Monad m, Reffable r m, Bitmapped b m, Functor m, Lockable l m) =>
     BlockDevice m
  -> BlockMap b r l
  -> m ()
writeBlockMap dev bmap = do
  withLockM (bmLock bmap) $ writeUsedBitmap dev (bmUsedMap bmap)

writeUsedBitmap ::
  (Monad m, Reffable r m, Bitmapped b m, Functor m, Lockable l m) =>
     BlockDevice m
  -> b
  -> m ()
writeUsedBitmap dev used = do
  -- Pack the given bitmap into the block map's block region
  forM_ [0..blockMapSzBlks - 1] $ \blkIdx -> do
    blockBS <- BS.pack `fmap` forM [0..bdBlockSize dev - 1] (getBytes blkIdx)
    bdWriteBlock dev (blkIdx + 1 {- +1 for superblock -}) blockBS
  where
    numBlks        = bdNumBlocks dev
    blockMapSzBlks = blockMapSizeBlks numBlks (bdBlockSize dev)
    --
    getBytes blkIdx byteIdx = do
      bs <- forM [0..7] $ \bitIdx -> do
        let base = blkIdx * bdBlockSize dev
            idx  = (base + byteIdx) * 8 + bitIdx
        checkBit used idx
      return $ foldr (\(b,i) r -> if b then B.setBit r i else r)
               (0::Word8) (bs `zip` [0..7])

-- | Allocate a set of blocks from the disk. This routine will attempt
-- to fetch a contiguous set of blocks, but isn't guaranteed to do so.
-- Contiguous blocks are represented via the Contig constructor of the
-- BlockGroup datatype, discontiguous blocks via Discontig.  If there
-- aren't enough blocks available, this function yields Nothing.
allocBlocks :: (Monad m, Reffable r m, Bitmapped b m, Lockable l m) =>
               BlockMap b r l
            -- ^ the block map 
            -> Word64
            -- ^ requested number of blocks to allocate
            -> m (Maybe BlockGroup)
allocBlocks bm numBlocks = do
  withLockM (bmLock bm) $ do 
  available <- readRef $ bmNumFree bm
  if available < numBlocks
    then do
      return Nothing
    else do
      freeTree <- readRef $ bmFreeTree bm
      let (blkGroup, freeTree') = findSpace numBlocks freeTree
      forM_ (blkRangeBG blkGroup) $ setBit $ bmUsedMap bm
      writeRef (bmFreeTree bm) freeTree'
      writeRef (bmNumFree bm) (available - numBlocks)
      return $ Just blkGroup

-- | Allocate a single block
alloc1 :: (Bitmapped b m, Reffable r m, Lockable l m) =>
          BlockMap b r l -> m (Maybe Word64)
alloc1 bm = do 
  res <- allocBlocks bm 1
  case res of
    Just (Contig ext) -> return $ Just $ extBase ext
    _                 -> return Nothing

-- | Unallocate a single block
unalloc1 :: (Bitmapped b m, Reffable r m, Lockable l m) =>
            BlockMap b r l -> Word64 -> m ()
unalloc1 bm addr = unallocBlocks bm $ Contig $ Extent addr 1 

-- | Mark a given block group as unused
unallocBlocks :: (Monad m, Reffable r m, Bitmapped b m, Lockable l m) =>
                 BlockMap b r l -- ^ the block map
              -> BlockGroup
              -> m ()
unallocBlocks bm bg = withLockM (bmLock bm) $ unallocBlocks_lckd bm bg

unallocBlocks_lckd :: (Monad m, Reffable r m, Bitmapped b m, Lockable l m) =>
                      BlockMap b r l -- ^ the block map
                   -> BlockGroup
                   -> m ()
unallocBlocks_lckd bm (Discontig exts) = do
  -- Precond: (bmLock bm) is currently held (can we assert this? TODO)
  mapM_ (unallocBlocks_lckd bm . Contig) exts
unallocBlocks_lckd bm (Contig ext)     = do
  -- Precond: (bmLock bm) is currently held (can we assert this? TODO)
  avail    <- numFreeBlocks_lckd bm
  freeTree <- readRef $ bmFreeTree bm
  forM_ (blkRangeExt ext)  $ clearBit $ bmUsedMap bm
  writeRef (bmFreeTree bm) $ insert ext freeTree
  writeRef (bmNumFree bm)  $ avail + extSz ext

-- | Return the number of blocks currently left
numFreeBlocks :: (Monad m, Reffable r m, Bitmapped b m, Lockable l m) =>
                 BlockMap b r l
               -> m Word64
numFreeBlocks bm = withLockM (bmLock bm) $ numFreeBlocks_lckd bm

numFreeBlocks_lckd :: (Monad m, Reffable r m, Bitmapped b m, Lockable l m) =>
                      BlockMap b r l
                   -> m Word64
numFreeBlocks_lckd bm =
  -- Precond: (bmLock bm) is currently held (can we assert this? TODO)  
  readRef $ bmNumFree bm

findSpace :: Word64 -> FreeTree -> (BlockGroup, FreeTree)
findSpace goalSz freeTree =
  -- Precondition: There is sufficient space in the free tree to accomodate the
  -- given goal size, although that space may not be contiguous
  assert (goalSz <= DF.foldr ((+) . extSz) 0 freeTree) $ do
  let (treeL, treeR) = splitBlockSz goalSz freeTree
  case viewl treeR of
    Extent b sz :< treeR' -> 
      -- Found an extent with size >= the goal size
      ( Contig $ Extent b goalSz 
      , -- Split the found extent when it exceeds the goal size
        let mid = if sz > goalSz
                  then singleton $ Extent (b + goalSz) (sz - goalSz)
                  else empty
        in treeL >< mid >< treeR'
      )

    EmptyL ->
      -- Cannot find an extent large enough, so gather smaller extents
      fmapFst Discontig $ gatherL (viewr treeL) 0 []
      where
        gatherL :: ViewR (FingerTree ExtentSize) Extent
                -> Word64
                -> [Extent]
                -> ([Extent], FreeTree)
        gatherL EmptyR _ _ = error "Precondition violated: insufficent space"
        gatherL !(treeL' :> ext@(Extent b sz)) !accSz !accExts
          | accSz + sz < goalSz = gatherL (viewr treeL')
                                          (accSz + sz)
                                          (ext : accExts) 
          | accSz + sz == goalSz = (ext : accExts, treeL')
          | otherwise = 
            -- We've exceeded the goal, so split the extent we just encountered
            (Extent (b + diff) (sz - diff) : accExts, treeL' >< extra)
            where diff  = accSz + sz - goalSz
                  extra = singleton $ Extent b diff

--------------------------------------------------------------------------------
-- Utility functions

blkRange :: Word64 -> Word64 -> [Word64]
blkRange b sz = [b .. b + sz - 1]

blkRangeExt :: Extent -> [Word64]
blkRangeExt (Extent b sz) = blkRange b sz

blkRangeBG :: BlockGroup -> [Word64]
blkRangeBG (Contig ext)     = blkRangeExt ext
blkRangeBG (Discontig exts) = concatMap blkRangeExt exts

blkGroupExts :: BlockGroup -> [Extent]
blkGroupExts (Contig ext)     = [ext]
blkGroupExts (Discontig exts) = exts

blkGroupSz :: BlockGroup -> Word64
blkGroupSz (Contig ext)     = extSz ext
blkGroupSz (Discontig exts) = foldr (\e -> (extSz e +)) 0 exts