use openTempFile from file-io

And follow-on changes.

Note that relatedTemplate was changed to operate on a RawFilePath, and
so when it counts the length, it is now the number of bytes, not the
number of code points. This will just make it truncate shorter strings
in some cases, the truncation is still unicode aware.

When not building with the OsPath flag, toOsPath . fromRawFilePath and
fromRawFilePath . fromOsPath do extra conversions back and forth between
String and ByteString. That overhead could be avoided, but that's the
non-optimised build mode, so didn't bother.

Sponsored-by: unqueued
This commit is contained in:
Joey Hess 2025-01-21 17:00:37 -04:00
parent 1faa3af9cd
commit 793ddecd4b
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
46 changed files with 235 additions and 178 deletions

View file

@ -5,7 +5,7 @@
- License: BSD-2-clause
-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-tabs #-}
module Utility.Tmp (
@ -18,28 +18,31 @@ module Utility.Tmp (
) where
import System.IO
import System.FilePath
import System.Directory
import Control.Monad.IO.Class
import System.IO.Error
import qualified Data.ByteString as B
import qualified System.FilePath.ByteString as P
import Utility.Exception
import Utility.FileSystemEncoding
import Utility.FileMode
import qualified Utility.RawFilePath as R
import qualified Utility.FileIO as F
import Utility.OsPath
type Template = String
type Template = OsString
{- This is the same as openTempFile, except when there is an
- error, it displays the template as well as the directory,
- to help identify what call was responsible.
-}
openTmpFileIn :: FilePath -> String -> IO (FilePath, Handle)
openTmpFileIn dir template = openTempFile dir template
openTmpFileIn :: OsPath -> Template -> IO (OsPath, Handle)
openTmpFileIn dir template = F.openTempFile dir template
`catchIO` decoraterrror
where
decoraterrror e = throwM $
let loc = ioeGetLocation e ++ " template " ++ template
let loc = ioeGetLocation e ++ " template " ++ decodeBS (fromOsPath template)
in annotateIOError e loc Nothing Nothing
{- Runs an action like writeFile, writing to a temp file first and
@ -50,34 +53,36 @@ openTmpFileIn dir template = openTempFile dir template
- mode as it would when using writeFile, unless the writer action changes
- it.
-}
viaTmp :: (MonadMask m, MonadIO m) => (FilePath -> v -> m ()) -> FilePath -> v -> m ()
viaTmp :: (MonadMask m, MonadIO m) => (OsPath -> v -> m ()) -> OsPath -> v -> m ()
viaTmp a file content = bracketIO setup cleanup use
where
(dir, base) = splitFileName file
template = relatedTemplate (base ++ ".tmp")
(dir, base) = P.splitFileName (fromOsPath file)
template = relatedTemplate (base <> ".tmp")
setup = do
createDirectoryIfMissing True dir
openTmpFileIn dir template
createDirectoryIfMissing True (fromRawFilePath dir)
openTmpFileIn (toOsPath dir) template
cleanup (tmpfile, h) = do
_ <- tryIO $ hClose h
tryIO $ removeFile tmpfile
tryIO $ R.removeLink (fromOsPath tmpfile)
use (tmpfile, h) = do
let tmpfile' = toRawFilePath tmpfile
let tmpfile' = fromOsPath tmpfile
-- Make mode the same as if the file were created usually,
-- not as a temp file. (This may fail on some filesystems
-- that don't support file modes well, so ignore
-- exceptions.)
_ <- liftIO $ tryIO $ R.setFileMode tmpfile' =<< defaultFileMode
_ <- liftIO $ tryIO $
R.setFileMode (fromOsPath tmpfile)
=<< defaultFileMode
liftIO $ hClose h
a tmpfile content
liftIO $ R.rename tmpfile' (toRawFilePath file)
liftIO $ R.rename tmpfile' (fromOsPath file)
{- Runs an action with a tmp file located in the system's tmp directory
- (or in "." if there is none) then removes the file. -}
withTmpFile :: (MonadIO m, MonadMask m) => Template -> (FilePath -> Handle -> m a) -> m a
withTmpFile :: (MonadIO m, MonadMask m) => Template -> (OsPath -> Handle -> m a) -> m a
withTmpFile template a = do
tmpdir <- liftIO $ catchDefaultIO "." getTemporaryDirectory
withTmpFileIn tmpdir template a
withTmpFileIn (toOsPath (toRawFilePath tmpdir)) template a
{- Runs an action with a tmp file located in the specified directory,
- then removes the file.
@ -85,13 +90,13 @@ withTmpFile template a = do
- Note that the tmp file will have a file mode that only allows the
- current user to access it.
-}
withTmpFileIn :: (MonadIO m, MonadMask m) => FilePath -> Template -> (FilePath -> Handle -> m a) -> m a
withTmpFileIn :: (MonadIO m, MonadMask m) => OsPath -> Template -> (OsPath -> Handle -> m a) -> m a
withTmpFileIn tmpdir template a = bracket create remove use
where
create = liftIO $ openTmpFileIn tmpdir template
remove (name, h) = liftIO $ do
hClose h
catchBoolIO (removeFile name >> return True)
tryIO $ R.removeLink (fromOsPath name)
use (name, h) = a name h
{- It's not safe to use a FilePath of an existing file as the template
@ -103,14 +108,15 @@ withTmpFileIn tmpdir template a = bracket create remove use
- anyway, which is enough for the current implementation and any
- likely implementation.)
-}
relatedTemplate :: FilePath -> FilePath
relatedTemplate :: RawFilePath -> Template
relatedTemplate f
| len > 20 =
{- Some filesystems like FAT have issues with filenames
- ending in ".", so avoid truncating a filename to end
- that way. -}
reverse $ dropWhile (== '.') $ reverse $
truncateFilePath (len - 20) f
| otherwise = f
toOsPath $ toRawFilePath $
reverse $ dropWhile (== '.') $ reverse $
truncateFilePath (len - 20) (fromRawFilePath f)
| otherwise = toOsPath f
where
len = length f
len = B.length f