13
13
import let WinSDK. INVALID_FILE_ATTRIBUTES
14
14
import WinSDK
15
15
16
+ extension URL {
17
+ fileprivate var NTPath : String {
18
+ // Use a NT style, device path to avoid the 261-character path
19
+ // limitation on Windows APIs. The addition of the prefix will bypass
20
+ // the Win32 layer for the path handling and thus must be fully resolved
21
+ // and normalised before being passed in. This allows us access to the
22
+ // complete path limit as imposed by the NT kernel rather than the 260
23
+ // character limit as imposed by Win32.
24
+ #"\\?\ \#( CFURLCopyFileSystemPath ( CFURLCopyAbsoluteURL ( _cfObject) , kCFURLWindowsPathStyle) !. _swiftObject) "#
25
+ }
26
+
27
+ fileprivate func withUnsafeNTPath< Result> ( _ body: ( UnsafePointer < WCHAR > ) throws -> Result ) rethrows -> Result {
28
+ try self . NTPath. withCString ( encodedAs: UTF16 . self, body)
29
+ }
30
+ }
31
+
32
+
33
+ private func withNTPathRepresentation< Result> ( of path: String , _ body: ( UnsafePointer < WCHAR > ) throws -> Result ) throws -> Result {
34
+ guard !path. isEmpty else {
35
+ throw CocoaError . error ( . fileReadInvalidFileName, userInfo: [ NSFilePathErrorKey: path] )
36
+ }
37
+
38
+ // 1. Normalize the path first.
39
+
40
+ var path = path
41
+
42
+ // Strip the leading `/` on a RFC8089 path (`/[drive-letter]:/...` ). A
43
+ // leading slash indicates a rooted path on the drive for teh current
44
+ // working directory.
45
+ var iter = path. makeIterator ( )
46
+ if iter. next ( ) == " / " , iter. next ( ) ? . isLetter ?? false , iter. next ( ) == " : " {
47
+ path. removeFirst ( )
48
+ }
49
+
50
+ // Win32 APIs can support `/` for the arc separator. However,
51
+ // symlinks created with `/` do not resolve properly, so normalize
52
+ // the path.
53
+ path = path. replacing ( " / " , with: " \\ " )
54
+
55
+ // Droop trailing slashes unless it follows a drive specification. The
56
+ // trailing arc separator after a drive specifier iindicates the root as
57
+ // opposed to a drive relative path.
58
+ while path. count > 1 , path [ path. index ( before: path. endIndex) ] == " \\ " ,
59
+ !( path. count == 3 &&
60
+ path [ path. index ( path. endIndex, offsetBy: - 2 ) ] == " : " &&
61
+ path [ path. index ( path. endIndex, offsetBy: - 3 ) ] . isLetter) {
62
+ path. removeLast ( )
63
+ }
64
+
65
+ // 2. Perform the operation on the normalized path.
66
+
67
+ return try path. withCString ( encodedAs: UTF16 . self) { pwszPath in
68
+ guard !path. hasPrefix ( #"\\"# ) else { return try body ( pwszPath) }
69
+
70
+ let dwLength = GetFullPathNameW ( pwszPath, 0 , nil , nil )
71
+ let path = withUnsafeTemporaryAllocation ( of: WCHAR . self, capacity: Int ( dwLength) ) {
72
+ _ = GetFullPathNameW ( pwszPath, DWORD ( $0. count) , $0. baseAddress, nil )
73
+ return String ( decodingCString: $0. baseAddress!, as: UTF16 . self)
74
+ }
75
+ return try #"\\?\ \#( path) "# . withCString ( encodedAs: UTF16 . self, body)
76
+ }
77
+ }
78
+
79
+ private func walk( directory path: URL , _ body: ( String , DWORD ) throws -> Void ) rethrows {
80
+ try " \( path. NTPath) \\ * " . withCString ( encodedAs: UTF16 . self) {
81
+ var ffd : WIN32_FIND_DATAW = . init( )
82
+
83
+ let hFind : HANDLE = FindFirstFileW ( $0, & ffd)
84
+ if hFind == INVALID_HANDLE_VALUE {
85
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: true , paths: [ path. path] )
86
+ }
87
+
88
+ defer { FindClose ( hFind) }
89
+
90
+ repeat {
91
+ let entry : String = withUnsafeBytes ( of: ffd. cFileName) {
92
+ $0. withMemoryRebound ( to: WCHAR . self) {
93
+ String ( decodingCString: $0. baseAddress!, as: UTF16 . self)
94
+ }
95
+ }
96
+
97
+ try body ( entry, ffd. dwFileAttributes)
98
+ } while FindNextFileW ( hFind, & ffd)
99
+ }
100
+ }
101
+
16
102
internal func joinPath( prefix: String , suffix: String ) -> String {
17
103
var pszPath : PWSTR ?
18
104
@@ -198,28 +284,13 @@ extension FileManager {
198
284
}
199
285
200
286
internal func _contentsOfDir( atPath path: String , _ closure: ( String , Int32 ) throws -> ( ) ) throws {
201
- guard path != " " else {
202
- throw NSError ( domain : NSCocoaErrorDomain , code : CocoaError . fileReadInvalidFileName . rawValue , userInfo: [ NSFilePathErrorKey : NSString ( path) ] )
287
+ guard !path . isEmpty else {
288
+ throw CocoaError . error ( . fileReadInvalidFileName , userInfo: [ NSFilePathErrorKey: path] )
203
289
}
204
- try FileManager . default. _fileSystemRepresentation ( withPath: path + " \\ * " ) {
205
- var ffd : WIN32_FIND_DATAW = WIN32_FIND_DATAW ( )
206
-
207
- let hDirectory : HANDLE = FindFirstFileW ( $0, & ffd)
208
- if hDirectory == INVALID_HANDLE_VALUE {
209
- throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: true , paths: [ path] )
210
- }
211
- defer { FindClose ( hDirectory) }
212
290
213
- repeat {
214
- let path : String = withUnsafePointer ( to: & ffd. cFileName) {
215
- $0. withMemoryRebound ( to: UInt16 . self, capacity: MemoryLayout . size ( ofValue: $0) / MemoryLayout< WCHAR> . size) {
216
- String ( decodingCString: $0, as: UTF16 . self)
217
- }
218
- }
219
- if path != " . " && path != " .. " {
220
- try closure ( path. standardizingPath, Int32 ( ffd. dwFileAttributes) )
221
- }
222
- } while FindNextFileW ( hDirectory, & ffd)
291
+ try walk ( directory: URL ( fileURLWithPath: path, isDirectory: true ) ) { entry, attributes in
292
+ if entry == " . " || entry == " .. " { return }
293
+ try closure ( entry. standardizingPath, Int32 ( attributes) )
223
294
}
224
295
}
225
296
@@ -239,13 +310,13 @@ extension FileManager {
239
310
}
240
311
241
312
internal func windowsFileAttributes( atPath path: String ) throws -> WIN32_FILE_ATTRIBUTE_DATA {
242
- return try FileManager . default. _fileSystemRepresentation ( withPath: path) {
243
- var faAttributes : WIN32_FILE_ATTRIBUTE_DATA = WIN32_FILE_ATTRIBUTE_DATA ( )
244
- if !GetFileAttributesExW( $0, GetFileExInfoStandard, & faAttributes) {
245
- throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: true , paths: [ path] )
313
+ return try withNTPathRepresentation ( of: path) {
314
+ var faAttributes : WIN32_FILE_ATTRIBUTE_DATA = . init( )
315
+ if !GetFileAttributesExW( $0, GetFileExInfoStandard, & faAttributes) {
316
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: true , paths: [ path] )
317
+ }
318
+ return faAttributes
246
319
}
247
- return faAttributes
248
- }
249
320
}
250
321
251
322
internal func _attributesOfFileSystemIncludingBlockSize( forPath path: String ) throws -> ( attributes: [ FileAttributeKey : Any ] , blockSize: UInt64 ? ) {
@@ -571,94 +642,83 @@ extension FileManager {
571
642
return
572
643
}
573
644
574
- let faAttributes : WIN32_FILE_ATTRIBUTE_DATA
575
- do {
576
- faAttributes = try windowsFileAttributes ( atPath: path)
577
- } catch {
578
- // removeItem on POSIX throws fileNoSuchFile rather than
579
- // fileReadNoSuchFile that windowsFileAttributes will
580
- // throw if it doesn't find the file.
581
- if ( error as NSError ) . code == CocoaError . fileReadNoSuchFile. rawValue {
645
+ try withNTPathRepresentation ( of: path) {
646
+ var faAttributes : WIN32_FILE_ATTRIBUTE_DATA = . init( )
647
+ if !GetFileAttributesExW( $0, GetFileExInfoStandard, & faAttributes) {
582
648
throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
583
- } else {
584
- throw error
585
649
}
586
- }
587
-
588
- if faAttributes. dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
589
- if try ! FileManager. default. _fileSystemRepresentation ( withPath: path, {
590
- SetFileAttributesW ( $0, faAttributes. dwFileAttributes & ~ FILE_ATTRIBUTE_READONLY)
591
- } ) {
592
- throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
593
- }
594
- }
595
-
596
- if faAttributes. dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == 0 {
597
- if try ! FileManager. default. _fileSystemRepresentation ( withPath: path, DeleteFileW) {
598
- throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
599
- }
600
- return
601
- }
602
-
603
- var dirStack = [ path]
604
- var itemPath = " "
605
- while let currentDir = dirStack. popLast ( ) {
606
- do {
607
- itemPath = currentDir
608
- guard alreadyConfirmed || shouldRemoveItemAtPath ( itemPath, isURL: isURL) else {
609
- continue
610
- }
611
-
612
- if try FileManager . default. _fileSystemRepresentation ( withPath: itemPath, RemoveDirectoryW) {
613
- continue
614
- }
615
- guard GetLastError ( ) == ERROR_DIR_NOT_EMPTY else {
616
- throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ itemPath] )
617
- }
618
- dirStack. append ( itemPath)
619
- var ffd : WIN32_FIND_DATAW = WIN32_FIND_DATAW ( )
620
- let capacity = MemoryLayout . size ( ofValue: ffd. cFileName)
621
650
622
- let handle : HANDLE = try FileManager . default. _fileSystemRepresentation ( withPath: itemPath + " \\ * " ) {
623
- FindFirstFileW ( $0, & ffd)
624
- }
625
- if handle == INVALID_HANDLE_VALUE {
626
- throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ itemPath] )
651
+ if faAttributes. dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
652
+ if !SetFileAttributesW( $0, faAttributes. dwFileAttributes & ~ FILE_ATTRIBUTE_READONLY) {
653
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
627
654
}
628
- defer { FindClose ( handle ) }
655
+ }
629
656
630
- repeat {
631
- let file = withUnsafePointer ( to: & ffd. cFileName) {
632
- $0. withMemoryRebound ( to: WCHAR . self, capacity: capacity) {
633
- String ( decodingCString: $0, as: UTF16 . self)
634
- }
657
+ if faAttributes. dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == 0 || faAttributes. dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT == FILE_ATTRIBUTE_REPARSE_POINT {
658
+ if faAttributes. dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
659
+ guard RemoveDirectoryW ( $0) else {
660
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
661
+ }
662
+ } else {
663
+ guard DeleteFileW ( $0) else {
664
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
635
665
}
666
+ }
667
+ return
668
+ }
636
669
637
- itemPath = " \( currentDir) \\ \( file) "
638
- if ffd. dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
639
- if try ! FileManager. default. _fileSystemRepresentation ( withPath: itemPath, {
640
- SetFileAttributesW ( $0, ffd. dwFileAttributes & ~ FILE_ATTRIBUTE_READONLY)
641
- } ) {
642
- throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ file] )
643
- }
670
+ var stack = [ path]
671
+ while let directory = stack. popLast ( ) {
672
+ do {
673
+ guard alreadyConfirmed || shouldRemoveItemAtPath ( directory, isURL: isURL) else {
674
+ continue
644
675
}
645
676
646
- if ( ffd. dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0 ) {
647
- if file != " . " && file != " .. " {
648
- dirStack. append ( itemPath)
649
- }
650
- } else {
651
- guard alreadyConfirmed || shouldRemoveItemAtPath ( itemPath, isURL: isURL) else {
652
- continue
653
- }
654
- if try ! FileManager. default. _fileSystemRepresentation ( withPath: itemPath, DeleteFileW) {
655
- throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ file] )
677
+ let root = URL ( fileURLWithPath: directory, isDirectory: true )
678
+ try root. withUnsafeNTPath {
679
+ if RemoveDirectoryW ( $0) { return }
680
+ guard GetLastError ( ) == ERROR_DIR_NOT_EMPTY else {
681
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ directory] )
682
+ }
683
+ stack. append ( directory)
684
+
685
+ try walk ( directory: root) { entry, attributes in
686
+ if entry == " . " || entry == " .. " { return }
687
+
688
+ let isDirectory = attributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY && attributes & FILE_ATTRIBUTE_REPARSE_POINT == 0
689
+ let path = root. appendingPathComponent ( entry, isDirectory: isDirectory)
690
+
691
+ if isDirectory {
692
+ stack. append ( path. path)
693
+ } else {
694
+ guard alreadyConfirmed || shouldRemoveItemAtPath ( path. path, isURL: isURL) else {
695
+ return
696
+ }
697
+
698
+ try path. withUnsafeNTPath {
699
+ if attributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
700
+ if !SetFileAttributesW( $0, attributes & ~ FILE_ATTRIBUTE_READONLY) {
701
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ entry] )
702
+ }
703
+ }
704
+
705
+ if attributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
706
+ if !RemoveDirectoryW( $0) {
707
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ entry] )
708
+ }
709
+ } else {
710
+ if !DeleteFileW( $0) {
711
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ entry] )
712
+ }
713
+ }
714
+ }
715
+ }
716
+ }
656
717
}
718
+ } catch {
719
+ if !shouldProceedAfterError( error, removingItemAtPath: directory, isURL: isURL) {
720
+ throw error
657
721
}
658
- } while FindNextFileW ( handle, & ffd)
659
- } catch {
660
- if !shouldProceedAfterError( error, removingItemAtPath: itemPath, isURL: isURL) {
661
- throw error
662
722
}
663
723
}
664
724
}
@@ -970,30 +1030,14 @@ extension FileManager {
970
1030
guard let _lastReturned else { return firstValidItem ( ) }
971
1031
972
1032
if _lastReturned. hasDirectoryPath && ( level == 0 || !_options. contains ( . skipsSubdirectoryDescendants) ) {
973
- var ffd = WIN32_FIND_DATAW ( )
974
- let capacity = MemoryLayout . size ( ofValue: ffd. cFileName)
975
-
976
- let handle = ( try ? FileManager . default. _fileSystemRepresentation ( withPath: _lastReturned. path + " \\ * " ) {
977
- FindFirstFileW ( $0, & ffd)
978
- } ) ?? INVALID_HANDLE_VALUE
979
- if handle == INVALID_HANDLE_VALUE { return firstValidItem ( ) }
980
- defer { FindClose ( handle) }
981
-
982
- repeat {
983
- let file = withUnsafePointer ( to: & ffd. cFileName) {
984
- $0. withMemoryRebound ( to: WCHAR . self, capacity: capacity) {
985
- String ( decodingCString: $0, as: UTF16 . self)
986
- }
987
- }
988
- if file == " . " || file == " .. " { continue }
989
- if _options. contains ( . skipsHiddenFiles) &&
990
- ffd. dwFileAttributes & FILE_ATTRIBUTE_HIDDEN == FILE_ATTRIBUTE_HIDDEN {
991
- continue
1033
+ try walk ( directory: _lastReturned) { entry, attributes in
1034
+ if entry == " . " || entry == " .. " { return }
1035
+ if _options. contains ( . skipsHiddenFiles) && attributes & FILE_ATTRIBUTE_HIDDEN == FILE_ATTRIBUTE_HIDDEN {
1036
+ return
992
1037
}
993
-
994
- let isDirectory = ffd. dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY && ffd. dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != FILE_ATTRIBUTE_REPARSE_POINT
995
- _stack. append ( _lastReturned. appendingPathComponent ( file, isDirectory: isDirectory) )
996
- } while FindNextFileW ( handle, & ffd)
1038
+ let isDirectory = attributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY && attributes & FILE_ATTRIBUTE_REPARSE_POINT != FILE_ATTRIBUTE_REPARSE_POINT
1039
+ _stack. append ( _lastReturned. appendingPathComponent ( entry, isDirectory: isDirectory) )
1040
+ }
997
1041
}
998
1042
999
1043
return firstValidItem ( )
0 commit comments