The third parameter to
copyfile is a copy state ‘object’ (
copyfile_state_t). To implement
COPY_QUIT, you’ll need to allocate one of these (
copyfile_state_alloc), configure it (
copyfile_state_set), pass it in to the
copyfile call, and then deallocate it when you’re done (
copyfile_state_free).
Once you have a copy state object, the specific state you need to set is
COPYFILE_STATE_STATUS_CB. This takes a C function pointer (of type
copyfile_callback_t) as a parameter.
Getting this all right is going to be tricky. It would be tricky even from a C-based language, and Swift doesn’t make it any easier.
The first thing I’d do is wrap the
copyfile function so that it takes
URL parameters directly:
Code Block private func copyfileQ(_ from: URL, _ to: URL, _ state: copyfile_state_t?, _ flags: copyfile_flags_t) -> Int32 { |
return from.withUnsafeFileSystemRepresentation { fromPtr -> Int32 in |
to.withUnsafeFileSystemRepresentation { toPtr -> Int32 in |
copyfile(fromPtr, toPtr, state, flags) |
} |
} |
} |
You can then call it like so:
Code Block let success = copyfileQ(self.sourceURL, self.destinationURL, nil, copyfile_flags_t(COPYFILE_DATA)) >= 0 |
guard success else { |
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil) |
} |
Now you have to deal with the copy state object. You can allocate this like so:
Code Block let state = copyfile_state_alloc() |
defer { |
copyfile_state_free(state) |
} |
Note that this uses a
defer statement to guarantee that it gets deallocated. Then pass it in to
copyfile like so:
Code Block let success = copyfileQ(self.sourceURL, self.destinationURL, state, copyfile_flags_t(COPYFILE_DATA)) >= 0 |
Now you need to set up the callback. This is where things get tricky. The problem is that the type of the third parameter to
copyfile_state_set depends on the selector you pass it. For example:
For COPYFILE_STATE_STATUS_CB, it is a C function pointer of type copyfile_callback_t.
For COPYFILE_STATE_STATUS_CTX, it is a C void * parameter.
These two cases have to be handled differently. For
COPYFILE_STATE_STATUS_CTX, the desired parameter is an object (
self) and thus you can use the standard
Unmanaged tricks.
Code Block let context = Unmanaged.passUnretained(self) |
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CTX), context.toOpaque()) |
For
COPYFILE_STATE_STATUS_CB, the desired parameter is a C function pointer, and that requires an ugly
unsafeBitCast(…).
Code Block let callback: copyfile_callback_t = { what, stage, state, src, dst, context in |
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB), unsafeBitCast(callback, to: UnsafeRawPointer.self)) |
Putting this all together, you’ll find pasted in below the source for a command-line tool that copies from a source to a destination. If the source looks like this:
Code Block $ find /Users/quinn/Test/source |
/Users/quinn/Test/source |
/Users/quinn/Test/source/.DS_Store |
/Users/quinn/Test/source/f1.txt |
/Users/quinn/Test/source/nested |
/Users/quinn/Test/source/nested/.DS_Store |
/Users/quinn/Test/source/nested/f2.txt |
it prints:
Code Block what: 2 |
stage: 1 |
state: 0x000000010183e680 |
src: /Users/quinn/Test/source |
dst: /Users/quinn/Test/destination/source |
|
what: 2 |
stage: 2 |
state: 0x000000010183e680 |
src: /Users/quinn/Test/source |
dst: /Users/quinn/Test/destination/source |
|
what: 1 |
stage: 1 |
state: 0x000000010050aa30 |
src: /Users/quinn/Test/source/.DS_Store |
dst: /Users/quinn/Test/destination/source/.DS_Store |
|
what: 4 |
stage: 4 |
state: 0x000000010050aa30 |
src: /Users/quinn/Test/source/.DS_Store |
dst: /Users/quinn/Test/destination/source/.DS_Store |
|
what: 1 |
stage: 2 |
state: 0x000000010050aa30 |
src: /Users/quinn/Test/source/.DS_Store |
dst: /Users/quinn/Test/destination/source/.DS_Store |
|
what: 1 |
stage: 1 |
state: 0x00000001006186e0 |
src: /Users/quinn/Test/source/f1.txt |
dst: /Users/quinn/Test/destination/source/f1.txt |
|
what: 4 |
stage: 4 |
state: 0x00000001006186e0 |
src: /Users/quinn/Test/source/f1.txt |
dst: /Users/quinn/Test/destination/source/f1.txt |
|
what: 1 |
stage: 2 |
state: 0x00000001006186e0 |
src: /Users/quinn/Test/source/f1.txt |
dst: /Users/quinn/Test/destination/source/f1.txt |
|
what: 2 |
stage: 1 |
state: 0x000000010183e680 |
src: /Users/quinn/Test/source/nested |
dst: /Users/quinn/Test/destination/source/nested |
|
what: 2 |
stage: 2 |
state: 0x000000010183e680 |
src: /Users/quinn/Test/source/nested |
dst: /Users/quinn/Test/destination/source/nested |
|
what: 1 |
stage: 1 |
state: 0x0000000101839d00 |
src: /Users/quinn/Test/source/nested/.DS_Store |
dst: /Users/quinn/Test/destination/source/nested/.DS_Store |
|
what: 4 |
stage: 4 |
state: 0x0000000101839d00 |
src: /Users/quinn/Test/source/nested/.DS_Store |
dst: /Users/quinn/Test/destination/source/nested/.DS_Store |
|
what: 1 |
stage: 2 |
state: 0x0000000101839d00 |
src: /Users/quinn/Test/source/nested/.DS_Store |
dst: /Users/quinn/Test/destination/source/nested/.DS_Store |
|
what: 1 |
stage: 1 |
state: 0x0000000101900540 |
src: /Users/quinn/Test/source/nested/f2.txt |
dst: /Users/quinn/Test/destination/source/nested/f2.txt |
|
what: 4 |
stage: 4 |
state: 0x0000000101900540 |
src: /Users/quinn/Test/source/nested/f2.txt |
dst: /Users/quinn/Test/destination/source/nested/f2.txt |
|
what: 1 |
stage: 2 |
state: 0x0000000101900540 |
src: /Users/quinn/Test/source/nested/f2.txt |
dst: /Users/quinn/Test/destination/source/nested/f2.txt |
|
what: 3 |
stage: 1 |
state: 0x0000000100736830 |
src: /Users/quinn/Test/source/nested |
dst: /Users/quinn/Test/destination/source/nested |
|
what: 3 |
stage: 2 |
state: 0x0000000100736830 |
src: /Users/quinn/Test/source/nested |
dst: /Users/quinn/Test/destination/source/nested |
|
what: 3 |
stage: 1 |
state: 0x0000000100736830 |
src: /Users/quinn/Test/source |
dst: /Users/quinn/Test/destination/source |
|
what: 3 |
stage: 2 |
state: 0x0000000100736830 |
src: /Users/quinn/Test/source |
dst: /Users/quinn/Test/destination/source |
|
callbackCount: 20 |
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Code Block import Foundation |
|
private func copyfileQ(_ from: URL, _ to: URL, _ state: copyfile_state_t?, _ flags: copyfile_flags_t) -> Int32 { |
return from.withUnsafeFileSystemRepresentation { fromPtr -> Int32 in |
to.withUnsafeFileSystemRepresentation { toPtr -> Int32 in |
copyfile(fromPtr, toPtr, state, flags) |
} |
} |
} |
|
class Copier { |
|
init(from sourceURL: URL, to destinationURL: URL) { |
self.sourceURL = sourceURL |
self.destinationURL = destinationURL |
} |
|
let sourceURL: URL |
let destinationURL: URL |
|
func run() throws { |
|
// Allocate our state, arranging to deallocate it when we return. |
|
let state = copyfile_state_alloc() |
defer { |
copyfile_state_free(state) |
} |
|
// Create a C function point that calls our `callback(…)` method. Note |
// that we can’t use `self` here because this is a C function pointer. |
// Rather, we have to recover `self` (here called `obj`) from the |
// `context` parameter. |
|
let callback: copyfile_callback_t = { what, stage, state, src, dst, context in |
let obj = Unmanaged<Copier>.fromOpaque(context!).takeUnretainedValue() |
return obj.callback(what: what, stage: stage, state: state!, src: src, dst: dst) |
} |
|
// Apply that to the state. Note the use of `unsafeBitCast(…)` here. |
// This is the best of a bad set of options for converting a C function |
// pointer to a C `void *`. |
|
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB), unsafeBitCast(callback, to: UnsafeRawPointer.self)) |
|
// We pass an unretained reference because the reference only needs to |
// persist as long as `state`, and we deallocate `state` before we return |
// from this function. |
|
let context = Unmanaged.passUnretained(self) |
copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CTX), context.toOpaque()) |
|
// We’re all set up, so let’s call `copyfile`. |
|
let success = copyfileQ(self.sourceURL, self.destinationURL, state, copyfile_flags_t(COPYFILE_DATA | COPYFILE_RECURSIVE)) >= 0 |
guard success else { |
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil) |
} |
print("callbackCount: \(self.callbackCount)") |
} |
|
var callbackCount = 0 |
|
private func callback(what: Int32, stage: Int32, state: copyfile_state_t, src: UnsafePointer<CChar>?, dst: UnsafePointer<CChar>?) -> Int32 { |
print("what: \(what)") |
print("stage: \(stage)") |
print("state: \(state)") |
print("src: \(src.flatMap { String(cString: $0) } ?? "nil")") |
print("dst: \(dst.flatMap { String(cString: $0) } ?? "nil")") |
print() |
|
// Increment the callback count to prove that `self` is functioning. |
|
self.callbackCount += 1 |
|
return COPYFILE_CONTINUE |
} |
} |
|
func main() { |
let src = URL(fileURLWithPath: "/Users/quinn/Test/source") |
let dst = URL(fileURLWithPath: "/Users/quinn/Test/destination") |
let copier = Copier(from: src, to: dst) |
try! copier.run() |
} |
|
main() |
exit(EXIT_SUCCESS) |