From c13a770e288741f9919da26750d5e09f1eb6ab36 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Tue, 23 Feb 2021 22:27:55 +0800 Subject: [PATCH 1/8] Handle empty path on Windows --- Sources/TSCBasic/Path.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index 0624ea75..fa884c73 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -449,12 +449,15 @@ private struct UNIXPath: Path { var dirname: String { #if os(Windows) + guard string != "" else { + return "." + } let fsr: UnsafePointer = string.fileSystemRepresentation defer { fsr.deallocate() } let path: String = String(cString: fsr) return path.withCString(encodedAs: UTF16.self) { - let data = UnsafeMutablePointer(mutating: $0) + let data = UnsafeMutaßlePointer(mutating: $0) PathCchRemoveFileSpec(data, path.count) return String(decodingCString: data, as: UTF16.self) } @@ -685,6 +688,9 @@ private struct UNIXPath: Path { init(validatingAbsolutePath path: String) throws { #if os(Windows) + guard path != "" else { + throw PathValidationError.invalidAbsolutePath(path) + } let fsr: UnsafePointer = path.fileSystemRepresentation defer { fsr.deallocate() } @@ -707,6 +713,9 @@ private struct UNIXPath: Path { init(validatingRelativePath path: String) throws { #if os(Windows) + guard path != "" else { + self.init(normalizingRelativePath: path) + } let fsr: UnsafePointer = path.fileSystemRepresentation defer { fsr.deallocate() } From 4aff4de8371329f8d6c74934c5fae9c5811d40d3 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Wed, 24 Feb 2021 13:14:46 +0800 Subject: [PATCH 2/8] Fix normalizing paths --- Sources/TSCBasic/Path.swift | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index fa884c73..b582813f 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -449,18 +449,20 @@ private struct UNIXPath: Path { var dirname: String { #if os(Windows) - guard string != "" else { - return "." - } let fsr: UnsafePointer = string.fileSystemRepresentation defer { fsr.deallocate() } let path: String = String(cString: fsr) - return path.withCString(encodedAs: UTF16.self) { - let data = UnsafeMutaßlePointer(mutating: $0) + let dir: String = path.withCString(encodedAs: UTF16.self) { + let data = UnsafeMutablePointer(mutating: $0) PathCchRemoveFileSpec(data, path.count) return String(decodingCString: data, as: UTF16.self) } + // These two expressions represent for the current directory. + if dir == "\\" || dir == "" { + return "." + } + return dir #else // FIXME: This method seems too complicated; it should be simplified, // if possible, and certainly optimized (using UTF8View). @@ -547,11 +549,13 @@ private struct UNIXPath: Path { init(normalizingAbsolutePath path: String) { #if os(Windows) - var buffer: [WCHAR] = Array(repeating: 0, count: Int(MAX_PATH + 1)) + var result: PWSTR? + defer { LocalFree(result) } + _ = path.withCString(encodedAs: UTF16.self) { - PathCanonicalizeW(&buffer, $0) + PathAllocCanonicalize($0, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &result) } - self.init(string: String(decodingCString: buffer, as: UTF16.self)) + self.init(string: String(decodingCString: result!, as: UTF16.self)) #else precondition(path.first == "/", "Failure normalizing \(path), absolute paths should start with '/'") @@ -617,11 +621,18 @@ private struct UNIXPath: Path { init(normalizingRelativePath path: String) { #if os(Windows) - var buffer: [WCHAR] = Array(repeating: 0, count: Int(MAX_PATH + 1)) + var result: PWSTR? + defer { LocalFree(result) } + _ = path.replacingOccurrences(of: "/", with: "\\").withCString(encodedAs: UTF16.self) { - PathCanonicalizeW(&buffer, $0) + PathAllocCanonicalize($0, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &result) + } + + var canonicalized: String = String(decodingCString: result!, as: UTF16.self) + if canonicalized == "" || canonicalized == "\\" { + canonicalized = "." } - self.init(string: String(decodingCString: buffer, as: UTF16.self)) + self.init(string: canonicalized) #else precondition(path.first != "/") @@ -715,6 +726,7 @@ private struct UNIXPath: Path { #if os(Windows) guard path != "" else { self.init(normalizingRelativePath: path) + return } let fsr: UnsafePointer = path.fileSystemRepresentation defer { fsr.deallocate() } From c048ed9bd69897e3509808d9645a698de7fac0f4 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Thu, 25 Feb 2021 01:48:24 +0800 Subject: [PATCH 3/8] Minor fix --- Sources/TSCBasic/Path.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index b582813f..1a8bb3c7 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -458,8 +458,8 @@ private struct UNIXPath: Path { PathCchRemoveFileSpec(data, path.count) return String(decodingCString: data, as: UTF16.self) } - // These two expressions represent for the current directory. - if dir == "\\" || dir == "" { + // Blank path represents for the current directory. + if dir == "" { return "." } return dir From f3bc044437b8eff02cb8a4793c1d637fb9c28f62 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Thu, 25 Feb 2021 02:52:30 +0800 Subject: [PATCH 4/8] Manually normalizing relative path on Windows --- Sources/TSCBasic/Path.swift | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index 1a8bb3c7..c325c4af 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -620,21 +620,14 @@ private struct UNIXPath: Path { } init(normalizingRelativePath path: String) { + let pathSeparator: Character #if os(Windows) - var result: PWSTR? - defer { LocalFree(result) } - - _ = path.replacingOccurrences(of: "/", with: "\\").withCString(encodedAs: UTF16.self) { - PathAllocCanonicalize($0, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &result) - } - - var canonicalized: String = String(decodingCString: result!, as: UTF16.self) - if canonicalized == "" || canonicalized == "\\" { - canonicalized = "." - } - self.init(string: canonicalized) + pathSeparator = "\\" + let path = path.replacingOccurrences(of: "/", with: "\\") #else - precondition(path.first != "/") + pathSeparator = "/" + #endif + precondition(path.first != pathSeparator) // FIXME: Here we should also keep track of whether anything actually has // to be changed in the string, and if not, just return the existing one. @@ -644,7 +637,7 @@ private struct UNIXPath: Path { // the normalized string representation. var parts: [String] = [] var capacity = 0 - for part in path.split(separator: "/") { + for part in path.split(separator: pathSeparator) { switch part.count { case 0: // Ignore empty path components. @@ -683,7 +676,7 @@ private struct UNIXPath: Path { if let first = iter.next() { result.append(contentsOf: first) while let next = iter.next() { - result.append("/") + result.append(pathSeparator) result.append(contentsOf: next) } } @@ -694,7 +687,6 @@ private struct UNIXPath: Path { // If the result is empty, return `.`, otherwise we return it as a string. self.init(string: result.isEmpty ? "." : result) - #endif } init(validatingAbsolutePath path: String) throws { From 8fd9767cf23fb0439c3d326725bd8b863bad070f Mon Sep 17 00:00:00 2001 From: YR Chen Date: Thu, 25 Feb 2021 08:47:41 +0800 Subject: [PATCH 5/8] Style update --- Sources/TSCBasic/Path.swift | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index c325c4af..18bedaa1 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -12,11 +12,7 @@ import Foundation import WinSDK #endif -#if os(Windows) -private typealias PathImpl = UNIXPath -#else private typealias PathImpl = UNIXPath -#endif /// Represents an absolute file system path, independently of what (or whether /// anything at all) exists at that path in the file system at any given time. @@ -453,16 +449,12 @@ private struct UNIXPath: Path { defer { fsr.deallocate() } let path: String = String(cString: fsr) - let dir: String = path.withCString(encodedAs: UTF16.self) { + let result: String = path.withCString(encodedAs: UTF16.self) { let data = UnsafeMutablePointer(mutating: $0) PathCchRemoveFileSpec(data, path.count) return String(decodingCString: data, as: UTF16.self) } - // Blank path represents for the current directory. - if dir == "" { - return "." - } - return dir + return result.isEmpty ? "." : result #else // FIXME: This method seems too complicated; it should be simplified, // if possible, and certainly optimized (using UTF8View). @@ -621,12 +613,12 @@ private struct UNIXPath: Path { init(normalizingRelativePath path: String) { let pathSeparator: Character - #if os(Windows) +#if os(Windows) pathSeparator = "\\" let path = path.replacingOccurrences(of: "/", with: "\\") - #else +#else pathSeparator = "/" - #endif +#endif precondition(path.first != pathSeparator) // FIXME: Here we should also keep track of whether anything actually has @@ -690,7 +682,7 @@ private struct UNIXPath: Path { } init(validatingAbsolutePath path: String) throws { - #if os(Windows) +#if os(Windows) guard path != "" else { throw PathValidationError.invalidAbsolutePath(path) } @@ -702,7 +694,7 @@ private struct UNIXPath: Path { throw PathValidationError.invalidAbsolutePath(path) } self.init(normalizingAbsolutePath: path) - #else +#else switch path.first { case "/": self.init(normalizingAbsolutePath: path) @@ -711,11 +703,11 @@ private struct UNIXPath: Path { default: throw PathValidationError.invalidAbsolutePath(path) } - #endif +#endif } init(validatingRelativePath path: String) throws { - #if os(Windows) +#if os(Windows) guard path != "" else { self.init(normalizingRelativePath: path) return @@ -728,14 +720,14 @@ private struct UNIXPath: Path { throw PathValidationError.invalidRelativePath(path) } self.init(normalizingRelativePath: path) - #else +#else switch path.first { case "/", "~": throw PathValidationError.invalidRelativePath(path) default: self.init(normalizingRelativePath: path) } - #endif +#endif } func suffix(withDot: Bool) -> String? { From a279f539ed17bca76d7348b34771fd46956031d6 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Fri, 26 Feb 2021 00:32:36 +0800 Subject: [PATCH 6/8] Various improvements --- Sources/TSCBasic/Path.swift | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index 18bedaa1..27b0bfcd 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -434,12 +434,17 @@ private struct UNIXPath: Path { static let root = UNIXPath(string: "/") static func isValidComponent(_ name: String) -> Bool { +#if os(Windows) + if name.contains("\\") { + return false + } +#endif return name != "" && name != "." && name != ".." && !name.contains("/") } #if os(Windows) static func isAbsolutePath(_ path: String) -> Bool { - return !path.withCString(encodedAs: UTF16.self, PathIsRelativeW) + return !path.prenormalized().withCString(encodedAs: UTF16.self, PathIsRelativeW) } #endif @@ -541,13 +546,13 @@ private struct UNIXPath: Path { init(normalizingAbsolutePath path: String) { #if os(Windows) - var result: PWSTR? + var result: [WCHAR] = Array(repeating: 0, count: Int(MAX_PATH + 1)) defer { LocalFree(result) } - _ = path.withCString(encodedAs: UTF16.self) { - PathAllocCanonicalize($0, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &result) + _ = path.prenormalized().withCString(encodedAs: UTF16.self) { + PathCchCanonicalize($0, result.length, $0) } - self.init(string: String(decodingCString: result!, as: UTF16.self)) + self.init(string: String(decodingCString: result, as: UTF16.self)) #else precondition(path.first == "/", "Failure normalizing \(path), absolute paths should start with '/'") @@ -615,7 +620,7 @@ private struct UNIXPath: Path { let pathSeparator: Character #if os(Windows) pathSeparator = "\\" - let path = path.replacingOccurrences(of: "/", with: "\\") + let path = path.prenormalized() #else pathSeparator = "/" #endif @@ -683,7 +688,9 @@ private struct UNIXPath: Path { init(validatingAbsolutePath path: String) throws { #if os(Windows) - guard path != "" else { + // Explicitly handle the empty path, since retrieving + // `fileSystemRepresentation` of it is illegal. + guard !path.isEmpty else { throw PathValidationError.invalidAbsolutePath(path) } let fsr: UnsafePointer = path.fileSystemRepresentation @@ -708,7 +715,9 @@ private struct UNIXPath: Path { init(validatingRelativePath path: String) throws { #if os(Windows) - guard path != "" else { + // Explicitly handle the empty path, since retrieving + // `fileSystemRepresentation` of it is illegal. + guard !path.isEmpty else { self.init(normalizingRelativePath: path) return } @@ -946,3 +955,11 @@ private func mayNeedNormalization(absolute string: String) -> Bool { } return false } + +#if os(Windows) +fileprivate extension String { + func prenormalized() -> String { + return self.replacingOccurrences(of: "/", with: "\\") + } +} +#endif \ No newline at end of file From 9aef4453b50a83007252aa284ddba3dfb627e250 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Fri, 26 Feb 2021 12:11:31 +0800 Subject: [PATCH 7/8] Minor fixes --- Sources/TSCBasic/Path.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index 27b0bfcd..a7f8a7dc 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -444,7 +444,8 @@ private struct UNIXPath: Path { #if os(Windows) static func isAbsolutePath(_ path: String) -> Bool { - return !path.prenormalized().withCString(encodedAs: UTF16.self, PathIsRelativeW) + return !path.standardizingPathSeparator() + .withCString(encodedAs: UTF16.self, PathIsRelativeW) } #endif @@ -547,9 +548,8 @@ private struct UNIXPath: Path { init(normalizingAbsolutePath path: String) { #if os(Windows) var result: [WCHAR] = Array(repeating: 0, count: Int(MAX_PATH + 1)) - defer { LocalFree(result) } - _ = path.prenormalized().withCString(encodedAs: UTF16.self) { + _ = path.standardizingPathSeparator().withCString(encodedAs: UTF16.self) { PathCchCanonicalize($0, result.length, $0) } self.init(string: String(decodingCString: result, as: UTF16.self)) @@ -620,7 +620,7 @@ private struct UNIXPath: Path { let pathSeparator: Character #if os(Windows) pathSeparator = "\\" - let path = path.prenormalized() + let path = path.standardizingPathSeparator() #else pathSeparator = "/" #endif @@ -958,8 +958,8 @@ private func mayNeedNormalization(absolute string: String) -> Bool { #if os(Windows) fileprivate extension String { - func prenormalized() -> String { + func standardizingPathSeparator() -> String { return self.replacingOccurrences(of: "/", with: "\\") } } -#endif \ No newline at end of file +#endif From acc3eaa6d7b3e55ba803c9bd20f6762ffe67a02f Mon Sep 17 00:00:00 2001 From: YR Chen Date: Sun, 7 Mar 2021 21:26:04 +0800 Subject: [PATCH 8/8] Bug fixes --- Sources/TSCBasic/Path.swift | 6 +++++- Sources/TSCBasic/PathShims.swift | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index a7f8a7dc..ed2716cf 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -431,7 +431,11 @@ extension Path { private struct UNIXPath: Path { let string: String +#if os(Windows) + static let root = UNIXPath(string: "\\") +#else static let root = UNIXPath(string: "/") +#endif static func isValidComponent(_ name: String) -> Bool { #if os(Windows) @@ -550,7 +554,7 @@ private struct UNIXPath: Path { var result: [WCHAR] = Array(repeating: 0, count: Int(MAX_PATH + 1)) _ = path.standardizingPathSeparator().withCString(encodedAs: UTF16.self) { - PathCchCanonicalize($0, result.length, $0) + PathCchCanonicalize(&result, result.count, $0) } self.init(string: String(decodingCString: result, as: UTF16.self)) #else diff --git a/Sources/TSCBasic/PathShims.swift b/Sources/TSCBasic/PathShims.swift index bae0210d..982abd7a 100644 --- a/Sources/TSCBasic/PathShims.swift +++ b/Sources/TSCBasic/PathShims.swift @@ -26,6 +26,8 @@ public func resolveSymlinks(_ path: AbsolutePath) -> AbsolutePath { var resolved: URL = URL(fileURLWithPath: path.pathString) if let destination = try? FileManager.default.destinationOfSymbolicLink(atPath: path.pathString) { resolved = URL(fileURLWithPath: destination, relativeTo: URL(fileURLWithPath: path.pathString)) + } else { + return try! AbsolutePath(validating: path.pathString) } return resolved.standardized.withUnsafeFileSystemRepresentation {