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
+ let capacity = MemoryLayout . size ( ofValue: ffd. cFileName) / MemoryLayout< WCHAR> . size
83
+
84
+ let hFind : HANDLE = FindFirstFileW ( $0, & ffd)
85
+ if hFind == INVALID_HANDLE_VALUE {
86
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: true , paths: [ path. path] )
87
+ }
88
+
89
+ defer { FindClose ( hFind) }
90
+
91
+ repeat {
92
+ let entry : String = withUnsafePointer ( to: ffd. cFileName) {
93
+ $0. withMemoryRebound ( to: WCHAR . self, capacity: capacity) {
94
+ String ( decodingCString: $0, as: UTF16 . self)
95
+ }
96
+ }
97
+
98
+ try body ( entry, ffd. dwFileAttributes)
99
+ } while FindNextFileW ( hFind, & ffd)
100
+ }
101
+ }
102
+
16
103
internal func joinPath( prefix: String , suffix: String ) -> String {
17
104
var pszPath : PWSTR ?
18
105
@@ -198,28 +285,13 @@ extension FileManager {
198
285
}
199
286
200
287
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) ] )
288
+ guard !path . isEmpty else {
289
+ throw CocoaError . error ( . fileReadInvalidFileName , userInfo: [ NSFilePathErrorKey: path] )
203
290
}
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
291
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)
292
+ try walk ( directory: URL ( fileURLWithPath: path, isDirectory: true ) ) { entry, attributes in
293
+ if entry == " . " || entry == " .. " { return }
294
+ try closure ( entry. standardizingPath, Int32 ( attributes) )
223
295
}
224
296
}
225
297
@@ -239,13 +311,13 @@ extension FileManager {
239
311
}
240
312
241
313
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] )
314
+ return try withNTPathRepresentation ( of: path) {
315
+ var faAttributes : WIN32_FILE_ATTRIBUTE_DATA = . init( )
316
+ if !GetFileAttributesExW( $0, GetFileExInfoStandard, & faAttributes) {
317
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: true , paths: [ path] )
318
+ }
319
+ return faAttributes
246
320
}
247
- return faAttributes
248
- }
249
321
}
250
322
251
323
internal func _attributesOfFileSystemIncludingBlockSize( forPath path: String ) throws -> ( attributes: [ FileAttributeKey : Any ] , blockSize: UInt64 ? ) {
@@ -571,94 +643,83 @@ extension FileManager {
571
643
return
572
644
}
573
645
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 {
646
+ try withNTPathRepresentation ( of: path) {
647
+ var faAttributes : WIN32_FILE_ATTRIBUTE_DATA = . init( )
648
+ if !GetFileAttributesExW( $0, GetFileExInfoStandard, & faAttributes) {
582
649
throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
583
- } else {
584
- throw error
585
650
}
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
651
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] )
652
+ if faAttributes. dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
653
+ if !SetFileAttributesW( $0, faAttributes. dwFileAttributes & ~ FILE_ATTRIBUTE_READONLY) {
654
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
627
655
}
628
- defer { FindClose ( handle ) }
656
+ }
629
657
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
- }
658
+ if faAttributes. dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == 0 || faAttributes. dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT == FILE_ATTRIBUTE_REPARSE_POINT {
659
+ if faAttributes. dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
660
+ guard RemoveDirectoryW ( $0) else {
661
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
662
+ }
663
+ } else {
664
+ guard DeleteFileW ( $0) else {
665
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
635
666
}
667
+ }
668
+ return
669
+ }
636
670
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
- }
671
+ var stack = [ path]
672
+ while let directory = stack. popLast ( ) {
673
+ do {
674
+ guard alreadyConfirmed || shouldRemoveItemAtPath ( directory, isURL: isURL) else {
675
+ continue
644
676
}
645
677
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] )
678
+ let root = URL ( fileURLWithPath: directory, isDirectory: true )
679
+ try root. withUnsafeNTPath {
680
+ if RemoveDirectoryW ( $0) { return }
681
+ guard GetLastError ( ) == ERROR_DIR_NOT_EMPTY else {
682
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ directory] )
683
+ }
684
+ stack. append ( directory)
685
+
686
+ try walk ( directory: root) { entry, attributes in
687
+ if entry == " . " || entry == " .. " { return }
688
+
689
+ let isDirectory = attributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY && attributes & FILE_ATTRIBUTE_REPARSE_POINT == 0
690
+ let path = root. appendingPathComponent ( entry, isDirectory: isDirectory)
691
+
692
+ if isDirectory {
693
+ stack. append ( path. path)
694
+ } else {
695
+ guard alreadyConfirmed || shouldRemoveItemAtPath ( path. path, isURL: isURL) else {
696
+ return
697
+ }
698
+
699
+ try path. withUnsafeNTPath {
700
+ if attributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
701
+ if !SetFileAttributesW( $0, attributes & ~ FILE_ATTRIBUTE_READONLY) {
702
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ entry] )
703
+ }
704
+ }
705
+
706
+ if attributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
707
+ if !RemoveDirectoryW( $0) {
708
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ entry] )
709
+ }
710
+ } else {
711
+ if !DeleteFileW( $0) {
712
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ entry] )
713
+ }
714
+ }
715
+ }
716
+ }
717
+ }
656
718
}
719
+ } catch {
720
+ if !shouldProceedAfterError( error, removingItemAtPath: directory, isURL: isURL) {
721
+ throw error
657
722
}
658
- } while FindNextFileW ( handle, & ffd)
659
- } catch {
660
- if !shouldProceedAfterError( error, removingItemAtPath: itemPath, isURL: isURL) {
661
- throw error
662
723
}
663
724
}
664
725
}
@@ -970,30 +1031,14 @@ extension FileManager {
970
1031
guard let _lastReturned else { return firstValidItem ( ) }
971
1032
972
1033
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
1034
+ try walk ( directory: _lastReturned) { entry, attributes in
1035
+ if entry == " . " || entry == " .. " { return }
1036
+ if _options. contains ( . skipsHiddenFiles) && attributes & FILE_ATTRIBUTE_HIDDEN == FILE_ATTRIBUTE_HIDDEN {
1037
+ return
992
1038
}
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)
1039
+ let isDirectory = attributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY && attributes & FILE_ATTRIBUTE_REPARSE_POINT != FILE_ATTRIBUTE_REPARSE_POINT
1040
+ _stack. append ( _lastReturned. appendingPathComponent ( entry, isDirectory: isDirectory) )
1041
+ }
997
1042
}
998
1043
999
1044
return firstValidItem ( )
0 commit comments