Skip to content

Commit b79edd8

Browse files
committed
Implemented _getFileSystemRepresentation for Windows
Modified the helper _getFileSystemRepresentation functions to return a UTF16 Windows style path.
1 parent 07df806 commit b79edd8

File tree

6 files changed

+140
-39
lines changed

6 files changed

+140
-39
lines changed

CoreFoundation/Base.subproj/ForFoundationOnly.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ CF_INLINE UInt64 __CFReadTSR(void) {
586586
#endif
587587
#endif
588588

589-
#if TARGET_OS_MAC
589+
#if TARGET_OS_MAC || TARGET_OS_WINDOWS
590590

591591
/* Identical to CFStringGetFileSystemRepresentation, but returns additional information about the failure.
592592
*/

CoreFoundation/String.subproj/CFStringEncodings.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -809,8 +809,9 @@ CFIndex CFStringGetMaximumSizeOfFileSystemRepresentation(CFStringRef string) {
809809
}
810810
}
811811

812-
#if TARGET_OS_MAC
812+
#if TARGET_OS_MAC || TARGET_OS_WINDOWS
813813
_CFStringFileSystemRepresentationError _CFStringGetFileSystemRepresentationWithErrorStatus(CFStringRef string, char *buffer, CFIndex maxBufLen, CFIndex *characterIndex) {
814+
#if TARGET_OS_WINDOWS
814815
#define MAX_STACK_BUFFER_LEN (255)
815816
const UTF16Char *characters = CFStringGetCharactersPtr(string);
816817
const char *origBuffer = buffer;
@@ -887,12 +888,12 @@ _CFStringFileSystemRepresentationError _CFStringGetFileSystemRepresentationWithE
887888
} else {
888889
return _kCFStringFileSystemRepresentationErrorBufferFull;
889890
}
890-
891+
#endif
891892
}
892893
#endif
893894

