file-io 0.1.2 → 0.1.3
raw patch · 7 files changed
+350/−39 lines, 7 filesdep ~basePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base
API changes (from Hackage documentation)
+ System.File.OsPath: openBinaryTempFile :: OsPath -> OsString -> IO (OsPath, Handle)
+ System.File.OsPath: openBinaryTempFileWithDefaultPermissions :: OsPath -> OsString -> IO (OsPath, Handle)
+ System.File.OsPath: openTempFile :: OsPath -> OsString -> IO (OsPath, Handle)
+ System.File.OsPath: openTempFileWithDefaultPermissions :: OsPath -> OsString -> IO (OsPath, Handle)
+ System.File.OsPath.Internal: any_ :: (OsChar -> Bool) -> OsString -> Bool
+ System.File.OsPath.Internal: openBinaryTempFile :: OsPath -> OsString -> IO (OsPath, Handle)
+ System.File.OsPath.Internal: openBinaryTempFileWithDefaultPermissions :: OsPath -> OsString -> IO (OsPath, Handle)
+ System.File.OsPath.Internal: openTempFile :: OsPath -> OsString -> IO (OsPath, Handle)
+ System.File.OsPath.Internal: openTempFile' :: String -> OsPath -> OsString -> Bool -> CMode -> IO (OsPath, Handle)
+ System.File.OsPath.Internal: openTempFileWithDefaultPermissions :: OsPath -> OsString -> IO (OsPath, Handle)
Files
- CHANGELOG.md +4/−0
- System/File/OsPath.hs +4/−0
- System/File/OsPath/Internal.hs +87/−2
- file-io.cabal +10/−11
- posix/System/File/Platform.hs +77/−3
- tests/Properties.hs +57/−6
- windows/System/File/Platform.hsc +111/−17
CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for file-io +## 0.1.3 -- 2024-08-08++* add `openTempFile` , `openBinaryTempFile` , `openTempFileWithDefaultPermissions` and `openBinaryTempFileWithDefaultPermissions` wrt [#2](https://github.com/hasufell/file-io/issues/2)+ ## 0.1.2 -- 2024-05-27 * expose internals via `.Internal` modules
System/File/OsPath.hs view
@@ -23,6 +23,10 @@ , appendFile' , openFile , openExistingFile+, openTempFile+, openBinaryTempFile+, openTempFileWithDefaultPermissions+, openBinaryTempFileWithDefaultPermissions ) where
System/File/OsPath/Internal.hs view
@@ -1,12 +1,15 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE QuasiQuotes #-} 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 Prelude ((.), ($), String, IO, ioError, pure, either, const, flip, Maybe(..), fmap, (<$>), id, Bool(..), FilePath, (++), return, show, (>>=), (==), otherwise, userError) import GHC.IO (catchException) import GHC.IO.Exception (IOException(..)) import GHC.IO.Handle (hClose_help)@@ -15,7 +18,7 @@ import Control.Concurrent.MVar import Control.Monad (void, when) import Control.DeepSeq (force)-import Control.Exception (SomeException, try, evaluate, mask, onException)+import Control.Exception (SomeException, try, evaluate, mask, onException, throwIO) import System.IO (IOMode(..), hSetBinaryMode, hClose) import System.IO.Unsafe (unsafePerformIO) import System.OsPath as OSP@@ -23,6 +26,12 @@ import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL+import System.Posix.Types (CMode)+#if MIN_VERSION_filepath(1, 5, 0)+import qualified System.OsString as OSS+#else+import Data.Coerce+#endif -- | Like 'openFile', but open the file in binary mode. -- On Windows, reading a file in text mode (which is the default)@@ -127,6 +136,56 @@ openExistingFileWithCloseOnExec :: OsPath -> IOMode -> IO Handle openExistingFileWithCloseOnExec osfp iomode = augmentError "openExistingFileWithCloseOnExec" osfp $ withOpenFile' osfp iomode False True True pure False ++-- | The function creates a temporary file in ReadWrite mode.+-- The created file isn\'t deleted automatically, so you need to delete it manually.+--+-- The file is created with permissions such that only the current+-- user can read\/write it.+--+-- With some exceptions (see below), the file will be created securely+-- in the sense that an attacker should not be able to cause+-- openTempFile to overwrite another file on the filesystem using your+-- credentials, by putting symbolic links (on Unix) in the place where+-- the temporary file is to be created. On Unix the @O_CREAT@ and+-- @O_EXCL@ flags are used to prevent this attack, but note that+-- @O_EXCL@ is sometimes not supported on NFS filesystems, so if you+-- rely on this behaviour it is best to use local filesystems only.+--+-- @since 0.1.3+openTempFile :: OsPath -- ^ Directory in which to create the file+ -> OsString -- ^ File name template. If the template is \"foo.ext\" then+ -- the created file will be \"fooXXX.ext\" where XXX is some+ -- random number. Note that this should not contain any path+ -- separator characters. On Windows, the template prefix may+ -- be truncated to 3 chars, e.g. \"foobar.ext\" will be+ -- \"fooXXX.ext\".+ -> IO (OsPath, Handle)+openTempFile tmp_dir template = openTempFile' "openTempFile" tmp_dir template False 0o600++-- | Like 'openTempFile', but opens the file in binary mode. See 'openBinaryFile' for more comments.+--+-- @since 0.1.3+openBinaryTempFile :: OsPath -> OsString -> IO (OsPath, Handle)+openBinaryTempFile tmp_dir template+ = openTempFile' "openBinaryTempFile" tmp_dir template True 0o600++-- | Like 'openTempFile', but uses the default file permissions+--+-- @since 0.1.3+openTempFileWithDefaultPermissions :: OsPath -> OsString+ -> IO (OsPath, Handle)+openTempFileWithDefaultPermissions tmp_dir template+ = openTempFile' "openTempFileWithDefaultPermissions" tmp_dir template False 0o666++-- | Like 'openBinaryTempFile', but uses the default file permissions+--+-- @since 0.1.3+openBinaryTempFileWithDefaultPermissions :: OsPath -> OsString+ -> IO (OsPath, Handle)+openBinaryTempFileWithDefaultPermissions tmp_dir template+ = openTempFile' "openBinaryTempFileWithDefaultPermissions" tmp_dir template True 0o666+ -- --------------------------------------------------------------------------- -- Internals @@ -172,4 +231,30 @@ augmentError :: String -> OsPath -> IO a -> IO a augmentError str osfp = flip catchException (ioError . addFilePathToIOError str osfp)+++openTempFile' :: String -> OsPath -> OsString -> Bool -> CMode+ -> IO (OsPath, Handle)+openTempFile' loc (OsString tmp_dir) template@(OsString tmpl) binary mode+ | any_ (== OSP.pathSeparator) template+ = throwIO $ userError $ "openTempFile': Template string must not contain path separator characters: " ++ P.lenientDecode tmpl+ | otherwise = do+ (fp, hdl) <- P.findTempName (prefix, suffix) loc tmp_dir mode+ when binary $ hSetBinaryMode hdl True+ pure (OsString fp, hdl)+ where+ -- We split off the last extension, so we can use .foo.ext files+ -- for temporary files (hidden on Unix OSes). Unfortunately we're+ -- below filepath in the hierarchy here.+ (OsString prefix, OsString suffix) = OSP.splitExtension template++#if MIN_VERSION_filepath(1, 5, 0)+any_ :: (OsChar -> Bool) -> OsString -> Bool+any_ = OSS.any++#else+any_ :: (OsChar -> Bool) -> OsString -> Bool+any_ = coerce P.any_++#endif
file-io.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: file-io-version: 0.1.2+version: 0.1.3 synopsis: Basic file IO operations via 'OsPath' description: Basic file IO operations like Prelude, but for 'OsPath'. homepage: https://github.com/hasufell/file-io@@ -17,8 +17,7 @@ GHC==9.2.8, GHC==9.0.2, GHC==8.10.7,- GHC==8.8.4,- GHC==8.6.5+ GHC==8.8.4 source-repository head type: git@@ -42,7 +41,7 @@ hs-source-dirs: . build-depends:- , base >=4.12 && <5+ , base >=4.13.0.0 && <5 , bytestring >=0.11.3.0 , deepseq @@ -66,7 +65,7 @@ main-is: T15.hs type: exitcode-stdio-1.0 default-language: Haskell2010- build-depends: base, tasty, tasty-hunit, file-io, filepath, temporary+ build-depends: base >=4.13.0.0 && <5, tasty, tasty-hunit, file-io, filepath, temporary ghc-options: -Wall -threaded -rtsopts "-with-rtsopts=-N10" if os(windows) build-depends: Win32 >=2.13.3.0@@ -77,9 +76,9 @@ type: exitcode-stdio-1.0 default-language: Haskell2010 if os(windows)- build-depends: base, tasty, tasty-hunit, file-io, filepath, temporary, Win32 >=2.13.3.0+ build-depends: base >=4.13.0.0 && <5, tasty, tasty-hunit, file-io, filepath, temporary, Win32 >=2.13.3.0 else- build-depends: base+ build-depends: base >=4.13.0.0 && <5 ghc-options: -Wall -threaded -rtsopts "-with-rtsopts=-N10" test-suite T14@@ -87,7 +86,7 @@ main-is: T14.hs type: exitcode-stdio-1.0 default-language: Haskell2010- build-depends: base, file-io, filepath, temporary+ build-depends: base >=4.13.0.0 && <5, file-io, filepath, temporary ghc-options: -Wall test-suite T8@@ -95,7 +94,7 @@ main-is: T8.hs type: exitcode-stdio-1.0 default-language: Haskell2010- build-depends: base, bytestring, file-io, filepath, temporary+ build-depends: base >=4.13.0.0 && <5, bytestring, file-io, filepath, temporary ghc-options: -Wall -threaded test-suite CLC237@@ -103,7 +102,7 @@ main-is: CLC237.hs type: exitcode-stdio-1.0 default-language: Haskell2010- build-depends: base, file-io, filepath, temporary+ build-depends: base >=4.13.0.0 && <5, file-io, filepath, temporary ghc-options: -Wall test-suite Properties@@ -111,6 +110,6 @@ main-is: Properties.hs type: exitcode-stdio-1.0 default-language: Haskell2010- build-depends: base, bytestring, tasty, tasty-hunit, file-io, filepath, temporary+ build-depends: base >=4.13.0.0 && <5, bytestring, tasty, tasty-hunit, file-io, filepath, temporary ghc-options: -Wall -threaded -rtsopts "-with-rtsopts=-N10"
posix/System/File/Platform.hs view
@@ -1,7 +1,11 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-}+{-# LANGUAGE PackageImports #-} module System.File.Platform where +import Data.Either (fromRight) import Control.Exception (try, onException, SomeException) import GHC.IO.Handle.FD (fdToHandle') import System.IO (IOMode(..), Handle)@@ -10,11 +14,29 @@ ( defaultFileFlags, openFd, closeFd,- OpenFileFlags(noctty, nonBlock, creat, append, trunc, cloexec),+ OpenFileFlags(noctty, nonBlock, creat, append, trunc, cloexec, exclusive), OpenMode(ReadWrite, ReadOnly, WriteOnly) )-import System.OsPath.Posix ( PosixPath )+import System.OsPath.Posix ( PosixPath, PosixString, (</>) ) import qualified System.OsPath.Posix as PS+import Data.IORef (IORef, newIORef)+import System.Posix (CMode)+import System.IO (utf8, latin1)+import System.IO.Unsafe (unsafePerformIO)+import System.Posix.Internals (c_getpid)+import GHC.IORef (atomicModifyIORef'_)+import Foreign.C (getErrno, eEXIST, errnoToIOError) +#if MIN_VERSION_filepath(1, 5, 0)+import "os-string" System.OsString.Internal.Types (PosixString(..), PosixChar(..))+import qualified "os-string" System.OsString.Data.ByteString.Short as BC+#else+import Data.Coerce (coerce)+import "filepath" System.OsString.Internal.Types (PosixString(..), PosixChar(..))+import qualified "filepath" System.OsPath.Data.ByteString.Short as BC+#endif+import System.CPUTime (cpuTimePrecision, getCPUTime)+import Text.Printf (printf)+ -- | Open a file and return the 'Handle'. openFile :: PosixPath -> IOMode -> IO Handle openFile = openFile_ defaultFileFlags'@@ -43,7 +65,7 @@ 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)+ fp' <- fromRight (fmap PS.toChar . PS.unpack $ fp) <$> try @SomeException (PS.decodeFS fp) fdToHandle' fd Nothing False fp' iomode True openFileWithCloseOnExec :: PosixPath -> IOMode -> IO Handle@@ -57,4 +79,56 @@ defaultExistingFileFlags :: OpenFileFlags defaultExistingFileFlags = defaultFileFlags { noctty = True, nonBlock = True, creat = Nothing }++findTempName :: (PosixString, PosixString)+ -> String+ -> PosixPath+ -> CMode+ -> IO (PosixPath, Handle)+findTempName (prefix, suffix) loc tmp_dir mode = go+ where+ go = do+ rs <- rand_string+ let filename = prefix <> rs <> suffix+ filepath = tmp_dir </> filename+ fd <- openTempFile_ filepath mode+ if fd < 0+ then do+ errno <- getErrno+ case errno of+ _ | errno == eEXIST -> go+ _ -> do+ let tmp_dir' = lenientDecode tmp_dir+ ioError (errnoToIOError loc errno Nothing (Just tmp_dir'))+ else fmap (filepath,) $ fdToHandle_ ReadWriteMode filepath fd++ openTempFile_ :: PosixPath -> CMode -> IO Fd+ openTempFile_ fp cmode = openFd fp ReadWrite defaultFileFlags' { creat = Just cmode, nonBlock = True, noctty = True, exclusive = True }++tempCounter :: IORef Int+tempCounter = unsafePerformIO $ newIORef 0+{-# NOINLINE tempCounter #-}++-- build large digit-alike number+rand_string :: IO PosixString+rand_string = do+ r1 <- fromIntegral @_ @Int <$> c_getpid+ (r2, _) <- atomicModifyIORef'_ tempCounter (+1)+ r3 <- (`quot` cpuTimePrecision) <$> getCPUTime+ return $ PS.pack $ fmap (PS.unsafeFromChar) (printf "%x-%x-%x" r1 r2 r3)++lenientDecode :: PosixString -> String+lenientDecode ps = let utf8' = PS.decodeWith utf8 ps+ latin1' = PS.decodeWith latin1 ps+ in case (utf8', latin1') of+ (Right s, ~_) -> s+ (_, Right s) -> s+ (Left _, Left _) -> error "lenientDecode: failed to decode"++#if !MIN_VERSION_filepath(1, 5, 0)++any_ :: (PosixChar -> Bool) -> PosixString -> Bool+any_ = coerce BC.any++#endif
tests/Properties.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-}@@ -10,7 +11,7 @@ import qualified System.FilePath as FP import Test.Tasty import Test.Tasty.HUnit-import System.OsPath ((</>), osp)+import System.OsPath ((</>), osp, OsPath, OsString) import qualified System.OsPath as OSP import qualified System.File.OsPath as OSP import GHC.IO.Exception (IOErrorType(..), IOException(..))@@ -40,6 +41,18 @@ , testCase "openExistingFile yes (Write)" existingFile2' , testCase "openExistingFile yes (Append)" existingFile3' , testCase "openExistingFile yes (ReadWrite)" existingFile4'+ , testCase "openTempFile" (openTempFile2 OSP.openTempFile)+ , testCase "openTempFile (reopen file)" (openTempFile1 OSP.openTempFile)+ , testCase "openTempFile (filepaths different)" (openTempFile3 OSP.openTempFile)+ , testCase "openBinaryTempFile" (openTempFile2 OSP.openBinaryTempFile)+ , testCase "openBinaryTempFile (reopen file)" (openTempFile1 OSP.openBinaryTempFile)+ , testCase "openBinaryTempFile (filepaths different)" (openTempFile3 OSP.openBinaryTempFile)+ , testCase "openTempFileWithDefaultPermissions" (openTempFile2 OSP.openTempFileWithDefaultPermissions)+ , testCase "openTempFileWithDefaultPermissions (reopen file)" (openTempFile1 OSP.openTempFileWithDefaultPermissions)+ , testCase "openTempFileWithDefaultPermissions (filepaths different)" (openTempFile3 OSP.openTempFileWithDefaultPermissions)+ , testCase "openBinaryTempFileWithDefaultPermissions" (openTempFile2 OSP.openBinaryTempFileWithDefaultPermissions)+ , testCase "openBinaryTempFileWithDefaultPermissions (reopen file)" (openTempFile1 OSP.openBinaryTempFileWithDefaultPermissions)+ , testCase "openBinaryTempFileWithDefaultPermissions (filepaths different)" (openTempFile3 OSP.openBinaryTempFileWithDefaultPermissions) ] ] @@ -112,8 +125,9 @@ baseDir <- OSP.encodeFS baseDir' let fp = baseDir </> [osp|foo|] OSP.writeFile fp ""- _ <- OSP.openFile fp ReadMode+ !h <- OSP.openFile fp ReadMode r <- try @IOException $ OSP.withFile fp WriteMode $ \h' -> do BS.hPut h' "test"+ _ <- try @IOException $ BS.hPut h "" IOError Nothing fileLockedType "withFile" fileLockedMsg Nothing (Just $ baseDir' FP.</> "foo") @==? r concFile2 :: Assertion@@ -122,8 +136,9 @@ baseDir <- OSP.encodeFS baseDir' let fp = baseDir </> [osp|foo|] OSP.writeFile fp "h"- _ <- OSP.openFile fp ReadMode+ !h <- OSP.openFile fp ReadMode r <- try @IOException $ OSP.withFile fp ReadMode BS.hGetContents+ _ <- try @IOException $ BS.hPut h "" Right "h" @=? r concFile3 :: Assertion@@ -132,8 +147,9 @@ baseDir <- OSP.encodeFS baseDir' let fp = baseDir </> [osp|foo|] OSP.writeFile fp ""- _ <- OSP.openFile fp WriteMode+ !h <- OSP.openFile fp ReadMode r <- try @IOException $ OSP.withFile fp WriteMode (flip BS.hPut "test")+ _ <- try @IOException $ BS.hPut h "" IOError Nothing fileLockedType "withFile" fileLockedMsg Nothing (Just $ baseDir' FP.</> "foo") @==? r existingFile :: Assertion@@ -209,11 +225,46 @@ OSP.openExistingFile fp ReadWriteMode >>= \h -> do hSetBuffering h NoBuffering BS.hPut h "boo"- c <- BS.hGetSome h 5+ !c <- BS.hGetSome h 5 hSeek h AbsoluteSeek 0- c' <- BS.hGetSome h 5+ !c' <- BS.hGetSome h 5+ hClose h pure (c, c') Right ("tx", "bootx") @=? r++openTempFile1 :: (OsPath -> OsString -> IO (OsPath, Handle)) -> Assertion+openTempFile1 open = do+ withSystemTempDirectory "test" $ \baseDir' -> do+ baseDir <- OSP.encodeFS baseDir'+ let file = [osp|foo.ext|]+ (!fp, h') <- open baseDir file+ hClose h'+ r <- try @IOException $ do+ OSP.openExistingFile fp ReadWriteMode >>= \h -> BS.hPut h "boo" >> hClose h+ OSP.readFile fp+ Right "boo" @=? r++openTempFile2 :: (OsPath -> OsString -> IO (OsPath, Handle)) -> Assertion+openTempFile2 open = do+ withSystemTempDirectory "test" $ \baseDir' -> do+ baseDir <- OSP.encodeFS baseDir'+ let file = [osp|foo.ext|]+ (fp, h) <- open baseDir file+ r <- try @IOException $ do+ BS.hPut h "boo" >> hClose h+ OSP.readFile fp+ Right "boo" @=? r++openTempFile3 :: (OsPath -> OsString -> IO (OsPath, Handle)) -> Assertion+openTempFile3 open = do+ withSystemTempDirectory "test" $ \baseDir' -> do+ baseDir <- OSP.encodeFS baseDir'+ let file = [osp|foo.ext|]+ (!fp, h) <- open baseDir file+ (!fp', h') <- open baseDir file+ hClose h+ hClose h'+ (fp /= fp') @? "Filepaths are different" compareIOError :: forall a . (Eq a, Show a, HasCallStack) => IOException -> Either IOException a -> Assertion
windows/System/File/Platform.hsc view
@@ -1,5 +1,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE TypeApplications #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE PackageImports #-} module System.File.Platform where @@ -9,10 +11,15 @@ import System.OsPath.Windows ( WindowsPath ) import qualified System.OsPath.Windows as WS import Foreign.C.Types-import Foreign.Ptr (ptrToIntPtr) +import qualified System.OsString.Windows as WS hiding (decodeFS)+import System.OsString.Windows ( pstr, WindowsString ) import qualified System.Win32 as Win32 import qualified System.Win32.WindowsString.File as WS+import System.Win32.WindowsString.Types (withTString, peekTString)+#if MIN_VERSION_Win32(2, 14, 0)+import System.Win32.WindowsString.Types (withFilePath)+#endif import Control.Monad (when, void) #if defined(__IO_MANAGER_WINIO__) import GHC.IO.SubSystem@@ -20,7 +27,31 @@ import GHC.IO.Handle.FD (fdToHandle') #include <fcntl.h> #endif+import GHC.IORef (atomicModifyIORef'_)+import Foreign.C (getErrno, errnoToIOError)+import Data.IORef (IORef, newIORef)+import Foreign.C.String+import Foreign.Ptr+import Foreign.Marshal.Alloc+import Foreign.Marshal.Utils (with)+import Foreign.Storable+import System.CPUTime (cpuTimePrecision, getCPUTime)+import System.Posix.Types (CMode)+import System.IO.Unsafe (unsafePerformIO)+import System.Posix.Internals (c_getpid, o_EXCL)+import Text.Printf (printf) +#if MIN_VERSION_filepath(1, 5, 0)+import System.OsString.Encoding+import "os-string" System.OsString.Internal.Types (WindowsString(..), WindowsChar(..))+import qualified "os-string" System.OsString.Data.ByteString.Short as BC+#else+import Data.Coerce (coerce)+import System.OsPath.Encoding+import "filepath" System.OsString.Internal.Types (WindowsString(..), WindowsChar(..))+import qualified "filepath" System.OsPath.Data.ByteString.Short.Word16 as BC+#endif+ -- | Open a file and return the 'Handle'. openFile :: WindowsPath -> IOMode -> IO Handle openFile fp iomode = bracketOnError@@ -40,19 +71,8 @@ #endif Nothing) Win32.closeHandle- toHandle+ (toHandle fp iomode) where-#if defined(__IO_MANAGER_WINIO__)- toHandle h = (`onException` Win32.closeHandle h) $ do- when (iomode == AppendMode ) $ void $ Win32.setFilePointerEx h 0 Win32.fILE_END- Win32.hANDLEToHandle h-#else- toHandle h = (`onException` Win32.closeHandle h) $ do- when (iomode == AppendMode ) $ void $ Win32.setFilePointerEx h 0 Win32.fILE_END- fd <- _open_osfhandle (fromIntegral (ptrToIntPtr h)) (#const _O_BINARY)- fp' <- either (const (fmap WS.toChar . WS.unpack $ fp)) id <$> try @SomeException (WS.decodeFS fp)- fdToHandle' fd Nothing False fp' iomode True-#endif accessMode = case iomode of ReadMode -> Win32.gENERIC_READ WriteMode -> Win32.gENERIC_WRITE@@ -101,11 +121,8 @@ #endif Nothing) Win32.closeHandle- toHandle+ (toHandle fp iomode) where- toHandle h = do- when (iomode == AppendMode ) $ void $ Win32.setFilePointerEx h 0 Win32.fILE_END- Win32.hANDLEToHandle h accessMode = case iomode of ReadMode -> Win32.gENERIC_READ WriteMode -> Win32.gENERIC_WRITE@@ -134,4 +151,81 @@ openExistingFileWithCloseOnExec :: WindowsPath -> IOMode -> IO Handle openExistingFileWithCloseOnExec = openExistingFile++findTempName :: (WindowsString, WindowsString)+ -> String+ -> WindowsPath+ -> CMode+ -> IO (WindowsPath, Handle)+findTempName (prefix, suffix) loc tmp_dir mode = go+ where+ go = do+ let label = if prefix == mempty then [pstr|ghc|] else prefix+#if MIN_VERSION_Win32(2, 14, 0)+ withFilePath tmp_dir $ \c_tmp_dir ->+#else+ withTString tmp_dir $ \c_tmp_dir ->+#endif+ withTString label $ \c_template ->+ withTString suffix $ \c_suffix ->+ with nullPtr $ \c_ptr -> do+ res <- c_createUUIDTempFileErrNo c_tmp_dir c_template c_suffix c_ptr+ if not res+ then do errno <- getErrno+ ioError (errnoToIOError loc errno Nothing (Just $ lenientDecode tmp_dir))+ else do c_p <- peek c_ptr+ filename <- peekTString c_p+ free c_p+ let flags = fromIntegral mode .&. o_EXCL+ handleResultsWinIO filename (flags == o_EXCL)++ handleResultsWinIO filename excl = do+ h <- (if excl then openExistingFile else openFile) filename ReadWriteMode+ return (filename, h)++foreign import ccall "__createUUIDTempFileErrNo" c_createUUIDTempFileErrNo+ :: CWString -> CWString -> CWString -> Ptr CWString -> IO Bool++++tempCounter :: IORef Int+tempCounter = unsafePerformIO $ newIORef 0+{-# NOINLINE tempCounter #-}++-- build large digit-alike number+rand_string :: IO WindowsPath+rand_string = do+ r1 <- fromIntegral @_ @Int <$> c_getpid+ (r2, _) <- atomicModifyIORef'_ tempCounter (+1)+ r3 <- (`quot` cpuTimePrecision) <$> getCPUTime+ return $ WS.pack $ fmap (WS.unsafeFromChar) (printf "%x-%x-%x" r1 r2 r3)++lenientDecode :: WindowsString -> String+lenientDecode ws = let utf16le' = WS.decodeWith utf16le_b ws+ ucs2' = WS.decodeWith ucs2le ws+ in case (utf16le', ucs2') of+ (Right s, ~_) -> s+ (_, Right s) -> s+ (Left _, Left _) -> error "lenientDecode: failed to decode"+++toHandle :: WindowsPath -> IOMode -> Win32.HANDLE -> IO Handle+#if defined(__IO_MANAGER_WINIO__)+toHandle _ iomode h = (`onException` Win32.closeHandle h) $ do+ when (iomode == AppendMode ) $ void $ Win32.setFilePointerEx h 0 Win32.fILE_END+ Win32.hANDLEToHandle h+#else+toHandle fp iomode h = (`onException` Win32.closeHandle h) $ do+ when (iomode == AppendMode ) $ void $ Win32.setFilePointerEx h 0 Win32.fILE_END+ fd <- _open_osfhandle (fromIntegral (ptrToIntPtr h)) (#const _O_BINARY)+ fp' <- either (const (fmap WS.toChar . WS.unpack $ fp)) id <$> try @SomeException (WS.decodeFS fp)+ fdToHandle' fd Nothing False fp' iomode True+#endif++#if !MIN_VERSION_filepath(1, 5, 0)++any_ :: (WindowsChar -> Bool) -> WindowsString -> Bool+any_ = coerce BC.any++#endif