From 2212d647e8e071c4fb7cb9a15aa843ceb0c4d9bf Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Fri, 16 Aug 2024 11:19:26 -0700 Subject: [PATCH] FileManager.fileExists(atPath:) should follow symlinks (#859) --- .../FileManager/FileManager+Files.swift | 15 +++++++++++---- .../FileManager/FileManagerTests.swift | 8 ++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift index fdd601020..b8cd50a4c 100644 --- a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift +++ b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift @@ -376,12 +376,19 @@ extension _FileManagerImpl { private func _fileExists(_ path: String) -> (exists: Bool, isDirectory: Bool) { #if os(Windows) guard !path.isEmpty else { return (false, false) } - return (try? path.withNTPathRepresentation { - var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = .init() - guard GetFileAttributesExW($0, GetFileExInfoStandard, &faAttributes) else { + return (try? path.withNTPathRepresentation { pwszPath in + let handle = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nil) + if handle == INVALID_HANDLE_VALUE { + return (false, false) + } + defer { CloseHandle(handle) } + + var info: BY_HANDLE_FILE_INFORMATION = BY_HANDLE_FILE_INFORMATION() + guard GetFileInformationByHandle(handle, &info) else { return (false, false) } - return (true, faAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY) + + return (true, info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY) }) ?? (false, false) #else path.withFileSystemRepresentation { rep -> (Bool, Bool) in diff --git a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift index 96d911a61..42247bf82 100644 --- a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift +++ b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift @@ -571,6 +571,9 @@ final class FileManagerTests : XCTestCase { "bar" } "other" + SymbolicLink("link_to_file", destination: "other") + SymbolicLink("link_to_dir", destination: "dir") + SymbolicLink("link_to_nonexistent", destination: "does_not_exist") }.test { #if FOUNDATION_FRAMEWORK var isDir: ObjCBool = false @@ -591,7 +594,12 @@ final class FileManagerTests : XCTestCase { XCTAssertTrue(isDirBool()) XCTAssertTrue($0.fileExists(atPath: "other", isDirectory: &isDir)) XCTAssertFalse(isDirBool()) + XCTAssertTrue($0.fileExists(atPath: "link_to_file", isDirectory: &isDir)) + XCTAssertFalse(isDirBool()) + XCTAssertTrue($0.fileExists(atPath: "link_to_dir", isDirectory: &isDir)) + XCTAssertTrue(isDirBool()) XCTAssertFalse($0.fileExists(atPath: "does_not_exist")) + XCTAssertFalse($0.fileExists(atPath: "link_to_nonexistent")) } }