{-# OPTIONS_GHC -fglasgow-exts #-} module System.IO.MMap ( -- $mmap_intro -- * Memory mapped files strict interface mmapFilePtr, mmapFileForeignPtr, mmapFileByteString, -- * Memory mapped files lazy interface mmapFilePtrLazy, mmapFileForeignPtrLazy, mmapFileByteStringLazy, -- * Mapping mode Mode(..) ) where import System.IO () import Foreign.Ptr (Ptr,FunPtr,nullPtr,plusPtr) import Foreign.C.Types (CInt,CLLong) import Foreign.C.String (CString,withCString) import Foreign.ForeignPtr (ForeignPtr,withForeignPtr,finalizeForeignPtr,newForeignPtr,newForeignPtrEnv) import Foreign.Storable( poke ) import Foreign.Marshal.Alloc( malloc ) import Foreign.C.Error ( throwErrno ) import qualified Foreign.Concurrent( newForeignPtr ) import System.IO.Unsafe (unsafePerformIO) import qualified Data.ByteString.Unsafe as BS (unsafePackCStringFinalizer) import Data.Int (Int64) import Control.Monad (when) import Control.Exception (bracket) import qualified Data.ByteString as BS (ByteString) import qualified Data.ByteString.Lazy as BSL (ByteString,fromChunks) -- $mmap_intro -- -- This module is an interface to mmap(2) system call under POSIX (Unix, Linux, -- Mac OS X) and CreateFileMapping,MapViewOfFile under Windows. -- -- We can consider mmap as lazy IO pushed into the virtual memory -- subsystem. -- -- It is only safe to mmap a file if you know you are the sole user. -- -- For more details about mmap, and its consequences, see: -- -- * -- -- * -- -- * -- -- | Mode of mapping. Three cases are supported. data Mode = ReadOnly -- ^ file is mapped read-only | ReadWrite -- ^ file is mapped read-write | WriteCopy -- ^ file is mapped read-write, but changes aren't propagated to disk deriving (Eq,Ord,Enum) -- | The 'mmapFilePtr' function maps a file or device into memory, -- returning a tripple containing pointer that accesses the mapped file, -- the finalizer to run to unmap region and size of mmaped memory. -- -- If the mmap fails for some reason, an error is thrown. -- -- Memory mapped files will behave as if they were read lazily -- -- pages from the file will be loaded into memory on demand. -- -- The storage manager is used to free the mapped memory. When -- the garbage collector notices there are no further references to the -- mapped memory, a call to munmap is made. It is not necessary to do -- this yourself. In tight memory situations, it may be profitable to -- use 'System.Mem.performGC' or 'finalizeForeignPtr' to force an unmap. -- -- File must be created with correct attributes prior to mapping it -- into memory. -- -- If mode is 'ReadWrite' or 'WriteCopy', the returned memory region may -- be written to with 'Foreign.Storable.poke' and friends. -- -- Range specified may be 'Nothing', then whole file is mapped. Otherwise -- range should be 'Just (offset,size)' where offsets is the beginning byte -- of file region to map and size tells its length. There are no alignment -- requirements. -- -- If range to map extends beyond end of file, it will be resized accordingly. -- mmapFilePtr :: FilePath -- ^ name of file to mmap -> Mode -- ^ access mode -> Maybe (Int64,Int) -- ^ range to map, maps whole file if Nothing -> IO (Ptr a,IO (),Int) -- ^ pointer, finalizer and size mmapFilePtr fp m range = do (ptr, size) <- mmapFilePtr' fp m range sizeptr <- malloc poke sizeptr (fromIntegral size) return (ptr, c_system_io_mmap_munmap sizeptr ptr, size) -- | Maps region of file and returns it as 'ForeignPtr'. See 'mmapFilePtr' for details. mmapFileForeignPtr :: FilePath -- ^ name of file to map -> Mode -- ^ access mode -> Maybe (Int64,Int) -- ^ range to map, maps whole file if Nothing -> IO (ForeignPtr a,Int) -- ^ foreign pointer to beginning of region and size mmapFileForeignPtr fp m range = do (ptr, size) <- mmapFilePtr' fp m range sizeptr <- malloc poke sizeptr (fromIntegral size) foreignptr <- newForeignPtrEnv c_system_io_mmap_munmap_funptr sizeptr ptr return (foreignptr,size) mmapFilePtr' :: FilePath -- ^ name of file to mmap -> Mode -- ^ access mode -> Maybe (Int64,Int) -- ^ range to map, maps whole file if Nothing -> IO (Ptr a,Int) -- ^ pointer and size mmapFilePtr' filepath mode offsetsize = do bracket (mmapFileOpen filepath mode) (finalizeForeignPtr) mmap where mmap handle = do (offset,size) <- case offsetsize of Just (offset,size) -> return (offset,size) Nothing -> do longsize <- withForeignPtr handle c_system_io_file_size when (longsize > fromIntegral (maxBound :: Int)) $ fail ("file is longer (" ++ show longsize ++ ") than maxBound::Int") return (0,fromIntegral longsize) withForeignPtr handle $ \handle -> do let align = offset `mod` fromIntegral c_system_io_granularity offsetraw = offset - align sizeraw = size + fromIntegral align ptr <- c_system_io_mmap_mmap handle (fromIntegral $ fromEnum mode) (fromIntegral offsetraw) (fromIntegral sizeraw) when (ptr == nullPtr) $ throwErrno $ "mmap of '" ++ filepath ++ "' failed" return (ptr `plusPtr` fromIntegral align,fromIntegral size) -- | Maps region of file and returns it as 'Data.ByteString.ByteString'. -- File is mapped in in 'ReadOnly' mode. See 'mmapFilePtr' for details -- -- Note: this operation may break referential transparency! If -- any other process on the system changes the file when it is mapped -- into Haskell, the contents of your 'Data.ByteString.ByteString' may change. -- mmapFileByteString :: FilePath -- ^ name of file to map -> Maybe (Int64,Int) -- ^ range to map, maps whole file if Nothing -> IO BS.ByteString -- ^ bytestring with file content mmapFileByteString filepath offsetsize = do (ptr,finalizer,size) <- mmapFilePtr filepath ReadOnly offsetsize bytestring <- BS.unsafePackCStringFinalizer ptr size finalizer return bytestring -- | The 'mmapFilePtrLazy' function maps a file or device into memory, -- returning a list of tripples containing pointer that accesses the mapped file, -- the finalizer to run to unmap that region and size of mapped memory. -- -- If the mmap fails for some reason, an error is thrown. -- -- Memory mapped files will behave as if they were read lazily -- -- pages from the file will be loaded into memory on demand. -- -- The storage manager is used to free the mapped memory. When -- the garbage collector notices there are no further references to the -- mapped memory, a call to munmap is made. It is not necessary to do -- this yourself. In tight memory situations, it may be profitable to -- use 'System.Mem.performGC' or 'finalizeForeignPtr' to force an unmap. -- -- File must be created with correct attributes prior to mapping it -- into memory. -- -- If mode is 'ReadWrite' or 'WriteCopy', the returned memory region may -- be written to with 'Foreign.Storable.poke' and friends. -- -- Range specified may be 'Nothing', then whole file is mapped. Otherwise -- range should be 'Just (offset,size)' where offsets is the beginning byte -- of file region to map and size tells its length. There are no alignment -- requirements. -- -- If range to map extends beyond end of file, it will be resized accordingly. -- mmapFilePtrLazy :: FilePath -- ^ name of file to mmap -> Mode -- ^ access mode -> Maybe (Int64,Int64) -- ^ range to map, maps whole file if Nothing -> IO [(Ptr a,IO (),Int)] -- ^ list of pointer, finalizer and size mmapFilePtrLazy filepath mode offsetsize = do handle <- mmapFileOpen filepath mode mmap handle where mmap handle = do (offset,size) <- case offsetsize of Just (offset,size) -> return (offset,size) Nothing -> do longsize <- withForeignPtr handle c_system_io_file_size return (0,fromIntegral longsize) return $ map (mapChunk handle) (chunks offset size) mapChunk handle (offset,size) = unsafePerformIO $ withForeignPtr handle $ \handle -> do let align = offset `mod` fromIntegral c_system_io_granularity offsetraw = offset - align sizeraw = size + fromIntegral align ptr <- c_system_io_mmap_mmap handle (fromIntegral $ fromEnum mode) (fromIntegral offsetraw) (fromIntegral sizeraw) when (ptr == nullPtr) $ throwErrno $ "mmap of '" ++ filepath ++ "' failed" sizeptr <- malloc poke sizeptr $ fromIntegral sizeraw let finalizer = c_system_io_mmap_munmap sizeptr ptr return (ptr `plusPtr` fromIntegral align,finalizer,fromIntegral size) chunks :: Int64 -> Int64 -> [(Int64,Int)] chunks offset size | size <= fromIntegral chunkSize = [(offset,fromIntegral size)] | otherwise = let offset2 = offset + fromIntegral chunkSize `div` fromIntegral chunkSize * fromIntegral chunkSize size2 = fromIntegral (offset2 - offset) in (offset,size2) : chunks (offset2) (size-fromIntegral size2) -- | Maps region of file and returns it as list of 'ForeignPtr's. See 'mmapFilePtr' for details. -- Each chunk is mapped in on demand only. mmapFileForeignPtrLazy :: FilePath -- ^ name of file to map -> Mode -- ^ access mode -> Maybe (Int64,Int64) -- ^ range to map, maps whole file if Nothing -> IO [(ForeignPtr a,Int)] -- ^ foreign pointer to beginning of region and size mmapFileForeignPtrLazy filepath mode offsetsize = do list <- mmapFilePtrLazy filepath mode offsetsize return (map turn list) where turn (ptr,finalizer,size) = unsafePerformIO $ do foreignptr <- Foreign.Concurrent.newForeignPtr ptr finalizer return (foreignptr,size) -- | Maps region of file and returns it as 'Data.ByteString.Lazy.ByteString'. -- File is mapped in in 'ReadOnly' mode. See 'mmapFilePtrLazy' for details. -- Chunks are mapped in on demand. -- -- Note: this operation may break referential transparency! If -- any other process on the system changes the file when it is mapped -- into Haskell, the contents of your 'Data.ByteString.Lazy.ByteString' may change. -- mmapFileByteStringLazy :: FilePath -- ^ name of file to map -> Maybe (Int64,Int64) -- ^ range to map, maps whole file if Nothing -> IO BSL.ByteString -- ^ bytestring with file content mmapFileByteStringLazy filepath offsetsize = do list <- mmapFilePtrLazy filepath ReadOnly offsetsize return (BSL.fromChunks (map turn list)) where turn (ptr,finalizer,size) = unsafePerformIO $ do bytestring <- BS.unsafePackCStringFinalizer ptr size finalizer return bytestring chunkSize :: Int chunkSize = fromIntegral $ (128*1024 `div` c_system_io_granularity) * c_system_io_granularity mmapFileOpen :: FilePath -> Mode -> IO (ForeignPtr ()) mmapFileOpen filepath mode = do ptr <- withCString filepath $ \filepath -> c_system_io_mmap_file_open filepath (fromIntegral $ fromEnum mode) when (ptr == nullPtr) $ throwErrno $ "opening of '" ++ filepath ++ "' failed" handle <- newForeignPtr c_system_io_mmap_file_close ptr return handle foreign import ccall unsafe "system_io_mmap_file_open" c_system_io_mmap_file_open :: CString -> CInt -> IO (Ptr ()) foreign import ccall unsafe "&system_io_mmap_file_close" c_system_io_mmap_file_close :: FunPtr(Ptr () -> IO ()) foreign import ccall unsafe "system_io_mmap_mmap" c_system_io_mmap_mmap :: Ptr () -> CInt -> CLLong -> CInt -> IO (Ptr a) foreign import ccall unsafe "&system_io_mmap_munmap" c_system_io_mmap_munmap_funptr :: FunPtr(Ptr CInt -> Ptr a -> IO ()) foreign import ccall unsafe "system_io_mmap_munmap" c_system_io_mmap_munmap :: Ptr CInt -> Ptr a -> IO () foreign import ccall unsafe "system_io_mmap_file_size" c_system_io_file_size :: Ptr () -> IO (CLLong) foreign import ccall unsafe "system_io_mmap_granularity" c_system_io_granularity :: CInt