vhd-0.2.1: Data/Vhd/Node.hs
{-# LANGUAGE OverloadedStrings #-}
module Data.Vhd.Node
( VhdNode (..)
, getVhdBlockMapper
, containsBlock
, lookupOrCreateBlock
, openCryptKey
, withVhdNode
, withMappedBlock
, iterateBlocks
, iterateBlockSectors
, batmapHeaderChange
) where
import Control.Applicative ((<$>))
import Control.Monad
import qualified Data.ByteString as B
import Data.ByteString.Char8 ()
import Data.IORef
import Data.Serialize (decode, encode)
import Data.Vhd.Block
import Data.Vhd.Header
import Data.Vhd.Footer
import Data.Vhd.Batmap
import qualified Data.Vhd.Bat as Bat
import Data.Vhd.Types
import Data.Vhd.Serialize ()
import Data.Vhd.Utils
import Data.Vhd.Crypt
import System.Directory
import System.IO
-- | Represent one VHD file, possibly part of a VHD chain
data VhdNode = VhdNode
{ nodeBat :: Bat.Bat
, nodeHeader :: Header
, nodeFooter :: Footer
, nodeHandle :: Handle
, nodeCryptCtx :: Maybe VhdCryptContext
, nodeFilePath :: FilePath
, nodeModified :: IORef Bool
}
-- | return the (reading, writing) blockDataMapper for a giving node
getVhdBlockMapper :: VhdNode -> (Maybe BlockDataMapper, Maybe BlockDataMapper)
getVhdBlockMapper node =
(vhdDecrypt `fmap` nodeCryptCtx node, vhdEncrypt `fmap` nodeCryptCtx node)
withVhdNode :: FilePath -> (VhdNode -> IO a) -> IO a
withVhdNode filePath f = do
e <- doesFileExist filePath
unless e $ error "file doesn't exist"
key <- findImplicitCryptKey filePath
withFile filePath ReadWriteMode $ \handle -> do
footer <- either error id . decode <$> B.hGet handle 512
header <- either error id . decode <$> B.hGet handle 1024
mBatmapHdr <- if footerCreatorVersion footer == Version 1 3
then do
-- skip the BAT, and try reading the batmap header
hSeek handle RelativeSeek (fromIntegral $ Bat.batGetSize header footer)
batmapHdr <- decode <$> B.hGet handle 512
case batmapHdr of
Left _ -> return Nothing
Right bHdr ->
if batmapHeaderCookie bHdr == cookie "tdbatmap"
then return $ Just bHdr
else return Nothing
else return Nothing
-- make sure the key match
case key of
Nothing -> return ()
Just k -> case batmapHeaderKeyHash `fmap` mBatmapHdr of
Just (KeyHash (Just (nonce, expected))) -> do
when (expected /= calculateHash nonce k) $ error "keyhash differ"
_ -> return ()
-- now mmap the bat
Bat.batMmap filePath header footer mBatmapHdr $ \bat -> do
bmodified <- newIORef False
a <- f $ VhdNode
{ nodeBat = bat
, nodeHeader = header
, nodeFooter = footer
, nodeHandle = handle
, nodeCryptCtx = fmap initCryptContext key
, nodeFilePath = filePath
, nodeModified = bmodified
}
return a
where initCryptContext ck = maybe (error "invalid crypt key") id $ vhdCryptInit ck
-- | Return the physical sector of a specific block, or create a new one if this block
-- has not been allocated yet
lookupOrCreateBlock :: VhdNode -> VirtualBlockAddress -> IO PhysicalSectorAddress
lookupOrCreateBlock node blockNumber = do
mpsa <- Bat.lookupBlock (nodeBat node) blockNumber
case mpsa of
Nothing -> appendEmptyBlock node blockNumber
Just psa -> return psa
containsBlock :: VhdNode -> VirtualBlockAddress -> IO Bool
containsBlock node = Bat.containsBlock (nodeBat node)
batmapHeaderChange :: VhdNode -> (BatmapHeader -> BatmapHeader) -> IO ()
batmapHeaderChange node f = Bat.batmapHeaderModify (nodeBat node) f
-- | Create a new empty block at the end of the vhd file
appendEmptyBlock :: VhdNode -> VirtualBlockAddress -> IO PhysicalSectorAddress
appendEmptyBlock node n = do
-- seek to the end of the file minus the footer
hSeek (nodeHandle node) SeekFromEnd 512
pos <- hTell (nodeHandle node)
let sector = addrToSector (fromIntegral pos)
Bat.batWrite (nodeBat node) n sector
modifyIORef (nodeModified node) (const True)
-- write bitmap, then write block with the optional block data mapper
B.hPut (nodeHandle node) $ B.replicate bitmapSize 0
B.hPut (nodeHandle node) $ maybe id (\cc -> vhdEncrypt cc n 0) (nodeCryptCtx node) $ B.replicate (fromIntegral bsz) 0
-- align to the next sector length and then re-write the footer
hAlign (nodeHandle node) sectorLength
B.hPut (nodeHandle node) $ encode (nodeFooter node)
return sector
where
bitmapSize = bitmapSizeOfBlockSize blockSize
blockSize@(BlockSize bsz) = headerBlockSize $ nodeHeader node
withMappedBlock :: VhdNode -> PhysicalSectorAddress -> VirtualBlockAddress -> (Block -> IO a) -> IO a
withMappedBlock vhd psa vba f = withBlock (nodeFilePath vhd) blockSize vba psa f
where blockSize = headerBlockSize $ nodeHeader vhd
-- | Iterate Present blocks in a vhd file
iterateBlocks :: VhdNode -- ^ the vhd file
-> (Block -> IO ()) -- ^ callback
-> IO ()
iterateBlocks vhd f = mapM_ callAt [0..(nbBlocks-1)]
where nbBlocks = VirtualBlockAddress $ headerMaxTableEntries $ nodeHeader vhd
callAt vba = do
mpsa <- Bat.lookupBlock (nodeBat vhd) vba
case mpsa of
Nothing -> return ()
Just psa -> withMappedBlock vhd psa vba f
-- | Iterate sectors in a specific block
iterateBlockSectors :: VhdNode -- ^ the vhd file
-> VirtualBlockAddress -- ^ block address
-> (Block -> BlockSectorAddress -> Bool -> IO ()) -- ^ callback
-> IO ()
iterateBlockSectors vhd vba f = do
mpsa <- Bat.lookupBlock (nodeBat vhd) vba
case mpsa of
Nothing -> return ()
Just psa -> withMappedBlock vhd psa vba (\block -> iterateSectors block (f block))