Skip to content

Add replaceFile #200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions System/Directory.hs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
, copyFile
, copyFileWithMetadata
, getFileSize
, replaceFile

Check failure on line 53 in System/Directory.hs

View workflow job for this annotation

GitHub Actions / build (macOS-13, lts-15.3, bytestring-0.11.3.0, file-io-0.1.4, filepath-1.4.100.0, unix-2.8.0.0)

Not in scope: ‘replaceFile’

Check failure on line 53 in System/Directory.hs

View workflow job for this annotation

GitHub Actions / build (macos-latest, lts-22.7, bytestring-0.11.5.3, file-io-0.1.4, filepath-1.5.2.0, os-string-2....

Not in scope: ‘replaceFile’

Check failure on line 53 in System/Directory.hs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 8.10.7, 3.8.1.0)

Not in scope: ‘replaceFile’

Check failure on line 53 in System/Directory.hs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 9.0.2, 3.8.1.0)

Not in scope: ‘replaceFile’

Check failure on line 53 in System/Directory.hs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 9.2.4, 3.8.1.0)

Not in scope: ‘replaceFile’

Check failure on line 53 in System/Directory.hs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 9.4.3, 3.8.1.0)

Not in scope: ‘replaceFile’

Check failure on line 53 in System/Directory.hs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, latest, 3.12.1.0, +os-string, -Werror=deprecations)

Not in scope: ‘replaceFile’

, canonicalizePath
, makeAbsolute
Expand Down
3 changes: 3 additions & 0 deletions System/Directory/Internal/Posix.hsc
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ removePathInternal False = Posix.removeLink . getOsString
renamePathInternal :: OsPath -> OsPath -> IO ()
renamePathInternal (OsString p1) (OsString p2) = Posix.rename p1 p2

replaceFileInternal :: OsPath -> OsPath -> Maybe OsPath -> IO ()
replaceFileInternal (OsString p1) (OsString p2) _ = Posix.rename p1 p2

-- On POSIX, the removability of a file is only affected by the attributes of
-- the containing directory.
filesAlwaysRemovable :: Bool
Expand Down
8 changes: 8 additions & 0 deletions System/Directory/Internal/Windows.hsc
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ renamePathInternal opath npath =
npath' <- furnishPath npath
Win32.moveFileEx opath' (Just npath') Win32.mOVEFILE_REPLACE_EXISTING

replaceFileInternal :: OsPath -> OsPath -> Maybe OsPath -> IO ()
replaceFileInternal replacedFile replacementFile mBackupFile =
(`ioeSetOsPath` replacedFile) `modifyIOError` do
replacedFile' <- furnishPath replacedFile
replacementFile' <- furnishPath replacementFile
mBackupFile' <- fmap furnishPath mBackupFile
Win32.replaceFile replacedFile' replacementFile' mBackupFile' Win32.rEPLACEFILE_IGNORE_MERGE_ERRORS

-- On Windows, the removability of a file may be affected by the attributes of
-- the file itself.
filesAlwaysRemovable :: Bool
Expand Down
64 changes: 64 additions & 0 deletions System/Directory/OsPath.hs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ module System.Directory.OsPath
, copyFile
, copyFileWithMetadata
, getFileSize
, replaceFile
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the convention is to provide the same set of functions both from System.Directory and from System.Directory.OsPath.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


, canonicalizePath
, makeAbsolute
Expand Down Expand Up @@ -709,6 +710,69 @@ renamePath opath npath =
(`ioeAddLocation` "renamePath") `modifyIOError` do
renamePathInternal opath npath

-- | 'replaceFile' replaces one file with another file. The replacement file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to elaborate explicitly how replaceFile is different from renameFile.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more description

Copy link

@noughtmare noughtmare May 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the documentation would help me much more if you add something like: "In constrast to renameFile, replaceFile ... (is atomic?) You should use this when ... For example, consider this program ..."

In other words, the current explanation raises these questions with me:

  • What do renamePath and ReplaceFileW do? (Perhaps this is just meant for people who are familiar with those functions and I should just ignore that sentence.)
  • What can go wrong if I use renameFile? Why is it important that it is atomic?

-- assumes the name of the replaced file and its identity.
--
-- This operation is atomic.
--
-- On the unix same as renamePath, on the Windows platform this is ReplaceFileW.
--
-- The operation on unix may fail with:
--
-- * @HardwareFault@
-- A physical I\/O error has occurred.
-- @[EIO]@
--
-- * @InvalidArgument@
-- Either operand is not a valid file name.
-- @[ENAMETOOLONG, ELOOP]@
--
-- * 'isDoesNotExistError'
-- The original file does not exist, or there is no path to the target.
-- @[ENOENT, ENOTDIR]@
--
-- * 'isPermissionError'
-- The process has insufficient privileges to perform the operation.
-- @[EROFS, EACCES, EPERM]@
--
-- * 'System.IO.isFullError'
-- Insufficient resources are available to perform the operation.
-- @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
--
-- * @UnsatisfiedConstraints@
-- Implementation-dependent constraints are not satisfied.
-- @[EBUSY]@
--
-- * @UnsupportedOperation@
-- The implementation does not support renaming in this situation.
-- @[EXDEV]@
--
-- * @InappropriateType@
-- Either the destination path refers to an existing directory, or one of the
-- parent segments in the destination path is not a directory.
-- @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
--
-- The operation on Windows may fail with:
--
-- ERROR_UNABLE_TO_MOVE_REPLACEMENT 1176 (0x498)
-- The replacement file could not be renamed. The replaced file no longer exists
-- and the replacement file exists under its original name.
--
-- ERROR_UNABLE_TO_MOVE_REPLACEMENT_2 1177 (0x499)
--
-- The replacement file could not be moved. The replacement file still exists
-- under its original name; however, it has inherited the file streams and
-- attributes from the file it is replacing. The file to be replaced still
-- exists with a different name.
--
-- ERROR_UNABLE_TO_REMOVE_REPLACED 1175 (0x497)
-- The replaced file could not be deleted. The replaced and replacement files
-- retain their original file names.
replaceFile :: OsPath -> OsPath -> IO ()
replaceFile opath npath =
(`ioeAddLocation` "replaceFile") `modifyIOError` do
replaceFileInternal opath npath Nothing

-- | Copy a file with its permissions. If the destination file already exists,
-- it is replaced atomically. Neither path may refer to an existing
-- directory. No exceptions are thrown if the permissions could not be
Expand Down
2 changes: 1 addition & 1 deletion directory.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Library
file-io >= 0.1.4 && < 0.2,
time >= 1.8.0 && < 1.15,
if os(windows)
build-depends: Win32 >= 2.14.1.0 && < 2.15
build-depends: Win32 >= 2.14.2.0 && < 2.15
else
build-depends: unix >= 2.8.0 && < 2.9

Expand Down
Loading