vhd 0.2 → 0.2.1
raw patch · 24 files changed
+1754/−997 lines, 24 filesdep +byteabledep +cipher-aesdep +cryptohashdep ~QuickCheckdep ~basedep ~test-framework
Dependencies added: byteable, cipher-aes, cryptohash, directory, vhd
Dependency ranges changed: QuickCheck, base, test-framework, test-framework-quickcheck2
Files
- Data/BitSet.hs +62/−58
- Data/Range.hs +7/−8
- Data/Vhd.hs +269/−242
- Data/Vhd/Bat.hs +60/−68
- Data/Vhd/Batmap.hs +73/−0
- Data/Vhd/Bitmap.hs +16/−16
- Data/Vhd/Block.hs +148/−69
- Data/Vhd/Checksum.hs +34/−28
- Data/Vhd/Crypt.hs +80/−0
- Data/Vhd/Footer.hs +83/−0
- Data/Vhd/Geometry.hs +21/−21
- Data/Vhd/Header.hs +74/−0
- Data/Vhd/Lowlevel.hs +47/−0
- Data/Vhd/Node.hs +126/−59
- Data/Vhd/Serialize.hs +77/−125
- Data/Vhd/Time.hs +30/−0
- Data/Vhd/Types.hs +74/−78
- Data/Vhd/UniqueId.hs +57/−0
- Data/Vhd/Utils.hs +17/−14
- Tests.hs +0/−89
- Tests/Tests.hs +104/−0
- Tools/Vhd.hs +246/−0
- Vhd.hs +0/−103
- vhd.cabal +49/−19
Data/BitSet.hs view
@@ -1,100 +1,104 @@ module Data.BitSet- ( BitSet- , empty- , fromByteString- , fromRange- , toList- , isEmpty- , intersect- , union- , subtract- )- where+ ( BitSet+ , empty+ , fromByteString+ , fromRange+ , toList+ , isEmpty+ , intersect+ , union+ , subtract+ )+ where -import Control.Exception import Data.Bits import qualified Data.ByteString as B import Data.Word import Prelude hiding (subtract) -data BitSet = BitSet B.ByteString+data BitSet a = BitSet B.ByteString -instance Eq BitSet- where (BitSet b1) == (BitSet b2) = b1' == b2'- where (b1', b2') = byteStringsPad b1 b2+instance Eq (BitSet a) where+ (BitSet b1) == (BitSet b2) = b1' == b2'+ where (b1', b2') = byteStringsPad b1 b2 -instance Show BitSet where show = show . toList+instance Show a => Show (BitSet a) where+ show = show . toList -empty :: BitSet+empty :: Num a => BitSet a empty = BitSet B.empty -fromByteString :: B.ByteString -> BitSet+fromByteString :: Num a => B.ByteString -> BitSet a fromByteString = BitSet -fromRange :: Int -> Int -> BitSet+fromRange :: Int -> Int -> BitSet a fromRange lo hi = BitSet generate where - generate- | lo < 0 = error "lower bound cannot be less than zero."- | lo > hi = error "lower bound cannot be greater than upper bound."- | lo == hi = B.empty- | lo == 0 && hiBit == 0 = setBytes- | loBit == 0 && hiBit == 0 = B.concat [clearBytes, setBytes]- | loBit == 0 && hiBit /= 0 = B.concat [clearBytes, setBytes, fallByte]- | loBit /= 0 && hiBit == 0 = B.concat [clearBytes, riseByte, setBytes]- | loByteFloor == hiByteFloor = B.concat [clearBytes, humpByte]- | loByteCeiling == hiByteFloor = B.concat [clearBytes, riseByte, fallByte]- | loByteCeiling < hiByteFloor = B.concat [clearBytes, riseByte, setBytes, fallByte]+ generate+ | lo < 0 = error "lower bound cannot be less than zero."+ | lo > hi = error "lower bound cannot be greater than upper bound."+ | lo == hi = B.empty+ | lo == 0 && hiBit == 0 = setBytes+ | loBit == 0 && hiBit == 0 = B.concat [clearBytes, setBytes]+ | loBit == 0 && hiBit /= 0 = B.concat [clearBytes, setBytes, fallByte]+ | loBit /= 0 && hiBit == 0 = B.concat [clearBytes, riseByte, setBytes]+ | loByteFloor == hiByteFloor = B.concat [clearBytes, humpByte]+ | loByteCeiling == hiByteFloor = B.concat [clearBytes, riseByte, fallByte]+ | loByteCeiling < hiByteFloor = B.concat [clearBytes, riseByte, setBytes, fallByte]+ | otherwise = error "cannot happen" - (loBit, loByteFloor, loByteCeiling) = (lo `mod` 8, lo `div` 8, (lo + 7) `div` 8)- (hiBit, hiByteFloor, hiByteCeiling) = (hi `mod` 8, hi `div` 8, (hi + 7) `div` 8)+ (loByteFloor, loBit) = lo `divMod` 8+ (hiByteFloor, hiBit) = hi `divMod` 8+ loByteCeiling = (lo + 7) `div` 8+ --hiByteCeiling = (hi + 7) `div` 8 - clearBytes = B.replicate (loByteFloor ) 0x00- setBytes = B.replicate (hiByteFloor - loByteCeiling) 0xff+ clearBytes = B.replicate (fromIntegral loByteFloor) 0x00+ setBytes = B.replicate (fromIntegral (hiByteFloor - loByteCeiling)) 0xff - riseByte = B.singleton $ setBits 0 loBit 8- fallByte = B.singleton $ setBits 0 0 hiBit- humpByte = B.singleton $ setBits 0 loBit hiBit+ riseByte = B.singleton $ setBits 0 loBit 8+ fallByte = B.singleton $ setBits 0 0 hiBit+ humpByte = B.singleton $ setBits 0 loBit hiBit -toList :: BitSet -> [Int]-toList (BitSet b) = map snd $ filter fst $ zip (byteStringBits b) [0 ..]+toList :: BitSet a -> [Int]+toList (BitSet b) = map snd $ filter fst $ zip (byteStringBits b) [0..] -isEmpty :: BitSet -> Bool+isEmpty :: BitSet a -> Bool isEmpty (BitSet b) = B.all (== 0) b -intersect :: BitSet -> BitSet -> BitSet-union :: BitSet -> BitSet -> BitSet-subtract :: BitSet -> BitSet -> BitSet+intersect :: BitSet a -> BitSet a -> BitSet a+union :: BitSet a -> BitSet a -> BitSet a+subtract :: BitSet a -> BitSet a -> BitSet a intersect = binaryOp (.&.) union = binaryOp (.|.) subtract = binaryOp (\x y -> x .&. complement y) +binaryOp :: (Word8 -> Word8 -> Word8) -> BitSet a -> BitSet b -> BitSet c binaryOp f (BitSet b1) (BitSet b2) =- BitSet $ byteStringPackZipWith f b1' b2'- where (b1', b2') = byteStringsPad b1 b2+ BitSet $ byteStringPackZipWith f b1' b2'+ where (b1', b2') = byteStringsPad b1 b2 +byteStringBits :: B.ByteString -> [Bool] byteStringBits byteString = do- word <- B.unpack byteString- bit <- word8Bits word- return bit+ word <- B.unpack byteString+ word8Bits word byteStringPackZipWith :: (Word8 -> Word8 -> Word8) -> B.ByteString -> B.ByteString -> B.ByteString byteStringPackZipWith = ((B.pack .) .) . B.zipWith byteStringsPad :: B.ByteString -> B.ByteString -> (B.ByteString, B.ByteString) byteStringsPad b1 b2 =- if length1 < length2- then (B.append b1 (B.replicate (length2 - length1) 0), b2)- else (b1, B.append b2 (B.replicate (length1 - length2) 0))- where- length1 = B.length b1- length2 = B.length b2+ if length1 < length2+ then (B.append b1 (B.replicate (length2 - length1) 0), b2)+ else (b1, B.append b2 (B.replicate (length1 - length2) 0))+ where+ length1 = B.length b1+ length2 = B.length b2 setBits :: Bits a => a -> Int -> Int -> a-setBits value loBit hiBit = if loBit < hiBit- then setBits (setBit value loBit) (loBit + 1) hiBit- else value+setBits acc loBit hiBit+ | loBit < hiBit = setBits (setBit acc loBit) (loBit + 1) hiBit+ | otherwise = acc word8Bits :: Word8 -> [Bool] word8Bits w = map (testBit w) [0 .. 7]
Data/Range.hs view
@@ -3,16 +3,15 @@ {-# LANGUAGE TypeSynonymInstances #-} module Data.Range- ( Range- , RangeLength- , range- , length- , minimum- , maximum- ) where+ ( Range+ , RangeLength+ , range+ , length+ , minimum+ , maximum+ ) where import Control.Exception-import Data.Word import Prelude hiding (length, minimum, maximum) data Range a = Range a a
Data/Vhd.hs view
@@ -2,72 +2,87 @@ {-# LANGUAGE TupleSections #-} module Data.Vhd- ( create- , CreateParameters (..)- , defaultCreateParameters- , getInfo- , snapshot- , readData- , readDataRange- , writeDataRange- , withVhd- , module Data.Vhd.Types- ) where+ ( create+ , CreateParameters (..)+ , defaultCreateParameters+ , getInfo+ , snapshot+ , readData+ , readDataRange+ , writeDataRange+ , withVhd+ , module Data.Vhd.Types+ , module Data.Vhd.Header+ , module Data.Vhd.Checksum+ , module Data.Vhd.Footer+ , module Data.Vhd.UniqueId+ ) where import Control.Applicative import Control.Monad-import Data.BitSet+import Data.BitSet as BitSet import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Internal as B-import qualified Data.ByteString.Unsafe as B import Data.Maybe import Data.Serialize-import Data.Time.Clock.POSIX+import Data.Monoid (mempty) import Data.Vhd.Bat-import Data.Vhd.Block hiding (readData, readDataRange, writeDataRange)+import Data.Vhd.Block (Block, BlockDataMapper) import qualified Data.Vhd.Block as Block import Data.Vhd.Checksum import Data.Vhd.Geometry import Data.Vhd.Node import Data.Vhd.Types+import Data.Vhd.Header+import Data.Vhd.Footer+import Data.Vhd.UniqueId import Data.Vhd.Utils+import Data.Vhd.Time+import Data.Vhd.Const+import Data.Vhd.Batmap import Data.Word-import Foreign.C.String-import Foreign.C.Types import Foreign.Ptr import Prelude hiding (subtract) import System.FilePath.Posix import System.IO data Vhd = Vhd- { vhdBlockCount :: VirtualBlockCount- , vhdBlockSize :: BlockByteCount- , vhdNodes :: [VhdNode]- }+ { vhdBlockCount :: VirtualBlockCount+ , vhdBlockSize :: BlockSize+ , vhdNodes :: [VhdNode]+ } +vhdSectorPerBlock :: Vhd -> Word32+vhdSectorPerBlock vhd = sz `div` Block.sectorLength+ where BlockSize sz = vhdBlockSize vhd+ virtualSize :: Vhd -> VirtualByteCount-virtualSize vhd = fromIntegral (vhdBlockCount vhd) * fromIntegral (vhdBlockSize vhd)+virtualSize vhd = fromIntegral (vhdBlockCount vhd) * fromIntegral bsz+ where BlockSize bsz = vhdBlockSize vhd withVhd :: FilePath -> (Vhd -> IO a) -> IO a-withVhd = withVhdInner [] where+withVhd = withVhdInner []+ where - blockCount node = headerMaxTableEntries $ nodeHeader node- blockSize node = headerBlockSize $ nodeHeader node- diskType node = footerDiskType $ nodeFooter node+ blockCount node = headerMaxTableEntries $ nodeHeader node+ blockSize node = headerBlockSize $ nodeHeader node+ diskType node = footerDiskType $ nodeFooter node - withVhdInner accumulatedNodes filePath f =- withVhdNode filePath $ \node ->- if diskType node == DiskTypeDifferencing- then withVhdInner (node : accumulatedNodes) (parentPath node) f- else f $ Vhd- -- TODO: require consistent block count and size across all nodes.- { vhdBlockCount = blockCount node- , vhdBlockSize = validateBlockSize $ blockSize node- , vhdNodes = reverse $ node : accumulatedNodes- }- where parentPath node = resolveColocatedFilePath filePath p- where ParentUnicodeName p = headerParentUnicodeName $ nodeHeader node+ withVhdInner accumulatedNodes filePath f =+ withVhdNode filePath $ \node -> do+ if diskType node == DiskTypeDifferencing+ then withVhdInner (node : accumulatedNodes) (parentPath node) f+ else do+ validateBlockSize $ blockSize node+ f $ Vhd+ -- TODO: require consistent block count and size across all nodes.+ { vhdBlockCount = blockCount node+ , vhdBlockSize = blockSize node+ , vhdNodes = reverse $ node : accumulatedNodes+ }+ where parentPath node = resolveColocatedFilePath filePath p+ where ParentUnicodeName p = headerParentUnicodeName $ nodeHeader node -- The VHD specification requires an unsigned 32-bit word to encode the -- block size of a VHD.@@ -84,248 +99,260 @@ -- to open a VHD with (block size) > (maxBound :: Int). Therefore, this -- function fails fast on attempting to open such a VHD file. ---validateBlockSize :: BlockByteCount -> BlockByteCount-validateBlockSize value =- if integerValue > integerLimit- then error- ( "Cannot open VHD file with block size " ++- "greater than upper bound of platform integer." )- else value- where- integerValue = fromIntegral (value ) :: Integer- integerLimit = fromIntegral (maxBound :: Int) :: Integer+validateBlockSize :: BlockSize -> IO ()+validateBlockSize (BlockSize value)+ | integerValue > integerLimit =+ error "Cannot open VHD file with block size greater than upper bound of platform integer."+ | otherwise =+ return ()+ where+ integerValue = fromIntegral (value ) :: Integer+ integerLimit = fromIntegral (maxBound :: Int) :: Integer data CreateParameters = CreateParameters- { createBlockSize :: BlockByteCount- , createDiskType :: DiskType- , createParentTimeStamp :: Maybe TimeStamp- , createParentUnicodeName :: Maybe ParentUnicodeName- , createParentUniqueId :: Maybe UniqueId- , createTimeStamp :: Maybe TimeStamp- , createUuid :: Maybe UniqueId- , createUseBatmap :: Bool- , createVirtualSize :: VirtualByteCount- } deriving (Show, Eq)+ { createBlockSize :: BlockSize+ , createDiskType :: DiskType+ , createParentTimeStamp :: Maybe VhdDiffTime+ , createParentUnicodeName :: Maybe ParentUnicodeName+ , createParentUniqueId :: Maybe UniqueId+ , createTimeStamp :: Maybe VhdDiffTime+ , createUuid :: Maybe UniqueId+ , createUseBatmap :: Bool+ , createVirtualSize :: VirtualByteCount+ } deriving (Show, Eq) +defaultCreateParameters :: CreateParameters defaultCreateParameters = CreateParameters- { createBlockSize = 2 * 1024 * 1024- , createDiskType = DiskTypeDynamic- , createParentTimeStamp = Nothing- , createParentUnicodeName = Nothing- , createParentUniqueId = Nothing- , createTimeStamp = Nothing- , createUuid = Nothing- , createUseBatmap = False- , createVirtualSize = 0- }+ { createBlockSize = 2 * 1024 * 1024+ , createDiskType = DiskTypeDynamic+ , createParentTimeStamp = Nothing+ , createParentUnicodeName = Nothing+ , createParentUniqueId = Nothing+ , createTimeStamp = Nothing+ , createUuid = Nothing+ , createUseBatmap = False+ , createVirtualSize = 0+ } -- | Retrieves the header and footer from a VHD file. getInfo :: FilePath -> IO (Either String (Header, Footer)) getInfo filePath = withFile filePath ReadMode $ \handle -> do- footer <- decode <$> B.hGet handle 512- header <- decode <$> B.hGet handle 1024- case (footer, header) of- (Left err, _) -> return $ Left err- (_, Left err) -> return $ Left err- (Right f, Right h) -> return $ Right (h, f)+ footer <- decode <$> B.hGet handle 512+ header <- decode <$> B.hGet handle 1024+ case (footer, header) of+ (Left err, _) -> return $ Left err+ (_, Left err) -> return $ Left err+ (Right f, Right h) -> return $ Right (h, f) -- | Creates an empty VHD file with the specified parameters. create :: FilePath -> CreateParameters -> IO () create filePath createParams- | createVirtualSize createParams == 0 = error "cannot create a 0-sized VHD"- | otherwise = do- nowUnixEpoch <- fromIntegral . fromEnum <$> getPOSIXTime- let nowVhdEpoch = fromIntegral (nowUnixEpoch - y2k)- uniqueid <- randomUniqueId- create' filePath $ createParams- { createTimeStamp = Just $ maybe nowVhdEpoch id $ createTimeStamp createParams- , createUuid = Just $ maybe uniqueid id $ createUuid createParams- }- where- y2k :: Word64- y2k = 946684800 -- seconds from the unix epoch to the vhd epoch+ | createVirtualSize createParams == 0 = error "cannot create a 0-sized VHD"+ | otherwise = do+ nowVhdEpoch <- getVHDTime+ uniqueid <- randomUniqueId+ create' filePath $ createParams+ { createTimeStamp = Just $ maybe nowVhdEpoch id $ createTimeStamp createParams+ , createUuid = Just $ maybe uniqueid id $ createUuid createParams+ } create' :: FilePath -> CreateParameters -> IO () create' filePath createParams = - withFile filePath WriteMode $ \handle -> do- B.hPut handle $ encode footer- B.hPut handle $ encode header- hAlign handle (fromIntegral sectorLength)- -- create a BAT with every entry initialized to 0xffffffff.- B.hPut handle $ B.replicate (fromIntegral batSize) 0xff- -- maybe create a batmap- when (createUseBatmap createParams) $ do- hAlign handle (fromIntegral sectorLength)- headerPos <- hTell handle- B.hPut handle $ encode $ BatmapHeader- { batmapHeaderCookie = cookie "tdbatmap"- , batmapHeaderOffset = fromIntegral (headerPos + fromIntegral sectorLength)- , batmapHeaderSize = (maxTableEntries `div` 8) `divRoundUp` sectorLength- , batmapHeaderVersion = Version 1 2- , batmapHeaderChecksum = 0xffffffff- }- hAlign handle (fromIntegral sectorLength)- B.hPut handle $ B.replicate (fromIntegral (maxTableEntries `div` 8)) 0x0- hAlign handle (fromIntegral sectorLength)- B.hPut handle $ encode footer+ withFile filePath WriteMode $ \handle -> do+ B.hPut handle $ encode footer+ B.hPut handle $ encode header+ hAlign handle sectorLength+ -- create a BAT with every entry initialized to 0xffffffff.+ B.hPut handle $ B.replicate (fromIntegral batSize) 0xff+ -- maybe create a batmap+ when (createUseBatmap createParams) $ do+ hAlign handle sectorLength+ headerPos <- hTell handle+ B.hPut handle $ encode $ batmapHeader headerPos+ hAlign handle sectorLength+ B.hPut handle $ B.replicate (fromIntegral (maxTableEntries `div` 8)) 0x0+ hAlign handle sectorLength+ B.hPut handle $ encode footer - where- virtualSize = createVirtualSize createParams- maxTableEntries = fromIntegral (virtualSize `divRoundUp` fromIntegral (createBlockSize createParams))- batSize = (maxTableEntries * 4) `roundUpToModulo` sectorLength- batPadSize = batSize - maxTableEntries * 4- footerSize = 512- headerSize = 1024+ where+ BlockSize bsz = createBlockSize createParams+ virtSize = createVirtualSize createParams - footer = adjustFooterChecksum $ Footer- { footerCookie = cookie "conectix"- , footerIsTemporaryDisk = False- , footerFormatVersion = Version 1 0- , footerDataOffset = footerSize- , footerTimeStamp = fromJust $ createTimeStamp createParams- , footerCreatorApplication = creatorApplication "tap\0"- , footerCreatorVersion = if createUseBatmap createParams then Version 1 3 else Version 1 0- , footerCreatorHostOs = CreatorHostOsWindows- , footerOriginalSize = virtualSize- , footerCurrentSize = virtualSize- , footerDiskGeometry = diskGeometry (virtualSize `div` fromIntegral sectorLength)- , footerDiskType = createDiskType createParams- , footerChecksum = 0- , footerUniqueId = fromJust $ createUuid createParams- , footerIsSavedState = False- }+ maxTableEntries = fromIntegral (virtSize `divRoundUp` fromIntegral bsz)+ batSize = (maxTableEntries * 4) `roundUpToModulo` sectorLength+ footerSize = 512+ headerSize = 1024 - header = adjustHeaderChecksum $ Header- { headerCookie = cookie "cxsparse"- , headerDataOffset = 0xffffffffffffffff- , headerTableOffset = footerSize + headerSize- , headerVersion = Version 1 0- , headerMaxTableEntries = maxTableEntries- , headerBlockSize = createBlockSize createParams- , headerChecksum = 0- , headerParentUniqueId = fromMaybe (uniqueId $ B.replicate 16 0) (createParentUniqueId createParams)- , headerParentTimeStamp = fromMaybe 0 (createParentTimeStamp createParams)- , headerReserved1 = B.replicate 4 0- , headerParentUnicodeName = fromMaybe (parentUnicodeName "") (createParentUnicodeName createParams)- , headerParentLocatorEntries = parentLocatorEntries $ replicate 8 (ParentLocatorEntry $ B.replicate 24 0)- }+ footer = adjustChecksum $ Footer+ { footerCookie = cookie "conectix"+ , footerIsTemporaryDisk = False+ , footerFormatVersion = Version 1 0+ , footerDataOffset = footerSize+ , footerTimeStamp = fromJust $ createTimeStamp createParams+ , footerCreatorApplication = creatorApplication "tap\0"+ , footerCreatorVersion = if createUseBatmap createParams then Version 1 3 else Version 1 0+ , footerCreatorHostOs = CreatorHostOsWindows+ , footerOriginalSize = virtSize+ , footerCurrentSize = virtSize+ , footerDiskGeometry = diskGeometry (virtSize `div` Block.sectorLength)+ , footerDiskType = createDiskType createParams+ , footerChecksum = mempty+ , footerUniqueId = fromJust $ createUuid createParams+ , footerIsSavedState = False+ } + header = adjustChecksum $ Header+ { headerCookie = cookie "cxsparse"+ , headerDataOffset = 0xffffffffffffffff+ , headerTableOffset = footerSize + headerSize+ , headerVersion = Version 1 0+ , headerMaxTableEntries = maxTableEntries+ , headerBlockSize = createBlockSize createParams+ , headerChecksum = mempty+ , headerParentUniqueId = fromMaybe (uniqueId $ B.replicate 16 0) (createParentUniqueId createParams)+ , headerParentTimeStamp = fromMaybe (VhdDiffTime 0) (createParentTimeStamp createParams)+ , headerReserved1 = B.replicate 4 0+ , headerParentUnicodeName = fromMaybe (parentUnicodeName "") (createParentUnicodeName createParams)+ , headerParentLocatorEntries = ParentLocatorEntries $ replicate 8 nullParentLocatorEntry+ }++ batmapHeader headerPos = BatmapHeader+ { batmapHeaderCookie = cookie "tdbatmap"+ , batmapHeaderOffset = fromIntegral (headerPos + sectorLength)+ , batmapHeaderSize = (maxTableEntries `div` 8) `divRoundUp` sectorLength+ , batmapHeaderVersion = Version 1 2+ , batmapHeaderChecksum = mempty+ , batmapHeaderMarker = 0+ , batmapHeaderKeyHash = KeyHash Nothing+ , batmapHeaderReserved = B.replicate 418 0+ }+ snapshot :: Vhd -> FilePath -> IO () snapshot parentVhd childFilePath = do- create childFilePath $ CreateParameters- { createBlockSize = vhdBlockSize parentVhd- , createDiskType = DiskTypeDifferencing- , createParentTimeStamp = Just $ footerTimeStamp headNodeFooter- , createParentUniqueId = Just $ footerUniqueId headNodeFooter- , createParentUnicodeName = Just $ parentUnicodeName parentFilePath- , createTimeStamp = Nothing- , createUuid = Nothing- , createUseBatmap = hasBitmap headNodeBat- , createVirtualSize = virtualSize parentVhd- }- where- headNode = head $ vhdNodes parentVhd- headNodeBat = nodeBat headNode- headNodeFooter = nodeFooter headNode- parentFilePath = makeRelative (takeDirectory childFilePath) (nodeFilePath headNode)+ create childFilePath $ CreateParameters+ { createBlockSize = vhdBlockSize parentVhd+ , createDiskType = DiskTypeDifferencing+ , createParentTimeStamp = Just $ footerTimeStamp headNodeFooter+ , createParentUniqueId = Just $ footerUniqueId headNodeFooter+ , createParentUnicodeName = Just $ parentUnicodeName parentFilePath+ , createTimeStamp = Nothing+ , createUuid = Nothing+ , createUseBatmap = hasBatmap headNodeBat+ , createVirtualSize = virtualSize parentVhd+ }+ where headNode = head $ vhdNodes parentVhd+ headNodeBat = nodeBat headNode+ headNodeFooter = nodeFooter headNode+ parentFilePath = makeRelative (takeDirectory childFilePath) (nodeFilePath headNode) -- | Reads data from the whole virtual address space of the given VHD. readData :: Vhd -> IO BL.ByteString readData vhd = readDataRange vhd 0 (virtualSize vhd) -- | Reads data from the given virtual address range of the given VHD.-readDataRange :: Vhd -> VirtualByteAddress -> VirtualByteCount -> IO BL.ByteString-readDataRange vhd offset length =- if offset + length > virtualSize vhd- then error "cannot read data past end of VHD."- else fmap (trim . BL.fromChunks) (sequence blocks)- where- blocks = map (readDataBlock vhd) [blockFirst .. blockLast]- blockFirst = fromIntegral $ (offset ) `div` blockSize- blockLast = fromIntegral $ (offset + length - 1) `div` blockSize- blockSize = fromIntegral $ vhdBlockSize vhd- trim = BL.take toTake . BL.drop toDrop- where- toTake = fromIntegral $ length- toDrop = fromIntegral $ offset `mod` blockSize+--+-- TODO: modify this function to read sub-blocks where appropriate.+readDataRange :: Vhd -- ^ Vhd chain to read from+ -> Word64 -- ^ offset address in the VHD+ -> Word64 -- ^ number of byte to read+ -> IO BL.ByteString+readDataRange vhd offset len+ | offset + len > virtualSize vhd = error "cannot read data past end of VHD."+ | otherwise = trim . BL.fromChunks <$> mapM (readDataBlock vhd) [blockFirst..blockLast]+ where+ (blockFirst,BlockByteAddress toDrop,_) = vaddrToBlock startAddr (vhdBlockSize vhd)+ (blockLast,_,_) = vaddrToBlock endAddr (vhdBlockSize vhd)+ trim = BL.take (fromIntegral len) . BL.drop (fromIntegral toDrop)+ startAddr = VirtualByteAddress offset+ endAddr = VirtualByteAddress (offset + len) -- | Writes data to the given virtual address of the given VHD.-writeDataRange :: Vhd -> VirtualByteAddress -> BL.ByteString -> IO ()-writeDataRange vhd offset content = write (fromIntegral offset) content where+writeDataRange :: Vhd -- ^ Vhd chain to write to+ -> Word64 -- ^ offset address in the VHD+ -> BL.ByteString -- ^ the data to write in the VHD+ -> IO ()+writeDataRange vhd iniOffset iniContent = write (VirtualByteAddress iniOffset) iniContent+ where+ write :: VirtualByteAddress -> BL.ByteString -> IO ()+ write offset content+ | offset > VirtualByteAddress offsetMax = error "cannot write data past end of VHD."+ | BL.null content = return ()+ | otherwise = do+ sectorOffset <- lookupOrCreateBlock node blockNumber+ withMappedBlock node sectorOffset blockNumber $ \block -> do+ Block.writeDataRange bmap block blockOffset chunk+ write offsetNext contentNext+ where+ (blockNumber, blockOffset, chunkLength) = vaddrToBlock offset blockSize+ chunk = B.concat $ BL.toChunks $ fst contentSplit+ contentSplit = BL.splitAt (fromIntegral chunkLength) content+ contentNext = snd contentSplit+ offsetNext = vaddrNextBlock offset blockSize - write offset content- | offset > offsetMax = error "cannot write data past end of VHD."- | BL.null content = return ()- | otherwise = do- sectorOffset <- lookupOrCreateBlock node (fromIntegral blockNumber)- withBlock file (fromIntegral blockSize) sectorOffset $ \block -> do- Block.writeDataRange block (fromIntegral blockOffset) chunk- write offsetNext contentNext- where- blockNumber = (fromIntegral $ offset `div` blockSize) :: VirtualBlockAddress- blockOffset = offset `mod` blockSize- chunk = B.concat $ BL.toChunks $ fst contentSplit- chunkLength = fromIntegral $ blockSize - (offset `mod` blockSize)- contentSplit = BL.splitAt chunkLength content- contentNext = snd contentSplit- offsetNext = ((offset `div` blockSize) * blockSize) + blockSize+ (_,bmap) = getVhdBlockMapper node - bat = nodeBat node- file = nodeFilePath node- node = head $ vhdNodes vhd- offsetMax = virtualSize vhd- blockSize = fromIntegral $ vhdBlockSize vhd+ node = head $ vhdNodes vhd+ offsetMax = virtualSize vhd+ blockSize = vhdBlockSize vhd --- | Reads a block of data from the given virtual address of the given VHD.+-- | Reads all available data from the given virtual block of the given VHD. readDataBlock :: Vhd -> VirtualBlockAddress -> IO B.ByteString-readDataBlock vhd blockNumber =- B.create- (fromIntegral $ blockSize)- (unsafeReadDataBlock vhd blockNumber)- where- blockSize = vhdBlockSize vhd---- | Reads a block of data from the given virtual address of the given VHD.-unsafeReadDataBlock :: Vhd -> VirtualBlockAddress -> Ptr Word8 -> IO ()-unsafeReadDataBlock vhd blockNumber resultPtr = buildResult where-- -- To do: modify this function so that it can read a sub-block.+readDataBlock vhd virtualBlockAddress =+ readDataBlockRange vhd virtualBlockAddress 0 (vhdSectorPerBlock vhd) - buildResult :: IO ()- buildResult = do- B.memset resultPtr 0 (fromIntegral blockSize)- copySectorsFromNodes sectorsToRead =<< nodeOffsets+-- | Reads data from the given sector range of the given virtual block of the given VHD.+readDataBlockRange :: Vhd -> VirtualBlockAddress -> BlockSectorAddress -> Word32 -> IO B.ByteString+readDataBlockRange vhd virtualBlockAddress sectorOffset sectorCount =+ B.create+ (fromIntegral $ sectorCount * Block.sectorLength)+ (unsafeReadDataBlockRange vhd virtualBlockAddress sectorOffset sectorCount) - blockSize = vhdBlockSize vhd+-- | Unsafely reads data from the given sector range of the given virtual block of the given VHD.+unsafeReadDataBlockRange :: Vhd+ -> VirtualBlockAddress+ -> BlockSectorAddress+ -> Word32+ -> Ptr Word8+ -> IO ()+unsafeReadDataBlockRange vhd virtualBlockAddress sectorOffset sectorCount resultPtr = do+ _ <- B.memset resultPtr 0 $ fromIntegral $ sectorCount * sectorLength+ copySectorsFromNodes sectorsToRead =<< nodeOffsets+ where+ sectorsToRead = fromRange (fromIntegral lo) (fromIntegral hi)+ where (BlockSectorAddress lo) = sectorOffset+ hi = lo + sectorCount - sectorsToRead = fromRange 0 $ fromIntegral $ blockSize `div` sectorLength+ nodeOffsets :: IO [(VhdNode, PhysicalSectorAddress)]+ nodeOffsets = fmap catMaybes $ mapM maybeNodeOffset $ vhdNodes vhd+ where maybeNodeOffset node = (fmap . fmap) (node, ) $ lookupBlock (nodeBat node) virtualBlockAddress - nodeOffsets :: IO [(VhdNode, PhysicalSectorAddress)]- nodeOffsets = fmap catMaybes $ mapM maybeNodeOffset $ vhdNodes vhd where- maybeNodeOffset node = (fmap . fmap) (node, ) $- lookupBlock (nodeBat node) blockNumber+ copySectorsFromNodes :: BitSet BlockSectorAddress -> [(VhdNode, PhysicalSectorAddress)] -> IO ()+ copySectorsFromNodes _ [] = return ()+ copySectorsFromNodes sectorsRequested (nodeOffset : tailNodes)+ | BitSet.isEmpty sectorsRequested = return ()+ | otherwise = do sectorsMissing <- copySectorsFromNode sectorsRequested nodeOffset+ copySectorsFromNodes sectorsMissing tailNodes - copySectorsFromNodes :: BitSet -> [(VhdNode, PhysicalSectorAddress)] -> IO ()- copySectorsFromNodes sectorsToCopy [] = return ()- copySectorsFromNodes sectorsToCopy (nodeOffset : tail) =- if Data.BitSet.isEmpty sectorsToCopy then return () else do- sectorsMissing <- copySectorsFromNode sectorsToCopy nodeOffset- copySectorsFromNodes sectorsMissing tail+ copySectorsFromNode :: BitSet BlockSectorAddress -> (VhdNode, PhysicalSectorAddress) -> IO (BitSet BlockSectorAddress)+ copySectorsFromNode sectorsRequested (node, physicalSectorOfBlock) =+ Block.withBlock (nodeFilePath node) (vhdBlockSize vhd) 0+ physicalSectorOfBlock $ copySectorsFromNodeBlock sectorsRequested bmap+ where (bmap,_) = getVhdBlockMapper node - copySectorsFromNode :: BitSet -> (VhdNode, PhysicalSectorAddress) -> IO BitSet- copySectorsFromNode sectorsRequested (node, sectorOffset) =- withBlock (nodeFilePath node) blockSize sectorOffset $ \block -> do- sectorsPresentByteString <- Block.readBitmap block- let sectorsPresent = fromByteString sectorsPresentByteString- let sectorsMissing = sectorsRequested `subtract` sectorsPresent- let sectorsToCopy = sectorsRequested `intersect` sectorsPresent- mapM_- (\offset -> unsafeReadDataRange block offset- (fromIntegral sectorLength)- (resultPtr `plusPtr` (fromIntegral offset)))- (map (byteOffsetOfSector . fromIntegral) $ toList sectorsToCopy)- return sectorsMissing+ copySectorsFromNodeBlock :: BitSet BlockSectorAddress -> Maybe BlockDataMapper -> Block -> IO (BitSet BlockSectorAddress)+ copySectorsFromNodeBlock sectorsRequested bmap block = do+ sectorsPresentByteString <- Block.readBitmap block+ let sectorsPresent = fromByteString sectorsPresentByteString+ sectorsMissing = sectorsRequested `subtract` sectorsPresent+ sectorsToCopy = sectorsRequested `intersect` sectorsPresent+ mapM_ (copySectorFromNodeBlock bmap block) (map fromIntegral $ BitSet.toList sectorsToCopy)+ return sectorsMissing - byteOffsetOfSector :: BlockSectorAddress -> BlockByteAddress- byteOffsetOfSector = (*) $ fromIntegral sectorLength+ copySectorFromNodeBlock :: Maybe BlockDataMapper -> Block -> BlockSectorAddress -> IO ()+ copySectorFromNodeBlock bmap block sectorToCopy =+ Block.unsafeReadDataRange bmap block sourceByteOffset Block.sectorLength target+ where+ sourceByteOffset = Block.blockSectorToByte sectorToCopy+ (BlockByteAddress targetByteOffset) = Block.blockSectorToByte (sectorToCopy - sectorOffset)+ target = plusPtr resultPtr $ fromIntegral $ targetByteOffset
Data/Vhd/Bat.hs view
@@ -1,99 +1,91 @@ module Data.Vhd.Bat- ( Bat (..)- , batGetSize- , containsBlock- , lookupBlock- , unsafeLookupBlock- , hasBitmap- , batWrite- , batMmap- , batIterate- , batUpdateChecksum- ) where+ ( Bat (..)+ , batGetSize+ , batmapHeaderModify+ , hasBatmap+ , containsBlock+ , lookupBlock+ , batWrite+ , batMmap+ , batIterate+ ) where import Control.Monad-import Data.Bits+import Control.Applicative+import Control.Concurrent.MVar+import qualified Data.ByteString.Internal as B+import Data.Byteable import Data.Storable.Endian-import Data.Word import Foreign.Ptr-import Foreign.Storable-import Data.Vhd.Bitmap-import Data.Vhd.Serialize+import Data.Serialize (encode)+import Data.Vhd.Batmap import Data.Vhd.Types+import Data.Vhd.Header+import Data.Vhd.Footer+import Data.Vhd.Const import Data.Vhd.Utils import System.IO.MMap -data Bat = Bat BatStart BatEnd (Maybe Batmap)-type BatStart = Ptr PhysicalSectorAddress-type BatEnd = Ptr PhysicalSectorAddress-data Batmap = Batmap Bitmap Int--sectorLength = 512+data Bat = Bat+ { batStart :: Ptr PhysicalSectorAddress+ , batEnd :: Ptr PhysicalSectorAddress+ , batBatmap :: Maybe (MVar BatmapHeader)+ } +emptyEntry :: PhysicalSectorAddress emptyEntry = 0xffffffff -hasBitmap (Bat _ _ (Nothing)) = False-hasBitmap (Bat _ _ (Just bm)) = True--batmapSet :: VirtualBlockAddress -> Batmap -> IO ()-batmapSet n (Batmap bitmap _) = bitmapSet bitmap (fromIntegral n)--batmapChecksum :: Batmap -> IO Checksum-batmapChecksum (Batmap (Bitmap p) sz) = complement `fmap` foldM addByte 0 [0 .. (sz - 1)]- where addByte acc i = (p `peekElemOff` i) >>= \w -> return (acc + fromIntegral w)+hasBatmap :: Bat -> Bool+hasBatmap = maybe False (const True) . batBatmap -- | Returns the padded size (in bytes) of the given BAT. batGetSize :: Header -> Footer -> Int-batGetSize header footer = fromIntegral ((maxEntries * 4) `roundUpToModulo` sectorLength)- where- maxEntries = headerMaxTableEntries header+batGetSize header _ = fromIntegral ((maxEntries * 4) `roundUpToModulo` sectorLength)+ where+ maxEntries = headerMaxTableEntries header -- | Returns true if (and only if) the given BAT contains an entry for the -- block at the given virtual address. containsBlock :: Bat -> VirtualBlockAddress -> IO Bool-containsBlock = (fmap (/= emptyEntry) .) . unsafeLookupBlock+containsBlock bat vba = maybe False (const True) `fmap` lookupBlock bat vba -- | Returns the physical sector address for the block at the given virtual--- address, if (and only if) the given BAT contains an entry for that block.+-- address. lookupBlock :: Bat -> VirtualBlockAddress -> IO (Maybe PhysicalSectorAddress)-lookupBlock b n =- fmap- (\x -> if x == emptyEntry then Nothing else Just x)- (unsafeLookupBlock b n)---- | Returns the physical sector address for the block at the given virtual--- address. The value returned is valid if (and only if) the specified BAT--- contains an entry for that block.-unsafeLookupBlock :: Bat -> VirtualBlockAddress -> IO PhysicalSectorAddress-unsafeLookupBlock (Bat bptr _ _) n = peekBE ptr- where ptr = bptr `plusPtr` ((fromIntegral n) * 4)+lookupBlock (Bat bptr _ _) (VirtualBlockAddress n) = justBlock `fmap` peekBE ptr+ where ptr = bptr `plusPtr` ((fromIntegral n) * 4)+ justBlock psa | psa == emptyEntry = Nothing+ | otherwise = Just psa -- | Sets the physical sector address for the block at the given virtual -- address, in the specified BAT. batWrite :: Bat -> VirtualBlockAddress -> PhysicalSectorAddress -> IO ()-batWrite (Bat bptr _ bmap) n v = pokeBE ptr v >> maybe (return ()) (batmapSet n) bmap- where ptr = bptr `plusPtr` ((fromIntegral n) * 4)+batWrite (Bat bptr _ _) (VirtualBlockAddress n) v = pokeBE ptr v+ where ptr = bptr `plusPtr` ((fromIntegral n) * 4) batMmap :: FilePath -> Header -> Footer -> Maybe BatmapHeader -> (Bat -> IO a) -> IO a batMmap file header footer batmapHeader f =- mmapWithFilePtr file ReadWrite (Just offsetSize) $ \(ptr, sz) ->- let batmap = Batmap (Bitmap (castPtr (ptr `plusPtr` batmapOffset))) batmapSize in- let batendPtr = ptr `plusPtr` batSize in- f . Bat (castPtr ptr) batendPtr $ fmap (const batmap) batmapHeader- where- absoluteOffset = fromIntegral (headerTableOffset header)- offsetSize = (absoluteOffset, fromIntegral (batSize + maybe 0 (const 512) batmapHeader + batmapSize))- batmapOffset = batSize + fromIntegral sectorLength- batSize = batGetSize header footer- batmapSize = maybe 0 (fromIntegral . (* sectorLength) . batmapHeaderSize) batmapHeader+ mmapWithFilePtr file ReadWrite (Just offsetSize) $ \(ptr, _) -> do+ m <- case batmapHeader of+ Nothing -> return Nothing+ Just bh -> Just <$> newMVar bh+ let batendPtr = ptr `plusPtr` batSize+ f $ Bat (castPtr ptr) batendPtr m+ where+ absoluteOffset = fromIntegral (headerTableOffset header)+ offsetSize = (absoluteOffset, fromIntegral (batSize + maybe 0 sized batmapHeader + batmapSize))+ --batmapOffset = batSize + sized (undefined :: BatmapHeader)+ batSize = batGetSize header footer+ batmapSize = maybe 0 (fromIntegral . (* sectorLength) . batmapHeaderSize) batmapHeader -batIterate :: Bat -> VirtualBlockAddress -> (VirtualBlockAddress -> PhysicalSectorAddress -> IO ()) -> IO ()-batIterate bat nb f = forM_ [0 .. (nb - 1)] (\i -> unsafeLookupBlock bat i >>= \n -> f i n)+batmapHeaderModify :: Bat -> (BatmapHeader -> BatmapHeader) -> IO ()+batmapHeaderModify bat f =+ case batBatmap bat of+ Nothing -> return ()+ Just batmapMvar -> modifyMVar_ batmapMvar $ \batmapHdr -> do+ let newbatmapHdr = f batmapHdr+ withBytePtr (encode $ newbatmapHdr) $ \src -> B.memcpy (castPtr $ batEnd bat) src (sized batmapHdr)+ return newbatmapHdr --- | Updates the checksum in the batmap, if the batmap exists.-batUpdateChecksum :: Bat -> IO ()-batUpdateChecksum (Bat _ _ Nothing) = return ()-batUpdateChecksum (Bat _ endptr (Just batmap)) = do- let batmapChecksumPtr = endptr `plusPtr` (8+8+4+4)- checksum <- batmapChecksum batmap- pokeBE batmapChecksumPtr checksum+batIterate :: Bat -> VirtualBlockAddress -> (VirtualBlockAddress -> Maybe PhysicalSectorAddress -> IO ()) -> IO ()+batIterate bat nb f = forM_ [0 .. (nb - 1)] (\i -> lookupBlock bat i >>= \n -> f i n)
+ Data/Vhd/Batmap.hs view
@@ -0,0 +1,73 @@+module Data.Vhd.Batmap+ ( BatmapHeader(..)+ , KeyHash(..)+ , batmapSetKeyHash+ , batmapClearKeyHash+ ) where++import Control.Applicative+import qualified Data.ByteString as B+import Data.Serialize+import Data.Word+import Data.Vhd.Serialize+import Data.Vhd.Types+import Data.Vhd.Checksum++data BatmapHeader = BatmapHeader+ { batmapHeaderCookie :: Cookie+ , batmapHeaderOffset :: PhysicalByteAddress+ , batmapHeaderSize :: Word32+ , batmapHeaderVersion :: Version+ , batmapHeaderChecksum :: Checksum+ , batmapHeaderMarker :: Word8+ , batmapHeaderKeyHash :: KeyHash+ , batmapHeaderReserved :: B.ByteString+ } deriving (Show, Eq)++newtype KeyHash = KeyHash (Maybe (B.ByteString, B.ByteString))+ deriving (Show,Eq)++instance Serialize BatmapHeader where+ get = BatmapHeader+ <$> getCookie+ <*> getDataOffset+ <*> getWord32be+ <*> getVersion+ <*> get+ <*> getWord8+ <*> get+ <*> getByteString 418+ put b = do+ putCookie $ batmapHeaderCookie b+ putDataOffset $ batmapHeaderOffset b+ putWord32be $ batmapHeaderSize b+ putVersion $ batmapHeaderVersion b+ put $ batmapHeaderChecksum b+ putWord8 $ batmapHeaderMarker b+ put $ batmapHeaderKeyHash b+ putByteString $ batmapHeaderReserved b++instance Serialize KeyHash where+ get = do+ c <- getWord8+ if c /= 1+ then return $ KeyHash Nothing+ else do nonce <- getByteString 32+ hash <- getByteString 32+ return $ KeyHash (Just (nonce, hash))+ put (KeyHash Nothing) = putByteString $ B.replicate (32+32+1) 0+ put (KeyHash (Just (nonce, hash))) = do+ putWord8 1+ putByteString nonce+ putByteString hash++instance Sized BatmapHeader where+ sized _ = 512++batmapClearKeyHash :: BatmapHeader -> BatmapHeader+batmapClearKeyHash bhdr = bhdr { batmapHeaderKeyHash = KeyHash Nothing }++batmapSetKeyHash :: B.ByteString -> B.ByteString -> BatmapHeader -> BatmapHeader+batmapSetKeyHash nonce hash bhdr+ | B.length nonce /= 32 || B.length hash /= 32 = error "not valid keyhash"+ | otherwise = bhdr { batmapHeaderKeyHash = KeyHash (Just (nonce, hash)) }
Data/Vhd/Bitmap.hs view
@@ -1,10 +1,10 @@ module Data.Vhd.Bitmap- ( Bitmap (..)- , bitmapGet- , bitmapSet- , bitmapSetRange- , bitmapClear- ) where+ ( Bitmap (..)+ , bitmapGet+ , bitmapSet+ , bitmapSetRange+ , bitmapClear+ ) where import Data.Bits import Data.Word@@ -15,24 +15,24 @@ bitmapGet :: Bitmap -> Int -> IO Bool bitmapGet (Bitmap ptr) n = test `fmap` peekByteOff ptr offset- where- test :: Word8 -> Bool- test = flip testBit (7 - bit)- (offset, bit) = n `divMod` 8+ where+ test :: Word8 -> Bool+ test = flip testBit (7 - nBit)+ (offset, nBit) = n `divMod` 8 bitmapModify :: Bitmap -> Int -> (Int -> Word8 -> Word8) -> IO ()-bitmapModify (Bitmap bptr) n f = peek ptr >>= poke ptr . f (7 - bit)- where- ptr = bptr `plusPtr` offset- (offset, bit) = n `divMod` 8+bitmapModify (Bitmap bptr) n f = peek ptr >>= poke ptr . f (7 - nBit)+ where+ ptr = bptr `plusPtr` offset+ (offset, nBit) = n `divMod` 8 bitmapSet :: Bitmap -> Int -> IO () bitmapSet bitmap n = bitmapModify bitmap n (flip setBit) bitmapSetRange :: Bitmap -> Int -> Int -> IO () bitmapSetRange bitmap start end- | start < end = bitmapSet bitmap start >> bitmapSetRange bitmap (start + 1) end- | otherwise = return ()+ | start < end = bitmapSet bitmap start >> bitmapSetRange bitmap (start + 1) end+ | otherwise = return () bitmapClear :: Bitmap -> Int -> IO () bitmapClear bitmap n = bitmapModify bitmap n (flip clearBit)
Data/Vhd/Block.hs view
@@ -1,113 +1,192 @@ module Data.Vhd.Block- ( Block- , Sector- , bitmapSizeOfBlockSize- , bitmapOfBlock- , withBlock- , readBitmap- , readData- , readDataRange- , unsafeReadData- , unsafeReadDataRange- , writeDataRange- , sectorLength- ) where+ ( 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.Bat 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.Storable+import Foreign.ForeignPtr (newForeignPtr_) import System.IO.MMap+import Data.Byteable -data Block = Block BlockByteCount (Ptr Word8)-data Sector = Sector (Ptr Word8)-data Data = Data (Ptr Word8)+type BlockDataMapper = VirtualBlockAddress -> BlockByteAddress -> ByteString -> ByteString -sectorLength :: Word32-sectorLength = 512+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 blockSize _) = bitmapSizeOfBlockSize blockSize+bitmapSizeOfBlock block = bitmapSizeOfBlockSize $ blockSize block -- | Finds the padded size (in bytes) of the bitmap for a given block size.-bitmapSizeOfBlockSize :: BlockByteCount -> Int-bitmapSizeOfBlockSize blockSize = fromIntegral ((nbSector `divRoundUp` 8) `roundUpToModulo` sectorLength)- where nbSector = blockSize `divRoundUp` sectorLength+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 _ ptr) = Bitmap ptr---- | Finds the size (in bytes) of data stored within the block.-blockSizeOfBlock :: Block -> BlockByteCount-blockSizeOfBlock (Block bs _) = bs+bitmapOfBlock block = Bitmap $ blockPtr block -- | Retrieves the data for the given block. dataOfBlock :: Block -> Data-dataOfBlock (Block bs ptr) = Data $ ptr `plusPtr` (bitmapSizeOfBlockSize bs)+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 -> BlockByteCount -> PhysicalSectorAddress -> (Block -> IO a) -> IO a-withBlock file blockSize sectorOffset f =- mmapWithFilePtr file ReadWrite (Just (offset, length)) $ \(ptr, sz) ->- f (Block blockSize $ castPtr ptr)- where- offset = (fromIntegral sectorOffset) * (fromIntegral sectorLength)- length = (fromIntegral blockSize) + (fromIntegral $ bitmapSizeOfBlockSize blockSize)+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 length) create where- length = bitmapSizeOfBlock block- create byteStringPtr = B.memcpy target source (fromIntegral length) where- source = case bitmapOfBlock block of Bitmap b -> b- target = castPtr byteStringPtr+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 :: Block -> IO ByteString-readData block =- readDataRange block 0 (fromIntegral $ blockSizeOfBlock 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 :: Block -> BlockByteAddress -> BlockByteCount -> IO ByteString-readDataRange block offset length =- B.create (fromIntegral length) (unsafeReadDataRange block offset length)+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 :: Block -> Ptr Word8 -> IO ()-unsafeReadData block =- unsafeReadDataRange block 0 (fromIntegral $ blockSizeOfBlock 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 :: Block -> BlockByteAddress -> BlockByteCount -> Ptr Word8 -> IO ()-unsafeReadDataRange block offset length target =- B.memcpy target source (fromIntegral length)- where- source = (pointerOfData $ dataOfBlock block) `plusPtr` (fromIntegral offset)+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 :: Block -> BlockByteAddress -> ByteString -> IO ()-writeDataRange block 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 content (\source -> B.memcpy target (castPtr source) length)- where- length = fromIntegral $ B.length content- bitmap = bitmapOfBlock block- target = (pointerOfData $ dataOfBlock block) `plusPtr` (fromIntegral offset)- sectorStart = fromIntegral offset `div` sectorLength- sectorEnd = fromIntegral (fromIntegral offset + B.length content) `div` sectorLength+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
Data/Vhd/Checksum.hs view
@@ -1,41 +1,47 @@ module Data.Vhd.Checksum- ( adjustFooterChecksum- , adjustHeaderChecksum- , verifyFooterChecksum- , verifyHeaderChecksum- ) where+ ( Checksum(..)+ , CheckSumable(..)+ , checksumCalculate+ , verifyChecksum+ , adjustChecksum+ ) where import Data.Bits+import Data.Monoid import qualified Data.ByteString as B import Data.Serialize-import Data.Vhd.Serialize-import Data.Vhd.Types import Data.Word -plus :: Checksum -> Word8 -> Checksum-plus a b = a + fromIntegral b+newtype Checksum = Checksum Word32+ deriving (Show,Eq) -getHeaderChecksum :: Header -> Checksum-getHeaderChecksum header = complement $ B.foldl' plus 0 headerData- where- headerData = encode $ header { headerChecksum = 0 }+instance Serialize Checksum where+ put (Checksum v) = putWord32be v+ get = Checksum `fmap` getWord32be -getFooterChecksum :: Footer -> Checksum-getFooterChecksum footer = complement $ B.foldl' plus 0 footerData- where footerData = encode $ footer { footerChecksum = 0 }+instance Monoid Checksum where+ mempty = Checksum 0+ mappend (Checksum a) (Checksum b) = Checksum (a+b) -adjustFooterChecksum :: Footer -> Footer-adjustFooterChecksum f = f { footerChecksum = checksum }- where checksum = getFooterChecksum f+class Serialize a => CheckSumable a where+ calculateChecksum :: a -> Checksum+ getChecksum :: a -> Checksum+ setChecksum :: Checksum -> a -> a -adjustHeaderChecksum :: Header -> Header-adjustHeaderChecksum h = h { headerChecksum = checksum }- where checksum = getHeaderChecksum h+adjustChecksum :: CheckSumable a => a -> a+adjustChecksum a = setChecksum checksum a+ where checksum = calculateChecksum a -verifyFooterChecksum :: Footer -> Bool-verifyFooterChecksum f = footerChecksum f == checksum- where checksum = getFooterChecksum f+verifyChecksum :: CheckSumable a => a -> Bool+verifyChecksum a = expected == got+ where expected = getChecksum a+ got = calculateChecksum a -verifyHeaderChecksum :: Header -> Bool-verifyHeaderChecksum h = headerChecksum h == checksum- where checksum = getHeaderChecksum h+checksumPlus :: Checksum -> Word8 -> Checksum+checksumPlus (Checksum a) b = Checksum (a + fromIntegral b)++checksumComplement :: Checksum -> Checksum+checksumComplement (Checksum s) = Checksum (complement s)++checksumCalculate :: B.ByteString -> Checksum+checksumCalculate = checksumComplement . B.foldl' checksumPlus (Checksum 0)
+ Data/Vhd/Crypt.hs view
@@ -0,0 +1,80 @@+module Data.Vhd.Crypt+ ( VhdCryptKey(..)+ , VhdCryptContext+ , findImplicitCryptFile+ , findImplicitCryptKey+ , openCryptKey+ , vhdCryptInit+ , vhdEncrypt+ , vhdDecrypt+ , calculateHash+ ) where++import Control.Applicative ((<$>))+import Data.List (isSuffixOf)+import Data.Bits (shiftR)+import Data.Vhd.Types (VirtualBlockAddress(..), BlockByteAddress(..))+import Crypto.Hash.SHA256 (hash)+import Crypto.Cipher.AES+import System.FilePath+import System.Directory+import qualified Data.ByteString as B++newtype VhdCryptKey = VhdCryptKey B.ByteString++newtype VhdCryptContext = VhdCryptContext (AES,AES)++-- | Find implicit cryptographic key associated with a vhd node.+--+-- given a vhd node called "a.vhd" or "a", this function will looks for+-- "a,aes-xts-plain,512.key" and "a,aes-xts-plain,256.key"+--+findImplicitCryptFile :: FilePath -> IO (Maybe FilePath)+findImplicitCryptFile filepath = loop allCryptFiles+ where baseName | ".vhd" `isSuffixOf` filepath = dropExtension filepath+ | otherwise = filepath+ suffixes = [ ",aes-xts-plain,512.key", ",aes-xts-plain,256.key" ]+ allCryptFiles = map (baseName ++) suffixes++ loop [] = return Nothing+ loop (f:fs) = do e <- doesFileExist f+ if e then return (Just f) else loop fs++findImplicitCryptKey :: FilePath -> IO (Maybe VhdCryptKey)+findImplicitCryptKey filepath = do+ fpr <- findImplicitCryptFile filepath+ case fpr of+ Nothing -> return Nothing+ Just fp -> Just <$> openCryptKey fp++openCryptKey :: FilePath -> IO VhdCryptKey+openCryptKey fp = VhdCryptKey <$> B.readFile fp++calculateHash :: B.ByteString -> VhdCryptKey -> B.ByteString+calculateHash nonce (VhdCryptKey cryptKey) = hash $ B.concat [nonce, cryptKey]++vhdCryptInit :: VhdCryptKey -> Maybe VhdCryptContext+vhdCryptInit (VhdCryptKey cryptKey)+ | B.length cryptKey /= 32 && B.length cryptKey /= 64 = Nothing+ | otherwise =+ let (k1,k2) = B.splitAt ((B.length cryptKey) `div` 2) cryptKey+ in Just $ VhdCryptContext (initAES k1, initAES k2)++-- | Encrypt using VhdCryptContext+vhdEncrypt :: VhdCryptContext -> VirtualBlockAddress -> BlockByteAddress -> B.ByteString -> B.ByteString+vhdEncrypt (VhdCryptContext cc) blockN blockOffset bs =+ encryptXTS cc (blockAddressToIV blockN blockOffset) 0 bs++-- | Decrypt using VhdCryptContext+vhdDecrypt :: VhdCryptContext -> VirtualBlockAddress -> BlockByteAddress -> B.ByteString -> B.ByteString+vhdDecrypt (VhdCryptContext cc) blockN blockOffset bs =+ decryptXTS cc (blockAddressToIV blockN blockOffset) 0 bs++-- | Create an IV in big endian mode of the virtual block address+blockAddressToIV :: VirtualBlockAddress -> BlockByteAddress -> B.ByteString+blockAddressToIV (VirtualBlockAddress n) (BlockByteAddress bba) = B.pack [a,b,c,d,0,0,0,0,0,0,0,0,0,0,0,0]+ where a = fromIntegral s+ b = fromIntegral (s `shiftR` 8)+ c = fromIntegral (s `shiftR` 16)+ d = fromIntegral (s `shiftR` 24)+ s = 2*2*1024*n + (bba `div` 512) -- FIXME de-hardcode : blocksize and sector size
@@ -0,0 +1,83 @@+module Data.Vhd.Footer+ ( Footer(..)+ ) where++import Control.Applicative+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import Data.Serialize+import Data.Monoid+import Data.Vhd.Types+import Data.Vhd.Serialize+import Data.Vhd.UniqueId+import Data.Vhd.Time+import Data.Vhd.Checksum++data Footer = Footer+ { footerCookie :: Cookie+ , footerIsTemporaryDisk :: Bool+ , footerFormatVersion :: Version+ , footerDataOffset :: PhysicalByteAddress+ , footerTimeStamp :: VhdDiffTime+ , footerCreatorApplication :: CreatorApplication+ , footerCreatorVersion :: Version+ , footerCreatorHostOs :: CreatorHostOs+ , footerOriginalSize :: VirtualByteCount+ , footerCurrentSize :: VirtualByteCount+ , footerDiskGeometry :: DiskGeometry+ , footerDiskType :: DiskType+ , footerChecksum :: Checksum+ , footerUniqueId :: UniqueId+ , footerIsSavedState :: Bool+ } deriving (Show, Eq)++instance Sized Footer where+ sized _ = 512++instance CheckSumable Footer where+ getChecksum = footerChecksum+ setChecksum v footer = footer { footerChecksum = v }+ calculateChecksum footer = checksumCalculate $ encode $ footer { footerChecksum = mempty }++instance Serialize Footer where+ get = Footer+ <$> getCookie+ <*> getIsTemporaryDisk+ <*> getFormatVersion+ <*> getDataOffset+ <*> getTimeStamp+ <*> getCreatorApplication+ <*> getCreatorVersion+ <*> getCreatorHostOs+ <*> getOriginalSize+ <*> getCurrentSize+ <*> getDiskGeometry+ <*> getDiskType+ <*> get+ <*> getUniqueId+ <*> getIsSavedState+ <* getFooterPadding+ put f = do+ putCookie $ footerCookie f+ putIsTemporaryDisk $ footerIsTemporaryDisk f+ putFormatVersion $ footerFormatVersion f+ putDataOffset $ footerDataOffset f+ putTimeStamp $ footerTimeStamp f+ putCreatorApplication $ footerCreatorApplication f+ putCreatorVersion $ footerCreatorVersion f+ putCreatorHostOs $ footerCreatorHostOs f+ putOriginalSize $ footerOriginalSize f+ putCurrentSize $ footerCurrentSize f+ putDiskGeometry $ footerDiskGeometry f+ putDiskType $ footerDiskType f+ put $ footerChecksum f+ putUniqueId $ footerUniqueId f+ putIsSavedState $ footerIsSavedState f+ putFooterPadding++footerPaddingLength :: Int+footerPaddingLength = 427+getFooterPadding :: Get ByteString+getFooterPadding = getByteString footerPaddingLength+putFooterPadding :: Put+putFooterPadding = putByteString $ B.replicate footerPaddingLength 0
Data/Vhd/Geometry.hs view
@@ -7,24 +7,24 @@ -- | the virtual hard disk image format specification (v 1.0). diskGeometry :: Word64 -> DiskGeometry diskGeometry totalSectors' =- let (sectorsPerTrack, heads, cylindersTimesHeads) = calculate in- let cylinders = cylindersTimesHeads `div` heads in- DiskGeometry- (fromIntegral cylinders)- (fromIntegral heads)- (fromIntegral sectorsPerTrack)- where- totalSectors = min totalSectors' (65535 * 16 * 255)- calculate- | totalSectors > 65536 * 16 * 63 = (255, 16, totalSectors `div` 255)- | otherwise =- let sectorsPerTrack = 17 in- let cylindersTimesHeads = totalSectors `div` 17 in- let heads = max 4 $ (cylindersTimesHeads + 1023) `div` 1024 in- let (sectorsPerTrack', heads', cylindersTimesHeads') =- if cylindersTimesHeads >= heads * 1024 || heads > 16- then (31, 16, totalSectors `div` 31)- else (sectorsPerTrack, heads, cylindersTimesHeads) in- if cylindersTimesHeads' >= heads' * 1024- then (63, 16, totalSectors `div` 63)- else (sectorsPerTrack', heads', cylindersTimesHeads')+ let (sectorsPerTrack, heads, cylindersTimesHeads) = calculate in+ let cylinders = cylindersTimesHeads `div` heads in+ DiskGeometry+ (fromIntegral cylinders)+ (fromIntegral heads)+ sectorsPerTrack+ where+ totalSectors = min totalSectors' (65535 * 16 * 255)+ calculate+ | totalSectors > 65536 * 16 * 63 = (255, 16, totalSectors `div` 255)+ | otherwise =+ let sectorsPerTrack = 17 in+ let cylindersTimesHeads = totalSectors `div` 17 in+ let heads = max 4 $ (cylindersTimesHeads + 1023) `div` 1024 in+ let (sectorsPerTrack', heads', cylindersTimesHeads') =+ if cylindersTimesHeads >= heads * 1024 || heads > 16+ then (31, 16, totalSectors `div` 31)+ else (sectorsPerTrack, heads, cylindersTimesHeads) in+ if cylindersTimesHeads' >= heads' * 1024+ then (63, 16, totalSectors `div` 63)+ else (sectorsPerTrack', heads', cylindersTimesHeads')
+ Data/Vhd/Header.hs view
@@ -0,0 +1,74 @@+module Data.Vhd.Header+ ( Header(..)+ ) where++import Control.Applicative+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import Data.Serialize+import Data.Monoid (mempty)+import Data.Vhd.Types+import Data.Vhd.Serialize+import Data.Vhd.UniqueId+import Data.Vhd.Checksum+import Data.Vhd.Time++data Header = Header+ { headerCookie :: Cookie+ , headerDataOffset :: PhysicalByteAddress+ , headerTableOffset :: PhysicalByteAddress+ , headerVersion :: Version+ , headerMaxTableEntries :: VirtualBlockCount+ , headerBlockSize :: BlockSize+ , headerChecksum :: Checksum+ , headerParentUniqueId :: UniqueId+ , headerParentTimeStamp :: VhdDiffTime+ , headerReserved1 :: ByteString+ , headerParentUnicodeName :: ParentUnicodeName+ , headerParentLocatorEntries :: ParentLocatorEntries+ } deriving (Show, Eq)++instance Sized Header where+ sized _ = 1024++instance CheckSumable Header where+ getChecksum = headerChecksum+ setChecksum v header = header { headerChecksum = v }+ calculateChecksum header = checksumCalculate $ encode $ header { headerChecksum = mempty }++instance Serialize Header where+ get = Header+ <$> getCookie+ <*> getDataOffset+ <*> getTableOffset+ <*> getVersion+ <*> getMaxTableEntries+ <*> getBlockSize+ <*> get+ <*> getParentUniqueId+ <*> getParentTimeStamp+ <*> getByteString 4+ <*> getParentUnicodeName+ <*> get+ <* getHeaderPadding+ put h = do+ putCookie $ headerCookie h+ putDataOffset $ headerDataOffset h+ putTableOffset $ headerTableOffset h+ putVersion $ headerVersion h+ putMaxTableEntries $ headerMaxTableEntries h+ putBlockSize $ headerBlockSize h+ put $ headerChecksum h+ putParentUniqueId $ headerParentUniqueId h+ putParentTimeStamp $ headerParentTimeStamp h+ putByteString $ headerReserved1 h+ putParentUnicodeName $ headerParentUnicodeName h+ put $ headerParentLocatorEntries h+ putHeaderPadding++headerPaddingLength :: Int+headerPaddingLength = 256+getHeaderPadding :: Get ()+getHeaderPadding = skip headerPaddingLength+putHeaderPadding :: Put+putHeaderPadding = putByteString $ B.replicate headerPaddingLength 0
+ Data/Vhd/Lowlevel.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE OverloadedStrings #-}++module Data.Vhd.Lowlevel+ ( readHeader+ , readFooter+ , writeHeader+ , writeFooter+ ) where++import Control.Applicative+import qualified Data.ByteString as B+import Data.Serialize+import Data.Vhd.Checksum+import Data.Vhd.Header+import Data.Vhd.Footer+import Prelude hiding (subtract)+import System.IO++-- | read footer directly from a vhd file.+readFooter :: FilePath -> IO (Either String Footer)+readFooter filepath = withFile filepath ReadMode $ \handle ->+ decode <$> B.hGet handle 512++-- | read header directly from a vhd file+readHeader :: FilePath -> IO (Either String Header)+readHeader filepath = withFile filepath ReadMode $ \handle -> do+ hSeek handle AbsoluteSeek 512+ decode <$> B.hGet handle 1024++-- | re-write both footer in a VHD file+writeFooter :: FilePath -> Footer -> IO ()+writeFooter filePath footer = do+ withFile filePath ReadWriteMode $ \handle -> do+ B.hPut handle footerBs+ hSeek handle SeekFromEnd 512+ B.hPut handle footerBs+ where+ footerBs = encode $ adjustChecksum footer++-- | re-write an header in a VHD file+writeHeader :: FilePath -> Header -> IO ()+writeHeader filePath header = do+ withFile filePath ReadWriteMode $ \handle -> do+ hSeek handle AbsoluteSeek 512+ B.hPut handle headerBs+ where+ headerBs = encode $ adjustChecksum header
Data/Vhd/Node.hs view
@@ -1,11 +1,17 @@ {-# LANGUAGE OverloadedStrings #-} module Data.Vhd.Node- ( VhdNode (..)- , containsBlock- , lookupOrCreateBlock- , withVhdNode- ) where+ ( VhdNode (..)+ , getVhdBlockMapper+ , containsBlock+ , lookupOrCreateBlock+ , openCryptKey+ , withVhdNode+ , withMappedBlock+ , iterateBlocks+ , iterateBlockSectors+ , batmapHeaderChange+ ) where import Control.Applicative ((<$>)) import Control.Monad@@ -14,74 +20,135 @@ 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.Serialize+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- , nodeFilePath :: FilePath- , nodeModified :: IORef Bool- }+ { 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- 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- Bat.batMmap filePath header footer mBatmapHdr $ \bat -> do- bmodified <- newIORef False- a <- f $ VhdNode- { nodeBat = bat- , nodeHeader = header- , nodeFooter = footer- , nodeHandle = handle- , nodeFilePath = filePath- , nodeModified = bmodified- }- modified <- readIORef bmodified- when (modified) $ Bat.batUpdateChecksum bat- return a+ 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- unlessM (Bat.containsBlock (nodeBat node) blockNumber) $ appendEmptyBlock node blockNumber- Bat.unsafeLookupBlock (nodeBat node) blockNumber+ 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) --- | create empty block at the end-appendEmptyBlock :: VhdNode -> VirtualBlockAddress -> IO ()+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- hSeek (nodeHandle node) SeekFromEnd 512- x <- hTell (nodeHandle node)- -- paranoid check- let (sector, m) = x `divMod` 512- unless (m == 0) $ error "wrong sector alignment"- Bat.batWrite (nodeBat node) n (fromIntegral sector)- modifyIORef (nodeModified node) (const True)- B.hPut (nodeHandle node) (B.replicate fullSize 0)- hAlign (nodeHandle node) (fromIntegral sectorLength)- B.hPut (nodeHandle node) $ encode (nodeFooter node)- where- fullSize = bitmapSize + fromIntegral blockSize- bitmapSize = bitmapSizeOfBlockSize blockSize- blockSize = headerBlockSize $ nodeHeader node+ -- 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))
Data/Vhd/Serialize.hs view
@@ -1,113 +1,27 @@ module Data.Vhd.Serialize where import Control.Applicative-import Control.Exception import Control.Monad import Data.Bits+import Data.Word+import Data.Byteable import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as BL import Data.Serialize-import Data.Serialize.Get-import Data.Serialize.Put import qualified Data.Text as T import Data.Text.Encoding import Data.Vhd.Types--instance Serialize Header where- get = Header- <$> getCookie- <*> getDataOffset- <*> getTableOffset- <*> getVersion- <*> getMaxTableEntries- <*> getBlockSize- <*> getChecksum- <*> getParentUniqueId- <*> getParentTimeStamp- <*> getByteString 4- <*> getParentUnicodeName- <*> getParentLocatorEntries- <* getHeaderPadding- put h = do- putCookie $ headerCookie h- putDataOffset $ headerDataOffset h- putTableOffset $ headerTableOffset h- putVersion $ headerVersion h- putMaxTableEntries $ headerMaxTableEntries h- putBlockSize $ headerBlockSize h- putChecksum $ headerChecksum h- putParentUniqueId $ headerParentUniqueId h- putParentTimeStamp $ headerParentTimeStamp h- putByteString $ headerReserved1 h- putParentUnicodeName $ headerParentUnicodeName h- putParentLocatorEntries $ headerParentLocatorEntries h- putHeaderPadding--instance Serialize Footer where- get = Footer- <$> getCookie- <*> getIsTemporaryDisk- <*> getFormatVersion- <*> getDataOffset- <*> getTimeStamp- <*> getCreatorApplication- <*> getCreatorVersion- <*> getCreatorHostOs- <*> getOriginalSize- <*> getCurrentSize- <*> getDiskGeometry- <*> getDiskType- <*> getChecksum- <*> getUniqueId- <*> getIsSavedState- <* getFooterPadding- put f = do- putCookie $ footerCookie f- putIsTemporaryDisk $ footerIsTemporaryDisk f- putFormatVersion $ footerFormatVersion f- putDataOffset $ footerDataOffset f- putTimeStamp $ footerTimeStamp f- putCreatorApplication $ footerCreatorApplication f- putCreatorVersion $ footerCreatorVersion f- putCreatorHostOs $ footerCreatorHostOs f- putOriginalSize $ footerOriginalSize f- putCurrentSize $ footerCurrentSize f- putDiskGeometry $ footerDiskGeometry f- putDiskType $ footerDiskType f- putChecksum $ footerChecksum f- putUniqueId $ footerUniqueId f- putIsSavedState $ footerIsSavedState f- putFooterPadding--instance Serialize BatmapHeader where- get = BatmapHeader- <$> getCookie- <*> getDataOffset- <*> getWord32be- <*> getVersion- <*> getChecksum- put b = do- putCookie $ batmapHeaderCookie b- putDataOffset $ batmapHeaderOffset b- putWord32be $ batmapHeaderSize b- putVersion $ batmapHeaderVersion b- putChecksum $ batmapHeaderChecksum b--footerPaddingLength = 427-getFooterPadding = getByteString footerPaddingLength-putFooterPadding = putByteString $ B.replicate footerPaddingLength 0--headerPaddingLength = 256-getHeaderPadding = skip headerPaddingLength-putHeaderPadding = putByteString $ B.replicate headerPaddingLength 0+import Data.Vhd.UniqueId+import Data.Vhd.Time +getCookie :: Get Cookie getCookie = cookie <$> getByteString 8+putCookie :: Cookie -> Put putCookie (Cookie c) = putByteString c -getBlockSize = getWord32be-putBlockSize = putWord32be-getChecksum = getWord32be-putChecksum = putWord32be+getBlockSize :: Get BlockSize+getBlockSize = BlockSize <$> getWord32be+putBlockSize :: BlockSize -> Put+putBlockSize (BlockSize sz) = putWord32be sz getCurrentSize = getWord64be putCurrentSize = putWord64be getDataOffset = getWord64be@@ -116,69 +30,107 @@ putMaxTableEntries = putWord32be getOriginalSize = getWord64be putOriginalSize = putWord64be-getParentTimeStamp = getWord32be-putParentTimeStamp = putWord32be+getTableOffset :: Get Word64 getTableOffset = getWord64be+putTableOffset :: Word64 -> Put putTableOffset = putWord64be-getTimeStamp = getWord32be-putTimeStamp = putWord32be +getParentTimeStamp :: Get VhdDiffTime+getParentTimeStamp = getTimeStamp+putParentTimeStamp :: VhdDiffTime -> Put+putParentTimeStamp = putTimeStamp++getTimeStamp :: Get VhdDiffTime+getTimeStamp = VhdDiffTime <$> getWord32be+putTimeStamp :: VhdDiffTime -> Put+putTimeStamp (VhdDiffTime ts) = putWord32be ts++getCreatorApplication :: Get CreatorApplication getCreatorApplication = creatorApplication <$> getByteString 4+putCreatorApplication :: CreatorApplication -> Put putCreatorApplication (CreatorApplication c) = putByteString c +getCreatorHostOs :: Get CreatorHostOs getCreatorHostOs = convert <$> getWord32be where- convert 0x4D616320 = CreatorHostOsMacintosh- convert 0x5769326B = CreatorHostOsWindows- convert _ = CreatorHostOsUnknown+ convert 0x4D616320 = CreatorHostOsMacintosh+ convert 0x5769326B = CreatorHostOsWindows+ convert _ = CreatorHostOsUnknown+putCreatorHostOs :: CreatorHostOs -> Put putCreatorHostOs v = putWord32be $ case v of- CreatorHostOsMacintosh -> 0x4D616320- CreatorHostOsWindows -> 0x5769326B- CreatorHostOsUnknown -> 0+ CreatorHostOsMacintosh -> 0x4D616320+ CreatorHostOsWindows -> 0x5769326B+ CreatorHostOsUnknown -> 0 +getDiskType :: Get DiskType getDiskType = convert <$> getWord32be where- convert 2 = DiskTypeFixed- convert 3 = DiskTypeDynamic- convert 4 = DiskTypeDifferencing- convert _ = error "invalid disk type"+ convert 2 = DiskTypeFixed+ convert 3 = DiskTypeDynamic+ convert 4 = DiskTypeDifferencing+ convert _ = error "invalid disk type"+putDiskType :: DiskType -> Put putDiskType v = putWord32be $ case v of- DiskTypeFixed -> 2- DiskTypeDynamic -> 3- DiskTypeDifferencing -> 4+ DiskTypeFixed -> 2+ DiskTypeDynamic -> 3+ DiskTypeDifferencing -> 4 +getDiskGeometry :: Get DiskGeometry getDiskGeometry = DiskGeometry <$> getWord16be <*> getWord8 <*> getWord8+putDiskGeometry :: DiskGeometry -> Put putDiskGeometry (DiskGeometry c h s) = putWord16be c >> putWord8 h >> putWord8 s +getIsTemporaryDisk :: Get Bool getIsTemporaryDisk = (\n -> n .&. 1 == 1) <$> getWord32be+putIsTemporaryDisk :: Bool -> Put putIsTemporaryDisk i = putWord32be ((if i then 1 else 0) .|. 0x2) +getIsSavedState :: Get Bool getIsSavedState = (== 1) <$> getWord8+putIsSavedState :: Bool -> Put putIsSavedState i = putWord8 (if i then 1 else 0) -getUniqueId = UniqueId <$> getByteString 16-putUniqueId (UniqueId i) = putByteString i+getUniqueId :: Get UniqueId+getUniqueId = uniqueId <$> getByteString 16+putUniqueId :: UniqueId -> Put+putUniqueId uid = putByteString $ toBytes uid +getParentUniqueId :: Get UniqueId getParentUniqueId = getUniqueId++putParentUniqueId :: UniqueId -> Put putParentUniqueId = putUniqueId +getVersion :: Get Version getVersion = Version <$> getWord16be <*> getWord16be+putVersion :: Version -> Put putVersion (Version major minor) = putWord16be major >> putWord16be minor +getCreatorVersion :: Get Version getCreatorVersion = getVersion+putCreatorVersion :: Version -> Put putCreatorVersion = putVersion++getFormatVersion :: Get Version getFormatVersion = getVersion+putFormatVersion :: Version -> Put putFormatVersion = putVersion getParentUnicodeName = parentUnicodeName . demarshall <$> getByteString 512- where demarshall = takeWhile ((/=) '\0') . T.unpack . decodeUtf16BE+ where demarshall = takeWhile ((/=) '\0') . T.unpack . decodeUtf16BE putParentUnicodeName (ParentUnicodeName c)- | blen > 512 = error "parent unicode name length is greater than 512"- | otherwise = putByteString b >> putByteString (B.replicate (512 - blen) 0)- where- b = encodeUtf16BE $ T.pack c- blen = B.length b+ | blen > 512 = error "parent unicode name length is greater than 512"+ | otherwise = putByteString b >> putByteString (B.replicate (512 - blen) 0)+ where+ b = encodeUtf16BE $ T.pack c+ blen = B.length b -getParentLocatorEntry = parentLocatorEntry <$> getByteString 24-putParentLocatorEntry (ParentLocatorEntry e) = putByteString e+instance Serialize ParentLocatorEntry where+ get = ParentLocatorEntry <$> getWord32be+ <*> getWord32be+ <*> getWord32be+ <*> (getWord32be *> getWord64be)+ put ent = mapM_ putWord32be [locatorCode ent,locatorDataSpace ent,locatorDataLength ent,0]+ >> putWord64be (locatorDataOffset ent) -getParentLocatorEntries = parentLocatorEntries <$> replicateM 8 getParentLocatorEntry-putParentLocatorEntries (ParentLocatorEntries es) = mapM_ putParentLocatorEntry es+instance Serialize ParentLocatorEntries where+ get = ParentLocatorEntries <$> replicateM 8 get+ put (ParentLocatorEntries es) = mapM_ put es
+ Data/Vhd/Time.hs view
@@ -0,0 +1,30 @@+module Data.Vhd.Time+ ( VhdDiffTime(..)+ , getVHDTime+ , toPosixSeconds+ , toUTCTime+ ) where++import Data.Word+import Control.Applicative+import Data.Time.Clock.POSIX+import Data.Time.Clock++-- | Represent number of seconds since VHD epoch.+newtype VhdDiffTime = VhdDiffTime Word32+ deriving (Show,Read,Eq,Ord)++y2k :: Word64+y2k = 946684800 -- seconds from the unix epoch to the vhd epoch ++-- | return the current time in vhd epoch time.+getVHDTime :: IO VhdDiffTime+getVHDTime = do+ nowUnixEpoch <- truncate <$> getPOSIXTime+ return $ VhdDiffTime $ fromIntegral (nowUnixEpoch - y2k)++toPosixSeconds :: VhdDiffTime -> POSIXTime+toPosixSeconds (VhdDiffTime ts) = fromIntegral (fromIntegral ts + y2k)++toUTCTime :: VhdDiffTime -> UTCTime+toUTCTime = posixSecondsToUTCTime . toPosixSeconds
Data/Vhd/Types.hs view
@@ -1,122 +1,118 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-} module Data.Vhd.Types where import Control.Exception-import Control.Monad import qualified Data.ByteString as B-import Data.List import qualified Data.Text as T+import Data.Vhd.Const import Data.Text.Encoding import Data.Word-import System.Random-import Text.Printf -data Header = Header- { headerCookie :: Cookie- , headerDataOffset :: PhysicalByteAddress- , headerTableOffset :: PhysicalByteAddress- , headerVersion :: Version- , headerMaxTableEntries :: VirtualBlockCount- , headerBlockSize :: BlockByteCount- , headerChecksum :: Checksum- , headerParentUniqueId :: UniqueId- , headerParentTimeStamp :: TimeStamp- , headerReserved1 :: B.ByteString- , headerParentUnicodeName :: ParentUnicodeName- , headerParentLocatorEntries :: ParentLocatorEntries- } deriving (Show, Eq)+class Sized a where+ sized :: Num n => a -> n -data Footer = Footer- { footerCookie :: Cookie- , footerIsTemporaryDisk :: Bool- , footerFormatVersion :: Version- , footerDataOffset :: PhysicalByteAddress- , footerTimeStamp :: TimeStamp- , footerCreatorApplication :: CreatorApplication- , footerCreatorVersion :: Version- , footerCreatorHostOs :: CreatorHostOs- , footerOriginalSize :: VirtualByteCount- , footerCurrentSize :: VirtualByteCount- , footerDiskGeometry :: DiskGeometry- , footerDiskType :: DiskType- , footerChecksum :: Checksum- , footerUniqueId :: UniqueId- , footerIsSavedState :: Bool- } deriving (Show, Eq)+-- | block size+newtype BlockSize = BlockSize Word32+ deriving (Show,Eq,Ord,Num) -data BatmapHeader = BatmapHeader- { batmapHeaderCookie :: Cookie- , batmapHeaderOffset :: PhysicalByteAddress- , batmapHeaderSize :: Word32- , batmapHeaderVersion :: Version- , batmapHeaderChecksum :: Checksum- } deriving (Show, Eq)+-- | The offset from the beginning of a block in bytes+newtype BlockByteAddress = BlockByteAddress Word32+ deriving (Show,Eq,Ord,Num) -type BlockByteAddress = Word32-type BlockByteCount = Word32-type BlockSectorAddress = Word32-type BlockSectorCount = Word32+-- | The offset from the beginning of a block in sectors+newtype BlockSectorAddress = BlockSectorAddress Word32+ deriving (Show,Eq,Ord,Num,Enum)++-- | The absolute number of the block+newtype VirtualBlockAddress = VirtualBlockAddress Word32+ deriving (Show,Eq,Ord,Num,Enum)++-- | An absolute address in byte in the vhd content space+newtype VirtualByteAddress = VirtualByteAddress Word64+ deriving (Show,Eq,Ord,Num,Enum)++ type DiskGeometryCylinders = Word16 type DiskGeometryHeads = Word8 type DiskGeometrySectorsPerTrack = Word8-type Checksum = Word32+ type PhysicalByteAddress = Word64 type PhysicalByteCount = Word64 type PhysicalSectorAddress = Word32 type PhysicalSectorCount = Word32-type TimeStamp = Word32-type VirtualBlockAddress = Word32+ type VirtualBlockCount = Word32-type VirtualByteAddress = Word64 type VirtualByteCount = Word64 type VirtualSectorAddress = Word32 type VirtualSectorCount = Word32 +vaddrPlus :: VirtualByteAddress -> Word64 -> VirtualByteAddress+vaddrPlus (VirtualByteAddress b) w = VirtualByteAddress (b + w)++vaddrToBlock :: VirtualByteAddress -> BlockSize -> (VirtualBlockAddress, BlockByteAddress, Word32)+vaddrToBlock (VirtualByteAddress b) (BlockSize blocksz) =+ (VirtualBlockAddress $ fromIntegral d, fromIntegral m, blocksz - fromIntegral m)+ where (d,m) = b `divMod` fromIntegral blocksz++-- | increment the virtual address to align to the next block+vaddrNextBlock :: VirtualByteAddress -> BlockSize -> VirtualByteAddress+vaddrNextBlock (VirtualByteAddress b) (BlockSize blocksz) =+ VirtualByteAddress (((b `div` sz) * sz) + sz)+ where sz = fromIntegral blocksz++addrToSector :: Word64 -> PhysicalSectorAddress+addrToSector w = fromIntegral (w `div` sectorLength)+ data Version = Version VersionMajor VersionMinor deriving (Show, Eq) type VersionMajor = Word16 type VersionMinor = Word16 data CreatorHostOs- = CreatorHostOsUnknown- | CreatorHostOsWindows- | CreatorHostOsMacintosh deriving (Show, Eq)+ = CreatorHostOsUnknown+ | CreatorHostOsWindows+ | CreatorHostOsMacintosh deriving (Show, Eq) data DiskGeometry = DiskGeometry- DiskGeometryCylinders- DiskGeometryHeads- DiskGeometrySectorsPerTrack deriving (Show, Eq)+ DiskGeometryCylinders+ DiskGeometryHeads+ DiskGeometrySectorsPerTrack deriving (Show, Eq) data DiskType- = DiskTypeFixed- | DiskTypeDynamic- | DiskTypeDifferencing deriving (Show, Eq)+ = DiskTypeFixed+ | DiskTypeDynamic+ | DiskTypeDifferencing deriving (Show, Eq) newtype Cookie = Cookie B.ByteString deriving (Show, Eq) newtype CreatorApplication = CreatorApplication B.ByteString deriving (Show, Eq)-newtype ParentLocatorEntry = ParentLocatorEntry B.ByteString deriving (Show, Eq)-newtype ParentUnicodeName = ParentUnicodeName String deriving (Show, Eq)-newtype UniqueId = UniqueId B.ByteString deriving (Eq)+data ParentLocatorEntry = ParentLocatorEntry+ { locatorCode :: Word32+ , locatorDataSpace :: Word32+ , locatorDataLength :: Word32+ , locatorDataOffset :: Word64+ } deriving (Show, Eq) -instance Show UniqueId where- show (UniqueId b) = intercalate "-" $ map disp [[0 .. 3], [4, 5], [6, 7], [8, 9], [10 .. 15]]- where disp = concatMap (printf "%02x" . B.index b)+nullParentLocatorEntry :: ParentLocatorEntry+nullParentLocatorEntry = ParentLocatorEntry 0 0 0 0 +newtype ParentUnicodeName = ParentUnicodeName String deriving (Show, Eq)+ newtype ParentLocatorEntries = ParentLocatorEntries [ParentLocatorEntry] deriving (Show, Eq) +-- | smart constructor for Cookie+cookie :: B.ByteString -> Cookie cookie c = assert (B.length c == 8) $ Cookie c++-- | smart constructor for CreatorApplication+creatorApplication :: B.ByteString -> CreatorApplication creatorApplication a = assert (B.length a == 4) $ CreatorApplication a++-- | smart constructor for ParentLocatorEntries+parentLocatorEntries :: [ParentLocatorEntry] -> ParentLocatorEntries parentLocatorEntries e = assert ( length e == 8) $ ParentLocatorEntries e-parentLocatorEntry e = assert (B.length e == 24) $ ParentLocatorEntry e-uniqueId i = assert (B.length i == 16) $ UniqueId i +parentUnicodeName :: [Char] -> ParentUnicodeName parentUnicodeName n- | encodedLength > 512 = error "parent unicode name length must be <= 512 bytes"- | otherwise = ParentUnicodeName n- where- encodedLength = B.length $ encodeUtf16BE $ T.pack n--randomUniqueId :: IO UniqueId-randomUniqueId- = liftM (uniqueId . B.pack)- $ replicateM 16- $ liftM fromIntegral- $ randomRIO (0 :: Int, 255)+ | encodedLength > 512 = error "parent unicode name length must be <= 512 bytes"+ | otherwise = ParentUnicodeName n+ where+ encodedLength = B.length $ encodeUtf16BE $ T.pack n
+ Data/Vhd/UniqueId.hs view
@@ -0,0 +1,57 @@+module Data.Vhd.UniqueId+ ( UniqueId+ , uniqueId+ , randomUniqueId+ ) where++import Control.Exception+import Control.Monad+import Data.Char+import Data.List+import Data.Byteable+import System.Random+import qualified Data.ByteString as B+import Text.Printf++newtype UniqueId = UniqueId B.ByteString deriving (Eq)++-- | smart constructor for uniqueId+uniqueId :: B.ByteString -> UniqueId+uniqueId i = assert (B.length i == 16) $ UniqueId i++instance Byteable UniqueId where+ toBytes (UniqueId b) = b++instance Show UniqueId where+ show (UniqueId b) = intercalate "-" $ map disp [[0 .. 3], [4, 5], [6, 7], [8, 9], [10 .. 15]]+ where disp = concatMap (printf "%02x" . B.index b)++instance Read UniqueId where+ readsPrec _ r = let (t,d) = splitAt 36 r+ in case ofString t of+ Nothing -> []+ Just uid -> [(uid, d)]++ofString :: String -> Maybe UniqueId+ofString s+ | length s /= 36 = Nothing+ | head r0 /= '-' = Nothing+ | head r1 /= '-' = Nothing+ | head r2 /= '-' = Nothing+ | head r3 /= '-' = Nothing+ | otherwise = Just $ UniqueId $ B.pack (unhex a8 ++ unhex b4 ++ unhex c4 ++ unhex d4 ++ unhex (drop 1 r3))+ where (a8, r0) = splitAt 8 s+ (b4, r1) = splitAt 4 $ drop 1 r0+ (c4, r2) = splitAt 4 $ drop 1 r1+ (d4, r3) = splitAt 4 $ drop 1 r2++ unhex [] = []+ unhex (v1:v2:xs) = fromIntegral (digitToInt v1 * 16 + digitToInt v2) : unhex xs+ unhex _ = error "internal error in unhex"++randomUniqueId :: IO UniqueId+randomUniqueId+ = liftM (uniqueId . B.pack)+ $ replicateM 16+ $ liftM fromIntegral+ $ randomRIO (0 :: Int, 255)
Data/Vhd/Utils.hs view
@@ -1,34 +1,37 @@ module Data.Vhd.Utils- ( divRoundUp- , resolveColocatedFilePath- , roundUpToModulo- , hAlign- , unlessM- ) where+ ( divRoundUp+ , resolveColocatedFilePath+ , roundUpToModulo+ , hAlign+ , unlessM+ ) where import Control.Monad (unless) import qualified Data.ByteString as B import System.FilePath.Posix import System.IO +divRoundUp :: Integral a => a -> a -> a divRoundUp a b = let (d, m) = a `divMod` b in d + if m > 0 then 1 else 0 resolveColocatedFilePath :: FilePath -> FilePath -> FilePath resolveColocatedFilePath baseFilePath colocatedFilePath =- if isAbsolute colocatedFilePath- then colocatedFilePath- else takeDirectory baseFilePath </> colocatedFilePath+ if isAbsolute colocatedFilePath+ then colocatedFilePath+ else takeDirectory baseFilePath </> colocatedFilePath +roundUpToModulo :: Integral a => a -> a -> a roundUpToModulo n m- | n `mod` m == 0 = n- | otherwise = n + m - (n `mod` m)+ | n `mod` m == 0 = n+ | otherwise = n + m - (n `mod` m) -- | align an handle to the next modulo hAlign :: Handle -> Int -> IO () hAlign h n = hTell h >>= \i -> unless ((i `mod` fromIntegral n) == 0) (realign i)- where realign i = B.hPut h $ B.replicate (n - fromIntegral (i `mod` fromIntegral n)) 0+ where realign i = B.hPut h $ B.replicate (n - fromIntegral (i `mod` fromIntegral n)) 0 +unlessM :: Monad m => m Bool -> m () -> m () unlessM condition elseBranch = do- c <- condition- unless c elseBranch+ c <- condition+ unless c elseBranch
− Tests.hs
@@ -1,89 +0,0 @@-import Test.QuickCheck-import Test.Framework (defaultMain, testGroup)-import Test.Framework.Providers.QuickCheck2 (testProperty)--import Control.Applicative-import Control.Monad--import qualified Data.ByteString as B-import Data.Serialize-import Data.Vhd.Types-import Data.Vhd.Serialize--instance Arbitrary Version where- arbitrary = Version <$> arbitrary <*> arbitrary--instance Arbitrary UniqueId where- arbitrary = uniqueId . B.pack <$> replicateM 16 arbitrary--instance Arbitrary Cookie where- arbitrary = cookie . B.pack <$> replicateM 8 arbitrary--instance Arbitrary CreatorApplication where- arbitrary = creatorApplication . B.pack <$> replicateM 4 arbitrary--instance Arbitrary ParentLocatorEntry where- arbitrary = parentLocatorEntry . B.pack <$> replicateM 24 arbitrary--instance Arbitrary ParentLocatorEntries where- arbitrary = parentLocatorEntries <$> replicateM 8 arbitrary--instance Arbitrary ParentUnicodeName where- arbitrary = parentUnicodeName <$> replicateM 64 (arbitrary `suchThat` (\w -> w /= '\0'))--instance Arbitrary DiskGeometry where- arbitrary = DiskGeometry <$> arbitrary <*> arbitrary <*> arbitrary--instance Arbitrary DiskType where- arbitrary = elements [ DiskTypeFixed, DiskTypeDynamic, DiskTypeDifferencing ]--instance Arbitrary CreatorHostOs where- arbitrary = elements [ CreatorHostOsUnknown, CreatorHostOsWindows, CreatorHostOsMacintosh ]--instance Arbitrary Header where- arbitrary = Header- <$> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> (pure $ B.replicate 4 0)- <*> arbitrary- <*> arbitrary--instance Arbitrary Footer where- arbitrary = Footer- <$> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary--prop_header_marshalling_id :: Header -> Bool-prop_header_marshalling_id h = decode (encode h) == Right h--prop_footer_marshalling_id :: Footer -> Bool-prop_footer_marshalling_id f = decode (encode f) == Right f--marshallingTests =- [ testProperty "header identity" prop_header_marshalling_id- , testProperty "footer identity" prop_footer_marshalling_id- ]--main = defaultMain- [ testGroup "marshalling" marshallingTests- ]
+ Tests/Tests.hs view
@@ -0,0 +1,104 @@+import Test.QuickCheck+import Test.Framework (defaultMain, testGroup)+import Test.Framework.Providers.QuickCheck2 (testProperty)++import Control.Applicative+import Control.Monad++import qualified Data.ByteString as B+import Data.Serialize+import Data.Vhd.Checksum+import Data.Vhd.Types+import Data.Vhd.Header+import Data.Vhd.Footer+import Data.Vhd.UniqueId+import Data.Vhd.Serialize+import Data.Vhd.Time+import Data.Monoid++instance Arbitrary Version where+ arbitrary = Version <$> arbitrary <*> arbitrary++instance Arbitrary UniqueId where+ arbitrary = uniqueId . B.pack <$> replicateM 16 arbitrary++instance Arbitrary Cookie where+ arbitrary = cookie . B.pack <$> replicateM 8 arbitrary++instance Arbitrary CreatorApplication where+ arbitrary = creatorApplication . B.pack <$> replicateM 4 arbitrary++instance Arbitrary ParentLocatorEntry where+ arbitrary = ParentLocatorEntry <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary++instance Arbitrary ParentLocatorEntries where+ arbitrary = parentLocatorEntries <$> replicateM 8 arbitrary++instance Arbitrary ParentUnicodeName where+ arbitrary = parentUnicodeName <$> replicateM 64 (arbitrary `suchThat` (\w -> w /= '\0'))++instance Arbitrary DiskGeometry where+ arbitrary = DiskGeometry <$> arbitrary <*> arbitrary <*> arbitrary++instance Arbitrary BlockSize where+ arbitrary = return $ BlockSize (2*1024*1024)++instance Arbitrary DiskType where+ arbitrary = elements [ DiskTypeFixed, DiskTypeDynamic, DiskTypeDifferencing ]++instance Arbitrary CreatorHostOs where+ arbitrary = elements [ CreatorHostOsUnknown, CreatorHostOsWindows, CreatorHostOsMacintosh ]++instance Arbitrary VhdDiffTime where+ arbitrary = VhdDiffTime <$> arbitrary++instance Arbitrary Header where+ arbitrary = Header+ <$> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> (pure $ B.replicate 4 0)+ <*> arbitrary+ <*> arbitrary++instance Arbitrary Checksum where+ arbitrary = return mempty++instance Arbitrary Footer where+ arbitrary = Footer+ <$> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary++prop_header_marshalling_id :: Header -> Bool+prop_header_marshalling_id h = decode (encode h) == Right h++prop_footer_marshalling_id :: Footer -> Bool+prop_footer_marshalling_id f = decode (encode f) == Right f++marshallingTests =+ [ testProperty "header identity" prop_header_marshalling_id+ , testProperty "footer identity" prop_footer_marshalling_id+ ]++main = defaultMain+ [ testGroup "marshalling" marshallingTests+ ]
+ Tools/Vhd.hs view
@@ -0,0 +1,246 @@+import Control.Applicative+import Control.Concurrent.MVar+import Control.Monad+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+import Data.Char+import Data.IORef+import Data.Vhd+import Data.Vhd.Lowlevel+import Data.Vhd.Bat+import Data.Vhd.Crypt+import qualified Data.Vhd.Block as Block+import Data.Vhd.Checksum+import Data.Vhd.Node+import Data.Vhd.Types+import Data.Vhd.Batmap+import Data.Vhd.Time+import System.Environment (getArgs)+import System.Console.GetOpt+import System.IO+import Text.Printf+import Data.Word++cmdConvert _ [fileRaw, fileVhd, size] = convert =<< rawSizeBytes where+ vhdSizeMiB = read size+ vhdSizeBytes = vhdSizeMiB * 1024 * 1024+ rawSizeBytes = fmap fromIntegral $ withFile fileRaw ReadMode hFileSize+ convert rawSizeBytes+ | vhdSizeMiB `mod` 2 /= 0 = error+ "specified VHD size is not a multiple of 2 MiB."+ | vhdSizeBytes < rawSizeBytes = error+ "specified VHD size is not large enough to contain raw data."+ | otherwise = do+ create fileVhd $ defaultCreateParameters+ { createVirtualSize = vhdSizeBytes }+ withVhd fileVhd $ \vhd -> BL.readFile fileRaw >>= writeDataRange vhd 0+cmdConvert _ _ = error "usage: convert <raw file> <vhd file> <size MiB>"++cmdTranslate opts [fileVhd, resumeFile]+ | isEncrypt && isDecrypt = error "encryption and decryption are exclusive option"+ | isEncrypt == False && isDecrypt == False = error "need either to encrypt or decrypt. doing nothing"+ | otherwise = do+ (writeBmap, changeBatmapHdrF) <-+ case hasEncryptFile of+ Just file -> do+ key <- openCryptKey file+ let bmap = maybe (error "invalid crypt key") vhdEncrypt $ vhdCryptInit key+ nonce = B.replicate 32 0+ return (Just bmap, batmapSetKeyHash nonce (calculateHash nonce key))+ Nothing -> return (Nothing, batmapClearKeyHash)+ + withVhdNode fileVhd $ \vhd -> do+ let (dbmap,_) = getVhdBlockMapper vhd+ iterateBlocks vhd $ \block ->+ Block.iterateSectors block $ \bsa isAllocated -> do+ --putStrLn ("blockAddr: " ++ show (Block.blockAddr block) ++ " sector " ++ show bsa ++ " is allocated: " ++ show isAllocated)+ case isAllocated of+ True -> do r <- Block.readSector dbmap block bsa+ case r of+ Nothing -> error "inconsistency: sector should be allocated"+ Just b -> Block.writeSector writeBmap block bsa b+ False -> return ()+ batmapHeaderChange vhd changeBatmapHdrF+ -- FIXME: set new hash if available+ return ()+ where isDecrypt = Decrypt `elem` opts+ isEncrypt = maybe False (const True) hasEncryptFile+ hasEncryptFile = foldl (\acc o -> case o of+ Encrypt f -> Just f+ otherwise -> acc) Nothing opts++cmdTranslate _ _ = error "usage: translate <vhd file> <resume file>"++cmdCreate _ [name, size] =+ create name $ defaultCreateParameters+ { createVirtualSize = read size * 1024 * 1024 }+cmdCreate _ _ = error "usage: create <name> <size MiB>"++cmdExtract opts [fileVhd, fileRaw] = do+ case foldl getRange (0,0) opts of+ (0,0) -> withVhd fileVhd $ readData >=> BL.writeFile fileRaw+ (startOffset,sz) -> withVhd fileVhd $ \vhd ->+ withFile fileRaw WriteMode $ \dst ->+ copyByBlock vhd dst (1024*1024) startOffset sz+ where getRange (_ ,sz) (Offset s) = (getByteStr s, sz)+ getRange (off,_ ) (Size s) = (off, getByteStr s)+ getRange acc _ = acc+ getByteStr s = case span isDigit s of+ ("", _) -> error ("expecting a number with optional suffix, got: " ++ s)+ (n, suffix) -> let mul = case map toLower suffix of+ "k" -> 1024+ "m" -> 1024 * 1024+ "g" -> 1024 * 1024 * 1024+ "" -> 1+ _ -> error ("unknown suffix: " ++ suffix ++ " in " ++ s)+ in read n * mul+ -- copy block of chunkSize size+ copyByBlock vhd dst chunkSize offset sz = loop offset sz+ where loop off left+ | left < chunkSize = readDataRange vhd off left >>= BL.hPut dst+ | otherwise = do+ readDataRange vhd off chunkSize >>= BL.hPut dst+ loop (off + chunkSize) (left - chunkSize)+cmdExtract _ _ = error "usage: extract <vhd file> <raw file>"++cmdPropGet _ [file, field] = withVhdNode file $ \node -> do+ case map toLower field of+ "max-table-entries" -> putStrLn $ show $ headerMaxTableEntries $ nodeHeader node+ "blocksize" -> putStrLn $ show $ headerBlockSize $ nodeHeader node+ "disk-type" -> putStrLn $ show $ footerDiskType $ nodeFooter node+ "current-size" -> putStrLn $ show $ footerCurrentSize $ nodeFooter node+ "uuid" -> putStrLn $ show $ footerUniqueId $ nodeFooter node+ "parent-uuid" -> putStrLn $ show $ headerParentUniqueId $ nodeHeader node+ "parent-timestamp" -> putStrLn $ show $ headerParentTimeStamp $ nodeHeader node+ "parent-filepath" -> putStrLn $ show $ headerParentUnicodeName $ nodeHeader node+ "timestamp" -> putStrLn $ show $ footerTimeStamp $ nodeFooter node+ _ -> error "unknown field"+cmdPropGet _ _ = error "usage: prop-get <file> <field>"++cmdRead opts [file] = withVhdNode file $ \node -> do+ let hdr = nodeHeader node+ let ftr = nodeFooter node+ mapM_ (\(f, s) -> putStrLn (f ++ " : " ++ s))+ [ ("cookie ", show $ headerCookie hdr)+ , ("version ", show $ headerVersion hdr)+ , ("max-table-entries", show $ headerMaxTableEntries hdr)+ , ("block-size ", showBlockSize $ headerBlockSize hdr)+ , ("header-checksum ", showChecksum (headerChecksum hdr) (verifyChecksum hdr))+ , ("parent-uuid ", show $ headerParentUniqueId hdr)+ , ("parent-filepath ", show $ headerParentUnicodeName hdr)+ , ("parent-timestamp ", showTimestamp $ headerParentTimeStamp hdr)+ ]+ let (ParentLocatorEntries ents) = headerParentLocatorEntries hdr+ putStrLn "locators:"+ forM_ (zip ents [0..]) $ \(ent,i) -> do+ printf " %d: code=%.8x space=%.8x len=%.8x offset=%.16x\n"+ (i :: Int) (locatorCode ent) (locatorDataSpace ent) (locatorDataSpace ent) (locatorDataOffset ent)+ return ()++ mapM_ (\(f, s) -> putStrLn (f ++ " : " ++ s))+ [ ("disk-geometry ", show $ footerDiskGeometry ftr)+ , ("original-size ", showSize $ footerOriginalSize ftr)+ , ("current-size ", showSize $ footerOriginalSize ftr)+ , ("type ", show $ footerDiskType ftr)+ , ("footer-checksum ", showChecksum (footerChecksum ftr) (verifyChecksum ftr))+ , ("uuid ", show $ footerUniqueId ftr)+ , ("timestamp ", showTimestamp $ footerTimeStamp ftr)+ ]+ let bat = nodeBat node+ case batBatmap bat of+ Nothing -> putStrLn "* no batmap"+ Just batmapHdrMvar -> do+ batmapHdr <- readMVar batmapHdrMvar+ putStrLn "BATMAP:"+ mapM_ (\(f, s) -> putStrLn (" " ++ f ++ " : " ++ s))+ [ ("version ", show $ headerVersion hdr)+ , ("keyhash ", showKeyHash $ batmapHeaderKeyHash batmapHdr)+ , ("batmap-checksum ", showChecksum (batmapHeaderChecksum batmapHdr) False)+ ]++ allocated <- newIORef 0+ batIterate (nodeBat node) (fromIntegral $ headerMaxTableEntries hdr) $ \i@(VirtualBlockAddress a) n -> do+ case n of+ Nothing -> return ()+ Just psa -> modifyIORef allocated ((+) 1) >> when (DumpBat `elem` opts) (printf "BAT[%.5x] = %08x\n" a psa)+ nb <- readIORef allocated+ putStrLn ("blocks allocated : " ++ show nb ++ "/" ++ show (headerMaxTableEntries hdr))+ where showKeyHash (KeyHash Nothing) = "nothing"+ showKeyHash (KeyHash (Just (nonce, hash))) = "nonce=" ++ show nonce ++ ", hash=" ++ show hash+cmdRead _ _ = error "usage: read <file>"++cmdSnapshot _ [fileVhdParent, fileVhdChild] =+ withVhd fileVhdParent $ \vhdParent -> snapshot vhdParent fileVhdChild+cmdSnapshot _ _ = error "usage: snapshot <parent vhd file> <child vhd file>"++showBlockSize (BlockSize bsz) = showSize bsz++showSize i+ | i < 1024 = printf "%d bytes" i+ | i < (1024^2) = printf "%d KiB" (i `div` 1024)+ | i < (1024^3) = printf "%d MiB" (i `div` (1024^2))+ | otherwise = printf "%d GiB" (i `div` (1024^3))++showChecksum (Checksum checksum) isValid =+ printf "%08x (%s)" checksum (if isValid then "valid" else "invalid")++showTimestamp timestamp@(VhdDiffTime r) =+ let utc = toUTCTime timestamp+ in show r ++ "s since VHD epoch (" ++ show utc ++ ")"++cmdHelp _ _ = usage Nothing++cmdPropSetUuid _ args = do+ let (file,uuidStr) = case args of+ [f, u] -> (f,u)+ _ -> error "usage: vhd <set-uuid> <file> <newuuid>"+ let uuid = read uuidStr+ footer <- either error id <$> readFooter file+ writeFooter file $ footer { footerUniqueId = uuid }++data OptFlag =+ Help+ | Offset String+ | Size String+ | DumpBat+ | Decrypt+ | Encrypt String+ deriving (Show,Eq)++knownCommands :: [ (String, [String] -> IO ()) ]+knownCommands =+ [ ("convert", wrapOpt cmdConvert [helpOpt])+ , ("create", wrapOpt cmdCreate [helpOpt])+ , ("extract", wrapOpt cmdExtract [helpOpt,offsetOpt,sizeOpt])+ , ("prop-get", wrapOpt cmdPropGet [helpOpt])+ , ("set-uuid", wrapOpt cmdPropSetUuid [helpOpt])+ , ("read" , wrapOpt cmdRead [helpOpt,dumpBatOpt])+ , ("snapshot", wrapOpt cmdSnapshot [helpOpt])+ , ("translate", wrapOpt cmdTranslate [helpOpt,decryptOpt,encryptOpt])+ , ("help" , wrapOpt cmdHelp [])+ ]+ where wrapOpt f opts xs =+ case getOpt Permute opts xs of+ (o,n,[]) | Help `elem` o -> usage Nothing+ | otherwise -> f o n+ (_,_,errs) -> mapM_ putStrLn errs >> putStrLn (usageInfo "" opts)+ helpOpt = Option ['h'] ["help"] (NoArg Help) "ask for help"+ offsetOpt = Option ['o'] ["offset"] (ReqArg Offset "offset") "change the start offset"+ sizeOpt = Option ['z'] ["size"] (ReqArg Size "size") "size in bytes"+ dumpBatOpt= Option [] ["dump-bat"] (NoArg DumpBat) "dump bat allocation"+ decryptOpt= Option [] ["decrypt"] (NoArg Decrypt) "decrypt the vhd"+ encryptOpt= Option [] ["encrypt"] (ReqArg Encrypt "file") "encrypt the vhd using a key specified from the file"++usage msg = do+ maybe (return ()) putStrLn msg+ putStrLn "usage: vhd <command>"+ putStrLn ""+ mapM_ (putStrLn . (" " ++) . fst) knownCommands++main = do+ args <- getArgs+ case args of+ [] -> usage Nothing+ cmd : xs -> case lookup cmd knownCommands of+ Nothing -> usage $ Just ("unknown command: " ++ cmd)+ Just f -> f xs
− Vhd.hs
@@ -1,103 +0,0 @@-import Control.Monad-import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as BL-import Data.Char-import Data.IORef-import Data.Vhd-import Data.Vhd.Bat-import qualified Data.Vhd.Block as Block-import Data.Vhd.Checksum-import Data.Vhd.Node-import Data.Vhd.Types-import System.Environment (getArgs)-import System.IO-import Text.Printf--cmdConvert [fileRaw, fileVhd, size] = convert =<< rawSizeBytes where- vhdSizeMiB = read size- vhdSizeBytes = vhdSizeMiB * 1024 * 1024- rawSizeBytes = fmap fromIntegral $ withFile fileRaw ReadMode hFileSize- convert rawSizeBytes- | vhdSizeMiB `mod` 2 /= 0 = error- "specified VHD size is not a multiple of 2 MiB."- | vhdSizeBytes < rawSizeBytes = error- "specified VHD size is not large enough to contain raw data."- | otherwise = do- create fileVhd $ defaultCreateParameters- { createVirtualSize = vhdSizeBytes }- withVhd fileVhd $ \vhd -> BL.readFile fileRaw >>= writeDataRange vhd 0-cmdConvert _ = error "usage: convert <raw file> <vhd file> <size MiB>"--cmdCreate [name, size] =- create name $ defaultCreateParameters- { createVirtualSize = read size * 1024 * 1024 }-cmdCreate _ = error "usage: create <name> <size MiB>"--cmdExtract [fileVhd, fileRaw] = withVhd fileVhd $ readData >=> BL.writeFile fileRaw-cmdExtract _ = error "usage: extract <vhd file> <raw file>"--cmdPropGet [file, key] = withVhdNode file $ \node -> do- case map toLower key of- "max-table-entries" -> putStrLn $ show $ headerMaxTableEntries $ nodeHeader node- "blocksize" -> putStrLn $ show $ headerBlockSize $ nodeHeader node- "disk-type" -> putStrLn $ show $ footerDiskType $ nodeFooter node- "current-size" -> putStrLn $ show $ footerCurrentSize $ nodeFooter node- "uuid" -> putStrLn $ show $ footerUniqueId $ nodeFooter node- "parent-uuid" -> putStrLn $ show $ headerParentUniqueId $ nodeHeader node- "parent-timestamp" -> putStrLn $ show $ headerParentTimeStamp $ nodeHeader node- "parent-filepath" -> putStrLn $ show $ headerParentUnicodeName $ nodeHeader node- "timestamp" -> putStrLn $ show $ footerTimeStamp $ nodeFooter node- _ -> error "unknown key"-cmdPropGet _ = error "usage: prop-get <file> <key>"--cmdRead [file] = withVhdNode file $ \node -> do- let hdr = nodeHeader node- let ftr = nodeFooter node- mapM_ (\(f, s) -> putStrLn (f ++ " : " ++ s))- [ ("cookie ", show $ headerCookie hdr)- , ("version ", show $ headerVersion hdr)- , ("max-table-entries", show $ headerMaxTableEntries hdr)- , ("block-size ", showBlockSize $ headerBlockSize hdr)- , ("header-checksum ", showChecksum (headerChecksum hdr) (verifyHeaderChecksum hdr))- , ("parent-uuid ", show $ headerParentUniqueId hdr)- , ("parent-filepath ", show $ headerParentUnicodeName hdr)- , ("parent-timestamp ", show $ headerParentTimeStamp hdr)- ]- mapM_ (\(f, s) -> putStrLn (f ++ " : " ++ s))- [ ("disk-geometry ", show $ footerDiskGeometry ftr)- , ("original-size ", showBlockSize $ footerOriginalSize ftr)- , ("current-size ", showBlockSize $ footerOriginalSize ftr)- , ("type ", show $ footerDiskType ftr)- , ("footer-checksum ", showChecksum (footerChecksum ftr) (verifyFooterChecksum ftr))- , ("uuid ", show $ footerUniqueId ftr)- , ("timestamp ", show $ footerTimeStamp ftr)- ]- allocated <- newIORef 0- batIterate (nodeBat node) (fromIntegral $ headerMaxTableEntries hdr) $ \i n -> do- unless (n == 0xffffffff) $ modifyIORef allocated ((+) 1) >> printf "BAT[%.5x] = %08x\n" i n- nb <- readIORef allocated- putStrLn ("blocks allocated : " ++ show nb ++ "/" ++ show (headerMaxTableEntries hdr))-cmdRead _ = error "usage: read <file>"--cmdSnapshot [fileVhdParent, fileVhdChild] =- withVhd fileVhdParent $ \vhdParent -> snapshot vhdParent fileVhdChild-cmdSnapshot _ = error "usage: snapshot <parent vhd file> <child vhd file>"--showBlockSize i- | i < 1024 = printf "%d bytes" i- | i < (1024^2) = printf "%d KiB" (i `div` 1024)- | i < (1024^3) = printf "%d MiB" (i `div` (1024^2))- | otherwise = printf "%d GiB" (i `div` (1024^3))--showChecksum checksum isValid =- printf "%08x (%s)" checksum (if isValid then "valid" else "invalid")--main = do- args <- getArgs- case args of- "convert" : xs -> cmdConvert xs- "create" : xs -> cmdCreate xs- "extract" : xs -> cmdExtract xs- "prop-get" : xs -> cmdPropGet xs- "read" : xs -> cmdRead xs- "snapshot" : xs -> cmdSnapshot xs
vhd.cabal view
@@ -1,23 +1,19 @@ Name: vhd-Version: 0.2+Version: 0.2.1 Synopsis: Provides functions to inspect and manipulate virtual hard disk (VHD) files. Description: Provides functions to inspect and manipulate virtual hard disk (VHD) files, according to the VHD specification (version 1.0). Author: Jonathan Knowles, Vincent Hanquez-Maintainer: jonathan.knowles@eu.citrix.com+Maintainer: vincent.hanquez@citrix.com Copyright: Citrix Systems Inc. License: BSD3 License-file: LICENCE Stability: Experimental Category: System Build-type: Simple-Homepage: https://github.com/jonathanknowles/hs-vhd-Cabal-version: >=1.6+Homepage: https://github.com/vincenthz/hs-vhd+Cabal-version: >=1.8 Data-files: README -Flag test- Description: Build unit tests- Default: False- Flag executable Description: Build executables Default: False@@ -25,46 +21,80 @@ Library Build-depends: base >= 4 && < 5 , bytestring+ , byteable , filepath+ , directory , mmap , text , time , cereal , random , storable-endian+ , cipher-aes >= 0.2.0+ , cryptohash Exposed-modules: Data.Vhd+ Data.Vhd.Lowlevel+ Data.Vhd.Time Other-modules: Data.BitSet Data.Range Data.Vhd.Bat+ Data.Vhd.Batmap+ Data.Vhd.Crypt Data.Vhd.Bitmap Data.Vhd.Block Data.Vhd.Checksum+ Data.Vhd.Header+ Data.Vhd.Footer Data.Vhd.Node Data.Vhd.Geometry Data.Vhd.Serialize Data.Vhd.Types Data.Vhd.Utils+ Data.Vhd.UniqueId+ ghc-options: -Wall -fwarn-tabs -Executable Tests- Main-Is: Tests.hs- if flag(test)- Buildable: True- Build-depends: base >= 3 && < 5- , QuickCheck >= 2+Test-Suite test-vhd+ type: exitcode-stdio-1.0+ hs-source-dirs: Tests .+ main-is: Tests.hs+ build-depends: base , bytestring- , test-framework >= 0.3- , test-framework-quickcheck2 >= 0.2- else- Buildable: False+ , byteable+ , test-framework >= 0.3.3+ , test-framework-quickcheck2+ , QuickCheck >= 2.4.0.1+ , bytestring+ , filepath+ , mmap+ , text+ , time+ , cereal+ , random+ , vhd+ , cryptohash Executable vhd Main-Is: Vhd.hs+ hs-source-dirs: Tools . if flag(executable) Buildable: True Build-depends: base >= 3 && < 5+ , bytestring+ , byteable+ , filepath+ , directory+ , mmap+ , text+ , time+ , cereal+ , random+ , storable-endian+ , vhd+ , cipher-aes >= 0.2.0+ , cryptohash else Buildable: False source-repository head type: git- location: git://github.com/jonathanknowles/hs-vhd+ location: git://github.com/vincenthz/hs-vhd