Skip to content

Commit 189a90d

Browse files
authored
Merge pull request #2339 from gmittert/VariousFMFixes
Fixing up Windows TestFileManager
2 parents 6cbc4bb + e75a099 commit 189a90d

File tree

3 files changed

+73
-34
lines changed

3 files changed

+73
-34
lines changed

Foundation/FileManager+Win32.swift

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ extension FileManager {
180180
try path.withCString(encodedAs: UTF16.self) {
181181
if !CreateDirectoryW($0, psaAttributes) {
182182
// FIXME(compnerd) pass along path
183-
throw _NSErrorWithWindowsError(GetLastError(), reading: false)
183+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
184184
}
185185
}
186186
if let attr = attributes {
@@ -197,7 +197,7 @@ extension FileManager {
197197

198198
let hDirectory: HANDLE = FindFirstFileW($0, &ffd)
199199
if hDirectory == INVALID_HANDLE_VALUE {
200-
throw _NSErrorWithWindowsError(GetLastError(), reading: true)
200+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
201201
}
202202
defer { FindClose(hDirectory) }
203203

@@ -208,7 +208,7 @@ extension FileManager {
208208
}
209209
}
210210
if path != "." && path != ".." {
211-
try closure(path, Int32(ffd.dwFileAttributes))
211+
try closure(path.standardizingPath, Int32(ffd.dwFileAttributes))
212212
}
213213
} while FindNextFileW(hDirectory, &ffd)
214214
}
@@ -222,7 +222,7 @@ extension FileManager {
222222
if entryType & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
223223
let subPath: String = joinPath(prefix: path, suffix: entryName)
224224
let entries = try subpathsOfDirectory(atPath: subPath)
225-
contents.append(contentsOf: entries.map { joinPath(prefix: entryName, suffix: $0) })
225+
contents.append(contentsOf: entries.map { joinPath(prefix: entryName, suffix: $0).standardizingPath })
226226
}
227227
})
228228
return contents
@@ -232,7 +232,7 @@ extension FileManager {
232232
var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = WIN32_FILE_ATTRIBUTE_DATA()
233233
return try path.withCString(encodedAs: UTF16.self) {
234234
if !GetFileAttributesExW($0, GetFileExInfoStandard, &faAttributes) {
235-
throw _NSErrorWithWindowsError(GetLastError(), reading: true)
235+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
236236
}
237237
return faAttributes
238238
}
@@ -248,24 +248,24 @@ extension FileManager {
248248
try path.withCString(encodedAs: UTF16.self) {
249249
let dwLength: DWORD = GetFullPathNameW($0, 0, nil, nil)
250250
guard dwLength != 0 else {
251-
throw _NSErrorWithWindowsError(GetLastError(), reading: true)
251+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
252252
}
253253
var szVolumePath: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(dwLength + 1))
254254

255255
guard GetVolumePathNameW($0, &szVolumePath, dwLength) else {
256-
throw _NSErrorWithWindowsError(GetLastError(), reading: true)
256+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
257257
}
258258

259259
var liTotal: ULARGE_INTEGER = ULARGE_INTEGER()
260260
var liFree: ULARGE_INTEGER = ULARGE_INTEGER()
261261

262262
guard GetDiskFreeSpaceExW(&szVolumePath, nil, &liTotal, &liFree) else {
263-
throw _NSErrorWithWindowsError(GetLastError(), reading: true)
263+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
264264
}
265265

266266
var volumeSerialNumber: DWORD = 0
267267
guard GetVolumeInformationW(&szVolumePath, nil, 0, &volumeSerialNumber, nil, nil, nil, 0) else {
268-
throw _NSErrorWithWindowsError(GetLastError(), reading: true)
268+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
269269
}
270270

271271
result[.systemSize] = NSNumber(value: liTotal.QuadPart)
@@ -284,15 +284,17 @@ extension FileManager {
284284
// other doesn't make a lot of sense, we allow it to throw, thus
285285
// disallowing the creation of broken symlinks on Windows (unlike with
286286
// POSIX).
287-
let faAttributes = try windowsFileAttributes(atPath: destPath)
287+
guard let faAttributes = try? windowsFileAttributes(atPath: destPath) else {
288+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path, destPath])
289+
}
288290
if faAttributes.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY) == DWORD(FILE_ATTRIBUTE_DIRECTORY) {
289291
dwFlags |= DWORD(SYMBOLIC_LINK_FLAG_DIRECTORY)
290292
}
291293

