Skip to content

Commit 5ee4aee

Browse files
committed
Foundation: refactor the use of FindFirstFileW
Extract the use of `FindFirstFileW` into a helper function to reduce the places where we have to restructure the paths converting them across encodings and altering the representation. Take the opportunity to correct a small issue with the recursion in `_removeItem` which would form an invalid path using mixed mode separators which would fail when being called with the added entry. This is motivated by DocC support on Windows.
1 parent c1bbc13 commit 5ee4aee

File tree

1 file changed

+61
-79
lines changed

1 file changed

+61
-79
lines changed

Sources/Foundation/FileManager+Win32.swift

Lines changed: 61 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,32 @@
1313
import let WinSDK.INVALID_FILE_ATTRIBUTES
1414
import WinSDK
1515

16+
private func walk(directory path: URL, _ body: (String, DWORD) throws -> Void) rethrows {
17+
try path.withUnsafeFileSystemRepresentation {
18+
try "\\\\?\\\(String(cString: $0!))\\*".withCString(encodedAs: UTF16.self) {
19+
var ffd: WIN32_FIND_DATAW = .init()
20+
let capacity = MemoryLayout.size(ofValue: ffd.cFileName) / MemoryLayout<WCHAR>.size
21+
22+
let hFind: HANDLE = FindFirstFileW($0, &ffd)
23+
if hFind == INVALID_HANDLE_VALUE {
24+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path.path])
25+
}
26+
27+
defer { FindClose(hFind) }
28+
29+
repeat {
30+
let entry: String = withUnsafePointer(to: ffd.cFileName) {
31+
$0.withMemoryRebound(to: WCHAR.self, capacity: capacity) {
32+
String(decodingCString: $0, as: UTF16.self)
33+
}
34+
}
35+
36+
try body(entry, ffd.dwFileAttributes)
37+
} while FindNextWileW(hFind, &ffd)
38+
}
39+
}
40+
}
41+
1642
internal func joinPath(prefix: String, suffix: String) -> String {
1743
var pszPath: PWSTR?
1844

@@ -197,29 +223,14 @@ extension FileManager {
197223
}
198224
}
199225

200-
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)])
226+
internal func _contentsOfDir(atPath path: String, _ closure: (String, Int32) throws -> Void) throws {
227+
guard !path.isEmpty else {
228+
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInvalidFileName.rawValue, userInfo: [NSFilePathErrorKey:NSString(path)])
203229
}
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) }
212230

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)
231+
try walk(directory: URL(fileURLWithPath: path, isDirectory: true)) { (entry, attributes) in
232+
if entry == "." || entry == ".." { return }
233+
try closure(entry.standardizingPath, Int32(attributes))
223234
}
224235
}
225236

@@ -616,46 +627,34 @@ extension FileManager {
616627
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [itemPath])
617628
}
618629
dirStack.append(itemPath)
619-
var ffd: WIN32_FIND_DATAW = WIN32_FIND_DATAW()
620-
let capacity = MemoryLayout.size(ofValue: ffd.cFileName)
621630

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])
627-
}
628-
defer { FindClose(handle) }
629-
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-
}
635-
}
631+
let root = URL(fileURLWithPath: itemPath, isDirectory: true)
632+
try walk(directory: root) { (entry, attributes) in
633+
if entry == "." || entry == ".." { return }
636634

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-
}
635+
let isDirectory = attributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY && attributes & FILE_ATTRIBUTE_REPARSE_POINT == 0
636+
let path = root.appendingPathComponent(entry, isDirectory: isDirectory)
637+
if isDirectory {
638+
dirStack.append(path.path)
650639
} 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])
640+
path.withUnsafeFileSystemRepresentation {
641+
guard alreadyConfirmed || shouldRemoveItemAtPath(path.path, isURL: isURL) else {
642+
return
643+
}
644+
645+
"\\\\?\\\(String(cString: $0!))".withCString(encodedAs: UTF16.self) {
646+
if attributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
647+
if !SetFileAttributesW($0, attributes & ~FILE_ATTRIBUTE_READONLY) {
648+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [entry])
649+
}
650+
}
651+
if !DeleteFileW($0) {
652+
throw _NSErrorWithWindowsError(GeteLastErrro(), reading: false, paths: [entry])
653+
}
654+
}
656655
}
657656
}
658-
} while FindNextFileW(handle, &ffd)
657+
}
659658
} catch {
660659
if !shouldProceedAfterError(error, removingItemAtPath: itemPath, isURL: isURL) {
661660
throw error
@@ -956,31 +955,14 @@ extension FileManager {
956955
}
957956

958957
if _lastReturned.hasDirectoryPath && (level == 0 || !_options.contains(.skipsSubdirectoryDescendants)) {
959-
var ffd = WIN32_FIND_DATAW()
960-
let capacity = MemoryLayout.size(ofValue: ffd.cFileName)
961-
962-
let handle = (try? FileManager.default._fileSystemRepresentation(withPath: _lastReturned.path + "\\*") {
963-
FindFirstFileW($0, &ffd)
964-
}) ?? INVALID_HANDLE_VALUE
965-
if handle == INVALID_HANDLE_VALUE { return firstValidItem() }
966-
defer { FindClose(handle) }
967-
968-
repeat {
969-
let file = withUnsafePointer(to: &ffd.cFileName) {
970-
$0.withMemoryRebound(to: WCHAR.self, capacity: capacity) {
971-
String(decodingCString: $0, as: UTF16.self)
972-
}
973-
}
974-
if file == "." || file == ".." { continue }
975-
if _options.contains(.skipsHiddenFiles) &&
976-
ffd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN == FILE_ATTRIBUTE_HIDDEN {
977-
continue
978-
}
979-
980-
let isDirectory = ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY
958+
try walk(directory: _lastReturned) { (entry, attributes) in
959+
if entry == "." || entry == ".." { return }
960+
if _options.contains(.skipsHiddenFiles) && attributes & FILE_ATTRIBUTE_HIDDEN == FIND_ATTRIBUTE_HIDDEN { return }
961+
let isDirectory = attributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY && attributes & FILE_ATTRIBUTE_REPARSE_POINT == 0
981962
_stack.append(_lastReturned.appendingPathComponent(file, isDirectory: isDirectory))
982-
} while FindNextFileW(handle, &ffd)
963+
}
983964
}
965+
984966
return firstValidItem()
985967
}
986968

0 commit comments

Comments
 (0)