Skip to content

Commit 50d8710

Browse files
committed
Implemented _getFileSystemRepresentation for Windows
Modified the helper _getFileSystemRepresentation functions to return a UTF16 Windows style path.
1 parent c79e7d6 commit 50d8710

File tree

4 files changed

+135
-35
lines changed

4 files changed

+135
-35
lines changed

Foundation/FileManager+Win32.swift

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -637,8 +637,8 @@ extension FileManager {
637637
NSUnimplemented()
638638
}
639639

640-
internal func _lstatFile(atPath path: String, withFileSystemRepresentation fsRep: UnsafePointer<Int8>? = nil) throws -> stat {
641-
let _fsRep: UnsafePointer<Int8>
640+
internal func _lstatFile(atPath path: String, withFileSystemRepresentation fsRep: UnsafePointer<NativeFSRCharType>? = nil) throws -> stat {
641+
let _fsRep: UnsafePointer<NativeFSRCharType>
642642
if fsRep == nil {
643643
_fsRep = try __fileSystemRepresentation(withPath: path)
644644
} else {
@@ -650,15 +650,13 @@ extension FileManager {
650650
}
651651

652652
var statInfo = stat()
653-
let h = path.withCString(encodedAs: UTF16.self) {
654-
CreateFileW(/*lpFileName=*/$0,
655-
/*dwDesiredAccess=*/DWORD(0),
656-
/*dwShareMode=*/DWORD(FILE_SHARE_READ),
657-
/*lpSecurityAttributes=*/nil,
658-
/*dwCreationDisposition=*/DWORD(OPEN_EXISTING),
659-
/*dwFlagsAndAttributes=*/DWORD(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS),
660-
/*hTemplateFile=*/nil)
661-
}
653+
let h = CreateFileW(/*lpFileName=*/_fsRep,
654+
/*dwDesiredAccess=*/DWORD(0),
655+
/*dwShareMode=*/DWORD(FILE_SHARE_READ),
656+
/*lpSecurityAttributes=*/nil,
657+
/*dwCreationDisposition=*/DWORD(OPEN_EXISTING),
658+
/*dwFlagsAndAttributes=*/DWORD(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS),
659+
/*hTemplateFile=*/nil)
662660
if h == INVALID_HANDLE_VALUE {
663661
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
664662
}
@@ -715,21 +713,16 @@ extension FileManager {
715713
}
716714

717715
internal func _updateTimes(atPath path: String,
718-
withFileSystemRepresentation fsr: UnsafePointer<Int8>,
716+
withFileSystemRepresentation fsr: UnsafePointer<NativeFSRCharType>,
719717
creationTime: Date? = nil,
720718
accessTime: Date? = nil,
721719
modificationTime: Date? = nil) throws {
722720
let stat = try _lstatFile(atPath: path, withFileSystemRepresentation: fsr)
723721

724-
var atime: FILETIME =
725-
FILETIME(from: time_t((accessTime ?? stat.lastAccessDate).timeIntervalSince1970))
726-
var mtime: FILETIME =
727-
FILETIME(from: time_t((modificationTime ?? stat.lastModificationDate).timeIntervalSince1970))
722+
var atime = FILETIME(from: time_t((accessTime ?? stat.lastAccessDate).timeIntervalSince1970))
723+
var mtime = FILETIME(from: time_t((modificationTime ?? stat.lastModificationDate).timeIntervalSince1970))
728724

729-
let hFile: HANDLE = String(utf8String: fsr)!.withCString(encodedAs: UTF16.self) {
730-
CreateFileW($0, DWORD(GENERIC_WRITE), DWORD(FILE_SHARE_WRITE),
731-
nil, DWORD(OPEN_EXISTING), 0, nil)
732-
}
725+
let hFile = CreateFileW(fsr, DWORD(GENERIC_WRITE), DWORD(FILE_SHARE_WRITE), nil, DWORD(OPEN_EXISTING), 0, nil)
733726
if hFile == INVALID_HANDLE_VALUE {
734727
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
735728
}

Foundation/FileManager.swift

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ import CoreFoundation
2020
import MSVCRT
2121
#endif
2222

23+
#if os(Windows)
24+
internal typealias NativeFSRCharType = WCHAR
25+
let NativeFSREncoding = String.Encoding.utf16LittleEndian.rawValue
26+
#else
27+
internal typealias NativeFSRCharType = CChar
28+
let NativeFSREncoding = String.Encoding.utf8.rawValue
29+
#endif
30+
2331
open class FileManager : NSObject {
2432

2533
/* Returns the default singleton instance.
@@ -383,7 +391,12 @@ open class FileManager : NSObject {
383391
#elseif os(Linux) || os(Android) || os(Windows)
384392
let modeT = number.uint32Value
385393
#endif
386-
guard chmod(fsRep, mode_t(modeT)) == 0 else {
394+
#if os(Windows)
395+
let result = _wchmod(fsRep, mode_t(modeT))
396+
#else
397+
let result = chmod(fsRep, mode_t(modeT))
398+
#endif
399+
guard result == 0 else {
387400
throw _NSErrorWithErrno(errno, reading: false, path: path)
388401
}
389402

@@ -992,15 +1005,31 @@ open class FileManager : NSObject {
9921005
*/
9931006
open func fileSystemRepresentation(withPath path: String) -> UnsafePointer<Int8> {
9941007
precondition(path != "", "Empty path argument")
1008+
#if os(Windows)
1009+
return try! _fileSystemRepresentation(withPath: path) {
1010+
String(decodingCString: $0, as: UTF16.self).withCString() {
1011+
let sz = strnlen($0, Int(MAX_PATH))
1012+
let buf = UnsafeMutablePointer<Int8>.allocate(capacity: sz + 1)
1013+
buf.initialize(from: $0, count: sz + 1)
1014+
return UnsafePointer(buf)
1015+
}
1016+
}
1017+
#else
9951018
return try! __fileSystemRepresentation(withPath: path)
1019+
#endif
9961020
}
9971021

998-
internal func __fileSystemRepresentation(withPath path: String) throws -> UnsafePointer<Int8> {
1022+
internal func __fileSystemRepresentation(withPath path: String) throws -> UnsafePointer<NativeFSRCharType> {
9991023
let len = CFStringGetMaximumSizeOfFileSystemRepresentation(path._cfObject)
10001024
if len != kCFNotFound {
1001-
let buf = UnsafeMutablePointer<Int8>.allocate(capacity: len)
1025+
let buf = UnsafeMutablePointer<NativeFSRCharType>.allocate(capacity: len)
10021026
buf.initialize(repeating: 0, count: len)
1003-
if path._nsObject.getFileSystemRepresentation(buf, maxLength: len) {
1027+
#if os(Windows)
1028+
let result = path._nsObject._getFileSystemRepresentation(buf, maxLength: len)
1029+
#else
1030+
let result = path._nsObject.getFileSystemRepresentation(buf, maxLength: len)
1031+
#endif
1032+
if result {
10041033
return UnsafePointer(buf)
10051034
}
10061035
buf.deinitialize(count: len)
@@ -1009,13 +1038,13 @@ open class FileManager : NSObject {
10091038
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInvalidFileName.rawValue, userInfo: [NSFilePathErrorKey: path])
10101039
}
10111040

1012-
internal func _fileSystemRepresentation<ResultType>(withPath path: String, _ body: (UnsafePointer<Int8>) throws -> ResultType) throws -> ResultType {
1041+
internal func _fileSystemRepresentation<ResultType>(withPath path: String, _ body: (UnsafePointer<NativeFSRCharType>) throws -> ResultType) throws -> ResultType {
10131042
let fsRep = try __fileSystemRepresentation(withPath: path)
10141043
defer { fsRep.deallocate() }
10151044
return try body(fsRep)
10161045
}
10171046

1018-
internal func _fileSystemRepresentation<ResultType>(withPath path1: String, andPath path2: String, _ body: (UnsafePointer<Int8>, UnsafePointer<Int8>) throws -> ResultType) throws -> ResultType {
1047+
internal func _fileSystemRepresentation<ResultType>(withPath path1: String, andPath path2: String, _ body: (UnsafePointer<NativeFSRCharType>, UnsafePointer<NativeFSRCharType>) throws -> ResultType) throws -> ResultType {
10191048
let fsRep1 = try __fileSystemRepresentation(withPath: path1)
10201049
defer { fsRep1.deallocate() }
10211050
let fsRep2 = try __fileSystemRepresentation(withPath: path2)
@@ -1029,7 +1058,11 @@ open class FileManager : NSObject {
10291058
open func string(withFileSystemRepresentation str: UnsafePointer<Int8>, length len: Int) -> String {
10301059
return NSString(bytes: str, length: len, encoding: String.Encoding.utf8.rawValue)!._swiftObject
10311060
}
1032-
1061+
1062+
internal func _string(withFileSystemRepresentation str: UnsafePointer<NativeFSRCharType>, length len: Int) -> String {
1063+
return NSString(bytes: str, length: len * MemoryLayout<NativeFSRCharType>.size, encoding: NativeFSREncoding)!._swiftObject
1064+
}
1065+
10331066
/* -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.
10341067

10351068
The `originalItemURL` is the item being replaced.

Foundation/NSPathUtilities.swift

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,9 +344,26 @@ extension NSString {
344344
}
345345

346346
public var standardizingPath: String {
347+
#if os(Windows)
348+
// Convert to a posix style '/' separated path
349+
var converted = self as String
350+
if converted.isAbsolutePath {
351+
// If there is anything before the drive letter, e.g. "\\?\, \\host\, \??\", remove it
352+
if let idx = converted.firstIndex(of: ":") {
353+
converted.removeSubrange(..<converted.index(before: idx))
354+
}
355+
356+
if !(converted.starts(with: "/") || converted.starts(with: "\\")) {
357+
converted = "/" + converted
358+
}
359+
}
360+
converted = String(converted.map({ $0 == "\\" ? "/" : $0 }))
361+
let expanded = converted.expandingTildeInPath
362+
#else
347363
let expanded = expandingTildeInPath
364+
#endif
348365
var resolved = expanded._bridgeToObjectiveC().resolvingSymlinksInPath
349-
366+
350367
let automount = "/var/automount"
351368
resolved = resolved._tryToRemovePathPrefix(automount) ?? resolved
352369
return resolved
@@ -550,13 +567,61 @@ extension NSString {
550567
}
551568

552569
public func getFileSystemRepresentation(_ cname: UnsafeMutablePointer<Int8>, maxLength max: Int) -> Bool {
570+
#if os(Windows)
571+
let fsr = UnsafeMutablePointer<WCHAR>.allocate(capacity: max)
572+
defer { fsr.deallocate() }
573+
guard _getFileSystemRepresentation(fsr, maxLength: max) else { return false }
574+
let str = String(decodingCString: fsr, as: UTF16.self)
575+
return str.withCString() {
576+
let chars = strnlen_s($0, max)
577+
guard chars < max else {
578+
return false
579+
}
580+
cname.assign(from: $0, count: chars + 1)
581+
return true
582+
}
583+
#else
553584
guard self.length > 0 else {
554585
return false
555586
}
556-
557587
return CFStringGetFileSystemRepresentation(self._cfObject, cname, max)
588+
#endif
558589
}
559590

591+
#if os(Windows)
592+
internal func _getFileSystemRepresentation(_ cname: UnsafeMutablePointer<NativeFSRCharType>, maxLength max: Int) -> Bool {
593+
guard self.length > 0 else {
594+
return false
595+
}
596+
597+
var fsr = self._swiftObject
598+
let idx = fsr.startIndex
599+
// If we have an RFC 8089 style path /[drive-letter]:/....., drop the
600+
// leading /, otherwise, a leading slash indicates a rooted path on the
601+
// drive for the current working directory
602+
if fsr.count >= 3 && fsr[idx] == "/" && fsr[fsr.index(after: idx)].isLetter && fsr[fsr.index(idx, offsetBy: 2)] == ":" {
603+
fsr.removeFirst()
604+
}
605+
606+
// Drop trailing slashes unless it follows a drive letter
607+
while
608+
fsr.count > 1
609+
&& (fsr[fsr.index(before: fsr.endIndex)] == "\\" || fsr[fsr.index(before: fsr.endIndex)] == "/")
610+
&& !(fsr.count == 3 && fsr[fsr.index(fsr.endIndex, offsetBy: -2)] == ":") {
611+
fsr.removeLast()
612+
}
613+
614+
return fsr.withCString(encodedAs: UTF16.self) {
615+
let wchars = wcsnlen_s($0, max)
616+
guard wchars < max else {
617+
return false
618+
}
619+
cname.assign(from: $0, count: wchars + 1)
620+
return true
621+
}
622+
}
623+
#endif
624+
560625
}
561626

562627
extension FileManager {

Foundation/NSURL.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,16 @@ internal let kCFURLPlatformPathStyle = kCFURLPOSIXPathStyle
2929
#endif
3030

3131
private func _standardizedPath(_ path: String) -> String {
32+
#if os(Windows)
33+
// Since Windows needs to support various types of paths, always attempt to
34+
// standardize what ever we're passed
35+
return path._nsObject.standardizingPath
36+
#else
3237
if !path.isAbsolutePath {
3338
return path._nsObject.standardizingPath
3439
}
3540
return path
41+
#endif
3642
}
3743

3844
internal func _pathComponents(_ path: String?) -> [String]? {
@@ -360,12 +366,15 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying {
360366
public init(fileURLWithPath path: String) {
361367
let thePath: String
362368
let pathString = NSString(string: path)
363-
if !pathString.isAbsolutePath {
364-
thePath = pathString.standardizingPath
365-
} else {
369+
#if os(Windows)
370+
thePath = pathString.standardizingPath
371+
#else
372+
if pathString.isAbsolutePath {
366373
thePath = path
374+
} else {
375+
thePath = pathString.standardizingPath
367376
}
368-
377+
#endif
369378
var isDir: ObjCBool = false
370379
if thePath.hasSuffix("/") {
371380
isDir = true
@@ -548,7 +557,7 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying {
548557

549558
open var path: String? {
550559
let absURL = CFURLCopyAbsoluteURL(_cfObject)
551-
return CFURLCopyFileSystemPath(absURL, kCFURLPlatformPathStyle)?._swiftObject
560+
return CFURLCopyFileSystemPath(absURL, kCFURLPOSIXPathStyle)?._swiftObject
552561
}
553562

554563
open var fragment: String? {
@@ -565,7 +574,7 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying {
565574

566575
// The same as path if baseURL is nil
567576
open var relativePath: String? {
568-
return CFURLCopyFileSystemPath(_cfObject, kCFURLPlatformPathStyle)?._swiftObject
577+
return CFURLCopyFileSystemPath(_cfObject, kCFURLPOSIXPathStyle)?._swiftObject
569578
}
570579

571580
/* 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.

0 commit comments

Comments
 (0)