Skip to content

Commit eac6cfc

Browse files
committed
Foundation: support _getFileSystemRepresentation on Windows
Update the `_getFileSystemRepresentation` helper to return a UTF-16 Windows style path. This enables us to properly model the file system representation of the path and the URL representation of the path.
1 parent 78c6b2f commit eac6cfc

File tree

7 files changed

+254
-112
lines changed

7 files changed

+254
-112
lines changed

CoreFoundation/URL.subproj/CFURL.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3965,10 +3965,15 @@ CF_EXPORT void __CFURLSetResourceInfoPtr(CFURLRef url, void *ptr) {
39653965
/* HFSPath<->URLPath functions at the bottom of the file */
39663966
static CFArrayRef WindowsPathToURLComponents(CFStringRef path, CFAllocatorRef alloc, Boolean isDir, Boolean isAbsolute) CF_RETURNS_RETAINED {
39673967
CFArrayRef tmp;
3968+
CFMutableStringRef mutablePath;
39683969
CFMutableArrayRef urlComponents = NULL;
39693970
CFIndex i=0;
39703971

3971-
tmp = CFStringCreateArrayBySeparatingStrings(alloc, path, CFSTR("\\"));
3972+
// Since '/' is a valid Windows path separator, we convert '/' to '\' before splitting
3973+
mutablePath = CFStringCreateMutableCopy(alloc, 0, path);
3974+
CFStringFindAndReplace(mutablePath, CFSTR("/"), CFSTR("\\"), CFRangeMake(0, CFStringGetLength(mutablePath)), 0);
3975+
tmp = CFStringCreateArrayBySeparatingStrings(alloc, mutablePath, CFSTR("\\"));
3976+
CFRelease(mutablePath);
39723977
urlComponents = CFArrayCreateMutableCopy(alloc, 0, tmp);
39733978
CFRelease(tmp);
39743979

Foundation/FileManager+Win32.swift

Lines changed: 57 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -175,22 +175,20 @@ extension FileManager {
175175
}
176176
}
177177

178-
var saAttributes: SECURITY_ATTRIBUTES =
179-
SECURITY_ATTRIBUTES(nLength: DWORD(MemoryLayout<SECURITY_ATTRIBUTES>.size),
180-
lpSecurityDescriptor: nil,
181-
bInheritHandle: false)
182-
let psaAttributes: UnsafeMutablePointer<SECURITY_ATTRIBUTES> =
183-
UnsafeMutablePointer<SECURITY_ATTRIBUTES>(&saAttributes)
184-
185-
186-
try path.withCString(encodedAs: UTF16.self) {
187-
if !CreateDirectoryW($0, psaAttributes) {
188-
// FIXME(compnerd) pass along path
189-
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
178+
try FileManager.default._fileSystemRepresentation(withPath: path) { fsr in
179+
var saAttributes: SECURITY_ATTRIBUTES =
180+
SECURITY_ATTRIBUTES(nLength: DWORD(MemoryLayout<SECURITY_ATTRIBUTES>.size),
181+
lpSecurityDescriptor: nil,
182+
bInheritHandle: false)
183+
try withUnsafeMutablePointer(to: &saAttributes) {
184+
if !CreateDirectoryW(fsr, $0) {
185+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
190186
}
191-
}
192-
if let attr = attributes {
187+
}
188+
189+
if let attr = attributes {
193190
try self.setAttributes(attr, ofItemAtPath: path)
191+
}
194192
}
195193
}
196194

@@ -236,15 +234,15 @@ extension FileManager {
236234
}
237235

238236
internal func windowsFileAttributes(atPath path: String) throws -> WIN32_FILE_ATTRIBUTE_DATA {
237+
return try FileManager.default._fileSystemRepresentation(withPath: path) {
239238
var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = WIN32_FILE_ATTRIBUTE_DATA()
240-
return try path.withCString(encodedAs: UTF16.self) {
241-
if !GetFileAttributesExW($0, GetFileExInfoStandard, &faAttributes) {
242-
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
243-
}
244-
return faAttributes
245-
}
239+
if !GetFileAttributesExW($0, GetFileExInfoStandard, &faAttributes) {
240+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
241+
}
242+
return faAttributes
243+
}
246244
}
247-
245+
248246
internal func _attributesOfFileSystemIncludingBlockSize(forPath path: String) throws -> (attributes: [FileAttributeKey : Any], blockSize: UInt64?) {
249247
return (attributes: try _attributesOfFileSystem(forPath: path), blockSize: nil)
250248
}
@@ -292,9 +290,10 @@ extension FileManager {
292290
case .some(false):
293291
break;
294292
case .none:
295-
let resolvedDest = destPath.isAbsolutePath
296-
? destPath
297-
: try joinPath(prefix: path.deletingLastPathComponent, suffix: destPath)
293+
let resolvedDest =
294+
destPath.isAbsolutePath ? destPath
295+
: joinPath(prefix: path.deletingLastPathComponent,
296+
suffix: destPath)
298297
guard let faAttributes = try? windowsFileAttributes(atPath: resolvedDest) else {
299298
// Note: windowsfileAttributes will throw if the destPath is not found.
300299
// Since on Windows, you are required to know the type of the symlink
@@ -309,12 +308,10 @@ extension FileManager {
309308
}
310309
}
311310

312-
try path.withCString(encodedAs: UTF16.self) { name in
313-
try destPath.withCString(encodedAs: UTF16.self) { dest in
314-
guard CreateSymbolicLinkW(name, dest, dwFlags) != 0 else {
315-
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path, destPath])
316-
}
317-
}
311+
try FileManager.default._fileSystemRepresentation(withPath: path, andPath: destPath) {
312+
guard CreateSymbolicLinkW($0, $1, dwFlags) != 0 else {
313+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path, destPath])
314+
}
318315
}
319316
}
320317