292294
try path.withCString(encodedAs: UTF16.self) { name in
293295
try destPath.withCString(encodedAs: UTF16.self) { dest in
294296
guard CreateSymbolicLinkW(name, dest, dwFlags) != 0 else {
295-
throw _NSErrorWithWindowsError(GetLastError(), reading: false)
297+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path, destPath])
296298
}
297299
}
298300
}
@@ -317,7 +319,7 @@ extension FileManager {
317319
var szPath = Array<WCHAR>(repeating: 0, count: Int(dwLength + 1))
318320
dwLength = GetFullPathNameW($0, DWORD(szPath.count), &szPath, nil)
319321
guard dwLength > 0 && dwLength <= szPath.count else {
320-
throw _NSErrorWithWindowsError(GetLastError(), reading: true)
322+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
321323
}
322324
return String(decodingCString: szPath, as: UTF16.self)
323325
}
@@ -335,7 +337,7 @@ extension FileManager {
335337
try srcPath.withCString(encodedAs: UTF16.self) { src in
336338
try dstPath.withCString(encodedAs: UTF16.self) { dst in
337339
if !CopyFileW(src, dst, false) {
338-
throw _NSErrorWithWindowsError(GetLastError(), reading: false)
340+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [srcPath, dstPath])
339341
}
340342
}
341343
}
@@ -358,8 +360,7 @@ extension FileManager {
358360
}
359361

