Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions Sources/SWBUtil/FSProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -757,16 +757,15 @@ class LocalFS: FSProxy, @unchecked Sendable {
}

func realpath(_ path: Path) throws -> Path {
#if os(Windows)
guard exists(path) else {
if path.isAbsolute && !exists(path) {
throw POSIXError(ENOENT, context: "realpath", path.str)
}
return Path(path.str.standardizingPath)
#else
guard let result = SWBLibc.realpath(path.str, nil) else { throw POSIXError(errno, context: "realpath", path.str) }
defer { free(result) }
return Path(String(cString: result))
#endif
let url = URL(fileURLWithPath: path.str)
let values = try url.resolvingSymlinksInPath().resourceValues(forKeys: Set([URLResourceKey.canonicalPathKey]))
guard let canonicalPath = values.canonicalPath else {
throw POSIXError(ENOENT, context: "realpath", path.str)
}
return try Path(canonicalPath.canonicalPathRepresentation)
}

func isOnPotentiallyRemoteFileSystem(_ path: Path) -> Bool {
Expand Down
33 changes: 33 additions & 0 deletions Sources/SWBUtil/Path.swift
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,39 @@ extension Path {
}
}

extension String {
@_spi(Testing) public var canonicalPathRepresentation: String {
get throws {
#if os(Windows)
return try withCString(encodedAs: UTF16.self) { platformPath in
return try platformPath.withCanonicalPathRepresentation { canonicalPath in
return String(decodingCString: canonicalPath, as: UTF16.self)
}
}
#else
return self
#endif
}
}
}

extension Path {
@_spi(Testing) public var canonicalPathRepresentation: String {
get throws {
#if os(Windows)
return try withPlatformString { platformPath in
return try platformPath.withCanonicalPathRepresentation { canonicalPath in
return String(decodingCString: canonicalPath, as: UTF16.self)
}
}
#else
return str
#endif
}
}
}


/// A wrapper for a string which is used to identify an absolute path on the file system.
public struct AbsolutePath: Hashable, Equatable, Serializable, Sendable {
public let path: Path
Expand Down
41 changes: 41 additions & 0 deletions Tests/SWBUtilTests/FSProxyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ import SWBLibc
import SWBTestSupport
@_spi(TestSupport) import SWBUtil

#if canImport(System)
public import System
#else
public import SystemPackage
#endif

@Suite fileprivate struct FSProxyTests {

#if !os(Windows)
Expand Down Expand Up @@ -1333,6 +1339,41 @@ import SWBTestSupport
#expect(try fs.read(dir.join("foo")) == ByteString(encodingAsUTF8: "a"))
}
}

@Test(.requireHostOS(.windows))
func realpathWindows() async throws {
let fs = localFS
let windir = try #require(getEnvironmentVariable("WINDIR"))
do {
// Case-insensitive comparison because WINDIR might be C:\WINDOWS while the actual path is C:\Windows
// The main thing is the \\?\ prefix handling
#expect(try fs.realpath(Path(windir)).str.caseInsensitiveCompare(windir) == .orderedSame)
#expect(try fs.realpath(Path(#"\\?\"# + windir)).str.caseInsensitiveCompare(windir) == .orderedSame)
}

do {
let root = Path(windir).drive
#expect(try fs.realpath(root.join("Program Files")).str.caseInsensitiveCompare(root.join("Program Files").str) == .orderedSame)

if !fs.exists(root.join("Progra~1")) {
withKnownIssue {
Issue.record("8.3 filenames are likely disabled in this environment (running in a container?)")
}
return
}

#expect(try fs.realpath(root.join("Progra~1")).str.caseInsensitiveCompare(root.join("Program Files").str) == .orderedSame)
#expect(try fs.realpath(Path(#"\\?\"# + root.join("Progra~1").str)).str.caseInsensitiveCompare(root.join("Program Files").str) == .orderedSame)
}
}
}

fileprivate extension Path {
var drive: Path {
var fp = FilePath(str)
fp.components.removeAll()
return Path(fp.string).withTrailingSlash
}
}

/// Helper method to test file tree removal method on the given file system.
Expand Down
34 changes: 1 addition & 33 deletions Tests/SWBUtilTests/PathWindowsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import Testing
import SWBTestSupport
import SWBUtil
@_spi(Testing) import SWBUtil

@Suite(.requireHostOS(.windows))
fileprivate struct PathWindowsTests {
Expand Down Expand Up @@ -58,35 +58,3 @@ fileprivate struct PathWindowsTests {
#expect(try Path(current.str.prefix(2) + String(repeating: "foo/bar/baz/", count: 22)).canonicalPathRepresentation == "\\\\?\\" + current.join(String(repeating: "\\foo\\bar\\baz", count: 22)).str)
}
}

fileprivate extension String {
var canonicalPathRepresentation: String {
get throws {
#if os(Windows)
return try withCString(encodedAs: UTF16.self) { platformPath in
return try platformPath.withCanonicalPathRepresentation { canonicalPath in
return String(decodingCString: canonicalPath, as: UTF16.self)
}
}
#else
return self
#endif
}
}
}

fileprivate extension Path {
var canonicalPathRepresentation: String {
get throws {
#if os(Windows)
return try withPlatformString { platformPath in
return try platformPath.withCanonicalPathRepresentation { canonicalPath in
return String(decodingCString: canonicalPath, as: UTF16.self)
}
}
#else
return str
#endif
}
}
}