packages feed

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 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