360362
internal func _copyOrLinkDirectoryHelper(atPath srcPath: String, toPath dstPath: String, variant: String = "Copy", _ body: (String, String, FileAttributeType) throws -> ()) throws {
361-
var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = WIN32_FILE_ATTRIBUTE_DATA()
362-
do { faAttributes = try windowsFileAttributes(atPath: srcPath) } catch { return }
363+
let faAttributes = try windowsFileAttributes(atPath: srcPath)
363364

364365
var fileType = FileAttributeType(attributes: faAttributes, atPath: srcPath)
365366
if fileType == .typeDirectory {
@@ -372,7 +373,7 @@ extension FileManager {
372373
let src = joinPath(prefix: srcPath, suffix: item)
373374
let dst = joinPath(prefix: dstPath, suffix: item)
374375

375-
do { faAttributes = try windowsFileAttributes(atPath: src) } catch { return }
376+
let faAttributes = try windowsFileAttributes(atPath: src)
376377
fileType = FileAttributeType(attributes: faAttributes, atPath: srcPath)
377378
if fileType == .typeDirectory {
378379
try createDirectory(atPath: dst, withIntermediateDirectories: false, attributes: nil)
@@ -397,7 +398,7 @@ extension FileManager {
397398
try srcPath.withCString(encodedAs: UTF16.self) { src in
398399
try dstPath.withCString(encodedAs: UTF16.self) { dst in
399400
if !MoveFileExW(src, dst, DWORD(MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH)) {
400-
throw _NSErrorWithWindowsError(GetLastError(), reading: false)
401+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [srcPath, dstPath])
401402
}
402403
}
403404
}
@@ -415,7 +416,7 @@ extension FileManager {
415416
try srcPath.withCString(encodedAs: UTF16.self) { src in
416417
try dstPath.withCString(encodedAs: UTF16.self) { dst in
417418
if !CreateHardLinkW(dst, src, nil) {
418-
throw _NSErrorWithWindowsError(GetLastError(), reading: false)
419+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [srcPath, dstPath])
419420
}
420421
}
421422
}
@@ -436,6 +437,11 @@ extension FileManager {
436437
guard alreadyConfirmed || shouldRemoveItemAtPath(path, isURL: isURL) else {
437438
return
438439
}
440+
441+
guard path != "" else {
442+
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInvalidFileName.rawValue, userInfo: [NSFilePathErrorKey : NSString(path)])
443+
}
444+
439445
let url = URL(fileURLWithPath: path)
440446
var fsrBuf: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(MAX_PATH))
441447
_CFURLGetWideFileSystemRepresentation(url._cfObject, false, &fsrBuf, Int(MAX_PATH))
@@ -447,13 +453,13 @@ extension FileManager {
447453
if faAttributes.dwFileAttributes & DWORD(FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY {
448454
let readableAttributes = faAttributes.dwFileAttributes & DWORD(bitPattern: ~FILE_ATTRIBUTE_READONLY)
449455
guard fsrPath.withCString(encodedAs: UTF16.self, { SetFileAttributesW($0, readableAttributes) }) else {
450-
throw _NSErrorWithWindowsError(GetLastError(), reading: false)
456+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
451457
}
452458
}
453459

454460
if faAttributes.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY) == 0 {
455461
if !fsrPath.withCString(encodedAs: UTF16.self, DeleteFileW) {
456-
throw _NSErrorWithWindowsError(GetLastError(), reading: false)
462+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
457463
}
458464
return
459465
}
@@ -469,15 +475,15 @@ extension FileManager {
469475
continue
470476
}
471477
guard GetLastError() == ERROR_DIR_NOT_EMPTY else {
472-
throw _NSErrorWithWindowsError(GetLastError(), reading: false)
478+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [itemPath])
473479
}
474480
dirStack.append(itemPath)
475481
var ffd: WIN32_FIND_DATAW = WIN32_FIND_DATAW()
476482
let h: HANDLE = (itemPath + "\\*").withCString(encodedAs: UTF16.self, {
477483
FindFirstFileW($0, &ffd)
478484
})
479485
guard h != INVALID_HANDLE_VALUE else {
480-
throw _NSErrorWithWindowsError(GetLastError(), reading: false)
486+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [itemPath])
481487
}
482488
defer { FindClose(h) }
483489

@@ -491,7 +497,7 @@ extension FileManager {
491497
if ffd.dwFileAttributes & DWORD(FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY {
492498
let readableAttributes = ffd.dwFileAttributes & DWORD(bitPattern: ~FILE_ATTRIBUTE_READONLY)
493499
guard file.withCString(encodedAs: UTF16.self, { SetFileAttributesW($0, readableAttributes) }) else {
494-
throw _NSErrorWithWindowsError(GetLastError(), reading: false)
500+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [file])
495501
}
496502
}
497503

@@ -504,7 +510,7 @@ extension FileManager {
504510
continue
505511
}
506512
if !itemPath.withCString(encodedAs: UTF16.self, DeleteFileW) {
507-
throw _NSErrorWithWindowsError(GetLastError(), reading: false)
513+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [file])
508514
}
509515
}
510516
} while FindNextFileW(h, &ffd)
@@ -604,7 +610,7 @@ extension FileManager {
604610
/*hTemplateFile=*/nil)
605611
}
606612
if h == INVALID_HANDLE_VALUE {
607-
throw _NSErrorWithWindowsError(GetLastError(), reading: false)
613+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
608614
}
609615
var info: BY_HANDLE_FILE_INFORMATION = BY_HANDLE_FILE_INFORMATION()
610616
GetFileInformationByHandle(h, &info)
@@ -675,12 +681,12 @@ extension FileManager {
675681
nil, DWORD(OPEN_EXISTING), 0, nil)
676682
}
677683
if hFile == INVALID_HANDLE_VALUE {
678-
throw _NSErrorWithWindowsError(GetLastError(), reading: true)
684+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
679685
}
680686
defer { CloseHandle(hFile) }
681687

