module Halfs.BasicIO (devBufferReadHost,
devBufferWriteHost, devBufferWriteSafe, bytesPerBlock,
getMemoryBlock, binSkip,
getInodeRead, getInodeWrite,
getDiskAddrOfBlockWrite,
unitTests, readPartOfBlock, writePartOfBlock,
getInodesBin, putPointersBin,
syncInode,
readInodeBootStrap,
getBlockFromCacheOrDeviceWrite,
readDirectoryBin, writeDirectoryBin, readInodeMapBin,
readBlockMapBin,
fsRootFreeInode, freeInodeData,
allBlocksInInode,
writeBlockMapBin, writeInodeMapBin,
module Binary
) where
import Data.Integral
import Halfs.Utils hiding (inodesPerBlock)
import qualified Halfs.Utils(inodesPerBlock)
import Binary (copyBytes, BinHandle, Bin(BinPtr), BinArray, tellBin,
seekBin, openBinIO, Bin(BinPtr), openBinMem,
put_, sizeBinMem, resetBin, get)
import Halfs.FSRW (unsafeLiftIORW)
import Halfs.BinaryMonad (FSRW, resetBinRW, seekBinRW, tellBinRW, getRW)
import Halfs.TestFramework (Test(..), UnitTests,
assertCmd, assertEqual, hunitToUnitTest)
import Halfs.FSRoot(FSRoot(..), InodeNum, InodeCache,
addToInodeCache, getFromInodeCache,
fsRootInode, fsRootUpdateInodeCache, fsRootRmFromDirectoryCache,
InodeCacheAddStyle(InodeCacheKeep))
import Halfs.BufferBlockCache (getBlockFromCacheOrDevice,getBlockFromDevice)
import Halfs.Inode (InodeBlock, Inode(..), InodeMetadata(..), inodeBumpSize,
newInode, goodInodeMagic)
import Halfs.BufferBlock (BufferBlock(..), writeToBufferBlock,Alloc(..),
copyFromBufferBlock, copyToBufferBlock, mkBufferBlock,
diskAddressListFromBufferBlock, PartBufferBlock, mkInodeBlock,
putPartBufferBlock, getPartBufferBlock, zeroBufferBlock, startBufferBlockCursor )
import Halfs.TheBlockMap(TheBlockMap,mkBlockMap)
import Halfs.Directory (DirectoryMap)
import System.RawDevice(RawDevice, devBufferRead, devBufferWrite,
makeRawDevice, finalizeDevice, BufferBlockHandle)
import Halfs.FSState (FSRead, writeToBuffer,
unsafeReadGet, unsafeLiftIOWrite, FSWrite,
unsafeWriteToRead, unsafeModifyFSRead,
readToWrite, readToWriteCont, modifyFSWrite, unsafeWriteGet)
import qualified Halfs.FSState (modify)
import Halfs.TheInodeMap(TheInodeMap(..), freeInode)
import Halfs.TheBlockMap(TheBlockMap(..), freeBlock)
import Halfs.Blocks (getDiskAddrOfBlockRead, getDiskAddrOfBlockRaw, markBlockDirtyWrite,
getDiskAddrOfBlockWrite, getBlockFromCacheOrDeviceWrite)
import System.IO (openFile, hClose, hFileSize, IOMode(..))
import System.Posix.Types (Fd)
import System.Posix.Files(unionFileModes, ownerReadMode, ownerWriteMode, groupReadMode)
import System.Posix.IO (OpenMode(..), openFd, closeFd, defaultFileFlags)
import System.Directory(removeFile, doesFileExist)
import Control.Exception(assert)
import Control.Monad(when, unless)
import Data.Queue(Queue, emptyQueue, addToQueue, queueToList)
import qualified Data.Map as Map
import Data.Array (elems, assocs)
inodesPerBlock :: INInt
inodesPerBlock = intToINInt Halfs.Utils.inodesPerBlock
binSkip :: (FSRW m)
=> BufferBlockHandle s
-> Int
-> m ()
binSkip binHandle bytes = do
(BinPtr bufLoc) <- tellBinRW binHandle
seekBinRW binHandle (BinPtr (bufLoc + bytes))
return ()
devBufferReadHost :: Fd
-> DiskAddress
-> BinHandle
-> Int
-> IO ()
devBufferReadHost h diskAddr buffer numBytes = do
fileHandle <- openBinIO h
seekBin fileHandle (locationOfBlock diskAddr)
copyBytes fileHandle buffer numBytes
devBufferWriteSafe :: RawDevice
-> DiskAddress
-> BufferBlockHandle s
-> FSWrite ()
devBufferWriteSafe r d b = unsafeLiftIOWrite $ devBufferWrite r d b
devBufferWriteHost :: Fd
-> DiskAddress
-> BinHandle
-> Int
-> IO ()
devBufferWriteHost h diskAddr buffer numBytes = do
fileHandle <- openBinIO h
seekBin fileHandle (locationOfBlock diskAddr)
copyBytes buffer fileHandle numBytes
readPartOfBlock :: DiskAddress
-> INInt
-> BinHandle
-> INInt
-> INInt
-> FSRead INInt
readPartOfBlock da blockOffset buffer
bufferOffset numToRead = do
FSRoot{device=dev, bbCache=cache} <- unsafeReadGet
getBlockFromCacheOrDevice cache dev da (\ bb -> do
unsafeLiftIORW $ copyFromBufferBlock bb (inIntToInt blockOffset) buffer (inIntToInt bufferOffset) (inIntToInt numToRead)
return numToRead)
writePartOfBlock :: DiskAddress
-> INInt
-> BinHandle
-> INInt
-> INInt
-> FSWrite INInt
writePartOfBlock da blockOffset buffer
bufferOffset numToWrite = do
FSRoot{device=dev, bbCache=cache} <- unsafeWriteGet
getBlockFromCacheOrDevice cache dev
(assert (blockOffset + numToWrite <= (intToINInt bytesPerBlock)) da)
(\ bb -> do
unsafeLiftIORW $ copyToBufferBlock buffer (inIntToInt bufferOffset) bb (inIntToInt blockOffset) (inIntToInt numToWrite)
markBlockDirtyWrite bb
return numToWrite)
getInodesBin :: (FSRW m) => BufferBlock s -> m InodeBlock
getInodesBin bb = do
sequence [ do pbb <- unsafeLiftIORW $ mkInodeBlock bb i
unsafeLiftIORW $ getPartBufferBlock pbb
| i <- [0..(inIntToInt inodesPerBlock 1) ]
]
putPointersBin :: (FSRW m) => BufferBlock s -> Inode -> m ()
putPointersBin bb (Inode _ ptrs) = do
unsafeLiftIORW $ zeroBufferBlock bb
unsafeLiftIORW $ writeToBufferBlock bb startBufferBlockCursor [ Alloc e | e <- elems ptrs ]
fsRootUpdateInode :: INInt
-> (Inode -> Inode)
-> FSWrite ()
fsRootUpdateInode inodeNum f = do
inode <- getInodeWrite inodeNum Nothing
modifyFSWrite (\fsroot -> return (fsRootUpdateInodeCache fsroot (f inode), ()))
unsafeMoveBinHandleForInodeNum :: INInt
-> Bool
-> (forall s . PartBufferBlock Inode s -> FSRead a)
-> FSRead a
unsafeMoveBinHandleForInodeNum inodeNum forWrite cont = do
rootInode <- (do fsroot <- unsafeReadGet
return (fsRootInode fsroot))
if forWrite
then unsafeWriteToRead (do
getBlockFromInodeWrite rootInode inodeBlock (\ bb -> do
let (minSizeForInode::INLong)
= inIntToINLong $ (inodeBlock + 1) * (intToINInt bytesPerBlock)
() <- fsRootUpdateInode rootInodeNum
(\i -> inodeBumpSize i minSizeForInode)
pbb <- unsafeLiftIORW $ mkInodeBlock bb (inIntToInt inodeIndexThisBlock)
readToWrite (cont pbb)))
else
getBlockFromInodeRead rootInode inodeBlock (\ bb -> do
pbb <- unsafeLiftIORW $ mkInodeBlock bb (inIntToInt inodeIndexThisBlock)
cont pbb)
where
inodeBlock = (inodeNum `div` inodesPerBlock) :: INInt
inodeBaseThisBlock = (inodeBlock * inodesPerBlock) :: INInt
inodeIndexThisBlock = inodeNum inodeBaseThisBlock
getBlockFromInodeWrite :: Inode
-> BlockNumber
-> (forall s . BufferBlock s -> FSWrite a)
-> FSWrite a
getBlockFromInodeWrite inode blockNumber ncont = do
addr <- getDiskAddrOfBlockWrite inode blockNumber
getBlockFromCacheOrDeviceWrite addr ncont
getBlockFromInodeRead :: Inode
-> BlockNumber
-> (forall s . BufferBlock s -> FSRead a)
-> FSRead a
getBlockFromInodeRead inode blockNumber ncont = do
FSRoot{device=dev, bbCache=cache} <- unsafeReadGet
addr <- getDiskAddrOfBlockRead inode blockNumber
getBlockFromCacheOrDevice cache dev addr ncont
syncInode ::Inode -> FSWrite ()
syncInode inode@Inode{metaData=InodeMetadata{inode_num=inodeNum}} = do
readToWriteCont (unsafeMoveBinHandleForInodeNum inodeNum True) (\ inode_pbb ->
when (goodInodeMagic inode)
(unsafeLiftIORW $ putPartBufferBlock inode_pbb (assert (goodInodeMagic inode) inode))
)
getInodeWrite :: INInt -> Maybe FileType -> FSWrite Inode
getInodeWrite num mType = readToWrite $ getInodeRead num mType
getInodeRead :: INInt
-> Maybe FileType
-> FSRead Inode
getInodeRead inodeNum mType = do
FSRoot{inodeCache=cache} <- unsafeReadGet
when (inodeNum < 0) (error $ "illegal inode value: " ++ (show inodeNum))
case getFromInodeCache cache inodeNum of
Nothing -> case mType of
Nothing -> do
unsafeMoveBinHandleForInodeNum inodeNum False (\ inode_pbb -> do
newElt' <- unsafeLiftIORW $ getPartBufferBlock inode_pbb
unsafeModifyFSRead
(\fsroot@FSRoot{inodeCache=the_cache}
-> (fsroot{inodeCache=fst $
addToInodeCache InodeCacheKeep
(the_cache, inodeNum) newElt'}
, ()))
unless ((inode_num $ metaData newElt') == inodeNum
&& (goodInodeMagic newElt'))
(error $ "illegal inode: " ++ (show newElt'))
assert ((inode_num $ metaData newElt') == inodeNum
&& (goodInodeMagic newElt'))
(return newElt'))
Just t -> return (newInode inodeNum t)
Just n -> return n
readInodeBootStrap :: InodeCache
-> RawDevice
-> Inode
-> InodeNum
-> IO (Inode, InodeCache)
readInodeBootStrap cache inDevice rootInode inodeNum =
case getFromInodeCache cache inodeNum of
Just n -> return (n, cache)
Nothing -> do let (blockNum::BlockNumber)
= inodeNum `div` inodesPerBlock
addrM <- getDiskAddrOfBlockRaw rootInode
blockNum inDevice
let addr = fromJustErr
("FIX: uninitialized read. blockNum:inodeNum "
++ (show blockNum) ++ ":" ++ (show inodeNum))
addrM
getBlockFromDevice inDevice addr (\ bb -> do
readInodeUpdateCache cache inodeNum bb)
readInodeUpdateCache :: (FSRW m)
=> InodeCache
-> InodeNum
-> BufferBlock s
-> m (Inode, InodeCache)
readInodeUpdateCache cache inodeNum bb = do
let blockNum = inodeNum `div` inodesPerBlock
inodeBlock <- getInodesBin bb
let (firstInodeNum::INInt) = blockNum * inodesPerBlock
let (newCache, _)
= foldl (addToInodeCache InodeCacheKeep)
(cache, firstInodeNum)
inodeBlock
let newInode' = fromJustErr "can't find inode which was just added to cache"
(getFromInodeCache newCache inodeNum)
return (newInode', newCache)
writeInodeMapBin :: BinHandle
-> TheInodeMap
-> FSWrite ()
writeInodeMapBin buffer (TheInodeMap freeN _ numInodes) = do
resetBinRW buffer
writeToBuffer buffer (length freeN)
mapM_ (writeToBuffer buffer) freeN
writeToBuffer buffer numInodes
inputFreeBlockList :: (FSRW m) => INInt
-> BinHandle
-> Queue DiskAddress
-> m (Queue DiskAddress)
inputFreeBlockList n buffer q
| n <= 0 = return q
| otherwise = do
da <- getRW buffer
inputFreeBlockList (n1) buffer $! (addToQueue q da)
writeBlockMapBin :: BinHandle
-> TheBlockMap
-> FSWrite ()
writeBlockMapBin buffer bm = do
resetBinRW buffer
let listQ = queueToList (freeBlocks bm)
writeToBuffer buffer (length listQ)
writeToBuffer buffer (bmTotalSize bm)
mapM_ (writeToBuffer buffer) listQ
writeDirectoryBin :: BinHandle -> DirectoryMap -> FSWrite ()
writeDirectoryBin buffer theMap = do
resetBinRW buffer
let l = Map.toList theMap
writeToBuffer buffer (length l)
mapM_ (writeToBuffer buffer) l
readDirectoryBin :: (FSRW m) => BinHandle -> m DirectoryMap
readDirectoryBin buffer = do
resetBinRW buffer
size <- getRW buffer
l <- sequence $ replicate size (do f <- getRW buffer
return f)
return $ Map.fromList l
readInodeMapBin :: (FSRW m) => BinHandle -> m TheInodeMap
readInodeMapBin buffer = do
resetBinRW buffer
numInodes <- getRW buffer
inodeNums <- inputInodeMap numInodes buffer
maxInodeNum <- getRW buffer
return $ TheInodeMap inodeNums False maxInodeNum
readBlockMapBin :: (FSRW m) => BinHandle
-> m TheBlockMap
readBlockMapBin buffer = do
resetBinRW buffer
numFree <- getRW buffer
size <- getRW buffer
freeBlks <- inputFreeBlockList numFree buffer emptyQueue
return (mkBlockMap freeBlks False size)
inputInodeMap :: (FSRW m) => INInt
-> BinHandle
-> m [INInt]
inputInodeMap num buffer
| num <= 0 = return []
| otherwise = do
inodeNum <- getRW buffer
tails <- inputInodeMap (num 1) buffer
return $ inodeNum : tails
freeInodeData :: Inode
-> FSWrite ()
freeInodeData inode = do
allBlocks <- allBlocksInInode' inode
mapM_ freeDA allBlocks
where freeDA :: DiskAddress -> FSWrite ()
freeDA bp = Halfs.FSState.modify (\fsroot'@FSRoot{blockMap=bm}
-> fsroot'{blockMap=freeBlock bm bp})
allBlocksInInode :: INInt
-> FSWrite [DiskAddress]
allBlocksInInode inodeNum = do
inode <- getInodeWrite inodeNum Nothing
allBlocksInInode' inode
allBlocksInInode' :: Inode
-> FSWrite [DiskAddress]
allBlocksInInode' inode@Inode{metaData=InodeMetadata{inode_num=inodeNum}} = do
let lev = level $ metaData inode
ptrs = [block | (index, block) <- assocs $ blockPtrs inode
, (block /= 0) || (block == 0 && inodeNum == 0 && index == 0)
]
mapM (allBlocksAt inodeNum lev) ptrs >>= return . concat
where
allBlocksAt :: InodeNum -> INInt -> DiskAddress -> FSWrite [DiskAddress]
allBlocksAt _ 1 bp = return [bp]
allBlocksAt inodeNum1 n indirBP = do
allBlocks <- getBlockFromCacheOrDeviceWrite indirBP (\ bb ->
unsafeLiftIORW $ diskAddressListFromBufferBlock bb)
let allBlockElems = [block | (index, block) <- zip [(0::Int)..] allBlocks
, (block /= 0) || (block == 0 && inodeNum1 == 0 && index == 0)
]
rest <- mapM (allBlocksAt inodeNum1 (n 1)) allBlockElems
return $ indirBP:(concat rest)
fsRootFreeInode :: Inode
-> FSWrite ()
fsRootFreeInode inode = do
let num = inode_num $ metaData inode
Halfs.FSState.modify (\fsroot@FSRoot{inodeMap=theMap}
-> fsroot{inodeMap=freeInode theMap num})
Halfs.FSState.modify (fsRootRmFromDirectoryCache num)
freeInodeData inode
binaryCopyFile :: FilePath -> FilePath -> IO ()
binaryCopyFile f1 f2 = do
size <- (do h <- openFile f1 ReadMode
s <- hFileSize h
hClose h
return s)
h <- openFd f1 ReadOnly Nothing defaultFileFlags
bh <- openBinIO h
h' <- openFd f2 WriteOnly (Just (foldl1 unionFileModes
[ ownerReadMode
, ownerWriteMode
, groupReadMode]))
defaultFileFlags
bh' <- openBinIO h'
copyBytes bh
bh'
(fromIntegral'' size)
closeFd h
closeFd h'
binaryCopyFile' :: FilePath -> FilePath -> IO ()
binaryCopyFile' from to = do
numBytes <- (do h <- openFile from ReadMode
s <- hFileSize h
hClose h
return s)
buffer <- openBinMem (fromIntegral numBytes)
hFrom <- openFd from ReadWrite Nothing defaultFileFlags
devBufferReadHost hFrom 0 buffer (fromIntegral'' numBytes)
resetBin buffer
hTo <- openFd to ReadWrite (Just (foldl1 unionFileModes [ ownerReadMode
, ownerWriteMode
, groupReadMode]))
defaultFileFlags
devBufferWriteHost hTo 0 buffer (fromIntegral'' numBytes)
closeFd hFrom
closeFd hTo
inodeHunitTests :: [Test]
inodeHunitTests = [
TestLabel "root inode tests" $ TestCase $ do
dev <- makeRawDevice Nothing "halfs-client1"
buffer <- getMemoryBlock
bb <- mkBufferBlock buffer dev 0
devBufferRead dev 0 buffer
(rootInode':_) <- getInodesBin bb
assertEqual "root inode magic number incorrect"
(magic1 $ metaData rootInode')
rootInodeMagicNum
assertEqual "root inode level unimplemented"
(level $ metaData rootInode') 1
finalizeDevice dev
]
unitTests :: UnitTests
unitTests = hunitToUnitTest hunitTests
hunitTests :: [Test]
hunitTests =
[TestLabel "binary copy file" $ TestCase $ do
doesFileExist "tests/to" >>= \e -> when e (removeFile "tests/to")
binaryCopyFile "tests/from" "tests/to"
assertCmd "diff tests/from tests/to" "binary file copy failed"
doesFileExist "tests/to" >>= \e -> when e (removeFile "tests/to")
binaryCopyFile' "tests/from" "tests/to"
assertCmd "diff tests/from tests/to" "binary file copy' failed"
doesFileExist "tests/bigTo" >>= \e -> when e (removeFile "tests/bigTo")
binaryCopyFile "tests/multiBlockFile" "tests/bigTo"
assertCmd "diff tests/multiBlockFile tests/bigTo" "binary file copy failed"
doesFileExist "tests/bigTo" >>= \e -> when e (removeFile "tests/bigTo")
binaryCopyFile' "tests/multiBlockFile" "tests/bigTo"
assertCmd "diff tests/multiBlockFile tests/bigTo" "binary file copy failed"
] ++ inodeHunitTests