module Halfs.Blocks (getDiskAddrOfBlockRead, getDiskAddrOfBlockRaw,
getDiskAddrOfBlockWrite, getBlockFromCacheOrDeviceWrite,
numBlocksForSize, markBlockDirtyWrite)
where
import Halfs.Inode(Inode(..), InodeMetadata(..), getPointerAt, updatePointers,
goodInodeMagic)
import Data.Integral(INInt, intToINInt, inIntToInt)
import Halfs.Utils(BlockNumber, DiskAddress,
unimplemented, bytesPerBlock, blockPointersPerInode,
blockPointersPerIndirectBlock)
import Halfs.BufferBlock (BufferBlock(..), getDiskAddr,
bufferBlockCursorIntoPointers,
BufferBlockCursor,
writeToBufferBlock,
readFromBufferBlock,
zeroBufferBlock,
Alloc(..))
import Halfs.FSRW (unsafeLiftIORW)
import Halfs.BinaryMonad(FSRW)
import Halfs.BasicIO (getInodeWrite, putPointersBin)
import Halfs.TheBlockMap(findAddress, allocateBlock)
import Halfs.FSRoot (FSRoot(..))
import Halfs.FSState (FSWrite, FSRead, unsafeLiftIOWrite,
unsafeReadGet, updateInodeCacheWrite,
modifyFSWrite, unsafeWriteGet)
import System.RawDevice(RawDevice)
import Halfs.BufferBlockCache (markBlockDirty,
getBlockFromCacheOrDevice,
getNewBlockFromCacheOrDevice,
getBlockFromDevice)
import Control.Exception(assert)
import Control.Monad(when)
import Data.Array(listArray)
import Control.Monad.Error(MonadError)
getDiskAddrFun :: (MonadError e m,FSRW m)
=> Inode
-> BlockNumber
-> (forall m1 . forall e1 . (MonadError e1 m1, FSRW m1) => DiskAddress -> (forall s . BufferBlock s -> m1 DiskAddress) -> m1 DiskAddress)
-> m DiskAddress
getDiskAddrFun inode blockNum findBlock = do
let inLevel = level $ metaData (assert (goodInodeMagic inode) inode)
let addrs = findAddress inode blockNum
let firstSeek = head (assert ((intToINInt $ length addrs) == inLevel) addrs)
let firstAddr = getPointerAt inode firstSeek
findBlock firstAddr (\ nextBlock ->
looper (tail addrs) nextBlock (\ b c ->
unsafeLiftIORW (readFromBufferBlock b c)))
where
looper :: (MonadError e m,FSRW m)
=> [INInt]
-> BufferBlock t
-> (forall s . forall m1 . forall e1 . (MonadError e1 m1,FSRW m1) => BufferBlock s -> BufferBlockCursor s -> m1 DiskAddress)
-> m DiskAddress
looper [] _b _cont = error "never happens"
looper (h:t) b cont = do
if length t > 0
then do addr <- unsafeLiftIORW (readFromBufferBlock b (bufferBlockCursorIntoPointers h))
findBlock addr (\ nextBlock ->
looper t nextBlock cont)
else cont b (bufferBlockCursorIntoPointers h)
internalGetInodeFromCache :: INInt -> FSWrite Inode
internalGetInodeFromCache inodeNum = getInodeWrite inodeNum Nothing
getDiskAddrOfBlockRead :: Inode
-> BlockNumber
-> FSRead DiskAddress
getDiskAddrOfBlockRead inode blockNumber = do
FSRoot{device=dev, bbCache=cache} <- unsafeReadGet
addr <- case (level $ metaData inode) of
0 -> unimplemented "Zero optimization (getDiskAddrOfBlockRead)"
1 -> return $ getPointerAt inode blockNumber
_ -> do getDiskAddrFun inode blockNumber
(getBlockFromCacheOrDevice cache dev)
case checkedAddr addr inode blockNumber of
Nothing -> error $ "UNINITIALIZED READ. Address is zero but inode number and block number aren't zero: "
++ (show blockNumber) ++ (show inode)
Just a -> return a
getDiskAddrOfBlockRaw :: (MonadError e m,FSRW m)
=> Inode
-> BlockNumber
-> RawDevice
-> m (Maybe DiskAddress)
getDiskAddrOfBlockRaw inode blockNumber rawDevice = do
addr <- case (level $ metaData inode) of
0 -> unimplemented "Zero optimization (getDiskAddrOfBlockRaw)"
1 -> return $ getPointerAt inode blockNumber
_ -> do getDiskAddrFun inode blockNumber
(getBlockFromDevice rawDevice)
return $ checkedAddr addr inode blockNumber
markBlockDirtyWrite :: BufferBlock s -> FSWrite ()
markBlockDirtyWrite block = do
addr <- unsafeLiftIOWrite $ getDiskAddr block
FSRoot{device=dev, bbCache=cache} <- unsafeWriteGet
markBlockDirty cache dev addr
getDiskAddrOfBlockWrite :: Inode
-> BlockNumber
-> FSWrite DiskAddress
getDiskAddrOfBlockWrite inodeIn blockNumber = do
inode <- ensureCapacity inodeIn blockNumber
updateInodeCacheWrite inode
addr <- case level $ metaData inode of
0 -> unimplemented "Zero optimization (getDiskAddrWrite)"
1 -> do
getAndPositionBlockWrite inode blockNumber (\ block _ -> unsafeLiftIOWrite $ getDiskAddr block)
_ ->
do
getAndPositionBlockWrite inode blockNumber (\ bb (Just pos) -> do
addr <- unsafeLiftIORW $ readFromBufferBlock bb pos
return addr)
case checkedAddr (assert (addr >= 0) addr) inode blockNumber of
Nothing -> error $ "internal: ensureCapacity failed to ensure: "
++ (show blockNumber) ++ (show inode)
Just a -> return a
checkedAddr :: DiskAddress
-> Inode
-> BlockNumber
-> Maybe DiskAddress
checkedAddr addr inode blockNumber=
if addr == 0
&& inode_num (metaData inode) == 0
&& blockNumber == 0
then Just addr
else if addr == 0
then Nothing
else Just addr
getBlockFromCacheOrDeviceWrite :: DiskAddress
-> (forall s . BufferBlock s -> FSWrite a)
-> FSWrite a
getBlockFromCacheOrDeviceWrite da cont = do
FSRoot{device=dev, bbCache=cache} <- unsafeWriteGet
getBlockFromCacheOrDevice cache dev da cont
allocateBlockWrite :: (forall s. BufferBlock s -> FSWrite a) -> FSWrite a
allocateBlockWrite cont = do
mDa <- modifyFSWrite (\fsr@FSRoot{blockMap=bm} -> do
case allocateBlock bm of
Nothing -> return (fsr, Nothing)
Just (da, bmNew) -> return (fsr{blockMap=bmNew}, Just da))
case mDa of
Just da -> do FSRoot{device=dev, bbCache=cache} <- unsafeWriteGet
getNewBlockFromCacheOrDevice cache dev da cont
Nothing -> error "Out of disk space."
getAndPositionBlockWrite :: Inode
-> BlockNumber
-> (forall s . BufferBlock s -> (Maybe (BufferBlockCursor s)) -> FSWrite a)
-> FSWrite a
getAndPositionBlockWrite inode blockNum cont = do
let inLevel = level $ metaData inode
let inodeNum = inode_num $ metaData inode
let addrs = findAddress inode blockNum
let firstSeek = head (assert ((intToINInt $ length addrs) == inLevel) addrs)
let firstAddr = getPointerAt inode firstSeek
updateInodeCacheWrite inode
getBlockFromCacheOrDeviceWrite firstAddr (\ mightBeNextBlock -> do
newAddr <- unsafeLiftIOWrite $ getDiskAddr mightBeNextBlock
inode1 <- internalGetInodeFromCache inodeNum
let i = updatePointers inode1 [(inIntToInt firstSeek, newAddr)]
updateInodeCacheWrite i
looper (tail addrs) inodeNum mightBeNextBlock cont)
where
looper :: [INInt]
-> INInt
-> BufferBlock s
-> (forall s1 . BufferBlock s1 -> (Maybe (BufferBlockCursor s1)) -> FSWrite a)
-> FSWrite a
looper [] _ b ncont = ncont b Nothing
looper (h:t) inodeNum b ncont = do
let cursor = bufferBlockCursorIntoPointers h
if length t > 0
then do addr <- unsafeLiftIORW $ readFromBufferBlock b cursor
getBlockFromCacheOrDeviceWrite addr
(\ mightBeNextBlock -> do
newAddr <- unsafeLiftIOWrite $ getDiskAddr mightBeNextBlock
when (newAddr /= addr) $ do
unsafeLiftIORW $ writeToBufferBlock b cursor [Alloc newAddr]
markBlockDirtyWrite b
looper t inodeNum mightBeNextBlock ncont)
else ncont b (Just cursor)
ensureCapacity :: Inode
-> BlockNumber
-> FSWrite Inode
ensureCapacity inode newBlockNum
= ensureCapacity' inode (numBlocksForSize inode) newBlockNum
ensureCapacity' :: Inode
-> BlockNumber
-> BlockNumber
-> FSWrite Inode
ensureCapacity' inodeIn currBlocksInFile newBlockNum
| (abs newBlockNum) < currBlocksInFile =
updateInodeCacheWrite inodeIn
>> return inodeIn
| otherwise = do
inode@Inode{metaData=InodeMetadata{inode_num=inodeNum}}
<- ensureLevel inodeIn newBlockNum
allocateBlockWrite (\ bb -> do
da <- unsafeLiftIORW $ getDiskAddr bb
case level $ metaData inode of
0 -> unimplemented "Zero optimization (ensureCapacity')"
1 -> ensureCapacity' (updatePointers inode
[(inIntToInt currBlocksInFile, da)])
(currBlocksInFile + 1)
newBlockNum
_ -> do when (abs newBlockNum `mod` intToINInt blockPointersPerIndirectBlock == 0) $ do
allocateBlockWrite (\ new_bb -> do
zeroBlock new_bb
new_da <- unsafeLiftIORW $ getDiskAddr new_bb
let ix = inIntToInt $ abs newBlockNum `div` intToINInt blockPointersPerIndirectBlock
let i = updatePointers inode [(ix, new_da)]
updateInodeCacheWrite i)
inode1 <- getInodeWrite inodeNum Nothing
writeAddressAt inode1 newBlockNum (assert (da /= 0) da)
inode2 <- getInodeWrite inodeNum Nothing
ensureCapacity' inode2 (currBlocksInFile + 1) newBlockNum)
maxBlockNum :: INInt
-> BlockNumber
maxBlockNum 1 = intToINInt blockPointersPerInode 1
maxBlockNum n = (intToINInt blockPointersPerIndirectBlock ^ (n 1)
* intToINInt blockPointersPerInode) 1
zeroBlock :: BufferBlock s -> FSWrite ()
zeroBlock buffer = unsafeLiftIORW $ zeroBufferBlock buffer
writeAddressAt :: Inode -> BlockNumber -> DiskAddress -> FSWrite ()
writeAddressAt inode blockNum da = do
getAndPositionBlockWrite inode blockNum (\ bb (Just cursor) -> do
unsafeLiftIOWrite $ writeToBufferBlock bb cursor [Alloc da]
markBlockDirtyWrite bb)
ensureLevel :: Inode
-> BlockNumber
-> FSWrite Inode
ensureLevel inode newBlockNum
| maxBlockNum (level $ metaData inode) >= (abs newBlockNum)
= updateInodeCacheWrite inode
>> return inode
| otherwise = do
allocateBlockWrite (\ newBB -> do
let currLevel = level $ metaData inode
let inodeNum = inode_num $ metaData inode
putPointersBin newBB inode
da <- unsafeLiftIORW $ getDiskAddr newBB
inode1 <-
(do inode2 <- getInodeWrite inodeNum Nothing
return inode2{blockPtrs=
listArray (0, blockPointersPerInode 1)
(da:(replicate (blockPointersPerInode 1) 0))
,metaData=(metaData inode){level = currLevel+1}
})
ensureLevel inode1 newBlockNum)
numBlocksForSize :: Inode -> BlockNumber
numBlocksForSize Inode{metaData=InodeMetadata{num_bytes=numBytes}}
= ceiling $ (toRational numBytes) / (toRational bytesPerBlock)