Skip to content

Implemented _getFileSystemRepresentation for Windows #2366

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 24, 2020
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
7 changes: 5 additions & 2 deletions CoreFoundation/URL.subproj/CFURL.c
Original file line number Diff line number Diff line change
Expand Up @@ -3965,10 +3965,13 @@ CF_EXPORT void __CFURLSetResourceInfoPtr(CFURLRef url, void *ptr) {
/* HFSPath<->URLPath functions at the bottom of the file */
static CFArrayRef WindowsPathToURLComponents(CFStringRef path, CFAllocatorRef alloc, Boolean isDir, Boolean isAbsolute) CF_RETURNS_RETAINED {
CFArrayRef tmp;
CFMutableStringRef mutablePath = CFStringCreateMutableCopy(alloc, 0, path);
CFMutableArrayRef urlComponents = NULL;
CFIndex i=0;

tmp = CFStringCreateArrayBySeparatingStrings(alloc, path, CFSTR("\\"));
// Since '/' is a valid Windows path separator, we convert / to \ before splitting
CFStringFindAndReplace(mutablePath, CFSTR("/"), CFSTR("\\"), CFRangeMake(0, CFStringGetLength(mutablePath)), 0);
tmp = CFStringCreateArrayBySeparatingStrings(alloc, mutablePath, CFSTR("\\"));
CFRelease(mutablePath);
urlComponents = CFArrayCreateMutableCopy(alloc, 0, tmp);
CFRelease(tmp);

Expand Down
29 changes: 12 additions & 17 deletions Foundation/FileManager+Win32.swift
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ extension FileManager {
var szDirectory: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(dwLength + 1))

GetCurrentDirectoryW(dwLength, &szDirectory)
return String(decodingCString: &szDirectory, as: UTF16.self)
return String(decodingCString: &szDirectory, as: UTF16.self).standardizingPath
}

@discardableResult
Expand Down Expand Up @@ -701,8 +701,8 @@ extension FileManager {
return true
}

internal func _lstatFile(atPath path: String, withFileSystemRepresentation fsRep: UnsafePointer<Int8>? = nil) throws -> stat {
let _fsRep: UnsafePointer<Int8>
internal func _lstatFile(atPath path: String, withFileSystemRepresentation fsRep: UnsafePointer<NativeFSRCharType>? = nil) throws -> stat {
let _fsRep: UnsafePointer<NativeFSRCharType>
if fsRep == nil {
_fsRep = try __fileSystemRepresentation(withPath: path)
} else {
Expand All @@ -714,15 +714,13 @@ extension FileManager {
}

var statInfo = stat()
let h = path.withCString(encodedAs: UTF16.self) {
CreateFileW(/*lpFileName=*/$0,
/*dwDesiredAccess=*/DWORD(0),
/*dwShareMode=*/DWORD(FILE_SHARE_READ),
/*lpSecurityAttributes=*/nil,
/*dwCreationDisposition=*/DWORD(OPEN_EXISTING),
/*dwFlagsAndAttributes=*/DWORD(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS),
/*hTemplateFile=*/nil)
}
let h = CreateFileW(_fsRep,
/*dwDesiredAccess=*/DWORD(0),
DWORD(FILE_SHARE_READ),
/*lpSecurityAttributes=*/nil,
DWORD(OPEN_EXISTING),
DWORD(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS),
/*hTemplateFile=*/nil)
if h == INVALID_HANDLE_VALUE {
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
}
Expand Down Expand Up @@ -858,7 +856,7 @@ extension FileManager {
}

internal func _updateTimes(atPath path: String,
withFileSystemRepresentation fsr: UnsafePointer<Int8>,
withFileSystemRepresentation fsr: UnsafePointer<NativeFSRCharType>,
creationTime: Date? = nil,
accessTime: Date? = nil,
modificationTime: Date? = nil) throws {
Expand All @@ -869,10 +867,7 @@ extension FileManager {
var mtime: FILETIME =
FILETIME(from: time_t((modificationTime ?? stat.lastModificationDate).timeIntervalSince1970))

let hFile: HANDLE = String(utf8String: fsr)!.withCString(encodedAs: UTF16.self) {
CreateFileW($0, DWORD(GENERIC_WRITE), DWORD(FILE_SHARE_WRITE),
nil, DWORD(OPEN_EXISTING), 0, nil)
}
let hFile = CreateFileW(fsr, DWORD(GENERIC_WRITE), DWORD(FILE_SHARE_WRITE), nil, DWORD(OPEN_EXISTING), 0, nil)
if hFile == INVALID_HANDLE_VALUE {
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
}
Expand Down
41 changes: 34 additions & 7 deletions Foundation/FileManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ import CoreFoundation
import MSVCRT
#endif

#if os(Windows)
internal typealias NativeFSRCharType = WCHAR
let NativeFSREncoding = String.Encoding.utf16LittleEndian.rawValue
#else
internal typealias NativeFSRCharType = CChar
let NativeFSREncoding = String.Encoding.utf8.rawValue
#endif

open class FileManager : NSObject {

/* Returns the default singleton instance.
Expand Down Expand Up @@ -383,7 +391,12 @@ open class FileManager : NSObject {
#elseif os(Linux) || os(Android) || os(Windows)
let modeT = number.uint32Value
#endif
guard chmod(fsRep, mode_t(modeT)) == 0 else {
#if os(Windows)
let result = _wchmod(fsRep, mode_t(modeT))
#else
let result = chmod(fsRep, mode_t(modeT))
#endif
guard result == 0 else {
throw _NSErrorWithErrno(errno, reading: false, path: path)
}

Expand Down Expand Up @@ -1021,15 +1034,29 @@ open class FileManager : NSObject {
*/
open func fileSystemRepresentation(withPath path: String) -> UnsafePointer<Int8> {
precondition(path != "", "Empty path argument")
#if os(Windows)
// On Windows, the internal _fileSystemRepresentation returns
// UTF16 encoded data, so we need to re-encode the result as
// UTF8 before returning.
return try! _fileSystemRepresentation(withPath: path) {
String(decodingCString: $0, as: UTF16.self).withCString() {
let sz = strnlen($0, Int(MAX_PATH))
let buf = UnsafeMutablePointer<Int8>.allocate(capacity: sz + 1)
buf.initialize(from: $0, count: sz + 1)
return UnsafePointer(buf)
}
}
#else
return try! __fileSystemRepresentation(withPath: path)
#endif
}

internal func __fileSystemRepresentation(withPath path: String) throws -> UnsafePointer<Int8> {
internal func __fileSystemRepresentation(withPath path: String) throws -> UnsafePointer<NativeFSRCharType> {
let len = CFStringGetMaximumSizeOfFileSystemRepresentation(path._cfObject)
if len != kCFNotFound {
let buf = UnsafeMutablePointer<Int8>.allocate(capacity: len)
let buf = UnsafeMutablePointer<NativeFSRCharType>.allocate(capacity: len)
buf.initialize(repeating: 0, count: len)
if path._nsObject.getFileSystemRepresentation(buf, maxLength: len) {
if path._nsObject._getFileSystemRepresentation(buf, maxLength: len) {
return UnsafePointer(buf)
}
buf.deinitialize(count: len)
Expand All @@ -1038,13 +1065,13 @@ open class FileManager : NSObject {
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInvalidFileName.rawValue, userInfo: [NSFilePathErrorKey: path])
}

internal func _fileSystemRepresentation<ResultType>(withPath path: String, _ body: (UnsafePointer<Int8>) throws -> ResultType) throws -> ResultType {
internal func _fileSystemRepresentation<ResultType>(withPath path: String, _ body: (UnsafePointer<NativeFSRCharType>) throws -> ResultType) throws -> ResultType {
let fsRep = try __fileSystemRepresentation(withPath: path)
defer { fsRep.deallocate() }
return try body(fsRep)
}

internal func _fileSystemRepresentation<ResultType>(withPath path1: String, andPath path2: String, _ body: (UnsafePointer<Int8>, UnsafePointer<Int8>) throws -> ResultType) throws -> ResultType {
internal func _fileSystemRepresentation<ResultType>(withPath path1: String, andPath path2: String, _ body: (UnsafePointer<NativeFSRCharType>, UnsafePointer<NativeFSRCharType>) throws -> ResultType) throws -> ResultType {
let fsRep1 = try __fileSystemRepresentation(withPath: path1)
defer { fsRep1.deallocate() }
let fsRep2 = try __fileSystemRepresentation(withPath: path2)
Expand All @@ -1058,7 +1085,7 @@ open class FileManager : NSObject {
open func string(withFileSystemRepresentation str: UnsafePointer<Int8>, length len: Int) -> String {
return NSString(bytes: str, length: len, encoding: String.Encoding.utf8.rawValue)!._swiftObject
}

/* -replaceItemAtURL:withItemAtURL:backupItemName:options:resultingItemURL:error: is for developers who wish to perform a safe-save without using the full NSDocument machinery that is available in the AppKit.

The `originalItemURL` is the item being replaced.
Expand Down
80 changes: 74 additions & 6 deletions Foundation/NSPathUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public func NSTemporaryDirectory() -> String {
guard GetTempPathW(DWORD(wszPath.count), &wszPath) <= cchLength else {
preconditionFailure("GetTempPathW mutation race")
}
return String(decodingCString: wszPath, as: UTF16.self)
return String(decodingCString: wszPath, as: UTF16.self).standardizingPath
#else
#if canImport(Darwin)
let safe_confstr = { (name: Int32, buf: UnsafeMutablePointer<Int8>?, len: Int) -> Int in
Expand Down Expand Up @@ -348,14 +348,32 @@ extension NSString {

return result
}


#if os(Windows)
// Convert to a posix style '/' separated path
internal var unixPath: String {
var droppedPrefix = self as String
// If there is anything before the drive letter,
// e.g. "\\?\, \\host\, \??\", remove it
if isAbsolutePath, let idx = droppedPrefix.firstIndex(of: ":") {
droppedPrefix.removeSubrange(..<droppedPrefix.index(before: idx))
}
let slashesConverted = String(droppedPrefix.map({ $0 == "\\" ? "/" : $0 }))
let compressTrailing = slashesConverted._stringByFixingSlashes(stripTrailing: false)
return compressTrailing
}
#endif

public var standardizingPath: String {
#if os(Windows)
let expanded = unixPath.expandingTildeInPath
#else
let expanded = expandingTildeInPath
var resolved = expanded._bridgeToObjectiveC().resolvingSymlinksInPath
#endif
let resolved = expanded._bridgeToObjectiveC().resolvingSymlinksInPath

let automount = "/var/automount"
resolved = resolved._tryToRemovePathPrefix(automount) ?? resolved
return resolved
return resolved._tryToRemovePathPrefix(automount) ?? resolved
}

public var resolvingSymlinksInPath: String {
Expand Down Expand Up @@ -554,11 +572,61 @@ extension NSString {
}

public func getFileSystemRepresentation(_ cname: UnsafeMutablePointer<Int8>, maxLength max: Int) -> Bool {
#if os(Windows)
let fsr = UnsafeMutablePointer<WCHAR>.allocate(capacity: max)
defer { fsr.deallocate() }
guard _getFileSystemRepresentation(fsr, maxLength: max) else { return false }
return String(decodingCString: fsr, as: UTF16.self).withCString() {
let chars = strnlen_s($0, max)
guard chars < max else { return false }
cname.assign(from: $0, count: chars + 1)
return true
}
#else
return _getFileSystemRepresentation(cname, maxLength: max)
#endif
}

internal func _getFileSystemRepresentation(_ cname: UnsafeMutablePointer<NativeFSRCharType>, maxLength max: Int) -> Bool {
guard self.length > 0 else {
return false
}

#if os(Windows)
var fsr = self._swiftObject
let idx = fsr.startIndex

// If we have an RFC 8089 style path e.g. `/[drive-letter]:/...`, drop the
// leading /, otherwise, a leading slash indicates a rooted path on the
// drive for the current working directory
if fsr.count >= 3 && fsr[idx] == "/" && fsr[fsr.index(after: idx)].isLetter && fsr[fsr.index(idx, offsetBy: 2)] == ":" {
fsr.removeFirst()
}

// Windows APIS that go through the path parser can handle
// forward slashes in paths. However, symlinks created with
// forward slashes do not resolve properly, so we normalize
// the path separators anyways.
fsr = fsr.replacingOccurrences(of: "/", with: "\\")

// Drop trailing slashes unless it follows a drive letter. On
// Windows the path `C:\` indicates the root directory of the
// `C:` drive. The path `C:` indicates the current working
// directory on the `C:` drive.
while fsr.count > 1
&& (fsr[fsr.index(before: fsr.endIndex)] == "\\")
&& !(fsr.count == 3 && fsr[fsr.index(fsr.endIndex, offsetBy: -2)] == ":") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish there was a more elegant way to write this.

fsr.removeLast()
}

return fsr.withCString(encodedAs: UTF16.self) {
let wchars = wcsnlen_s($0, max)
guard wchars < max else { return false }
cname.assign(from: $0, count: wchars + 1)
return true
}
#else
return CFStringGetFileSystemRepresentation(self._cfObject, cname, max)
#endif
}

}
Expand Down
33 changes: 21 additions & 12 deletions Foundation/NSURL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ private func _standardizedPath(_ path: String) -> String {
if !path.isAbsolutePath {
return path._nsObject.standardizingPath
}
#if os(Windows)
return path.unixPath
#else
return path
#endif
}

internal func _pathComponents(_ path: String?) -> [String]? {
Expand Down Expand Up @@ -335,7 +339,7 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying {
let thePath = _standardizedPath(path)

var isDir: ObjCBool = false
if thePath.hasSuffix("/") {
if validPathSeps.contains(where: { thePath.hasSuffix(String($0)) }) {
isDir = true
} else {
let absolutePath: String
Expand All @@ -356,16 +360,9 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying {
}

public init(fileURLWithPath path: String) {
let thePath: String
let pathString = NSString(string: path)
if !pathString.isAbsolutePath {
thePath = pathString.standardizingPath
} else {
thePath = path
}

let thePath = _standardizedPath(path)
var isDir: ObjCBool = false
if thePath.hasSuffix("/") {
if validPathSeps.contains(where: { thePath.hasSuffix(String($0)) }) {
isDir = true
} else {
if !FileManager.default.fileExists(atPath: path, isDirectory: &isDir) {
Expand Down Expand Up @@ -542,7 +539,19 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying {

open var path: String? {
let absURL = CFURLCopyAbsoluteURL(_cfObject)
return CFURLCopyFileSystemPath(absURL, kCFURLPlatformPathStyle)?._swiftObject
guard var url = CFURLCopyFileSystemPath(absURL, kCFURLPOSIXPathStyle)?._swiftObject else {
return nil
}
#if os(Windows)
// Per RFC 8089:E.2, if we have an absolute Windows/DOS path
// we can begin the url with a drive letter rather than a '/'
let scalars = Array(url.unicodeScalars)
if isFileURL, url.isAbsolutePath,
scalars.count >= 3, scalars[0] == "/", scalars[2] == ":" {
url.removeFirst()
}
#endif
return url
}

open var fragment: String? {
Expand All @@ -559,7 +568,7 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying {

// The same as path if baseURL is nil
open var relativePath: String? {
return CFURLCopyFileSystemPath(_cfObject, kCFURLPlatformPathStyle)?._swiftObject
return CFURLCopyFileSystemPath(_cfObject, kCFURLPOSIXPathStyle)?._swiftObject
}

/* Determines if a given URL string's path represents a directory (i.e. the path component in the URL string ends with a '/' character). This does not check the resource the URL refers to.
Expand Down
Loading