#18216 make moveDir work across partitions on windows (#18223)

* return false if AccessDeniedError in tryMoveFSObject - fixes #18216

* add moveDir & moveFile tests

* rename `isMoveDir` parameter to `isDir`
This commit is contained in:
Fröhlich A 2021-06-10 14:28:00 +02:00 • committed by GitHub
commit 7bf0404dd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 70 additions and 19 deletions

View file

@ -2009,28 +2009,32 @@ proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect], n
if not tryRemoveFile(file): if not tryRemoveFile(file):
raiseOSError(osLastError(), file) raiseOSError(osLastError(), file)
proc tryMoveFSObject(source, dest: string): bool {.noWeirdTarget.} = proc tryMoveFSObject(source, dest: string, isDir: bool): bool {.noWeirdTarget.} =
## Moves a file or directory from `source` to `dest`. ## Moves a file (or directory if `isDir` is true) from `source` to `dest`.
## ##
## Returns false in case of `EXDEV` error. ## Returns false in case of `EXDEV` error or `AccessDeniedError` on windows (if `isDir` is true).
## In case of other errors `OSError` is raised. ## In case of other errors `OSError` is raised.
## Returns true in case of success. ## Returns true in case of success.
when defined(windows): when defined(windows):
when useWinUnicode: when useWinUnicode:
let s = newWideCString(source) let s = newWideCString(source)
let d = newWideCString(dest) let d = newWideCString(dest)
if moveFileExW(s, d, MOVEFILE_COPY_ALLOWED or MOVEFILE_REPLACE_EXISTING) == 0'i32: raiseOSError(osLastError(), $(source, dest)) result = moveFileExW(s, d, MOVEFILE_COPY_ALLOWED or MOVEFILE_REPLACE_EXISTING) != 0'i32
else: else:
if moveFileExA(source, dest, MOVEFILE_COPY_ALLOWED or MOVEFILE_REPLACE_EXISTING) == 0'i32: raiseOSError(osLastError(), $(source, dest)) result = moveFileExA(source, dest, MOVEFILE_COPY_ALLOWED or MOVEFILE_REPLACE_EXISTING) != 0'i32
else: else:
if c_rename(source, dest) != 0'i32: result = c_rename(source, dest) == 0'i32
let err = osLastError()
if err == EXDEV.OSErrorCode: if not result:
return false let err = osLastError()
let isAccessDeniedError =
when defined(windows):
const AccessDeniedError = OSErrorCode(5)
isDir and err == AccessDeniedError
else: else:
# see whether `strerror(errno)` is redundant with what raiseOSError already shows err == EXDEV.OSErrorCode
raiseOSError(err, $(source, dest, strerror(errno))) if not isAccessDeniedError:
return true raiseOSError(err, $(source, dest))
proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
tags: [ReadDirEffect, ReadIOEffect, WriteIOEffect], noWeirdTarget.} = tags: [ReadDirEffect, ReadIOEffect, WriteIOEffect], noWeirdTarget.} =
@ -2051,8 +2055,10 @@ proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
## * `removeFile proc <#removeFile,string>`_ ## * `removeFile proc <#removeFile,string>`_
## * `tryRemoveFile proc <#tryRemoveFile,string>`_ ## * `tryRemoveFile proc <#tryRemoveFile,string>`_
if not tryMoveFSObject(source, dest): if not tryMoveFSObject(source, dest, isDir = false):
when not defined(windows): when defined(windows):
doAssert false
else:
# Fallback to copy & del # Fallback to copy & del
copyFile(source, dest, {cfSymlinkAsIs}) copyFile(source, dest, {cfSymlinkAsIs})
try: try:
@ -2061,6 +2067,7 @@ proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
discard tryRemoveFile(dest) discard tryRemoveFile(dest)
raise raise
proc exitStatusLikeShell*(status: cint): cint = proc exitStatusLikeShell*(status: cint): cint =
## Converts exit code from `c_system` into a shell exit code. ## Converts exit code from `c_system` into a shell exit code.
when defined(posix) and not weirdTarget: when defined(posix) and not weirdTarget:
@ -2614,11 +2621,10 @@ proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect], noWei
## * `removeDir proc <#removeDir,string>`_ ## * `removeDir proc <#removeDir,string>`_
## * `existsOrCreateDir proc <#existsOrCreateDir,string>`_ ## * `existsOrCreateDir proc <#existsOrCreateDir,string>`_
## * `createDir proc <#createDir,string>`_ ## * `createDir proc <#createDir,string>`_
if not tryMoveFSObject(source, dest): if not tryMoveFSObject(source, dest, isDir = true):
when not defined(windows): # Fallback to copy & del
# Fallback to copy & del copyDir(source, dest)
copyDir(source, dest) removeDir(source)
removeDir(source)
proc createHardlink*(src, dest: string) {.noWeirdTarget.} = proc createHardlink*(src, dest: string) {.noWeirdTarget.} =
## Create a hard link at `dest` which points to the item specified ## Create a hard link at `dest` which points to the item specified

View file

@ -261,6 +261,51 @@ block fileOperations:
removeDir(dname) removeDir(dname)
block: # moveFile
let tempDir = getTempDir() / "D20210609T151608"
createDir(tempDir)
defer: removeDir(tempDir)
writeFile(tempDir / "a.txt", "")
moveFile(tempDir / "a.txt", tempDir / "b.txt")
doAssert not fileExists(tempDir / "a.txt")
doAssert fileExists(tempDir / "b.txt")
removeFile(tempDir / "b.txt")
createDir(tempDir / "moveFile_test")
writeFile(tempDir / "moveFile_test/a.txt", "")
moveFile(tempDir / "moveFile_test/a.txt", tempDir / "moveFile_test/b.txt")
doAssert not fileExists(tempDir / "moveFile_test/a.txt")
doAssert fileExists(tempDir / "moveFile_test/b.txt")
removeDir(tempDir / "moveFile_test")
createDir(tempDir / "moveFile_test")
writeFile(tempDir / "a.txt", "")
moveFile(tempDir / "a.txt", tempDir / "moveFile_test/b.txt")
doAssert not fileExists(tempDir / "a.txt")
doAssert fileExists(tempDir / "moveFile_test/b.txt")
removeDir(tempDir / "moveFile_test")
block: # moveDir
let tempDir = getTempDir() / "D20210609T161443"
createDir(tempDir)
defer: removeDir(tempDir)
createDir(tempDir / "moveDir_test")
moveDir(tempDir / "moveDir_test/", tempDir / "moveDir_test_dest")
doAssert not dirExists(tempDir / "moveDir_test")
doAssert dirExists(tempDir / "moveDir_test_dest")
removeDir(tempDir / "moveDir_test_dest")
createDir(tempDir / "moveDir_test")
writeFile(tempDir / "moveDir_test/a.txt", "")
moveDir(tempDir / "moveDir_test", tempDir / "moveDir_test_dest")
doAssert not dirExists(tempDir / "moveDir_test")
doAssert not fileExists(tempDir / "moveDir_test/a.txt")
doAssert dirExists(tempDir / "moveDir_test_dest")
doAssert fileExists(tempDir / "moveDir_test_dest/a.txt")
removeDir(tempDir / "moveDir_test_dest")
import times import times
block modificationTime: block modificationTime:
# Test get/set modification times # Test get/set modification times