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
data VhdNode = VhdNode
{ nodeBat :: Bat.Bat
, nodeHeader :: Header
, nodeFooter :: Footer
, nodeHandle :: Handle
, nodeCryptCtx :: Maybe VhdCryptContext
, nodeFilePath :: FilePath
, nodeModified :: IORef Bool
}
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
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
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 ()
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
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
appendEmptyBlock :: VhdNode -> VirtualBlockAddress -> IO PhysicalSectorAddress
appendEmptyBlock node n = do
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)
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
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
iterateBlocks :: VhdNode
-> (Block -> IO ())
-> IO ()
iterateBlocks vhd f = mapM_ callAt [0..(nbBlocks1)]
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
iterateBlockSectors :: VhdNode
-> VirtualBlockAddress
-> (Block -> BlockSectorAddress -> Bool -> IO ())
-> 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))