diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d28669..63ad789 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Revision history for file-io +## 0.1.2 -- 2024-05-27 + +* expose internals via `.Internal` modules +* add `openFileWithCloseOnExec` and `openExistingFileWithCloseOnExec` to `.Internal` modules wrt [#21](https://github.com/hasufell/file-io/issues/21) + ## 0.1.1 -- 2024-01-20 * fix a severe bug on windows, where `readFile` may create a missing file, wrt [#14](https://github.com/hasufell/file-io/issues/14) diff --git a/System/File/OsPath.hs b/System/File/OsPath.hs index 3dbfeda..cfbc507 100644 --- a/System/File/OsPath.hs +++ b/System/File/OsPath.hs @@ -1,6 +1,14 @@ -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE BangPatterns #-} +{- | +Module : System.File.OsPath +Copyright : (c) Julian Ospald 2023-2024 +License : BSD3 +Maintainer : hasufell@posteo.de +Stability : stable +Portability : portable + +This module mimics base API wrt file IO, but using 'OsPath'. +-} module System.File.OsPath ( openBinaryFile , withFile @@ -18,154 +26,6 @@ module System.File.OsPath ( ) where -import qualified System.File.Platform as P - -import Prelude ((.), ($), String, IO, ioError, pure, either, const, flip, Maybe(..), fmap, (<$>), id, Bool(..), FilePath, (++), return, show, (>>=)) -import GHC.IO (catchException) -import GHC.IO.Exception (IOException(..)) -import GHC.IO.Handle (hClose_help) -import GHC.IO.Handle.Internals (debugIO) -import GHC.IO.Handle.Types (Handle__, Handle(..)) -import Control.Concurrent.MVar -import Control.Monad (void, when) -import Control.DeepSeq (force) -import Control.Exception (SomeException, try, evaluate, mask, onException) -import System.IO (IOMode(..), hSetBinaryMode, hClose) -import System.IO.Unsafe (unsafePerformIO) -import System.OsPath as OSP -import System.OsString.Internal.Types - -import qualified Data.ByteString as BS -import qualified Data.ByteString.Lazy as BSL - --- | Like 'openFile', but open the file in binary mode. --- On Windows, reading a file in text mode (which is the default) --- will translate CRLF to LF, and writing will translate LF to CRLF. --- This is usually what you want with text files. With binary files --- this is undesirable; also, as usual under Microsoft operating systems, --- text mode treats control-Z as EOF. Binary mode turns off all special --- treatment of end-of-line and end-of-file characters. --- (See also 'System.IO.hSetBinaryMode'.) - --- On POSIX systems, 'openBinaryFile' is an /interruptible operation/ as --- described in "Control.Exception". -openBinaryFile :: OsPath -> IOMode -> IO Handle -openBinaryFile osfp iomode = augmentError "openBinaryFile" osfp $ withOpenFile' osfp iomode True False pure False - - --- | Run an action on a file. --- --- The 'Handle' is automatically closed afther the action. -withFile :: OsPath -> IOMode -> (Handle -> IO r) -> IO r -withFile osfp iomode act = (augmentError "withFile" osfp - $ withOpenFile' osfp iomode False False (try . act) True) - >>= either ioError pure - -withBinaryFile :: OsPath -> IOMode -> (Handle -> IO r) -> IO r -withBinaryFile osfp iomode act = (augmentError "withBinaryFile" osfp - $ withOpenFile' osfp iomode True False (try . act) True) - >>= either ioError pure - --- | Run an action on a file. --- --- The 'Handle' is not automatically closed to allow lazy IO. Use this --- with caution. -withFile' - :: OsPath -> IOMode -> (Handle -> IO r) -> IO r -withFile' osfp iomode act = (augmentError "withFile'" osfp - $ withOpenFile' osfp iomode False False (try . act) False) - >>= either ioError pure - -withBinaryFile' - :: OsPath -> IOMode -> (Handle -> IO r) -> IO r -withBinaryFile' osfp iomode act = (augmentError "withBinaryFile'" osfp - $ withOpenFile' osfp iomode True False (try . act) False) - >>= either ioError pure - --- | The 'readFile' function reads a file and returns the contents of the file --- as a 'ByteString'. The file is read lazily, on demand. -readFile :: OsPath -> IO BSL.ByteString -readFile fp = withFile' fp ReadMode BSL.hGetContents - --- | The 'readFile'' function reads a file and returns the contents of the file --- as a 'ByteString'. The file is fully read before being returned. -readFile' - :: OsPath -> IO BS.ByteString -readFile' fp = withFile fp ReadMode BS.hGetContents - --- | The computation 'writeFile' @file str@ function writes the lazy 'ByteString' @str@, --- to the file @file@. -writeFile :: OsPath -> BSL.ByteString -> IO () -writeFile fp contents = withFile fp WriteMode (`BSL.hPut` contents) - --- | The computation 'writeFile' @file str@ function writes the strict 'ByteString' @str@, --- to the file @file@. -writeFile' - :: OsPath -> BS.ByteString -> IO () -writeFile' fp contents = withFile fp WriteMode (`BS.hPut` contents) - --- | The computation 'appendFile' @file str@ function appends the lazy 'ByteString' @str@, --- to the file @file@. -appendFile :: OsPath -> BSL.ByteString -> IO () -appendFile fp contents = withFile fp AppendMode (`BSL.hPut` contents) - --- | The computation 'appendFile' @file str@ function appends the strict 'ByteString' @str@, --- to the file @file@. -appendFile' - :: OsPath -> BS.ByteString -> IO () -appendFile' fp contents = withFile fp AppendMode (`BS.hPut` contents) - --- | Open a file and return the 'Handle'. -openFile :: OsPath -> IOMode -> IO Handle -openFile osfp iomode = augmentError "openFile" osfp $ withOpenFile' osfp iomode False False pure False - - --- | Open an existing file and return the 'Handle'. -openExistingFile :: OsPath -> IOMode -> IO Handle -openExistingFile osfp iomode = augmentError "openExistingFile" osfp $ withOpenFile' osfp iomode False True pure False - - --- --------------------------------------------------------------------------- --- Internals - -handleFinalizer :: FilePath -> MVar Handle__ -> IO () -handleFinalizer _fp m = do - handle_ <- takeMVar m - (handle_', _) <- hClose_help handle_ - putMVar m handle_' - return () - -type HandleFinalizer = FilePath -> MVar Handle__ -> IO () - --- | Add a finalizer to a 'Handle'. Specifically, the finalizer --- will be added to the 'MVar' of a file handle or the write-side --- 'MVar' of a duplex handle. See Handle Finalizers for details. -addHandleFinalizer :: Handle -> HandleFinalizer -> IO () -addHandleFinalizer hndl finalizer = do - debugIO $ "Registering finalizer: " ++ show filepath - void $ mkWeakMVar mv (finalizer filepath mv) - where - !(filepath, !mv) = case hndl of - FileHandle fp m -> (fp, m) - DuplexHandle fp _ write_m -> (fp, write_m) - -withOpenFile' :: OsPath -> IOMode -> Bool -> Bool -> (Handle -> IO r) -> Bool -> IO r -withOpenFile' (OsString fp) iomode binary existing action close_finally = mask $ \restore -> do - hndl <- if existing - then P.openExistingFile fp iomode - else P.openFile fp iomode - addHandleFinalizer hndl handleFinalizer - when binary $ hSetBinaryMode hndl True - r <- restore (action hndl) `onException` hClose hndl - when close_finally $ hClose hndl - pure r - -addFilePathToIOError :: String -> OsPath -> IOException -> IOException -addFilePathToIOError fun fp ioe = unsafePerformIO $ do - fp' <- either (const (fmap OSP.toChar . OSP.unpack $ fp)) id <$> try @SomeException (OSP.decodeFS fp) - fp'' <- evaluate $ force fp' - pure $ ioe{ ioe_location = fun, ioe_filename = Just fp'' } - -augmentError :: String -> OsPath -> IO a -> IO a -augmentError str osfp = flip catchException (ioError . addFilePathToIOError str osfp) +import System.File.OsPath.Internal +import Prelude () diff --git a/System/File/OsPath/Internal.hs b/System/File/OsPath/Internal.hs new file mode 100644 index 0000000..f7e9cce --- /dev/null +++ b/System/File/OsPath/Internal.hs @@ -0,0 +1,175 @@ +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE BangPatterns #-} + +module System.File.OsPath.Internal where + + +import qualified System.File.Platform as P + +import Prelude ((.), ($), String, IO, ioError, pure, either, const, flip, Maybe(..), fmap, (<$>), id, Bool(..), FilePath, (++), return, show, (>>=)) +import GHC.IO (catchException) +import GHC.IO.Exception (IOException(..)) +import GHC.IO.Handle (hClose_help) +import GHC.IO.Handle.Internals (debugIO) +import GHC.IO.Handle.Types (Handle__, Handle(..)) +import Control.Concurrent.MVar +import Control.Monad (void, when) +import Control.DeepSeq (force) +import Control.Exception (SomeException, try, evaluate, mask, onException) +import System.IO (IOMode(..), hSetBinaryMode, hClose) +import System.IO.Unsafe (unsafePerformIO) +import System.OsPath as OSP +import System.OsString.Internal.Types + +import qualified Data.ByteString as BS +import qualified Data.ByteString.Lazy as BSL + +-- | Like 'openFile', but open the file in binary mode. +-- On Windows, reading a file in text mode (which is the default) +-- will translate CRLF to LF, and writing will translate LF to CRLF. +-- This is usually what you want with text files. With binary files +-- this is undesirable; also, as usual under Microsoft operating systems, +-- text mode treats control-Z as EOF. Binary mode turns off all special +-- treatment of end-of-line and end-of-file characters. +-- (See also 'System.IO.hSetBinaryMode'.) + +-- On POSIX systems, 'openBinaryFile' is an /interruptible operation/ as +-- described in "Control.Exception". +openBinaryFile :: OsPath -> IOMode -> IO Handle +openBinaryFile osfp iomode = augmentError "openBinaryFile" osfp $ withOpenFile' osfp iomode True False False pure False + + +-- | Run an action on a file. +-- +-- The 'Handle' is automatically closed afther the action. +withFile :: OsPath -> IOMode -> (Handle -> IO r) -> IO r +withFile osfp iomode act = (augmentError "withFile" osfp + $ withOpenFile' osfp iomode False False False (try . act) True) + >>= either ioError pure + +withBinaryFile :: OsPath -> IOMode -> (Handle -> IO r) -> IO r +withBinaryFile osfp iomode act = (augmentError "withBinaryFile" osfp + $ withOpenFile' osfp iomode True False False (try . act) True) + >>= either ioError pure + +-- | Run an action on a file. +-- +-- The 'Handle' is not automatically closed to allow lazy IO. Use this +-- with caution. +withFile' + :: OsPath -> IOMode -> (Handle -> IO r) -> IO r +withFile' osfp iomode act = (augmentError "withFile'" osfp + $ withOpenFile' osfp iomode False False False (try . act) False) + >>= either ioError pure + +withBinaryFile' + :: OsPath -> IOMode -> (Handle -> IO r) -> IO r +withBinaryFile' osfp iomode act = (augmentError "withBinaryFile'" osfp + $ withOpenFile' osfp iomode True False False (try . act) False) + >>= either ioError pure + +-- | The 'readFile' function reads a file and returns the contents of the file +-- as a 'ByteString'. The file is read lazily, on demand. +readFile :: OsPath -> IO BSL.ByteString +readFile fp = withFile' fp ReadMode BSL.hGetContents + +-- | The 'readFile'' function reads a file and returns the contents of the file +-- as a 'ByteString'. The file is fully read before being returned. +readFile' + :: OsPath -> IO BS.ByteString +readFile' fp = withFile fp ReadMode BS.hGetContents + +-- | The computation 'writeFile' @file str@ function writes the lazy 'ByteString' @str@, +-- to the file @file@. +writeFile :: OsPath -> BSL.ByteString -> IO () +writeFile fp contents = withFile fp WriteMode (`BSL.hPut` contents) + +-- | The computation 'writeFile' @file str@ function writes the strict 'ByteString' @str@, +-- to the file @file@. +writeFile' + :: OsPath -> BS.ByteString -> IO () +writeFile' fp contents = withFile fp WriteMode (`BS.hPut` contents) + +-- | The computation 'appendFile' @file str@ function appends the lazy 'ByteString' @str@, +-- to the file @file@. +appendFile :: OsPath -> BSL.ByteString -> IO () +appendFile fp contents = withFile fp AppendMode (`BSL.hPut` contents) + +-- | The computation 'appendFile' @file str@ function appends the strict 'ByteString' @str@, +-- to the file @file@. +appendFile' + :: OsPath -> BS.ByteString -> IO () +appendFile' fp contents = withFile fp AppendMode (`BS.hPut` contents) + +-- | Open a file and return the 'Handle'. +openFile :: OsPath -> IOMode -> IO Handle +openFile osfp iomode = augmentError "openFile" osfp $ withOpenFile' osfp iomode False False False pure False + + +-- | Open an existing file and return the 'Handle'. +openExistingFile :: OsPath -> IOMode -> IO Handle +openExistingFile osfp iomode = augmentError "openExistingFile" osfp $ withOpenFile' osfp iomode False True False pure False + +-- | Open a file and return the 'Handle'. +-- +-- Sets @O_CLOEXEC@ on posix. +-- +-- @since 0.1.2 +openFileWithCloseOnExec :: OsPath -> IOMode -> IO Handle +openFileWithCloseOnExec osfp iomode = augmentError "openFileWithCloseOnExec" osfp $ withOpenFile' osfp iomode False False True pure False + + +-- | Open an existing file and return the 'Handle'. +-- +-- Sets @O_CLOEXEC@ on posix. +-- +-- @since 0.1.2 +openExistingFileWithCloseOnExec :: OsPath -> IOMode -> IO Handle +openExistingFileWithCloseOnExec osfp iomode = augmentError "openExistingFileWithCloseOnExec" osfp $ withOpenFile' osfp iomode False True True pure False + +-- --------------------------------------------------------------------------- +-- Internals + +handleFinalizer :: FilePath -> MVar Handle__ -> IO () +handleFinalizer _fp m = do + handle_ <- takeMVar m + (handle_', _) <- hClose_help handle_ + putMVar m handle_' + return () + +type HandleFinalizer = FilePath -> MVar Handle__ -> IO () + +-- | Add a finalizer to a 'Handle'. Specifically, the finalizer +-- will be added to the 'MVar' of a file handle or the write-side +-- 'MVar' of a duplex handle. See Handle Finalizers for details. +addHandleFinalizer :: Handle -> HandleFinalizer -> IO () +addHandleFinalizer hndl finalizer = do + debugIO $ "Registering finalizer: " ++ show filepath + void $ mkWeakMVar mv (finalizer filepath mv) + where + !(filepath, !mv) = case hndl of + FileHandle fp m -> (fp, m) + DuplexHandle fp _ write_m -> (fp, write_m) + +withOpenFile' :: OsPath -> IOMode -> Bool -> Bool -> Bool -> (Handle -> IO r) -> Bool -> IO r +withOpenFile' (OsString fp) iomode binary existing cloExec action close_finally = mask $ \restore -> do + hndl <- case (existing, cloExec) of + (True, False) -> P.openExistingFile fp iomode + (False, False) -> P.openFile fp iomode + (True, True) -> P.openExistingFileWithCloseOnExec fp iomode + (False, True) -> P.openFileWithCloseOnExec fp iomode + addHandleFinalizer hndl handleFinalizer + when binary $ hSetBinaryMode hndl True + r <- restore (action hndl) `onException` hClose hndl + when close_finally $ hClose hndl + pure r + +addFilePathToIOError :: String -> OsPath -> IOException -> IOException +addFilePathToIOError fun fp ioe = unsafePerformIO $ do + fp' <- either (const (fmap OSP.toChar . OSP.unpack $ fp)) id <$> try @SomeException (OSP.decodeFS fp) + fp'' <- evaluate $ force fp' + pure $ ioe{ ioe_location = fun, ioe_filename = Just fp'' } + +augmentError :: String -> OsPath -> IO a -> IO a +augmentError str osfp = flip catchException (ioError . addFilePathToIOError str osfp) + diff --git a/System/File/PlatformPath.hs b/System/File/PlatformPath.hs index a510c4c..1d7c79c 100644 --- a/System/File/PlatformPath.hs +++ b/System/File/PlatformPath.hs @@ -1,65 +1,33 @@ -module System.File.PlatformPath where +{- | +Module : System.File.PlatformPath +Copyright : (c) Julian Ospald 2023-2024 +License : BSD3 + +Maintainer : hasufell@posteo.de +Stability : stable +Portability : portable + +This module is only interesting when you are implementing low-level libraries +based on 'OsPath' API. + +Usually you want "System.File.OsPath". +-} +module System.File.PlatformPath ( + openBinaryFile +, withFile +, withBinaryFile +, withFile' +, withBinaryFile' +, readFile +, readFile' +, writeFile +, writeFile' +, appendFile +, appendFile' +, openFile +, openExistingFile +) where + +import System.File.PlatformPath.Internal +import Prelude () - -import System.IO (IOMode(..), Handle) -import System.OsPath.Types - -import qualified Data.ByteString as BS -import qualified Data.ByteString.Lazy as BSL - -import qualified System.File.OsPath as OsPath -import System.OsString.Internal.Types - -import Data.Coerce (coerce) - --- | Like `OsPath.openBinaryFile`, but takes a `PlatformPath` instead of an `OsPath`. -openBinaryFile :: PlatformPath -> IOMode -> IO Handle -openBinaryFile = OsPath.openBinaryFile . coerce - --- | Like `OsPath.withFile`, but takes a `PlatformPath` instead of an `OsPath`. -withFile :: PlatformPath -> IOMode -> (Handle -> IO r) -> IO r -withFile = OsPath.withFile . coerce - --- | Like `OsPath.withBinaryFile`, but takes a `PlatformPath` instead of an `OsPath`. -withBinaryFile :: PlatformPath -> IOMode -> (Handle -> IO r) -> IO r -withBinaryFile = OsPath.withBinaryFile . coerce - --- | Like `OsPath.withFile'`, but takes a `PlatformPath` instead of an `OsPath`. -withFile' :: PlatformPath -> IOMode -> (Handle -> IO r) -> IO r -withFile' = OsPath.withFile' . coerce - --- | Like `OsPath.withBinaryFile'`, but takes a `PlatformPath` instead of an `OsPath`. -withBinaryFile' :: PlatformPath -> IOMode -> (Handle -> IO r) -> IO r -withBinaryFile' = OsPath.withBinaryFile' . coerce - --- | Like `OsPath.readFile`, but takes a `PlatformPath` instead of an `OsPath`. -readFile :: PlatformPath -> IO BSL.ByteString -readFile = OsPath.readFile . coerce - --- | Like `OsPath.readFile'`, but takes a `PlatformPath` instead of an `OsPath`. -readFile' :: PlatformPath -> IO BS.ByteString -readFile' = OsPath.readFile' . coerce - --- | Like `OsPath.writeFile`, but takes a `PlatformPath` instead of an `OsPath`. -writeFile :: PlatformPath -> BSL.ByteString -> IO () -writeFile = OsPath.writeFile . coerce - --- | Like `OsPath.writeFile'`, but takes a `PlatformPath` instead of an `OsPath`. -writeFile' :: PlatformPath -> BS.ByteString -> IO () -writeFile' = OsPath.writeFile' . coerce - --- | Like `OsPath.appendFile`, but takes a `PlatformPath` instead of an `OsPath`. -appendFile :: PlatformPath -> BSL.ByteString -> IO () -appendFile = OsPath.appendFile . coerce - --- | Like `OsPath.appendFile'`, but takes a `PlatformPath` instead of an `OsPath`. -appendFile' :: PlatformPath -> BS.ByteString -> IO () -appendFile' = OsPath.appendFile' . coerce - --- | Like `OsPath.openFile`, but takes a `PlatformPath` instead of an `OsPath`. -openFile :: PlatformPath -> IOMode -> IO Handle -openFile = OsPath.openFile . coerce - --- | Like `OsPath.openExistingFile`, but takes a `PlatformPath` instead of an `OsPath`. -openExistingFile :: PlatformPath -> IOMode -> IO Handle -openExistingFile = OsPath.openExistingFile . coerce diff --git a/System/File/PlatformPath/Internal.hs b/System/File/PlatformPath/Internal.hs new file mode 100644 index 0000000..cb0ed6e --- /dev/null +++ b/System/File/PlatformPath/Internal.hs @@ -0,0 +1,118 @@ +module System.File.PlatformPath.Internal ( + openBinaryFile + , withFile + , withBinaryFile + , withFile' + , withBinaryFile' + , readFile + , readFile' + , writeFile + , writeFile' + , appendFile + , appendFile' + , openFile + , openExistingFile + , openFileWithCloseOnExec + , openExistingFileWithCloseOnExec + , OsPath.handleFinalizer + , OsPath.HandleFinalizer + , OsPath.addHandleFinalizer + , withOpenFile' + , addFilePathToIOError + , augmentError +) where + + +import System.IO (IOMode(..), Handle) +import System.OsPath.Types +import GHC.IO.Exception (IOException(..)) + +import qualified Data.ByteString as BS +import qualified Data.ByteString.Lazy as BSL + +import qualified System.File.OsPath.Internal as OsPath +import System.OsString.Internal.Types + +import Data.Coerce (coerce) +import Prelude hiding (readFile, writeFile, appendFile) + +-- | Like `OsPath.openBinaryFile`, but takes a `PlatformPath` instead of an `OsPath`. +openBinaryFile :: PlatformPath -> IOMode -> IO Handle +openBinaryFile = OsPath.openBinaryFile . coerce + +-- | Like `OsPath.withFile`, but takes a `PlatformPath` instead of an `OsPath`. +withFile :: PlatformPath -> IOMode -> (Handle -> IO r) -> IO r +withFile = OsPath.withFile . coerce + +-- | Like `OsPath.withBinaryFile`, but takes a `PlatformPath` instead of an `OsPath`. +withBinaryFile :: PlatformPath -> IOMode -> (Handle -> IO r) -> IO r +withBinaryFile = OsPath.withBinaryFile . coerce + +-- | Like `OsPath.withFile'`, but takes a `PlatformPath` instead of an `OsPath`. +withFile' :: PlatformPath -> IOMode -> (Handle -> IO r) -> IO r +withFile' = OsPath.withFile' . coerce + +-- | Like `OsPath.withBinaryFile'`, but takes a `PlatformPath` instead of an `OsPath`. +withBinaryFile' :: PlatformPath -> IOMode -> (Handle -> IO r) -> IO r +withBinaryFile' = OsPath.withBinaryFile' . coerce + +-- | Like `OsPath.readFile`, but takes a `PlatformPath` instead of an `OsPath`. +readFile :: PlatformPath -> IO BSL.ByteString +readFile = OsPath.readFile . coerce + +-- | Like `OsPath.readFile'`, but takes a `PlatformPath` instead of an `OsPath`. +readFile' :: PlatformPath -> IO BS.ByteString +readFile' = OsPath.readFile' . coerce + +-- | Like `OsPath.writeFile`, but takes a `PlatformPath` instead of an `OsPath`. +writeFile :: PlatformPath -> BSL.ByteString -> IO () +writeFile = OsPath.writeFile . coerce + +-- | Like `OsPath.writeFile'`, but takes a `PlatformPath` instead of an `OsPath`. +writeFile' :: PlatformPath -> BS.ByteString -> IO () +writeFile' = OsPath.writeFile' . coerce + +-- | Like `OsPath.appendFile`, but takes a `PlatformPath` instead of an `OsPath`. +appendFile :: PlatformPath -> BSL.ByteString -> IO () +appendFile = OsPath.appendFile . coerce + +-- | Like `OsPath.appendFile'`, but takes a `PlatformPath` instead of an `OsPath`. +appendFile' :: PlatformPath -> BS.ByteString -> IO () +appendFile' = OsPath.appendFile' . coerce + +-- | Like `OsPath.openFile`, but takes a `PlatformPath` instead of an `OsPath`. +openFile :: PlatformPath -> IOMode -> IO Handle +openFile = OsPath.openFile . coerce + +-- | Like `OsPath.openExistingFile`, but takes a `PlatformPath` instead of an `OsPath`. +openExistingFile :: PlatformPath -> IOMode -> IO Handle +openExistingFile = OsPath.openExistingFile . coerce + +-- | Open a file and return the 'Handle'. +-- +-- Sets @O_CLOEXEC@ on posix. +-- +-- @since 0.1.2 +openFileWithCloseOnExec :: PlatformPath -> IOMode -> IO Handle +openFileWithCloseOnExec = OsPath.openFileWithCloseOnExec . coerce + +-- | Open an existing file and return the 'Handle'. +-- +-- Sets @O_CLOEXEC@ on posix. +-- +-- @since 0.1.2 +openExistingFileWithCloseOnExec :: PlatformPath -> IOMode -> IO Handle +openExistingFileWithCloseOnExec = OsPath.openExistingFileWithCloseOnExec . coerce + +-- --------------------------------------------------------------------------- +-- Internals + +withOpenFile' :: PlatformPath -> IOMode -> Bool -> Bool -> Bool -> (Handle -> IO r) -> Bool -> IO r +withOpenFile' = OsPath.withOpenFile' . coerce + +addFilePathToIOError :: String -> PlatformPath -> IOException -> IOException +addFilePathToIOError = coerce OsPath.addFilePathToIOError + +augmentError :: String -> PlatformPath -> IO a -> IO a +augmentError fp = OsPath.augmentError fp . coerce + diff --git a/file-io.cabal b/file-io.cabal index d2e21dd..ba341ec 100644 --- a/file-io.cabal +++ b/file-io.cabal @@ -1,6 +1,6 @@ cabal-version: 2.4 name: file-io -version: 0.1.1 +version: 0.1.2 synopsis: Basic file IO operations via 'OsPath' description: Basic file IO operations like Prelude, but for 'OsPath'. homepage: https://github.com/hasufell/file-io @@ -53,7 +53,9 @@ library exposed-modules: System.File.OsPath + System.File.OsPath.Internal System.File.PlatformPath + System.File.PlatformPath.Internal other-modules: System.File.Platform diff --git a/posix/System/File/Platform.hs b/posix/System/File/Platform.hs index 14c2438..760f22b 100644 --- a/posix/System/File/Platform.hs +++ b/posix/System/File/Platform.hs @@ -10,35 +10,51 @@ import System.Posix.IO.PosixString ( defaultFileFlags, openFd, closeFd, - OpenFileFlags(noctty, nonBlock, creat, append, trunc), + OpenFileFlags(noctty, nonBlock, creat, append, trunc, cloexec), OpenMode(ReadWrite, ReadOnly, WriteOnly) ) import System.OsPath.Posix ( PosixPath ) import qualified System.OsPath.Posix as PS -- | Open a file and return the 'Handle'. openFile :: PosixPath -> IOMode -> IO Handle -openFile fp iomode = fdToHandle_ iomode fp =<< case iomode of +openFile = openFile_ defaultFileFlags' + +openFile_ :: OpenFileFlags -> PosixPath -> IOMode -> IO Handle +openFile_ df fp iomode = fdToHandle_ iomode fp =<< case iomode of ReadMode -> open ReadOnly df WriteMode -> open WriteOnly df { trunc = True, creat = Just 0o666 } AppendMode -> open WriteOnly df { append = True, creat = Just 0o666 } ReadWriteMode -> open ReadWrite df { creat = Just 0o666 } where open = openFd fp - df = defaultFileFlags { noctty = True, nonBlock = True } -- | Open an existing file and return the 'Handle'. openExistingFile :: PosixPath -> IOMode -> IO Handle -openExistingFile fp iomode = fdToHandle_ iomode fp =<< case iomode of +openExistingFile = openExistingFile_ defaultExistingFileFlags + +openExistingFile_ :: OpenFileFlags -> PosixPath -> IOMode -> IO Handle +openExistingFile_ df fp iomode = fdToHandle_ iomode fp =<< case iomode of ReadMode -> open ReadOnly df WriteMode -> open WriteOnly df { trunc = True } AppendMode -> open WriteOnly df { append = True } ReadWriteMode -> open ReadWrite df where open = openFd fp - df = defaultFileFlags { noctty = True, nonBlock = True, creat = Nothing } fdToHandle_ :: IOMode -> PosixPath -> Fd -> IO Handle fdToHandle_ iomode fp (Fd fd) = (`onException` closeFd (Fd fd)) $ do fp' <- either (const (fmap PS.toChar . PS.unpack $ fp)) id <$> try @SomeException (PS.decodeFS fp) fdToHandle' fd Nothing False fp' iomode True +openFileWithCloseOnExec :: PosixPath -> IOMode -> IO Handle +openFileWithCloseOnExec = openFile_ defaultFileFlags' { cloexec = True } + +openExistingFileWithCloseOnExec :: PosixPath -> IOMode -> IO Handle +openExistingFileWithCloseOnExec = openExistingFile_ defaultExistingFileFlags { cloexec = True } + +defaultFileFlags' :: OpenFileFlags +defaultFileFlags' = defaultFileFlags { noctty = True, nonBlock = True } + +defaultExistingFileFlags :: OpenFileFlags +defaultExistingFileFlags = defaultFileFlags { noctty = True, nonBlock = True, creat = Nothing } + diff --git a/windows/System/File/Platform.hsc b/windows/System/File/Platform.hsc index 88959bf..85b34d3 100644 --- a/windows/System/File/Platform.hsc +++ b/windows/System/File/Platform.hsc @@ -129,3 +129,9 @@ foreign import ccall "_open_osfhandle" _open_osfhandle :: CIntPtr -> CInt -> IO CInt #endif +openFileWithCloseOnExec :: WindowsPath -> IOMode -> IO Handle +openFileWithCloseOnExec = openFile + +openExistingFileWithCloseOnExec :: WindowsPath -> IOMode -> IO Handle +openExistingFileWithCloseOnExec = openExistingFile +