682688
if !SetFileTime(hFile, nil, &atime, &mtime) {
683-
throw _NSErrorWithWindowsError(GetLastError(), reading: false)
689+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
684690
}
685691

686692
}
@@ -705,7 +711,7 @@ extension FileManager {
705711
while let url = _stack.popLast() {
706712
if !FileManager.default.fileExists(atPath: url.path, isDirectory: nil) {
707713
guard let handler = _errorHandler,
708-
handler(url, _NSErrorWithWindowsError(GetLastError(), reading: true))
714+
handler(url, _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [url.path]))
709715
else { return nil }
710716
continue
711717
}
@@ -719,7 +725,7 @@ extension FileManager {
719725
var isDir: ObjCBool = false
720726
guard FileManager.default.fileExists(atPath: _lastReturned.path, isDirectory: &isDir) else {
721727
guard let handler = _errorHandler,
722-
handler(_lastReturned, _NSErrorWithWindowsError(GetLastError(), reading: true))
728+
handler(_lastReturned, _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [_lastReturned.path]))
723729
else { return nil }
724730
return firstValidItem()
725731
}

Foundation/FoundationErrors.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ internal func _NSErrorWithErrno(_ posixErrno : Int32, reading : Bool, path : Str
211211
// https://docs.microsoft.com/en-us/windows/desktop/Debug/system-error-codes
212212
internal let _NSWindowsErrorDomain = "org.swift.Foundation.WindowsError"
213213

214-
internal func _NSErrorWithWindowsError(_ windowsError: DWORD, reading: Bool) -> NSError {
214+
internal func _NSErrorWithWindowsError(_ windowsError: DWORD, reading: Bool, paths: [String]? = nil) -> NSError {
215215
var cocoaError : CocoaError.Code
216216
switch Int32(bitPattern: windowsError) {
217217
case ERROR_LOCK_VIOLATION: cocoaError = .fileLocking
@@ -225,8 +225,12 @@ internal func _NSErrorWithWindowsError(_ windowsError: DWORD, reading: Bool) ->
225225
default:
226226
if reading {
227227
switch Int32(bitPattern: windowsError) {
228-
case ERROR_FILE_NOT_FOUND: cocoaError = .fileReadNoSuchFile
229-
case ERROR_PATH_NOT_FOUND: cocoaError = .fileReadNoSuchFile
228+
case ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND:
229+
// On an empty path, Windows will return FILE/PATH_NOT_FOUND
230+
// rather than invalid path as posix does
231+
cocoaError = paths?.contains("") ?? false
232+
? .fileReadInvalidFileName
233+
: .fileReadNoSuchFile
230234
case ERROR_ACCESS_DENIED: cocoaError = .fileReadNoPermission
231235
case ERROR_INVALID_ACCESS: cocoaError = .fileReadNoPermission
232236
case ERROR_INVALID_DRIVE: cocoaError = .fileReadNoSuchFile
@@ -240,8 +244,12 @@ internal func _NSErrorWithWindowsError(_ windowsError: DWORD, reading: Bool) ->
240244
}
241245
} else {
242246
switch Int32(bitPattern: windowsError) {
243-
case ERROR_FILE_NOT_FOUND: cocoaError = .fileNoSuchFile
244-
case ERROR_PATH_NOT_FOUND: cocoaError = .fileNoSuchFile
247+
case ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND:
248+
// On an empty path, Windows will return FILE/PATH_NOT_FOUND
249+
// rather than invalid path as posix does
250+
cocoaError = paths?.contains("") ?? false
251+
? .fileReadInvalidFileName
252+
: .fileReadNoSuchFile
245253
case ERROR_ACCESS_DENIED: cocoaError = .fileWriteNoPermission
246254
case ERROR_INVALID_ACCESS: cocoaError = .fileWriteNoPermission
247255
case ERROR_INVALID_DRIVE: cocoaError = .fileNoSuchFile

TestFoundation/TestFileManager.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,11 @@ class TestFileManager : XCTestCase {
6969
XCTFail("Failed to clean up file")
7070
}
7171

72+
#if os(Windows)
73+
let permissions = NSNumber(value: Int16(0o700))
74+
#else
7275
let permissions = NSNumber(value: Int16(0o753))
76+
#endif
7377
let attributes = [FileAttributeKey.posixPermissions: permissions]
7478
XCTAssertTrue(fm.createFile(atPath: path, contents: Data(),
7579
attributes: attributes))
@@ -155,7 +159,12 @@ class TestFileManager : XCTestCase {
155159
try fm.createDirectory(atPath: tmpDir.path, withIntermediateDirectories: false, attributes: nil)
156160
XCTAssertTrue(fm.createFile(atPath: testFile.path, contents: Data()))
157161
try fm.createSymbolicLink(atPath: goodSymLink.path, withDestinationPath: testFile.path)
162+
#if os(Windows)
163+
// Creating a broken symlink is expected to fail on Windows
164+
XCTAssertNil(try? fm.createSymbolicLink(atPath: badSymLink.path, withDestinationPath: "no_such_file"))
165+
#else
158166
try fm.createSymbolicLink(atPath: badSymLink.path, withDestinationPath: "no_such_file")
167+
#endif
159168
try fm.createSymbolicLink(atPath: dirSymLink.path, withDestinationPath: "..")
160169

161170
var isDirFlag: ObjCBool = false
@@ -197,7 +206,12 @@ class TestFileManager : XCTestCase {
197206

198207
// test unReadable if file has no permissions
199208
try fm.setAttributes([.posixPermissions : NSNumber(value: Int16(0o0000))], ofItemAtPath: path)
209+
#if os(Windows)
210+
// Files are always readable on Windows
211+
XCTAssertTrue(fm.isReadableFile(atPath: path))
212+
#else
200213
XCTAssertFalse(fm.isReadableFile(atPath: path))
214+
#endif
201215

202216
// test readable if file has read permissions
203217
try fm.setAttributes([.posixPermissions : NSNumber(value: Int16(0o0400))], ofItemAtPath: path)
@@ -237,7 +251,12 @@ class TestFileManager : XCTestCase {
237251

238252
// test unExecutable if file has no permissions
239253
try fm.setAttributes([.posixPermissions : NSNumber(value: Int16(0o0000))], ofItemAtPath: path)
254+
#if os(Windows)
255+
// Files are always executable on Windows
256+
XCTAssertTrue(fm.isExecutableFile(atPath: path))
257+
#else
240258
XCTAssertFalse(fm.isExecutableFile(atPath: path))
259+
#endif
241260

242261
// test executable if file has execute permissions
243262
try fm.setAttributes([.posixPermissions : NSNumber(value: Int16(0o0100))], ofItemAtPath: path)
@@ -300,8 +319,10 @@ class TestFileManager : XCTestCase {
300319
let fileSystemNumber = attrs[.systemNumber] as? NSNumber
301320
XCTAssertNotEqual(fileSystemNumber!.int64Value, 0)
302321

322+
#if !os(Windows)
303323
let fileSystemFileNumber = attrs[.systemFileNumber] as? NSNumber
304324
XCTAssertNotEqual(fileSystemFileNumber!.int64Value, 0)
325+
#endif
305326

306327
let fileType = attrs[.type] as? FileAttributeType
307328
XCTAssertEqual(fileType!, .typeRegular)
@@ -401,7 +422,11 @@ class TestFileManager : XCTestCase {
401422
//read back the attributes
402423
do {
403424
let attributes = try fm.attributesOfItem(atPath: path)
425+
#if os(Windows)
426+
XCTAssert((attributes[.posixPermissions] as? NSNumber)?.int16Value == 0o0700)
427+
#else
404428
XCTAssert((attributes[.posixPermissions] as? NSNumber)?.int16Value == 0o0600)
429+
#endif
405430
}
406431
catch { XCTFail("\(error)") }
407432

0 commit comments

Comments
 (0)