@@ -494,12 +491,10 @@ extension FileManager {
494491
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileWriteFileExists.rawValue, userInfo: [NSFilePathErrorKey : NSString(dstPath)])
495492
}
496493

497-
try srcPath.withCString(encodedAs: UTF16.self) { src in
498-
try dstPath.withCString(encodedAs: UTF16.self) { dst in
499-
if !MoveFileExW(src, dst, DWORD(MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH)) {
500-
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [srcPath, dstPath])
501-
}
502-
}
494+
try FileManager.default._fileSystemRepresentation(withPath: srcPath, andPath: dstPath) {
495+
if !MoveFileExW($0, $1, DWORD(MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH)) {
496+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [srcPath, dstPath])
497+
}
503498
}
504499
}
505500

@@ -512,12 +507,10 @@ extension FileManager {
512507
do {
513508
switch fileType {
514509
case .typeRegular:
515-
try srcPath.withCString(encodedAs: UTF16.self) { src in
516-
try dstPath.withCString(encodedAs: UTF16.self) { dst in
517-
if !CreateHardLinkW(dst, src, nil) {
518-
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [srcPath, dstPath])
519-
}
520-
}
510+
try FileManager.default._fileSystemRepresentation(withPath: srcPath, andPath: dstPath) {
511+
if !CreateHardLinkW($1, $0, nil) {
512+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [srcPath, dstPath])
513+
}
521514
}
522515
case .typeSymbolicLink:
523516
try _copySymlink(atPath: srcPath, toPath: dstPath)
@@ -637,12 +630,14 @@ extension FileManager {
637630
var szDirectory: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(dwLength + 1))
638631

639632
GetCurrentDirectoryW(dwLength, &szDirectory)
640-
return String(decodingCString: &szDirectory, as: UTF16.self)
633+
return String(decodingCString: &szDirectory, as: UTF16.self).standardizingPath
641634
}
642635

643636
@discardableResult
644637
internal func _changeCurrentDirectoryPath(_ path: String) -> Bool {
645-
return path.withCString(encodedAs: UTF16.self) { SetCurrentDirectoryW($0) }
638+
return try! FileManager.default._fileSystemRepresentation(withPath: path) {
639+
SetCurrentDirectoryW($0)
640+
}
646641
}
647642

648643
internal func _fileExists(atPath path: String, isDirectory: UnsafeMutablePointer<ObjCBool>?) -> Bool {
@@ -701,8 +696,8 @@ extension FileManager {
701696
return true
702697
}
703698

704-
internal func _lstatFile(atPath path: String, withFileSystemRepresentation fsRep: UnsafePointer<Int8>? = nil) throws -> stat {
705-
let _fsRep: UnsafePointer<Int8>
699+
internal func _lstatFile(atPath path: String, withFileSystemRepresentation fsRep: UnsafePointer<NativeFSRCharType>? = nil) throws -> stat {
700+
let _fsRep: UnsafePointer<NativeFSRCharType>
706701
if fsRep == nil {
707702
_fsRep = try __fileSystemRepresentation(withPath: path)
708703
} else {
@@ -714,20 +709,20 @@ extension FileManager {
714709
}
715710

716711
var statInfo = stat()
717-
let h = path.withCString(encodedAs: UTF16.self) {
718-
CreateFileW(/*lpFileName=*/$0,
719-
/*dwDesiredAccess=*/DWORD(0),
720-
/*dwShareMode=*/DWORD(FILE_SHARE_READ),
721-
/*lpSecurityAttributes=*/nil,
722-
/*dwCreationDisposition=*/DWORD(OPEN_EXISTING),
723-
/*dwFlagsAndAttributes=*/DWORD(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS),
712+
let handle =
713+
CreateFileW(_fsRep, /*dwDesiredAccess=*/DWORD(0),
714+
DWORD(FILE_SHARE_READ), /*lpSecurityAttributes=*/nil,
715+
DWORD(OPEN_EXISTING),
716+
DWORD(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS),
724717
/*hTemplateFile=*/nil)
725-
}
726-
if h == INVALID_HANDLE_VALUE {
718+
if handle == INVALID_HANDLE_VALUE {
727719
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
728720
}
721+
defer { CloseHandle(handle) }
722+
729723
var info: BY_HANDLE_FILE_INFORMATION = BY_HANDLE_FILE_INFORMATION()
730-
GetFileInformationByHandle(h, &info)
724+
GetFileInformationByHandle(handle, &info)
725+
731726
// Group id is always 0 on Windows
732727
statInfo.st_gid = 0
733728
statInfo.st_atime = info.ftLastAccessTime.time_t
@@ -751,13 +746,12 @@ extension FileManager {
751746

752747
statInfo.st_mtime = info.ftLastWriteTime.time_t
753748
statInfo.st_nlink = Int16(info.nNumberOfLinks)
754-
if info.nFileSizeHigh != 0 {
749+
guard info.nFileSizeHigh == 0 else {
755750
throw _NSErrorWithErrno(EOVERFLOW, reading: true, path: path)
756751
}
757752
statInfo.st_size = Int32(info.nFileSizeLow)
758753
// Uid is always 0 on Windows systems
759754
statInfo.st_uid = 0
760-
CloseHandle(h)
761755
return statInfo
762756
}
763757

@@ -858,7 +852,7 @@ extension FileManager {
858852
}
859853

860854
internal func _updateTimes(atPath path: String,
861-
withFileSystemRepresentation fsr: UnsafePointer<Int8>,
855+
withFileSystemRepresentation fsr: UnsafePointer<NativeFSRCharType>,
862856
creationTime: Date? = nil,
863857
accessTime: Date? = nil,
864858
modificationTime: Date? = nil) throws {
@@ -869,10 +863,9 @@ extension FileManager {
869863
var mtime: FILETIME =
870864
FILETIME(from: time_t((modificationTime ?? stat.lastModificationDate).timeIntervalSince1970))
871865

872-
let hFile: HANDLE = String(utf8String: fsr)!.withCString(encodedAs: UTF16.self) {
873-
CreateFileW($0, DWORD(GENERIC_WRITE), DWORD(FILE_SHARE_WRITE),
866+
let hFile: HANDLE =
867+
CreateFileW(fsr, DWORD(GENERIC_WRITE), DWORD(FILE_SHARE_WRITE),
874868
nil, DWORD(OPEN_EXISTING), 0, nil)
875-
}
876869
if hFile == INVALID_HANDLE_VALUE {
877870
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
878871
}
@@ -927,10 +920,10 @@ extension FileManager {
927920
if isDir && (level == 0 || !_options.contains(.skipsSubdirectoryDescendants)) {
928921
var ffd = WIN32_FIND_DATAW()
929922
let dirPath = joinPath(prefix: _lastReturned.path, suffix: "*")
930-
let handle = dirPath.withCString(encodedAs: UTF16.self) {
923+
let handle = try? FileManager.default._fileSystemRepresentation(withPath: dirPath) {
931924
FindFirstFileW($0, &ffd)
932925
}
933-
guard handle != INVALID_HANDLE_VALUE else { return firstValidItem() }
926+
if handle == INVALID_HANDLE_VALUE { return firstValidItem() }
934927
defer { FindClose(handle) }
935928

936929
repeat {

Foundation/FileManager.swift

Lines changed: 38 additions & 11 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+
internal let NativeFSREncoding = String.Encoding.utf16LittleEndian.rawValue
26+
#else
27+
internal typealias NativeFSRCharType = CChar
28+
internal let NativeFSREncoding = String.Encoding.utf8.rawValue
29+
#endif
30+
2331
open class FileManager : NSObject {
2432

2533
/* Returns the default singleton instance.
@@ -352,7 +360,7 @@ open class FileManager : NSObject {
352360
attributes.formIntersection(FileAttributeKey.allPublicKeys)
353361
}
354362

355-
try _fileSystemRepresentation(withPath: path, { fsRep in
363+
try _fileSystemRepresentation(withPath: path) { fsRep in
356364
var flagsToSet: UInt32 = 0
357365
var flagsToUnset: UInt32 = 0
358366

@@ -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

@@ -423,8 +436,8 @@ open class FileManager : NSObject {
423436
let hiddenAttrs = isHidden
424437
? attrs | DWORD(FILE_ATTRIBUTE_HIDDEN)
425438
: attrs & DWORD(bitPattern: ~FILE_ATTRIBUTE_HIDDEN)
426-
guard path.withCString(encodedAs: UTF16.self, { SetFileAttributesW($0, hiddenAttrs) }) else {
427-
fatalError("Couldn't set \(path) to be hidden")
439+
guard SetFileAttributesW(fsRep, hiddenAttrs) else {
440+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
428441
}
429442
#else
430443
prepareToSetOrUnsetFlag(UF_HIDDEN)
@@ -465,7 +478,7 @@ open class FileManager : NSObject {
465478
// Set dates as the very last step, to avoid other operations overwriting these values:
466479
try _updateTimes(atPath: path, withFileSystemRepresentation: fsRep, accessTime: newAccessDate, modificationTime: newModificationDate)
467480
}
468-
})
481+
}
469482
}
470483

471484
/* createDirectoryAtPath:withIntermediateDirectories:attributes:error: creates a directory at the specified path. If you pass 'NO' for createIntermediates, the directory must not exist at the time this call is made. Passing 'YES' for 'createIntermediates' will create any necessary intermediate directories. This method returns YES if all directories specified in 'path' were created and attributes were set. Directories are created with attributes specified by the dictionary passed to 'attributes'. If no dictionary is supplied, directories are created according to the umask of the process. This method returns NO if a failure occurs at any stage of the operation. If an error parameter was provided, a presentable NSError will be returned by reference.
@@ -1021,15 +1034,29 @@ open class FileManager : NSObject {
10211034
*/
10221035
open func fileSystemRepresentation(withPath path: String) -> UnsafePointer<Int8> {
10231036
precondition(path != "", "Empty path argument")
1037+
#if os(Windows)
1038+
// On Windows, the internal _fileSystemRepresentation returns UTF16
1039+
// encoded data, so we need to re-encode the result as UTF-8 before
1040+
// returning.
1041+
return try! _fileSystemRepresentation(withPath: path) {
1042+
String(decodingCString: $0, as: UTF16.self).withCString() {
1043+
let size = strnlen($0, Int(MAX_PATH))
1044+
let buffer = UnsafeMutablePointer<Int8>.allocate(capacity: size + 1)
1045+
buffer.initialize(from: $0, count: size + 1)
1046+
return UnsafePointer(buffer)
1047+
}
1048+
}
1049+
#else
10241050
return try! __fileSystemRepresentation(withPath: path)
1051+
#endif
10251052
}
10261053

1027-
internal func __fileSystemRepresentation(withPath path: String) throws -> UnsafePointer<Int8> {
1054+
internal func __fileSystemRepresentation(withPath path: String) throws -> UnsafePointer<NativeFSRCharType> {
10281055
let len = CFStringGetMaximumSizeOfFileSystemRepresentation(path._cfObject)
10291056
if len != kCFNotFound {
1030-
let buf = UnsafeMutablePointer<Int8>.allocate(capacity: len)
1057+
let buf = UnsafeMutablePointer<NativeFSRCharType>.allocate(capacity: len)
10311058
buf.initialize(repeating: 0, count: len)
1032-
if path._nsObject.getFileSystemRepresentation(buf, maxLength: len) {
1059+
if path._nsObject._getFileSystemRepresentation(buf, maxLength: len) {
10331060
return UnsafePointer(buf)
10341061
}
10351062
buf.deinitialize(count: len)
@@ -1038,13 +1065,13 @@ open class FileManager : NSObject {
10381065
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInvalidFileName.rawValue, userInfo: [NSFilePathErrorKey: path])
10391066
}
10401067

1041-
internal func _fileSystemRepresentation<ResultType>(withPath path: String, _ body: (UnsafePointer<Int8>) throws -> ResultType) throws -> ResultType {
1068+
internal func _fileSystemRepresentation<ResultType>(withPath path: String, _ body: (UnsafePointer<NativeFSRCharType>) throws -> ResultType) throws -> ResultType {
10421069
let fsRep = try __fileSystemRepresentation(withPath: path)
10431070
defer { fsRep.deallocate() }
10441071
return try body(fsRep)
10451072
}
10461073

1047-
internal func _fileSystemRepresentation<ResultType>(withPath path1: String, andPath path2: String, _ body: (UnsafePointer<Int8>, UnsafePointer<Int8>) throws -> ResultType) throws -> ResultType {
1074+
internal func _fileSystemRepresentation<ResultType>(withPath path1: String, andPath path2: String, _ body: (UnsafePointer<NativeFSRCharType>, UnsafePointer<NativeFSRCharType>) throws -> ResultType) throws -> ResultType {
10481075
let fsRep1 = try __fileSystemRepresentation(withPath: path1)
10491076
defer { fsRep1.deallocate() }
10501077
let fsRep2 = try __fileSystemRepresentation(withPath: path2)
@@ -1058,7 +1085,7 @@ open class FileManager : NSObject {
10581085
open func string(withFileSystemRepresentation str: UnsafePointer<Int8>, length len: Int) -> String {
10591086
return NSString(bytes: str, length: len, encoding: String.Encoding.utf8.rawValue)!._swiftObject
10601087
}
1061-
1088+
10621089
/* -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.
10631090

10641091
The `originalItemURL` is the item being replaced.

0 commit comments

Comments
 (0)