894895
Boolean CFStringGetFileSystemRepresentation(CFStringRef string, char *buffer, CFIndex maxBufLen) {
895-
#if TARGET_OS_MAC
896+
#if TARGET_OS_MAC || TARGET_OS_WINDOWS
896897
return _CFStringGetFileSystemRepresentationWithErrorStatus(string, buffer, maxBufLen, NULL) == _kCFStringFileSystemRepresentationErrorNone;
897898
#else
898899
return CFStringGetCString(string, buffer, maxBufLen, CFStringFileSystemEncoding());

Foundation/FileManager+Win32.swift

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -587,8 +587,8 @@ extension FileManager {
587587
NSUnimplemented()
588588
}
589589

590-
internal func _lstatFile(atPath path: String, withFileSystemRepresentation fsRep: UnsafePointer<Int8>? = nil) throws -> stat {
591-
let _fsRep: UnsafePointer<Int8>
590+
internal func _lstatFile(atPath path: String, withFileSystemRepresentation fsRep: UnsafePointer<NativeFSRCharType>? = nil) throws -> stat {
591+
let _fsRep: UnsafePointer<NativeFSRCharType>
592592
if fsRep == nil {
593593
_fsRep = try __fileSystemRepresentation(withPath: path)
594594
} else {
@@ -600,15 +600,13 @@ extension FileManager {
600600
}
601601

602602
var statInfo = stat()
603-
let h = path.withCString(encodedAs: UTF16.self) {
604-
CreateFileW(/*lpFileName=*/$0,
605-
/*dwDesiredAccess=*/DWORD(0),
606-
/*dwShareMode=*/DWORD(FILE_SHARE_READ),
607-
/*lpSecurityAttributes=*/nil,
608-
/*dwCreationDisposition=*/DWORD(OPEN_EXISTING),
609-
/*dwFlagsAndAttributes=*/DWORD(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS),
610-
/*hTemplateFile=*/nil)
611-
}
603+
let h = CreateFileW(/*lpFileName=*/_fsRep,
604+
/*dwDesiredAccess=*/DWORD(0),
605+
/*dwShareMode=*/DWORD(FILE_SHARE_READ),
606+
/*lpSecurityAttributes=*/nil,
607+
/*dwCreationDisposition=*/DWORD(OPEN_EXISTING),
608+
/*dwFlagsAndAttributes=*/DWORD(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS),
609+
/*hTemplateFile=*/nil)
612610
if h == INVALID_HANDLE_VALUE {
613611
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
614612
}
@@ -665,21 +663,16 @@ extension FileManager {
665663
}
666664

667665
internal func _updateTimes(atPath path: String,
668-
withFileSystemRepresentation fsr: UnsafePointer<Int8>,
666+
withFileSystemRepresentation fsr: UnsafePointer<NativeFSRCharType>,
669667
creationTime: Date? = nil,
670668
accessTime: Date? = nil,
671669
modificationTime: Date? = nil) throws {
672670
let stat = try _lstatFile(atPath: path, withFileSystemRepresentation: fsr)
673671

674-
var atime: FILETIME =
675-
FILETIME(from: time_t((accessTime ?? stat.lastAccessDate).timeIntervalSince1970))
676-
var mtime: FILETIME =
677-
FILETIME(from: time_t((modificationTime ?? stat.lastModificationDate).timeIntervalSince1970))
672+
var atime = FILETIME(from: time_t((accessTime ?? stat.lastAccessDate).timeIntervalSince1970))
673+
var mtime = FILETIME(from: time_t((modificationTime ?? stat.lastModificationDate).timeIntervalSince1970))
678674

679-
let hFile: HANDLE = String(utf8String: fsr)!.withCString(encodedAs: UTF16.self) {
680-
CreateFileW($0, DWORD(GENERIC_WRITE), DWORD(FILE_SHARE_WRITE),
681-
nil, DWORD(OPEN_EXISTING), 0, nil)
682-
}
675+
let hFile = CreateFileW(fsr, DWORD(GENERIC_WRITE), DWORD(FILE_SHARE_WRITE), nil, DWORD(OPEN_EXISTING), 0, nil)
683676
if hFile == INVALID_HANDLE_VALUE {
684677
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
685678
}

Foundation/FileManager.swift

Lines changed: 36 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,27 @@ 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+
let result = path._nsObject._getFileSystemRepresentation(buf, maxLength: len)
1028+
if result {
10041029
return UnsafePointer(buf)
10051030
}
10061031
buf.deinitialize(count: len)
@@ -1009,13 +1034,13 @@ open class FileManager : NSObject {
10091034
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInvalidFileName.rawValue, userInfo: [NSFilePathErrorKey: path])
10101035
}
10111036

1012-
internal func _fileSystemRepresentation<ResultType>(withPath path: String, _ body: (UnsafePointer<Int8>) throws -> ResultType) throws -> ResultType {
1037+
internal func _fileSystemRepresentation<ResultType>(withPath path: String, _ body: (UnsafePointer<NativeFSRCharType>) throws -> ResultType) throws -> ResultType {
10131038
let fsRep = try __fileSystemRepresentation(withPath: path)
10141039
defer { fsRep.deallocate() }
10151040
return try body(fsRep)
10161041
}
10171042

1018-
internal func _fileSystemRepresentation<ResultType>(withPath path1: String, andPath path2: String, _ body: (UnsafePointer<Int8>, UnsafePointer<Int8>) throws -> ResultType) throws -> ResultType {
1043+
internal func _fileSystemRepresentation<ResultType>(withPath path1: String, andPath path2: String, _ body: (UnsafePointer<NativeFSRCharType>, UnsafePointer<NativeFSRCharType>) throws -> ResultType) throws -> ResultType {
10191044
let fsRep1 = try __fileSystemRepresentation(withPath: path1)
10201045
defer { fsRep1.deallocate() }
10211046
let fsRep2 = try __fileSystemRepresentation(withPath: path2)
@@ -1029,7 +1054,11 @@ open class FileManager : NSObject {
10291054
open func string(withFileSystemRepresentation str: UnsafePointer<Int8>, length len: Int) -> String {
10301055
return NSString(bytes: str, length: len, encoding: String.Encoding.utf8.rawValue)!._swiftObject
10311056
}
1032-
1057+
1058+
internal func _string(withFileSystemRepresentation str: UnsafePointer<NativeFSRCharType>, length len: Int) -> String {
1059+
return NSString(bytes: str, length: len * MemoryLayout<NativeFSRCharType>.size, encoding: NativeFSREncoding)!._swiftObject
1060+
}
1061+
10331062
/* -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.
10341063

10351064
The `originalItemURL` is the item being replaced.

Foundation/NSPathUtilities.swift

Lines changed: 71 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,11 +567,63 @@ 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
589+
}
590+
591+
internal func _getFileSystemRepresentation(_ cname: UnsafeMutablePointer<NativeFSRCharType>, maxLength max: Int) -> Bool {
592+
#if os(Windows)
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+
fsr = fsr.replacingOccurrences(of: "/", with: "\\")
607+
608+
// Drop trailing slashes unless it follows a drive letter
609+
while
610+
fsr.count > 1
611+
&& (fsr[fsr.index(before: fsr.endIndex)] == "\\")
612+
&& !(fsr.count == 3 && fsr[fsr.index(fsr.endIndex, offsetBy: -2)] == ":") {
613+
fsr.removeLast()
614+
}
615+
616+
return fsr.withCString(encodedAs: UTF16.self) {
617+
let wchars = wcsnlen_s($0, max)
618+
guard wchars < max else {
619+
return false
620+
}
621+
cname.assign(from: $0, count: wchars + 1)
622+
return true
623+
}
624+
#else
625+
return getFileSystemRepresentation(cname, maxLength: max)
626+
#endif
558627
}
559628

560629
}

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)