{-# LANGUAGE PackageImports #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} module System.IO.RandomAccessFile.MMap (MMaped, AccessParams (..), extendFile ) where import Control.Monad import Control.Concurrent.STM import qualified Control.Concurrent.ReadWriteLock as RWL import qualified Data.Map as M import qualified Data.ByteString as B import Data.ByteString.Unsafe import Data.Int import System.IO import System.Posix.Types import System.Posix.IO import Foreign.Ptr import Foreign.C.Types import Foreign.Marshal import System.Posix.Memory import Text.Printf import System.IO.RandomAccessFile.Common data MMaped = MMaped { mmFile :: TVar Fd , mmPath :: FilePath , mmData :: TVar (Ptr CChar) , mmExtendable :: Bool , mmFileSize :: TVar CSize , mmLockPageSize :: Size , mmLocks :: TVar FileLocks , mmResizeLock :: RWL.RWLock } mmap :: CSize -> Fd -> IO (Ptr CChar) mmap size fd = memoryMap Nothing size [MemoryProtectionRead, MemoryProtectionWrite] MemoryMapShared (Just fd) 0 -- | Resize file to be at least of specified size. -- Does nothing if size of file is already greater or equal -- to specified. -- While file is resized, all reading and writing to it are locked. extendFile :: MMaped -> Size -> IO () extendFile handle newSize = when (mmExtendable handle) $ do let sizeVar = mmFileSize handle ptrVar = mmData handle page = mmLockPageSize handle ptr <- atomically $ readTVar ptrVar oldSize <- atomically $ readTVar sizeVar let delta :: Int64 delta = fromIntegral newSize - fromIntegral oldSize when (delta > 0) $ -- We are acquiring "general" lock here to -- make sure that there will be no newly created -- page-level locks withLock (mmResizeLock handle) WriteAccess $ do -- Acquire all existing page-level locks in Write mode -- so that noone may read or write the file: we are going to -- unmap it locks <- atomically $ readTVar (mmLocks handle) withLocks (M.elems locks) WriteAccess $ do -- Unmap existing mapping memorySync ptr oldSize [MemorySyncSync, MemorySyncInvalidate] memoryUnmap ptr oldSize -- Open file again h <- openFile (mmPath handle) ReadWriteMode -- Write zeros to the end size <- hFileSize h hSeek h AbsoluteSeek size B.hPut h $ B.replicate (fromIntegral page) 0 hFlush h fd <- handleToFd h -- MMap file again let newSize' = oldSize + fromIntegral page ptr' <- mmap (fromIntegral newSize') fd atomically $ do writeTVar (mmFile handle) fd writeTVar ptrVar ptr' writeTVar sizeVar (fromIntegral newSize') instance FileAccess MMaped where data AccessParams MMaped = MMapedParams Size Bool initFile (MMapedParams lockPageSize extendable) path = do locks <- atomically $ newTVar M.empty let fileMode = Just 0o644 let flags = defaultFileFlags handle <- openFile path ReadWriteMode size <- do sz <- hFileSize handle if sz == 0 then do B.hPut handle $ B.replicate (fromIntegral lockPageSize) 0 hFlush handle return lockPageSize else return $ fromIntegral sz -- printf "Init size: %d\n" size sizeVar <- newTVarIO (fromIntegral size) fd <- handleToFd handle -- fd <- openFd path ReadWrite fileMode flags -- handle <- fdToHandle fd fdVar <- newTVarIO fd ptr <- mmap (fromIntegral size) fd ptrVar <- newTVarIO ptr resizeLock <- RWL.new return $ MMaped fdVar path ptrVar extendable sizeVar lockPageSize locks resizeLock readBytes handle offset size = do withLock_ (mmExtendable handle) (mmResizeLock handle) ReadAccess $ do -- just check that file is not currently being resized let ptrVar = mmData handle lockPageSize = mmLockPageSize handle locks = mmLocks handle (ptr, fsize) <- atomically $ do p <- readTVar ptrVar s <- readTVar (mmFileSize handle) return (p, s) when (offset + size > fromIntegral fsize) $ fail $ printf "readBytes: read after EOF: offset %d, size %s, file size %s" offset (show size) (show fsize) let dataOffset0 = offset `mod` lockPageSize pageOffset0 = offset - dataOffset0 dataOffset1 = (offset + size) `mod` lockPageSize pageOffset1 = (offset + size) - dataOffset1 pageOffsets = [pageOffset0, pageOffset0 + lockPageSize .. pageOffset1] underBlockLocks locks ReadAccess pageOffsets $ do let bstrPtr = plusPtr ptr (fromIntegral offset) -- unsafePackCStringLen (bstrPtr, fromIntegral size) B.packCStringLen (bstrPtr, fromIntegral size) writeBytes handle offset bstr = do let ptrVar = mmData handle sizeVar = mmFileSize handle lockPageSize = mmLockPageSize handle locks = mmLocks handle fsize <- atomically $ readTVar sizeVar let size = fromIntegral $ B.length bstr dataOffset0 = offset `mod` lockPageSize pageOffset0 = offset - dataOffset0 dataOffset1 = (offset + size) `mod` lockPageSize pageOffset1 = (offset + size) - dataOffset1 pageOffsets = [pageOffset0, pageOffset0 + lockPageSize .. pageOffset1] -- printf "write: offset %d, size %d, fsize %s\n" offset size (show fsize) if (offset + size) > fromIntegral fsize then do extendFile handle (offset+size) writeBytes handle offset bstr else -- just check that file is not currently being resized withLock_ (mmExtendable handle) (mmResizeLock handle) ReadAccess $ underBlockLocks locks WriteAccess pageOffsets $ do ptr <- atomically $ readTVar ptrVar unsafeUseAsCStringLen bstr $ \(bstrPtr,len) -> copyBytes (plusPtr ptr (fromIntegral offset)) bstrPtr len currentFileSize handle = do sz <- atomically $ readTVar $ mmFileSize handle return $ fromIntegral sz syncFile handle = do (ptr, size) <- atomically $ do p <- readTVar (mmData handle) s <- readTVar (mmFileSize handle) return (p,s) memorySync ptr size [MemorySyncSync, MemorySyncInvalidate] closeFile handle = do (ptr, size) <- atomically $ do p <- readTVar (mmData handle) s <- readTVar (mmFileSize handle) return (p,s) memoryUnmap ptr size