module Data.Vhd.Block ( Block , blockAddr , BlockDataMapper , sectorPerBlock , blockSectorToByte , bitmapSizeOfBlockSize , bitmapOfBlock , withBlock , readBitmap , readData , readDataRange , unsafeReadData , unsafeReadDataRange , writeDataRange -- * sector manipulation , readSector , writeSector , sectorLength , iterateSectors ) where import Data.ByteString (ByteString) import qualified Data.ByteString as B import qualified Data.ByteString.Unsafe as B import qualified Data.ByteString.Internal as B import Data.Vhd.Bitmap import Data.Vhd.Types import Data.Vhd.Const import Data.Vhd.Utils import Data.Word import Control.Applicative import Control.Monad import Foreign.Ptr import Foreign.ForeignPtr (newForeignPtr_) import System.IO.MMap import Data.Byteable type BlockDataMapper = VirtualBlockAddress -> BlockByteAddress -> ByteString -> ByteString data Block = Block { blockSize :: BlockSize -- ^ block size in bytes , blockAddr :: VirtualBlockAddress -- ^ block address , blockPtr :: Ptr Word8 -- ^ block data pointer } newtype Data = Data (Ptr Word8) blockSectorToByte :: BlockSectorAddress -> BlockByteAddress blockSectorToByte (BlockSectorAddress s) = BlockByteAddress (s * sectorLength) sectorPerBlock :: Block -> BlockSectorAddress sectorPerBlock block = BlockSectorAddress (fromIntegral bsz `div` sectorLength) where BlockSize bsz = blockSize block -- | Finds the padded size (in bytes) of the bitmap for a given block. bitmapSizeOfBlock :: Block -> Int bitmapSizeOfBlock block = bitmapSizeOfBlockSize $ blockSize block -- | Finds the padded size (in bytes) of the bitmap for a given block size. bitmapSizeOfBlockSize :: BlockSize -> Int bitmapSizeOfBlockSize (BlockSize blocksz) = fromIntegral ((nbSector `divRoundUp` 8) `roundUpToModulo` sectorLength) where nbSector = blocksz `divRoundUp` sectorLength -- | Retrieves the bitmap for the given block. bitmapOfBlock :: Block -> Bitmap bitmapOfBlock block = Bitmap $ blockPtr block -- | Retrieves the data for the given block. dataOfBlock :: Block -> Data dataOfBlock (Block bs _ ptr) = Data $ ptr `plusPtr` (bitmapSizeOfBlockSize bs) -- | Obtains a direct pointer to the given data. pointerOfData :: Data -> Ptr Word8 pointerOfData (Data ptr) = ptr -- | Maps into memory a block of the given size, at the given file path and sector address. withBlock :: FilePath -> BlockSize -> VirtualBlockAddress -> PhysicalSectorAddress -> (Block -> IO a) -> IO a withBlock file blocksz@(BlockSize bsz) vba sectorOffset f = mmapWithFilePtr file ReadWrite (Just (offset, len)) $ \(ptr, _) -> f (Block blocksz vba $ castPtr ptr) where offset = (fromIntegral sectorOffset) * sectorLength len = (fromIntegral bsz) + (fromIntegral $ bitmapSizeOfBlockSize blocksz) -- | Reads into memory the contents of the bitmap for the specified block. readBitmap :: Block -> IO ByteString readBitmap block = B.create (fromIntegral len) create where len = bitmapSizeOfBlock block create byteStringPtr = B.memcpy target source (fromIntegral len) where source = case bitmapOfBlock block of Bitmap b -> b target = castPtr byteStringPtr -- | Reads all available data from the specified block. readData :: Maybe BlockDataMapper -> Block -> IO ByteString readData blockMapper block = readDataRange blockMapper block 0 sz where (BlockSize sz) = blockSize block -- | Reads a range of data from within the specified block. readDataRange :: Maybe BlockDataMapper -> Block -> BlockByteAddress -> Word32 -> IO ByteString readDataRange blockDataMapper block offset len = B.create (fromIntegral len) $ unsafeReadDataRange blockDataMapper block offset len -- | Unsafely reads all available data from the specified block. unsafeReadData :: Maybe BlockDataMapper -> Block -> Ptr Word8 -> IO () unsafeReadData blockDataMapper block = unsafeReadDataRange blockDataMapper block 0 (fromIntegral sz) where (BlockSize sz) = blockSize block -- | Unsafely reads a range of data from within the specified block. unsafeReadDataRange :: Maybe BlockDataMapper -- ^ an optional data mapper function -> Block -- ^ the block -> BlockByteAddress -- ^ offset in bytes on this block -> Word32 -- ^ number of bytes -> Ptr Word8 -- ^ output buffer -> IO () unsafeReadDataRange blockDataMapper block bba@(BlockByteAddress offset) len target = case blockDataMapper of Nothing -> B.memcpy target source (fromIntegral len) Just bmap -> do fptr <- newForeignPtr_ source let mappedSource = bmap (blockAddr block) bba $ B.fromForeignPtr fptr 0 (fromIntegral len) withBytePtr mappedSource $ \src -> B.memcpy target src (fromIntegral len) where source = (pointerOfData $ dataOfBlock block) `plusPtr` (fromIntegral offset) -- | Writes data to the given byte address of the specified block. writeDataRange :: Maybe BlockDataMapper -> Block -> BlockByteAddress -> ByteString -> IO () writeDataRange blockMapper block bba@(BlockByteAddress offset) content = do -- sectors need to be prepared for differential disk if the bitmap was clear before, -- at the moment assumption is it's 0ed bitmapSetRange bitmap (fromIntegral sectorStart) (fromIntegral sectorEnd) B.unsafeUseAsCString (maybe id (\bm -> bm (blockAddr block) bba) blockMapper $ content) (\source -> B.memcpy target (castPtr source) len) where len = fromIntegral $ B.length content bitmap = bitmapOfBlock block target = (pointerOfData $ dataOfBlock block) `plusPtr` (fromIntegral offset) sectorStart = offset `div` sectorLength sectorEnd = (fromIntegral offset + B.length content) `div` sectorLength -- | Return the whole sector of a specific block if present readSector :: Maybe BlockDataMapper -- ^ an optional data mapper function -> Block -- ^ the mapped block -> BlockSectorAddress -- ^ the sector address -> IO (Maybe ByteString) readSector blockMapper block (BlockSectorAddress bsa) = allocated >>= \isAllocated -> case isAllocated of False -> return Nothing True -> Just . applyMapper <$> B.create sectorLength copy where allocated = bitmapGet bitmap (fromIntegral bsa) applyMapper = maybe id (\bm -> bm (blockAddr block) bba) blockMapper bba = BlockByteAddress $ fromIntegral offset bitmap = bitmapOfBlock block offset = fromIntegral bsa * sectorLength addr = (pointerOfData $ dataOfBlock block) `plusPtr` offset copy dst = B.memcpy dst addr sectorLength -- | Write the whole sector of a specific block -- -- the content passed need to be the size of the sector length writeSector :: Maybe BlockDataMapper -- ^ an optional data mapper function -> Block -- ^ the mapped block -> BlockSectorAddress -- ^ the sector address -> ByteString -- ^ content (of sector length) -> IO () writeSector blockMapper block (BlockSectorAddress bsa) content | B.length content /= sectorLength = error "writeSector data need to be sector'ed size" | otherwise = do bitmapSet bitmap (fromIntegral bsa) B.unsafeUseAsCString (applyMapper content) $ \source -> B.memcpy target (castPtr source) sectorLength where applyMapper = maybe id (\bm -> bm (blockAddr block) bba) blockMapper bba = BlockByteAddress $ fromIntegral offset bitmap = bitmapOfBlock block offset = fromIntegral bsa * sectorLength target = (pointerOfData $ dataOfBlock block) `plusPtr` offset iterateSectors :: Block -> (BlockSectorAddress -> Bool -> IO ()) -> IO () iterateSectors block f = forM_ [0..(nbSectors-1)] $ \sector@(BlockSectorAddress bsa) -> bitmapGet (bitmapOfBlock block) (fromIntegral bsa) >>= f sector where nbSectors = sectorPerBlock block