1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#if canImport(Darwin)
import Darwin
#elseif os(Android)
import Android
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#elseif os(Windows)
import CRT
import WinSDK
#elseif os(WASI)
import WASILibc
#endif
#if FOUNDATION_FRAMEWORK
internal import _ForSwiftFoundation
internal import Foundation_Private.NSFileManager
#if os(macOS)
internal import QuarantinePrivate
#endif
#endif
internal import _FoundationCShims
extension CocoaError {
fileprivate static func fileOperationError(_ code: CocoaError.Code, _ sourcePath: String, _ destinationPath: String? = nil, variant: String? = nil) -> CocoaError {
var info: [String : AnyHashable] = [NSSourceFilePathErrorKey:sourcePath]
if let destinationPath {
info[NSDestinationFilePathErrorKey] = destinationPath
}
return CocoaError.errorWithFilePath(code, sourcePath, variant: variant, userInfo: info)
}
#if os(Windows)
private static func fileOperationError(_ dwError: DWORD, _ suspectedErroneousPath: String, sourcePath: String? = nil, destinationPath: String? = nil, variant: String? = nil) -> CocoaError {
var path = suspectedErroneousPath
if let sourcePath, let destinationPath, dwError == ERROR_BUFFER_OVERFLOW {
let lastLength = destinationPath.lastPathComponent.withFileSystemRepresentation {
strlen($0!)
}
let fullLength = destinationPath.withFileSystemRepresentation {
strlen($0!)
}
path = lastLength > MAX_PATH || fullLength > MAX_PATH ? destinationPath : sourcePath
}
var info: [String : AnyHashable] = [:]
if let sourcePath {
info[NSSourceFilePathErrorKey] = sourcePath
}
if let destinationPath {
info[NSDestinationFilePathErrorKey] = destinationPath
}
return CocoaError.errorWithFilePath(path, win32: dwError, reading: false, variant: variant, userInfo: info)
}
fileprivate static func removeFileError(_ dwError: DWORD, _ path: String) -> CocoaError {
var err = CocoaError.fileOperationError(dwError, path, variant: "Remove")
if dwError == ERROR_DIR_NOT_EMPTY {
err = CocoaError(.fileWriteNoPermission, userInfo: err.userInfo)
}
return err
}
fileprivate static func moveFileError(_ error: DWORD, _ src: URL, _ dst: URL) -> CocoaError {
CocoaError.fileOperationError(error, src.path, sourcePath: src.path, destinationPath: dst.path, variant: "Move")
}
fileprivate static func linkFileError(_ error: DWORD, _ srcPath: String, _ dstPath: String) -> CocoaError {
CocoaError.fileOperationError(error, srcPath, sourcePath: srcPath, destinationPath: dstPath, variant: "Link")
}
fileprivate static func copyFileError(_ error: DWORD, _ srcPath: String, _ dstPath: String) -> CocoaError {
CocoaError.fileOperationError(error, srcPath, sourcePath: srcPath, destinationPath: dstPath, variant: "Copy")
}
#else
private static func fileOperationError(_ errNum: Int32, _ suspectedErroneousPath: String, sourcePath: String? = nil, destinationPath: String? = nil, variant: String? = nil) -> CocoaError {
// Try to be a little bit more intelligent about which path should be reported in the error. In the case of ENAMETOOLONG, we can more accurately guess which path is causing the error without racily checking the file system after the fact. This may not be perfect in the face of operations which span file systems, or on file systems that only support names/paths less than NAME_MAX or PATH_MAX, but it's better than nothing.
var erroneousPath = suspectedErroneousPath
if let sourcePath, let destinationPath, errNum == ENAMETOOLONG {
let lastLength = destinationPath.lastPathComponent.withFileSystemRepresentation { fsRep in
guard let fsRep else { return 0 }
return strnlen(fsRep, Int(NAME_MAX) + 1)
}
let fullLength = destinationPath.withFileSystemRepresentation { fsRep in
guard let fsRep else { return 0 }
return strnlen(fsRep, Int(PATH_MAX) + 1)
}
if lastLength > NAME_MAX || fullLength > PATH_MAX {
erroneousPath = destinationPath
} else {
erroneousPath = sourcePath
}
}
var userInfo: [String : AnyHashable] = [:]
if let sourcePath {
userInfo[NSSourceFilePathErrorKey] = sourcePath
}
if let destinationPath {
userInfo[NSDestinationFilePathErrorKey] = destinationPath
}
return CocoaError.errorWithFilePath(erroneousPath, errno: errNum, reading: false, variant: variant, additionalUserInfo: userInfo)
}
fileprivate static func removeFileError(_ errNum: Int32, _ path: String) -> CocoaError {
var err = CocoaError.fileOperationError(errNum, path, variant: "Remove")
if errNum == ENOTEMPTY {
err = CocoaError(.fileWriteNoPermission, userInfo: err.userInfo)
}
return err
}
fileprivate static func moveFileError(_ errNum: Int32, _ src: URL, _ dst: URL) -> CocoaError {
CocoaError.fileOperationError(errNum, src.path, sourcePath: src.path, destinationPath: dst.path, variant: "Move")
}
fileprivate static func linkFileError(_ errNum: Int32, _ srcPath: String, _ dstPath: String) -> CocoaError {
CocoaError.fileOperationError(errNum, srcPath, sourcePath: srcPath, destinationPath: dstPath, variant: "Link")
}
fileprivate static func copyFileError(_ errNum: Int32, _ srcPath: String, _ dstPath: String) -> CocoaError {
CocoaError.fileOperationError(errNum, srcPath, sourcePath: srcPath, destinationPath: dstPath, variant: "Copy")
}
#endif
}
extension FileManager {
fileprivate func _shouldProceedAfter(error: Error, removingItemAtPath path: String) -> Bool {
var delegateResponse: Bool?
if let delegate = self.safeDelegate {
#if FOUNDATION_FRAMEWORK
delegateResponse = delegate.fileManager?(self, shouldProceedAfterError: error, removingItemAt: URL(fileURLWithPath: path))
if delegateResponse == nil {
delegateResponse = delegate.fileManager?(self, shouldProceedAfterError: error, removingItemAtPath: path)
}
#else
delegateResponse = delegate.fileManager(self, shouldProceedAfterError: error, removingItemAt: URL(fileURLWithPath: path))
#endif
}
return delegateResponse ?? false
}
fileprivate func _shouldRemoveItemAtPath(_ path: String) -> Bool {
var delegateResponse: Bool?
if let delegate = self.safeDelegate {
#if FOUNDATION_FRAMEWORK
delegateResponse = delegate.fileManager?(self, shouldRemoveItemAt: URL(fileURLWithPath: path))
if delegateResponse == nil {
delegateResponse = delegate.fileManager?(self, shouldRemoveItemAtPath: path)
}
#else
delegateResponse = delegate.fileManager(self, shouldRemoveItemAt: URL(fileURLWithPath: path))
#endif
}
return delegateResponse ?? true
}
fileprivate func _shouldProceedAfter(error: Error, copyingItemAtPath path: String, to dst: String) -> Bool {
var delegateResponse: Bool?
if let delegate = self.safeDelegate {
#if FOUNDATION_FRAMEWORK
delegateResponse = delegate.fileManager?(self, shouldProceedAfterError: error, copyingItemAt: URL(fileURLWithPath: path), to: URL(fileURLWithPath: dst))
if delegateResponse == nil {
delegateResponse = delegate.fileManager?(self, shouldProceedAfterError: error, copyingItemAtPath: path, toPath: dst)
}
#else
delegateResponse = delegate.fileManager(self, shouldProceedAfterError: error, copyingItemAt: URL(fileURLWithPath: path), to: URL(fileURLWithPath: dst))
#endif
}
return delegateResponse ?? false
}
fileprivate func _shouldCopyItemAtPath(_ path: String, to dst: String) -> Bool {
var delegateResponse: Bool?
if let delegate = self.safeDelegate {
#if FOUNDATION_FRAMEWORK
delegateResponse = delegate.fileManager?(self, shouldCopyItemAt: URL(fileURLWithPath: path), to: URL(fileURLWithPath: dst))
if delegateResponse == nil {
delegateResponse = delegate.fileManager?(self, shouldCopyItemAtPath: path, toPath: dst)
}
#else
delegateResponse = delegate.fileManager(self, shouldCopyItemAt: URL(fileURLWithPath: path), to: URL(fileURLWithPath: dst))
#endif
}
return delegateResponse ?? true
}
fileprivate func _shouldProceedAfter(error: Error, linkingItemAtPath path: String, to dst: String) -> Bool {
var delegateResponse: Bool?
if let delegate = self.safeDelegate {
#if FOUNDATION_FRAMEWORK
delegateResponse = delegate.fileManager?(self, shouldProceedAfterError: error, linkingItemAt: URL(fileURLWithPath: path), to: URL(fileURLWithPath: dst))
if delegateResponse == nil {
delegateResponse = delegate.fileManager?(self, shouldProceedAfterError: error, linkingItemAtPath: path, toPath: dst)
}
#else
delegateResponse = delegate.fileManager(self, shouldProceedAfterError: error, linkingItemAt: URL(fileURLWithPath: path), to: URL(fileURLWithPath: dst))
#endif
}
return delegateResponse ?? false
}
fileprivate func _shouldLinkItemAtPath(_ path: String, to dst: String) -> Bool {
var delegateResponse: Bool?
if let delegate = self.safeDelegate {
#if FOUNDATION_FRAMEWORK
delegateResponse = delegate.fileManager?(self, shouldLinkItemAt: URL(fileURLWithPath: path), to: URL(fileURLWithPath: dst))
if delegateResponse == nil {
delegateResponse = delegate.fileManager?(self, shouldLinkItemAtPath: path, toPath: dst)
}
#else
delegateResponse = delegate.fileManager(self, shouldLinkItemAt: URL(fileURLWithPath: path), to: URL(fileURLWithPath: dst))
#endif
}
return delegateResponse ?? true
}
fileprivate func _shouldProceedAfter(error: Error, movingItemAtPath path: String, to dst: String) -> Bool {
var delegateResponse: Bool?
if let delegate = self.safeDelegate {
#if FOUNDATION_FRAMEWORK
delegateResponse = delegate.fileManager?(self, shouldProceedAfterError: error, movingItemAt: URL(fileURLWithPath: path), to: URL(fileURLWithPath: dst))
if delegateResponse == nil {
delegateResponse = delegate.fileManager?(self, shouldProceedAfterError: error, movingItemAtPath: path, toPath: dst)
}
#else
delegateResponse = delegate.fileManager(self, shouldProceedAfterError: error, movingItemAt: URL(fileURLWithPath: path), to: URL(fileURLWithPath: dst))
#endif
}
return delegateResponse ?? false
}
fileprivate func _shouldMoveItemAtPath(_ path: String, to dst: String) -> Bool {
var delegateResponse: Bool?
if let delegate = self.safeDelegate {
#if FOUNDATION_FRAMEWORK
delegateResponse = delegate.fileManager?(self, shouldMoveItemAt: URL(fileURLWithPath: path), to: URL(fileURLWithPath: dst))
if delegateResponse == nil {
delegateResponse = delegate.fileManager?(self, shouldMoveItemAtPath: path, toPath: dst)
}
#else
delegateResponse = delegate.fileManager(self, shouldMoveItemAt: URL(fileURLWithPath: path), to: URL(fileURLWithPath: dst))
#endif
}
return delegateResponse ?? true
}
}
#if !FOUNDATION_FRAMEWORK
struct NSFileManagerCopyOptions: ExpressibleByArrayLiteral {
init(arrayLiteral elements: Void...) {}
}
struct NSFileManagerMoveOptions: ExpressibleByArrayLiteral {
init(arrayLiteral elements: Void...) {}
}
#endif
private protocol LinkOrCopyDelegate {
#if os(Windows)
typealias ErrorType = DWORD
#else
typealias ErrorType = Int32
#endif
func shouldPerformOnItemAtPath(_ path: String, to destination: String) -> Bool
func throwIfNecessary(_ errno: ErrorType, _ source: String, _ destination: String) throws
func throwIfNecessary(_ errno: any Error, _ source: String, _ destination: String) throws
var extraCopyFileFlags: Int32 { get }
var copyData: Bool { get }
}
private extension LinkOrCopyDelegate {
var extraCopyFileFlags: Int32 { 0 }
}
#if canImport(Darwin)
private typealias RemoveFileCallback = @convention(c) (removefile_state_t, UnsafePointer<CChar>, UnsafeRawPointer) -> Int
extension removefile_state_t {
fileprivate var errnum: Int32 {
var num: Int32 = 0
removefile_state_get(self, UInt32(REMOVEFILE_STATE_ERRNO), &num)
return num
}
fileprivate func attachCallbacks(context: UnsafeRawPointer?, confirm: RemoveFileCallback, error: RemoveFileCallback) {
removefile_state_set(self, UInt32(REMOVEFILE_STATE_CONFIRM_CONTEXT), context)
removefile_state_set(self, UInt32(REMOVEFILE_STATE_CONFIRM_CALLBACK), unsafeBitCast(confirm, to: UnsafeRawPointer.self))
removefile_state_set(self, UInt32(REMOVEFILE_STATE_ERROR_CONTEXT), context)
removefile_state_set(self, UInt32(REMOVEFILE_STATE_ERROR_CALLBACK), unsafeBitCast(error, to: UnsafeRawPointer.self))
}
}
#endif
enum _FileOperations {
// MARK: removefile
#if os(Windows)
static func removeFile(_ path: String, with filemanager: FileManager?) throws {
try path.withNTPathRepresentation {
var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = .init()
guard GetFileAttributesExW($0, GetFileExInfoStandard, &faAttributes) else {
// NOTE: in the case that the 'stat' failed, we want to ensure
// that we query if the item should be removed. If the item
// should not be removed, we can continue, else we should check
// if we should proceed after the error.
guard filemanager?._shouldRemoveItemAtPath(path) ?? true else { return }
let error = CocoaError.removeFileError(GetLastError(), path)
guard (filemanager?._shouldProceedAfter(error: error, removingItemAtPath: path) ?? false) else {
throw error
}
return
}
if faAttributes.dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
guard SetFileAttributesW($0, faAttributes.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY) else {
throw CocoaError.removeFileError(GetLastError(), path)
}
}
if faAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == 0 || faAttributes.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT == FILE_ATTRIBUTE_REPARSE_POINT {
guard filemanager?._shouldRemoveItemAtPath(path) ?? true else { return }
if faAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
guard RemoveDirectoryW($0) else {
throw CocoaError.removeFileError(GetLastError(), path)
}
return
} else {
guard DeleteFileW($0) else {
throw CocoaError.removeFileError(GetLastError(), path)
}
return
}
}
var stack = [(path, false)]
while let (directory, checked) = stack.popLast() {
try directory.withNTPathRepresentation {
let ntpath = String(decodingCString: $0, as: UTF16.self)
guard checked || filemanager?._shouldRemoveItemAtPath(ntpath) ?? true else { return }
if RemoveDirectoryW($0) { return }
let dwError: DWORD = GetLastError()
guard dwError == ERROR_DIR_NOT_EMPTY else {
let error = CocoaError.removeFileError(dwError, directory)
guard (filemanager?._shouldProceedAfter(error: error, removingItemAtPath: ntpath) ?? false) else {
throw error
}
return
}
stack.append((directory, true))
for entry in _Win32DirectoryContentsSequence(path: directory, appendSlashForDirectory: false, prefix: [directory]) {
try entry.fileNameWithPrefix.withNTPathRepresentation {
let ntpath = String(decodingCString: $0, as: UTF16.self)
if entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY,
entry.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != FILE_ATTRIBUTE_REPARSE_POINT {
if filemanager?._shouldRemoveItemAtPath(ntpath) ?? true {
stack.append((ntpath, true))
}
} else {
if entry.dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
guard SetFileAttributesW($0, entry.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY) else {
throw CocoaError.removeFileError(GetLastError(), ntpath)
}
}
if entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
guard filemanager?._shouldRemoveItemAtPath(ntpath) ?? true else { return }
if !RemoveDirectoryW($0) {
let error = CocoaError.removeFileError(GetLastError(), entry.fileName)
guard (filemanager?._shouldProceedAfter(error: error, removingItemAtPath: entry.fileNameWithPrefix) ?? false) else {
throw error
}
}
} else {
guard filemanager?._shouldRemoveItemAtPath(ntpath) ?? true else { return }
if !DeleteFileW($0) {
let error = CocoaError.removeFileError(GetLastError(), entry.fileName)
guard (filemanager?._shouldProceedAfter(error: error, removingItemAtPath: entry.fileNameWithPrefix) ?? false) else {
throw error
}
}
}
}
}
}
}
}
}
}
#else
static func removeFile(_ path: String, with fileManager: FileManager?) throws {
try path.withFileSystemRepresentation { rep in
guard let rep else {
throw CocoaError.errorWithFilePath(.fileNoSuchFile, path)
}
try Self._removeFile(rep, path, with: fileManager)
}
}
#if canImport(Darwin)
fileprivate class _FileRemoveContext {
var error: CocoaError?
var manager: FileManager?
init(_ manager: FileManager?) {
self.manager = manager
}
}
private static func _removeFile(_ pathPtr: UnsafePointer<CChar>, _ pathStr: String, with fileManager: FileManager?) throws {
let state = removefile_state_alloc()!
defer { removefile_state_free(state) }
let ctx = _FileRemoveContext(fileManager)
try withExtendedLifetime(ctx) {
let ctxPtr = Unmanaged.passUnretained(ctx).toOpaque()
state.attachCallbacks(context: ctxPtr, confirm: { _, pathPtr, contextPtr in
let context = Unmanaged<_FileOperations._FileRemoveContext>.fromOpaque(contextPtr).takeUnretainedValue()
let path = String(cString: pathPtr)
// Proceed unless the delegate says to skip
return (context.manager?._shouldRemoveItemAtPath(path) ?? true) ? REMOVEFILE_PROCEED : REMOVEFILE_SKIP
}, error: { state, pathPtr, contextPtr in
let context = Unmanaged<_FileOperations._FileRemoveContext>.fromOpaque(contextPtr).takeUnretainedValue()
let path = String(cString: pathPtr)
let err = CocoaError.removeFileError(state.errnum, path)
// Proceed only if the delegate says so
if context.manager?._shouldProceedAfter(error: err, removingItemAtPath: path) ?? false {
return REMOVEFILE_PROCEED
} else {
context.error = err
return REMOVEFILE_STOP
}
})
let err = removefile(pathPtr, state, removefile_flags_t(REMOVEFILE_RECURSIVE))
if err < 0 {
if errno != 0 {
throw CocoaError.removeFileError(Int32(errno), pathStr)
}
throw CocoaError.removeFileError(state.errnum, pathStr)
}
if let error = ctx.error {
throw error
}
}
}
#else
private static func _removeFile(_ path: UnsafePointer<CChar>, _ pathStr: String, with fileManager: FileManager?) throws {
let currentDirectoryPath = fileManager?.currentDirectoryPath ?? ""
func resolve(path: String) -> String {
if path.starts(with: "/") {
return path
} else {
return currentDirectoryPath.appendingPathComponent(path)
}
}
var stat = stat()
guard lstat(path, &stat) == 0 && stat.isDirectory else {
// Was not a directory, so unlink it
guard fileManager?._shouldRemoveItemAtPath(pathStr) ?? true else { return }
guard unlink(path) == 0 else {
let error = CocoaError.removeFileError(errno, pathStr)
if !(fileManager?._shouldProceedAfter(error: error, removingItemAtPath: pathStr) ?? false) {
throw error
}
return
}
return
}
guard fileManager?._shouldRemoveItemAtPath(resolve(path: pathStr)) ?? true else { return }
let trivialResult = rmdir(path)
if trivialResult == 0 {
// Was an empty directory that we removed, so exit
return
} else if errno != ENOTEMPTY {
// We failed for a reason other than the directory not being empty, so throw
throw CocoaError.removeFileError(errno, resolve(path: pathStr))
}
let seq = _FTSSequence(path, FTS_PHYSICAL | FTS_XDEV | FTS_NOCHDIR | FTS_NOSTAT)
let iterator = seq.makeIterator()
var isFirst = true
while let item = iterator.next() {
switch item {
case let .error(err, errPath):
throw CocoaError.removeFileError(err, errPath)
case let .entry(entry):
let fts_path = entry.ftsEnt.fts_path!
switch Int32(entry.ftsEnt.fts_info) {
case FTS_DEFAULT, FTS_F, FTS_NSOK, FTS_SL, FTS_SLNONE:
let currentPathStr = resolve(path: String(cString: fts_path))
guard fileManager?._shouldRemoveItemAtPath(currentPathStr) ?? true else {
break
}
if unlink(fts_path) != 0 {
let error = CocoaError.removeFileError(errno, currentPathStr)
if !(fileManager?._shouldProceedAfter(error: error, removingItemAtPath: currentPathStr) ?? false) {
throw error
}
}
case FTS_D:
if isFirst {
// The first directory was already approved above
isFirst = false
break
}
let currentPathStr = resolve(path: String(cString: fts_path))
if !(fileManager?._shouldRemoveItemAtPath(currentPathStr) ?? true) {
iterator.skipDescendants(of: entry, skipPostProcessing: true)
}
case FTS_DP:
if rmdir(fts_path) != 0 {
let currentPathStr = resolve(path: String(cString: fts_path))
let error = CocoaError.removeFileError(errno, currentPathStr)
if !(fileManager?._shouldProceedAfter(error: error, removingItemAtPath: currentPathStr) ?? false) {
throw error
}
}
case FTS_DNR, FTS_ERR, FTS_NS:
let currentPathStr = resolve(path: String(cString: fts_path))
throw CocoaError.removeFileError(entry.ftsEnt.fts_errno, currentPathStr)
default:
break
}
}
}
}
#endif
#endif
// MARK: Move File
static func moveFile(_ src: URL, to dst: URL, with fileManager: FileManager, options: NSFileManagerMoveOptions) throws {
#if os(Windows)
try src.withUnsafeFileSystemRepresentation { pszSource in
let source = String(cString: pszSource!)
try dst.withUnsafeFileSystemRepresentation { pszDestination in
let destination = String(cString: pszDestination!)
guard fileManager._shouldMoveItemAtPath(source, to: destination) else { return }
try source.withNTPathRepresentation { pwszSource in
var faSourceAttributes: WIN32_FILE_ATTRIBUTE_DATA = .init()
if !GetFileAttributesExW(pwszSource, GetFileExInfoStandard, &faSourceAttributes) {
let error = CocoaError.moveFileError(GetLastError(), src, dst)
guard fileManager._shouldProceedAfter(error: error, movingItemAtPath: source, to: destination) else {
throw error
}
return
}
try destination.withNTPathRepresentation { pwszDestination in
var faDestinationAttributes: WIN32_FILE_ATTRIBUTE_DATA = .init()
if GetFileAttributesExW(pwszDestination, GetFileExInfoStandard, &faDestinationAttributes) {
let error = CocoaError.moveFileError(GetLastError(), src, dst)
guard fileManager._shouldProceedAfter(error: error, movingItemAtPath: source, to: destination) else {
throw error
}
}
// `MoveFileExW` does not work if the source and
// destination are on different volumes and the source
// is a directory. In that case, we need to do a
// recursive copy and then remove the source.
if PathIsSameRootW(pwszSource, pwszDestination) ||
faSourceAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != FILE_ATTRIBUTE_DIRECTORY {
if !MoveFileExW(pwszSource, pwszDestination, MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH) {
let error = CocoaError.moveFileError(GetLastError(), src, dst)
guard fileManager._shouldProceedAfter(error: error, movingItemAtPath: source, to: destination) else {
throw error
}
}
} else {
var stack: [String] = [source]
while let entry = stack.popLast() {
do {
try entry.withNTPathRepresentation { pwszEntry in
var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = .init()
guard GetFileAttributesExW(pwszEntry, GetFileExInfoStandard, &faAttributes) else {
throw CocoaError.moveFileError(GetLastError(), src, dst)
}
var pwszDestination: PWSTR? = nil
guard SUCCEEDED(PathAllocCombine(destination, entry, PATHCCH_ALLOW_LONG_PATHS, &pwszDestination)) else {
throw CocoaError.moveFileError(GetLastError(), src, dst)
}
defer { LocalFree(pwszDestination) }
if faAttributes.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT == FILE_ATTRIBUTE_REPARSE_POINT {
let aliasee = try fileManager.destinationOfSymbolicLink(atPath: entry)
// TODO(compnerd) - is there a way to avoid the round-trip of decoding, encoding here?
let destination = String(decodingCString: pwszDestination!, as: UTF16.self)
var faDestinationAttributes: WIN32_FILE_ATTRIBUTE_DATA = .init()
if GetFileAttributesExW(pwszDestination, GetFileExInfoStandard, &faDestinationAttributes) {
try removeFile(destination, with: fileManager)
}
try fileManager.createSymbolicLink(atPath: destination, withDestinationPath: aliasee)
} else {
if faAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
guard CreateDirectoryW(pwszDestination, nil) else {
throw CocoaError.moveFileError(GetLastError(), src, dst)
}
stack.append(entry)
for entry in _Win32DirectoryContentsSequence(path: entry, appendSlashForDirectory: true) {
stack.append(entry.fileName)
}
} else {
guard CopyFileW(pwszEntry, pwszDestination, true) else {
throw CocoaError.moveFileError(GetLastError(), src, dst)
}
}
}
}
} catch let error {
guard fileManager._shouldProceedAfter(error: error, movingItemAtPath: source, to: destination) else {
throw error
}
}
}
}
}
}
}
}
#else
try src.withUnsafeFileSystemRepresentation { srcPath in
guard let srcPath else {
throw CocoaError.errorWithFilePath(.fileNoSuchFile, src)
}
try dst.withUnsafeFileSystemRepresentation { dstPath in
guard let dstPath else {
throw CocoaError.errorWithFilePath(.fileNoSuchFile, dst)
}
guard fileManager._shouldMoveItemAtPath(String(cString: srcPath), to: String(cString: dstPath)) else { return }
// If the destination path already exists, we're going to bail out completely & set the error.
var fileInfoBuffer = stat()
var delegateIgnoredOverwriteError = false
if lstat(dstPath, &fileInfoBuffer) == 0 {
// The lstat succeeded, so this is the error case for a file existing at the destination
// If the last components are case-insensitively the same, then we might be dealing with a case-only rename which should proceed ...
var shouldProceed = false
if src.lastPathComponent.compare(dst.lastPathComponent, options: [.caseInsensitive]) == .orderedSame {
#if FOUNDATION_FRAMEWORK
// TODO: Support case-only rename in swift-foundation
if let srcAttrs = try? src.resourceValues(forKeys: [.parentDirectoryURLKey]), let dstAttrs = try? dst.resourceValues(forKeys: [.parentDirectoryURLKey, .volumeSupportsCasePreservedNamesKey, .volumeSupportsCaseSensitiveNamesKey]) {
// ... but not if the source and destination are in different directories, meaning they're definitely different directory entries ...
if srcAttrs.parentDirectory == dstAttrs.parentDirectory {
// ... but only in volumes that are case insensitive, but case preserving.
if dstAttrs.volumeSupportsCasePreservedNames! && !dstAttrs.volumeSupportsCaseSensitiveNames! {
shouldProceed = true
}
}
}
#endif
}
if !shouldProceed {
let err = CocoaError.moveFileError(EEXIST, src, dst)
guard fileManager._shouldProceedAfter(error: err, movingItemAtPath: String(cString: srcPath), to: String(cString: dstPath)) else {
throw err
}
delegateIgnoredOverwriteError = true
}
}
// First, we should just rename (who knows? We might get lucky).
var renameError = rename(srcPath, dstPath) != 0
let renameErrno = errno
#if os(macOS) && FOUNDATION_FRAMEWORK
// If the rename was successful, stamp with DO_NOT_TRANSLOCATE, if requested. If rename failed with EXDEV, the copy operation will take care of it instead. Ignore failure. 26556142.
if !renameError && options.contains(.allowRunningResultInPlace) {
if let qtn = _qtn_file_alloc() {
defer { _qtn_file_free(qtn) }
if _qtn_file_init_with_path(qtn, dstPath) == 0 {
var flags = _qtn_file_get_flags(qtn)
if flags & QTN_FLAG_DO_NOT_TRANSLOCATE.rawValue == 0 {
flags |= QTN_FLAG_DO_NOT_TRANSLOCATE.rawValue
_qtn_file_set_flags(qtn, flags);
_qtn_file_apply_to_path(qtn, dstPath);
}
}
}
}
#endif
#if (os(macOS) || os(iOS)) && FOUNDATION_FRAMEWORK
if renameError && renameErrno == ENOENT {
// Could this perhaps be a faulted-out iCloud file that we're attempting to move?
var handled: ObjCBool = false
try fileManager._handleFaultedOutCloudDoc(fromSource: src, toDestination: dst, handled: &handled)
if handled.boolValue {
renameError = false
}
}
#endif
if renameError {
if renameErrno == EXDEV {
// We tried to move something across a device. We should copy and then unlink the original.
var copyOptions: NSFileManagerCopyOptions = []
#if os(macOS) && FOUNDATION_FRAMEWORK
if options.contains(.allowRunningResultInPlace) {
copyOptions.insert(.allowRunningResultInPlace)
}
#endif
do {
try Self.copyFile(src.path, to: dst.path, with: fileManager, options: copyOptions)
} catch let copyError as CocoaError {
// The error occurred on the copy operation, however we don't want to report those paths - we want to report the paths on the top-level item; the one the move was requested for. We'll wrap the copy operation error in the underlying error key, but we'll pick up the error code from the underlying error itself.
if !delegateIgnoredOverwriteError {
// Remove the incomplete copy at the destination, but only if the delegate didn't ignore the overwrite failure.
try? Self.removeFile(dst.path, with: nil)
}
throw CocoaError(copyError.code, userInfo: [
NSFilePathErrorKey : src.path,
NSDestinationFilePathErrorKey : dst.path,
NSUserStringVariantErrorKey : "Move",
NSUnderlyingErrorKey : copyError
])
}
// The copy was successful. Remove the original, but only if the delegate didn't ignore the rename failure.
do {
try Self.removeFile(src.path, with: nil)
} catch let removeError as CocoaError {
// Like the error from the copy operation above, make the "Remove" error the underlying error to a "Move" error.
throw CocoaError(removeError.code, userInfo: [
NSFilePathErrorKey : src.path,
NSDestinationFilePathErrorKey : dst.path,
NSUserStringVariantErrorKey : "Move",
NSUnderlyingErrorKey : removeError
])
}
} else {
// The error was something other than EXDEV, which means the rename() failed. The error codes for the rename need to be translated into something good for Cocoa domain errors.
let renameError = CocoaError.moveFileError(renameErrno, src, dst)
guard fileManager._shouldProceedAfter(error: renameError, movingItemAtPath: src.path, to: dst.path) else {
throw renameError
}
}
}
}
}
#endif
}
// MARK: Link/Copy File
#if os(Windows)
private static func linkOrCopyFile(_ src: String, dst: String, with fileManager: FileManager, delegate: some LinkOrCopyDelegate) throws {
let bCopyFile = delegate.copyData
try src.withNTPathRepresentation { pwszSource in
var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = .init()
guard GetFileAttributesExW(pwszSource, GetFileExInfoStandard, &faAttributes) else {
throw CocoaError.fileOperationError(.fileReadNoSuchFile, src, dst, variant: bCopyFile ? "Copy" : "Link")
}
guard delegate.shouldPerformOnItemAtPath(src, to: dst) else { return }
try dst.withNTPathRepresentation { pwszDestination in
// Check for reparse points first because symlinks to directories are reported as both reparse points and directories, and we should copy the symlink not the contents of the linked directory
if faAttributes.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT == FILE_ATTRIBUTE_REPARSE_POINT {
do {
let linkDest = try fileManager.destinationOfSymbolicLink(atPath: src)
try fileManager.createSymbolicLink(atPath: dst, withDestinationPath: linkDest)
} catch {
try delegate.throwIfNecessary(error, src, dst)
return
}
} else if faAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
do {
try fileManager.createDirectory(atPath: dst, withIntermediateDirectories: true)
} catch {
try delegate.throwIfNecessary(error, src, dst)
}
for item in _Win32DirectoryContentsSequence(path: src, appendSlashForDirectory: true) {
try linkOrCopyFile(src.appendingPathComponent(item.fileName), dst: dst.appendingPathComponent(item.fileName), with: fileManager, delegate: delegate)
}
} else if bCopyFile {
var ExtendedParameters: COPYFILE2_EXTENDED_PARAMETERS = .init()
ExtendedParameters.dwSize = DWORD(MemoryLayout<COPYFILE2_EXTENDED_PARAMETERS>.size)
ExtendedParameters.dwCopyFlags = COPY_FILE_FAIL_IF_EXISTS | COPY_FILE_NO_BUFFERING
if FAILED(CopyFile2(pwszSource, pwszDestination, &ExtendedParameters)) {
try delegate.throwIfNecessary(GetLastError(), src, dst)
}
} else {
do {
try fileManager.createSymbolicLink(atPath: dst, withDestinationPath: src)
} catch {
try delegate.throwIfNecessary(error, src, dst)
}
}
}
}
}
#else
#if !canImport(Darwin)
private static func _copyRegularFile(_ srcPtr: UnsafePointer<CChar>, _ dstPtr: UnsafePointer<CChar>, delegate: some LinkOrCopyDelegate) throws {
var fileInfo = stat()
guard stat(srcPtr, &fileInfo) >= 0 else {
try delegate.throwIfNecessary(errno, String(cString: srcPtr), String(cString: dstPtr))
return
}
let srcfd = open(srcPtr, O_RDONLY)
guard srcfd >= 0 else {
try delegate.throwIfNecessary(errno, String(cString: srcPtr), String(cString: dstPtr))
return
}
defer { close(srcfd) }
let dstfd = open(dstPtr, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, 0o666)
guard dstfd >= 0 else {
try delegate.throwIfNecessary(errno, String(cString: srcPtr), String(cString: dstPtr))
return
}
defer { close(dstfd) }
#if !os(WASI) // WASI doesn't have fchmod for now
// Set the file permissions using fchmod() instead of when open()ing to avoid umask() issues
let permissions = fileInfo.st_mode & ~S_IFMT
guard fchmod(dstfd, permissions) == 0 else {
try delegate.throwIfNecessary(errno, String(cString: srcPtr), String(cString: dstPtr))
return
}
#endif
if fileInfo.st_size == 0 {
// no copying required
return
}
let total: Int = Int(fileInfo.st_size)
let chunkSize: Int = Int(fileInfo.st_blksize)
var current: off_t = 0
#if os(WASI)
// WASI doesn't have sendfile, so we need to do it in user space with read/write
try withUnsafeTemporaryAllocation(of: UInt8.self, capacity: chunkSize) { buffer in
while current < total {
let readSize = Swift.min(total - Int(current), chunkSize)
let bytesRead = read(srcfd, buffer.baseAddress, readSize)
guard bytesRead >= 0 else {
try delegate.throwIfNecessary(errno, String(cString: srcPtr), String(cString: dstPtr))
return
}
guard write(dstfd, buffer.baseAddress, bytesRead) == bytesRead else {
try delegate.throwIfNecessary(errno, String(cString: srcPtr), String(cString: dstPtr))
return
}
current += off_t(bytesRead)
}
}
#else
while current < total {
guard sendfile(dstfd, srcfd, ¤t, Swift.min(total - Int(current), chunkSize)) != -1 else {
try delegate.throwIfNecessary(errno, String(cString: srcPtr), String(cString: dstPtr))
return
}
}
#endif
}
#endif
private static func _linkOrCopyFile(_ srcPtr: UnsafePointer<CChar>, _ dstPtr: UnsafePointer<CChar>, with fileManager: FileManager, delegate: some LinkOrCopyDelegate) throws {
try withUnsafeTemporaryAllocation(of: CChar.self, capacity: FileManager.MAX_PATH_SIZE) { buffer in
let dstLen = Platform.copyCString(dst: buffer.baseAddress!, src: dstPtr, size: FileManager.MAX_PATH_SIZE)
let srcLen = strlen(srcPtr)
let dstAppendPtr = buffer.baseAddress!.advanced(by: dstLen)
let remainingBuffer = FileManager.MAX_PATH_SIZE - dstLen
let seq = _FTSSequence(srcPtr, .init(FTS_PHYSICAL | FTS_NOCHDIR))
let iterator = seq.makeIterator()
while let item = iterator.next() {
switch item {
case let .error(errno, path):
throw CocoaError.errorWithFilePath(path, errno: errno, reading: true)
case let .entry(entry):
let fts_path = entry.ftsEnt.fts_path!
let trimmedPathPtr = fts_path.advanced(by: srcLen)
Platform.copyCString(dst: dstAppendPtr, src: trimmedPathPtr, size: remainingBuffer)
// we don't want to ask the delegate on the way back -up- the hierarchy if they want to copy a directory they've already seen and therefore already said "YES" to.
guard entry.ftsEnt.fts_info == FTS_DP || delegate.shouldPerformOnItemAtPath(String(cString: fts_path), to: String(cString: buffer.baseAddress!)) else {
if entry.ftsEnt.fts_info == FTS_D {
iterator.skipDescendants(of: entry, skipPostProcessing: true)
}
continue
}
let extraFlags = entry.ftsEnt.fts_level == 0 ? delegate.extraCopyFileFlags : 0
switch Int32(entry.ftsEnt.fts_info) {
case FTS_D:
// Directory being visited in pre-order - create it with whatever default perms will be on the destination.
#if canImport(Darwin)
if copyfile(fts_path, buffer.baseAddress!, nil, copyfile_flags_t(COPYFILE_DATA | COPYFILE_EXCL | COPYFILE_NOFOLLOW | extraFlags)) != 0 {
try delegate.throwIfNecessary(errno, String(cString: fts_path), String(cString: buffer.baseAddress!))
}
#else
do {
try fileManager.createDirectory(atPath: String(cString: buffer.baseAddress!), withIntermediateDirectories: true)
} catch {
try delegate.throwIfNecessary(error, String(cString: fts_path), String(cString: buffer.baseAddress!))
}
#endif
case FTS_DP:
// Directory being visited in post-order - copy the permissions over.
#if canImport(Darwin)
if copyfile(fts_path, buffer.baseAddress!, nil, copyfile_flags_t(COPYFILE_METADATA | COPYFILE_NOFOLLOW | extraFlags)) != 0 {
try delegate.throwIfNecessary(errno, String(cString: fts_path), String(cString: buffer.baseAddress!))
}
#else
do {
let attributes = try fileManager.attributesOfItem(atPath: String(cString: fts_path))
try fileManager.setAttributes(attributes, ofItemAtPath: String(cString: buffer.baseAddress!))
} catch {
try delegate.throwIfNecessary(error, String(cString: fts_path), String(cString: buffer.baseAddress!))
}
#endif
case FTS_SL: fallthrough // Symlink.
case FTS_SLNONE: // Symlink with no target.
// Do what the documentation says (and what linkPath:toPath:handler: does) - copy the symlink, instead of creating a hard link.
#if canImport(Darwin)
var flags: Int32
if delegate.copyData {
flags = COPYFILE_CLONE | COPYFILE_ALL | COPYFILE_EXCL | COPYFILE_NOFOLLOW | extraFlags
} else {
flags = COPYFILE_DATA | COPYFILE_METADATA | COPYFILE_EXCL | COPYFILE_NOFOLLOW | extraFlags
}
if copyfile(fts_path, buffer.baseAddress!, nil, copyfile_flags_t(flags)) != 0 {
try delegate.throwIfNecessary(errno, String(cString: fts_path), String(cString: buffer.baseAddress!))
}
#else
try withUnsafeTemporaryAllocation(of: CChar.self, capacity: FileManager.MAX_PATH_SIZE) { tempBuff in
tempBuff.initialize(repeating: 0)
defer { tempBuff.deinitialize() }
let len = readlink(fts_path, tempBuff.baseAddress!, FileManager.MAX_PATH_SIZE - 1)
if len >= 0, symlink(tempBuff.baseAddress!, buffer.baseAddress!) != -1 {
return
}
try delegate.throwIfNecessary(errno, String(cString: fts_path), String(cString: buffer.baseAddress!))
}
#endif
case FTS_DEFAULT: fallthrough // Something not defined anywhere else.
case FTS_F: // Regular file.
if delegate.copyData {
#if canImport(Darwin)
if copyfile(fts_path, buffer.baseAddress!, nil, copyfile_flags_t(COPYFILE_CLONE | COPYFILE_ALL | COPYFILE_EXCL | COPYFILE_NOFOLLOW | extraFlags)) != 0 {
try delegate.throwIfNecessary(errno, String(cString: fts_path), String(cString: buffer.baseAddress!))
}
#else
try Self._copyRegularFile(fts_path, buffer.baseAddress!, delegate: delegate)
#endif
} else {
if link(fts_path, buffer.baseAddress!) != 0 {
try delegate.throwIfNecessary(errno, String(cString: fts_path), String(cString: buffer.baseAddress!))
}
}
// Error returns
case FTS_DNR: fallthrough // Directory cannot be read.
case FTS_ERR: fallthrough // Some error occurred, but we don't know what.
case FTS_NS: // No stat(2) information is available.
try delegate.throwIfNecessary(entry.ftsEnt.fts_errno, String(cString: fts_path), String(cString: buffer.baseAddress!))
default: break
}
}
}
}
}
private static func linkOrCopyFile(_ src: String, dst: String, with fileManager: FileManager, delegate: some LinkOrCopyDelegate) throws {
try src.withFileSystemRepresentation { srcPtr in
guard let srcPtr else {
throw CocoaError.errorWithFilePath(.fileReadNoSuchFile, src)
}
try dst.withFileSystemRepresentation { dstPtr in
guard let dstPtr else {
throw CocoaError.errorWithFilePath(.fileNoSuchFile, dst)
}
try Self._linkOrCopyFile(srcPtr, dstPtr, with: fileManager, delegate: delegate)
}
}
}
#endif
static func copyFile(_ src: String, to dst: String, with fileManager: FileManager, options: NSFileManagerCopyOptions) throws {
struct CopyFileDelegate : LinkOrCopyDelegate {
let copyData = true
let extraCopyFileFlags: Int32
let fileManager: FileManager
init(inPlace: Bool, fileManager: FileManager) {
self.fileManager = fileManager
#if canImport(Darwin)
extraCopyFileFlags = inPlace ? COPYFILE_RUN_IN_PLACE : 0
#else
extraCopyFileFlags = 0
#endif
}
func shouldPerformOnItemAtPath(_ path: String, to destination: String) -> Bool {
fileManager._shouldCopyItemAtPath(path, to: destination)
}
func throwIfNecessary(_ error: ErrorType, _ source: String, _ destination: String) throws {
let error = CocoaError.copyFileError(error, source, destination)
guard fileManager._shouldProceedAfter(error: error, copyingItemAtPath: source, to: destination) else {
throw error
}
}
func throwIfNecessary(_ error: any Error, _ source: String, _ destination: String) throws {
guard fileManager._shouldProceedAfter(error: error, copyingItemAtPath: source, to: destination) else {
throw error
}
}
}
var inPlace = false
#if FOUNDATION_FRAMEWORK
if options.contains(.allowRunningResultInPlace) {
inPlace = true
}
#endif
try Self.linkOrCopyFile(src, dst: dst, with: fileManager, delegate: CopyFileDelegate(inPlace: inPlace, fileManager: fileManager))
}
static func linkFile(_ src: String, to dst: String, with fileManager: FileManager) throws {
struct LinkFileDelegate : LinkOrCopyDelegate {
let copyData = false
let fileManager: FileManager
init(_ fileManager: FileManager) {
self.fileManager = fileManager
}
func shouldPerformOnItemAtPath(_ path: String, to destination: String) -> Bool {
fileManager._shouldLinkItemAtPath(path, to: destination)
}
func throwIfNecessary(_ error: ErrorType, _ source: String, _ destination: String) throws {
let error = CocoaError.linkFileError(error, source, destination)
guard fileManager._shouldProceedAfter(error: error, linkingItemAtPath: source, to: destination) else {
throw error
}
}
func throwIfNecessary(_ error: any Error, _ source: String, _ destination: String) throws {
guard fileManager._shouldProceedAfter(error: error, linkingItemAtPath: source, to: destination) else {
throw error
}
}
}
try Self.linkOrCopyFile(src, dst: dst, with: fileManager, delegate: LinkFileDelegate(fileManager))
}
}
|