1313import let WinSDK. INVALID_FILE_ATTRIBUTES
1414import WinSDK
1515
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+
16106internal func joinPath( prefix: String , suffix: String ) -> String {
17107 var pszPath : PWSTR ?
18108
@@ -198,28 +288,15 @@ extension FileManager {
198288 }
199289
200290 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) ] )
203295 }
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) }
212296
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) )
223300 }
224301 }
225302
@@ -239,13 +316,13 @@ extension FileManager {
239316 }
240317
241318 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
246325 }
247- return faAttributes
248- }
249326 }
250327
251328 internal func _attributesOfFileSystemIncludingBlockSize( forPath path: String ) throws -> ( attributes: [ FileAttributeKey : Any ] , blockSize: UInt64 ? ) {
@@ -571,94 +648,83 @@ extension FileManager {
571648 return
572649 }
573650
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) {
582654 throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
583- } else {
584- throw error
585655 }
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- }
602656
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] )
617660 }
618- dirStack. append ( itemPath)
619- var ffd : WIN32_FIND_DATAW = WIN32_FIND_DATAW ( )
620- let capacity = MemoryLayout . size ( ofValue: ffd. cFileName)
661+ }
621662
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+ }
627672 }
628- defer { FindClose ( handle) }
673+ return
674+ }
629675
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
635681 }
636682
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+ }
656723 }
724+ } catch {
725+ if !shouldProceedAfterError( error, removingItemAtPath: directory, isURL: isURL) {
726+ throw error
657727 }
658- } while FindNextFileW ( handle, & ffd)
659- } catch {
660- if !shouldProceedAfterError( error, removingItemAtPath: itemPath, isURL: isURL) {
661- throw error
662728 }
663729 }
664730 }
@@ -970,30 +1036,14 @@ extension FileManager {
9701036 guard let _lastReturned else { return firstValidItem ( ) }
9711037
9721038 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
9921043 }
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+ }
9971047 }
9981048
9991049 return firstValidItem ( )
0 commit comments