summary refs log tree commit diff stats
path: root/lib
diff options
context:
space:
mode:
authorAnatoly Galiulin <galiulin.anatoly@gmail.com>2017-03-24 05:40:03 +0700
committerAndreas Rumpf <rumpf_a@web.de>2017-03-23 23:40:03 +0100
commit434a7c842694469c532287319049e35340bb8494 (patch)
treedbf47d6d5cb9c22aa391a6e975997945d52709ca /lib
parent568c954062c203383be0073126b2e7090721364f (diff)
downloadNim-434a7c842694469c532287319049e35340bb8494.tar.gz
Fix posix version of moveFile between different filesystems (#5580)
Diffstat (limited to 'lib')
-rw-r--r--lib/pure/os.nim66
-rw-r--r--lib/windows/winlean.nim2
2 files changed, 47 insertions, 21 deletions
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index 39cdf6d81..a7f7116ef 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -617,20 +617,6 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
     flushFile(d)
     close(d)
 
-proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
-  tags: [ReadIOEffect, WriteIOEffect].} =
-  ## Moves a file from `source` to `dest`. If this fails, `OSError` is raised.
-  when defined(Windows):
-    when useWinUnicode:
-      let s = newWideCString(source)
-      let d = newWideCString(dest)
-      if moveFileW(s, d) == 0'i32: raiseOSError(osLastError())
-    else:
-      if moveFileA(source, dest) == 0'i32: raiseOSError(osLastError())
-  else:
-    if c_rename(source, dest) != 0'i32:
-      raiseOSError(osLastError(), $strerror(errno))
-
 when not declared(ENOENT) and not defined(Windows):
   when NoFakeVars:
     const ENOENT = cint(2) # 2 on most systems including Solaris
@@ -647,25 +633,63 @@ when defined(Windows):
     template setFileAttributes(file, attrs: untyped): untyped =
       setFileAttributesA(file, attrs)
 
-proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
-  ## Removes the `file`. If this fails, `OSError` is raised. This does not fail
+proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
+  ## Removes the `file`. If this fails, returns `false`. This does not fail
   ## if the file never existed in the first place.
   ## On Windows, ignores the read-only attribute.
+  result = true
   when defined(Windows):
     when useWinUnicode:
       let f = newWideCString(file)
     else:
       let f = file
     if deleteFile(f) == 0:
-      if getLastError() == ERROR_ACCESS_DENIED:
-        if setFileAttributes(f, FILE_ATTRIBUTE_NORMAL) == 0:
-          raiseOSError(osLastError())
-        if deleteFile(f) == 0:
-          raiseOSError(osLastError())
+      result = false
+      let err = getLastError()
+      if err == ERROR_FILE_NOT_FOUND or err == ERROR_PATH_NOT_FOUND:
+        result = true
+      elif err == ERROR_ACCESS_DENIED and
+         setFileAttributes(f, FILE_ATTRIBUTE_NORMAL) != 0 and
+         deleteFile(f) != 0:
+        result = true
   else:
     if c_remove(file) != 0'i32 and errno != ENOENT:
+      result = false
+
+proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
+  ## Removes the `file`. If this fails, `OSError` is raised. This does not fail
+  ## if the file never existed in the first place.
+  ## On Windows, ignores the read-only attribute.
+  if not tryRemoveFile(file):
+    when defined(Windows):
+      raiseOSError(osLastError())
+    else:
       raiseOSError(osLastError(), $strerror(errno))
 
+proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
+  tags: [ReadIOEffect, WriteIOEffect].} =
+  ## Moves a file from `source` to `dest`. If this fails, `OSError` is raised.
+  when defined(Windows):
+    when useWinUnicode:
+      let s = newWideCString(source)
+      let d = newWideCString(dest)
+      if moveFileW(s, d) == 0'i32: raiseOSError(osLastError())
+    else:
+      if moveFileA(source, dest) == 0'i32: raiseOSError(osLastError())
+  else:
+    if c_rename(source, dest) != 0'i32:
+      let err = osLastError()
+      if err == EXDEV.OSErrorCode:
+        # Fallback to copy & del
+        copyFile(source, dest)
+        try:
+          removeFile(source)
+        except:
+          discard tryRemoveFile(dest)
+          raise
+      else:
+        raiseOSError(err, $strerror(errno))
+
 proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
   tags: [ExecIOEffect].} =
   ## Executes a `shell command`:idx:.
diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim
index 1a251d0cc..1f8dc9ad6 100644
--- a/lib/windows/winlean.nim
+++ b/lib/windows/winlean.nim
@@ -667,6 +667,8 @@ const
 
 # Error Constants
 const
+  ERROR_FILE_NOT_FOUND* = 2
+  ERROR_PATH_NOT_FOUND* = 3
   ERROR_ACCESS_DENIED* = 5
   ERROR_HANDLE_EOF* = 38
   ERROR_BAD_ARGUMENTS* = 165