summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md4
-rw-r--r--lib/pure/os.nim31
-rw-r--r--tests/stdlib/tos.nim23
3 files changed, 49 insertions, 9 deletions
diff --git a/changelog.md b/changelog.md
index 22ae9c359..5f5222e59 100644
--- a/changelog.md
+++ b/changelog.md
@@ -121,6 +121,10 @@ with other backends. see #9125. Use `-d:nimLegacyJsRound` for previous behavior.
 
 - `typetraits.distinctBase` now is identity instead of error for non distinct types.
 
+- `os.copyFile` is now 2.5x faster on OSX, by using `copyfile` from `copyfile.h`;
+  use `-d:nimLegacyCopyFile` for OSX < 10.5.
+
+
 ## Compiler changes
 
 - Added `--declaredlocs` to show symbol declaration location in messages.
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index 002085d97..d642e5242 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -1634,6 +1634,24 @@ proc setFilePermissions*(filename: string, permissions: set[FilePermission]) {.
       var res2 = setFileAttributesA(filename, res)
     if res2 == - 1'i32: raiseOSError(osLastError(), $(filename, permissions))
 
+const hasCCopyfile = defined(osx) and not defined(nimLegacyCopyFile)
+  # xxx instead of `nimLegacyCopyFile`, support something like: `when osxVersion >= (10, 5)`
+
+when hasCCopyfile:
+  # `copyfile` API available since osx 10.5.
+  {.push nodecl, header: "<copyfile.h>".}
+  type
+    copyfile_state_t {.nodecl.} = pointer
+    copyfile_flags_t = cint
+  proc copyfile_state_alloc(): copyfile_state_t
+  proc copyfile_state_free(state: copyfile_state_t): cint
+  proc c_copyfile(src, dst: cstring,  state: copyfile_state_t, flags: copyfile_flags_t): cint {.importc: "copyfile".}
+  # replace with `let` pending bootstrap >= 1.4.0
+  var
+    COPYFILE_DATA {.nodecl.}: copyfile_flags_t
+    COPYFILE_XATTR {.nodecl.}: copyfile_flags_t
+  {.pop.}
+
 proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
   tags: [ReadIOEffect, WriteIOEffect], noWeirdTarget.} =
   ## Copies a file from `source` to `dest`, where `dest.parentDir` must exist.
@@ -1653,6 +1671,9 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
   ##
   ## If `dest` already exists, the file attributes
   ## will be preserved and the content overwritten.
+  ## 
+  ## On OSX, `copyfile` C api will be used (available since OSX 10.5) unless
+  ## `-d:nimLegacyCopyFile` is used.
   ##
   ## See also:
   ## * `copyDir proc <#copyDir,string,string>`_
@@ -1668,6 +1689,16 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
       if copyFileW(s, d, 0'i32) == 0'i32: raiseOSError(osLastError(), $(source, dest))
     else:
       if copyFileA(source, dest, 0'i32) == 0'i32: raiseOSError(osLastError(), $(source, dest))
+  elif hasCCopyfile:
+    let state = copyfile_state_alloc()
+    # xxx `COPYFILE_STAT` could be used for one-shot `copyFileWithPermissions`.
+    let status = c_copyfile(source.cstring, dest.cstring, state, COPYFILE_DATA)
+    if status != 0:
+      let err = osLastError()
+      discard copyfile_state_free(state)
+      raiseOSError(err, $(source, dest))
+    let status2 = copyfile_state_free(state)
+    if status2 != 0: raiseOSError(osLastError(), $(source, dest))
   else:
     # generic version of copyFile which works for any platform:
     const bufSize = 8000 # better for memory manager
diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim
index c053c16f2..af3606a4a 100644
--- a/tests/stdlib/tos.nim
+++ b/tests/stdlib/tos.nim
@@ -40,20 +40,25 @@ block fileOperations:
     doAssertRaises(OSError): copyFile(dname/"nonexistant.txt", dname/"nonexistant.txt")
     let fname = "D20201009T112235"
     let fname2 = "D20201009T112235.2"
-    writeFile(dname/fname, "foo")
+    let str = "foo1\0foo2\nfoo3\0"
+    let file = dname/fname
+    let file2 = dname/fname2
+    writeFile(file, str)
+    doAssert readFile(file) == str
     let sub = "sub"
-    doAssertRaises(OSError): copyFile(dname/fname, dname/sub/fname2)
-    doAssertRaises(OSError): copyFileToDir(dname/fname, dname/sub)
-    doAssertRaises(ValueError): copyFileToDir(dname/fname, "")
-    copyFile(dname/fname, dname/fname2)
-    doAssert fileExists(dname/fname2)
+    doAssertRaises(OSError): copyFile(file, dname/sub/fname2)
+    doAssertRaises(OSError): copyFileToDir(file, dname/sub)
+    doAssertRaises(ValueError): copyFileToDir(file, "")
+    copyFile(file, file2)
+    doAssert fileExists(file2)
+    doAssert readFile(file2) == str
     createDir(dname/sub)
-    copyFileToDir(dname/fname, dname/sub)
+    copyFileToDir(file, dname/sub)
     doAssert fileExists(dname/sub/fname)
     removeDir(dname/sub)
     doAssert not dirExists(dname/sub)
-    removeFile(dname/fname)
-    removeFile(dname/fname2)
+    removeFile(file)
+    removeFile(file2)
 
   # Test creating files and dirs
   for dir in dirs: