1313import let WinSDK. INVALID_FILE_ATTRIBUTES
1414import WinSDK
1515
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+
16102internal func joinPath( prefix: String , suffix: String ) -> String {
17103 var pszPath : PWSTR ?
18104
@@ -198,28 +284,13 @@ extension FileManager {
198284 }
199285
200286 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] )
203289 }
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) }
212290
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) )
223294 }
224295 }
225296
@@ -239,13 +310,13 @@ extension FileManager {
239310 }
240311
241312 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
246319 }
247- return faAttributes
248- }
249320 }
250321
251322 internal func _attributesOfFileSystemIncludingBlockSize( forPath path: String ) throws -> ( attributes: [ FileAttributeKey : Any ] , blockSize: UInt64 ? ) {
@@ -571,94 +642,83 @@ extension FileManager {
571642 return
572643 }
573644
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) {
582648 throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: false , paths: [ path] )
583- } else {
584- throw error
585649 }
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)
621650
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] )
627654 }
628- defer { FindClose ( handle ) }
655+ }
629656
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] )
635665 }
666+ }
667+ return
668+ }
636669
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
644675 }
645676
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+ }
656717 }
718+ } catch {
719+ if !shouldProceedAfterError( error, removingItemAtPath: directory, isURL: isURL) {
720+ throw error
657721 }
658- } while FindNextFileW ( handle, & ffd)
659- } catch {
660- if !shouldProceedAfterError( error, removingItemAtPath: itemPath, isURL: isURL) {
661- throw error
662722 }
663723 }
664724 }
@@ -970,30 +1030,14 @@ extension FileManager {
9701030 guard let _lastReturned else { return firstValidItem ( ) }
9711031
9721032 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
9921037 }
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+ }
9971041 }
9981042
9991043 return firstValidItem ( )
0 commit comments