13
13
import let WinSDK. INVALID_FILE_ATTRIBUTES
14
14
import WinSDK
15
15
16
+ extension URL {
17
+ fileprivate var NTPath : String {
18
+ " \\ \\ ? \\ \( CFURLCopyFileSystemPath ( CFURLCopyAbsoluteURL ( _cfObject) , kCFURLWindowsPathStyle) !. _swiftObject) "
19
+ }
20
+
21
+ fileprivate func withUnsafeNTPath< Result> ( _ body: ( UnsafePointer < WCHAR > ) throws -> Result ) rethrows -> Result {
22
+ return try NTPath . withCString ( encodedAs: UTF16 . self, body)
23
+ }
24
+ }
25
+
26
+
27
+ private func withNTPathRepresentation< Result> ( of path: String , _ body: ( UnsafePointer < WCHAR > ) throws -> Result ) throws -> Result {
28
+ func withNormalizedRepresentation< Result> ( of path: String , _ body: ( UnsafePointer < WCHAR > ) throws -> Result ) rethrows -> Result {
29
+ var path = path
30
+
31
+ // Strip the leading `/` on a RFC8089 path (`/[drive-letter]:/...` ). A
32
+ // leading slash indicates a rooted path on the drive for teh current
33
+ // working directory.
34
+ if path. count > 3 {
35
+ let index0 = path. startIndex
36
+ let index1 = path. index ( path. startIndex, offsetBy: 1 )
37
+ let index2 = path. index ( path. startIndex, offsetBy: 2 )
38
+
39
+ if path [ index0] == " / " , path [ index1] . isLetter, path [ index2] == " : " {
40
+ path. removeFirst ( )
41
+ }
42
+ }
43
+
44
+ // Win32 APIs can support `/` for the arc separator. However,
45
+ // symlinks created with `/` do not resolve properly, so normalize
46
+ // the path.
47
+ path = path. replacing ( " / " , with: " \\ " )
48
+
49
+ // Droop trailing slashes unless it follows a drive specification. The
50
+ // trailing arc separator after a drive specifier iindicates the root as
51
+ // opposed to a drive relative path.
52
+ while path. count > 1 , path [ path. index ( before: path. endIndex) ] == " \\ " ,
53
+ !( path. count == 3 &&
54
+ path [ path. index ( path. endIndex, offsetBy: - 2 ) ] == " : " &&
55
+ path [ path. index ( path. endIndex, offsetBy: - 3 ) ] . isLetter) {
56
+ path. removeLast ( )
57
+ }
58
+
59
+ return try path. withCString ( encodedAs: UTF16 . self, body)
60
+ }
61
+
62
+ guard !path. isEmpty else {
63
+ throw NSError ( domain: NSCocoaErrorDomain,
64
+ code: CocoaError . fileReadInvalidFileName. rawValue,
65
+ userInfo: [ NSFilePathErrorKey: path] )
66
+ }
67
+
68
+ return try withNormalizedRepresentation ( of: path) { pwszPath in
69
+ guard !path. hasPrefix ( " \\ \\ " ) , !path. hasPrefix ( " // " ) else {
70
+ return try body ( pwszPath)
71
+ }
72
+
73
+ let dwLength = GetFullPathNameW ( pwszPath, 0 , nil , nil )
74
+ let path = withUnsafeTemporaryAllocation ( of: WCHAR . self, capacity: Int ( dwLength) ) {
75
+ _ = GetFullPathNameW ( pwszPath, DWORD ( $0. count) , $0. baseAddress, nil )
76
+ return String ( decodingCString: $0. baseAddress!, as: UTF16 . self)
77
+ }
78
+ return try " \\ \\ ? \\ \( path) " . withCString ( encodedAs: UTF16 . self, body)
79
+ }
80
+ }
81
+
82
+ private func walk( directory path: URL , _ body: ( String , DWORD ) throws -> Void ) rethrows {
83
+ try " \( path. NTPath) \\ * " . withCString ( encodedAs: UTF16 . self) {
84
+ var ffd : WIN32_FIND_DATAW = . init( )
85
+ let capacity = MemoryLayout . size ( ofValue: ffd. cFileName) / MemoryLayout< WCHAR> . size
86
+
87
+ let hFind : HANDLE = FindFirstFileW ( $0, & ffd)
88
+ if hFind == INVALID_HANDLE_VALUE {
89
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: true , paths: [ path. path] )
90
+ }
91
+
92
+ defer { FindClose ( hFind) }
93
+
94
+ repeat {
95
+ let entry : String = withUnsafePointer ( to: ffd. cFileName) {
96
+ $0. withMemoryRebound ( to: WCHAR . self, capacity: capacity) {
97
+ String ( decodingCString: $0, as: UTF16 . self)
98
+ }
99
+ }
100
+
101
+ try body ( entry, ffd. dwFileAttributes)
102
+ } while FindNextFileW ( hFind, & ffd)
103
+ }
104
+ }
105
+
16
106
internal func joinPath( prefix: String , suffix: String ) -> String {
17
107
var pszPath : PWSTR ?
18
108
@@ -198,28 +288,15 @@ extension FileManager {
198
288
}
199
289
200
290
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) ] )
291
+ guard !path. isEmpty else {
292
+ throw NSError ( domain: NSCocoaErrorDomain,
293
+ code: CocoaError . fileReadInvalidFileName. rawValue,
294
+ userInfo: [ NSFilePathErrorKey: NSString ( path) ] )
203
295
}
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
296
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)
297
+ try walk ( directory: URL ( fileURLWithPath: path, isDirectory: true ) ) { entry, attributes in
298
+ if entry == " . " || entry == " .. " { return }
299
+ try closure ( entry. standardizingPath, Int32 ( attributes) )
223
300
}
224
301
}
225
302
@@ -239,13 +316,13 @@ extension FileManager {
239
316
}
240
317
241
318
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] )
319
+ return try withNTPathRepresentation ( of: path) {
320
+ var faAttributes : WIN32_FILE_ATTRIBUTE_DATA = . init( )
321
+ if !GetFileAttributesExW( $0, GetFileExInfoStandard, & faAttributes) {
322
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: true , paths: [ path] )
323
+ }
324
+ return faAttributes
246
325
}
247
- return faAttributes
248
- }
249
326
}
250
327
251
328
internal func _attributesOfFileSystemIncludingBlockSize( forPath path: String ) throws -> ( attributes: [ FileAttributeKey : Any ] , blockSize: UInt64 ? ) {
@@ -571,94 +648,83 @@ extension FileManager {
571
648
return
572
649
}
573
650
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 {
651
+ try withNTPathRepresentation ( of: path) {
652
+ var faAttributes : WIN32_FILE_ATTRIBUTE_DATA = . init( )
653
+ if !GetFileAttributesExW( $0, GetFileExInfoStandard, & faAttributes) {
582
654
throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
583
- } else {
584
- throw error
585
655
}
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
656
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] )
657
+ if faAttributes. dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
658
+ if !SetFileAttributesW( $0, faAttributes. dwFileAttributes & ~ FILE_ATTRIBUTE_READONLY) {
659
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
617
660
}
618
- dirStack. append ( itemPath)
619
- var ffd : WIN32_FIND_DATAW = WIN32_FIND_DATAW ( )
620
- let capacity = MemoryLayout . size ( ofValue: ffd. cFileName)
661
+ }
621
662
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] )
663
+ if faAttributes. dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == 0 || faAttributes. dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT == FILE_ATTRIBUTE_REPARSE_POINT {
664
+ if faAttributes. dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
665
+ guard RemoveDirectoryW ( $0) else {
666
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
667
+ }
668
+ } else {
669
+ guard DeleteFileW ( $0) else {
670
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
671
+ }
627
672
}
628
- defer { FindClose ( handle) }
673
+ return
674
+ }
629
675
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
- }
676
+ var stack = [ path ]
677
+ while let directory = stack . popLast ( ) {
678
+ do {
679
+ guard alreadyConfirmed || shouldRemoveItemAtPath ( directory , isURL : isURL ) else {
680
+ continue
635
681
}
636
682
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
- }
644
- }
645
-
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] )
683
+ let root = URL ( fileURLWithPath: directory, isDirectory: true )
684
+ try root. withUnsafeNTPath {
685
+ if RemoveDirectoryW ( $0) { return }
686
+ guard GetLastError ( ) == ERROR_DIR_NOT_EMPTY else {
687
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ directory] )
688
+ }
689
+ stack. append ( directory)
690
+
691
+ try walk ( directory: root) { entry, attributes in
692
+ if entry == " . " || entry == " .. " { return }
693
+
694
+ let isDirectory = attributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY && attributes & FILE_ATTRIBUTE_REPARSE_POINT == 0
695
+ let path = root. appendingPathComponent ( entry, isDirectory: isDirectory)
696
+
697
+ if isDirectory {
698
+ stack. append ( path. path)
699
+ } else {
700
+ guard alreadyConfirmed || shouldRemoveItemAtPath ( path. path, isURL: isURL) else {
701
+ return
702
+ }
703
+
704
+ try path. withUnsafeNTPath {
705
+ if attributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
706
+ if !SetFileAttributesW( $0, attributes & ~ FILE_ATTRIBUTE_READONLY) {
707
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ entry] )
708
+ }
709
+ }
710
+
711
+ if attributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
712
+ if !RemoveDirectoryW( $0) {
713
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ entry] )
714
+ }
715
+ } else {
716
+ if !DeleteFileW( $0) {
717
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ entry] )
718
+ }
719
+ }
720
+ }
721
+ }
722
+ }
656
723
}
724
+ } catch {
725
+ if !shouldProceedAfterError( error, removingItemAtPath: directory, isURL: isURL) {
726
+ throw error
657
727
}
658
- } while FindNextFileW ( handle, & ffd)
659
- } catch {
660
- if !shouldProceedAfterError( error, removingItemAtPath: itemPath, isURL: isURL) {
661
- throw error
662
728
}
663
729
}
664
730
}
@@ -970,30 +1036,14 @@ extension FileManager {
970
1036
guard let _lastReturned else { return firstValidItem ( ) }
971
1037
972
1038
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
1039
+ try walk ( directory: _lastReturned) { entry, attributes in
1040
+ if entry == " . " || entry == " .. " { return }
1041
+ if _options. contains ( . skipsHiddenFiles) && attributes & FILE_ATTRIBUTE_HIDDEN == FILE_ATTRIBUTE_HIDDEN {
1042
+ return
992
1043
}
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)
1044
+ let isDirectory = attributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY && attributes & FILE_ATTRIBUTE_REPARSE_POINT != FILE_ATTRIBUTE_REPARSE_POINT
1045
+ _stack. append ( _lastReturned. appendingPathComponent ( entry, isDirectory: isDirectory) )
1046
+ }
997
1047
}
998
1048
999
1049
return firstValidItem ( )
0 commit comments