Skip to content

Commit 61e4ca4

Browse files
authored
Merge pull request #231 from apple/main
Merge main into release/1.5.0
2 parents f3154e3 + 99feed6 commit 61e4ca4

File tree

8 files changed

+139
-29
lines changed

8 files changed

+139
-29
lines changed

.github/workflows/pull_request.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Pull request
2+
3+
on:
4+
pull_request:
5+
types: [opened, reopened, synchronize]
6+
7+
jobs:
8+
tests:
9+
name: Test
10+
uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main
11+
with:
12+
linux_exclude_swift_versions: '[{"swift_version": "5.8"}]'
13+
soundness:
14+
name: Soundness
15+
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
16+
with:
17+
license_header_check_project_name: "Swift.org"
18+
# https://github.com/apple/swift-system/issues/224
19+
docs_check_enabled: false
20+
unacceptable_language_check_enabled: false
21+
license_header_check_enabled: false
22+
format_check_enabled: false
23+
python_lint_check_enabled: false

Sources/System/FilePath/FilePathTempWindows.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ fileprivate func forEachFile(
4040

4141
try searchPath.withPlatformString { szPath in
4242
var findData = WIN32_FIND_DATAW()
43-
let hFind = FindFirstFileW(szPath, &findData)
43+
let hFind = try szPath.withCanonicalPathRepresentation({ szPath in FindFirstFileW(szPath, &findData) })
4444
if hFind == INVALID_HANDLE_VALUE {
4545
throw Errno(windowsError: GetLastError())
4646
}
@@ -95,8 +95,8 @@ internal func _recursiveRemove(
9595
let subpath = path.appending(component)
9696

9797
if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) == 0 {
98-
try subpath.withPlatformString {
99-
if !DeleteFileW($0) {
98+
try subpath.withPlatformString { subpath in
99+
if try !subpath.withCanonicalPathRepresentation({ DeleteFileW($0) }) {
100100
throw Errno(windowsError: GetLastError())
101101
}
102102
}
@@ -105,7 +105,7 @@ internal func _recursiveRemove(
105105

106106
// Finally, delete the parent
107107
try path.withPlatformString {
108-
if !RemoveDirectoryW($0) {
108+
if try !$0.withCanonicalPathRepresentation({ RemoveDirectoryW($0) }) {
109109
throw Errno(windowsError: GetLastError())
110110
}
111111
}

Sources/System/FilePath/FilePathWindows.swift

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,3 +461,78 @@ extension SystemString {
461461
return lexer.current
462462
}
463463
}
464+
465+
#if os(Windows)
466+
import WinSDK
467+
468+
// FIXME: Rather than canonicalizing the path at every call site to a Win32 API,
469+
// we should consider always storing absolute paths with the \\?\ prefix applied,
470+
// for better performance.
471+
extension UnsafePointer where Pointee == CInterop.PlatformChar {
472+
/// Invokes `body` with a resolved and potentially `\\?\`-prefixed version of the pointee,
473+
/// to ensure long paths greater than MAX_PATH (260) characters are handled correctly.
474+
///
475+
/// - seealso: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
476+
internal func withCanonicalPathRepresentation<Result>(_ body: (Self) throws -> Result) throws -> Result {
477+
// 1. Normalize the path first.
478+
// Contrary to the documentation, this works on long paths independently
479+
// of the registry or process setting to enable long paths (but it will also
480+
// not add the \\?\ prefix required by other functions under these conditions).
481+
let dwLength: DWORD = GetFullPathNameW(self, 0, nil, nil)
482+
return try withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength)) { pwszFullPath in
483+
guard (1..<dwLength).contains(GetFullPathNameW(self, DWORD(pwszFullPath.count), pwszFullPath.baseAddress, nil)) else {
484+
throw Errno(rawValue: _mapWindowsErrorToErrno(GetLastError()))
485+
}
486+
487+
// 1.5 Leave \\.\ prefixed paths alone since device paths are already an exact representation and PathCchCanonicalizeEx will mangle these.
488+
if let base = pwszFullPath.baseAddress,
489+
base[0] == UInt8(ascii: "\\"),
490+
base[1] == UInt8(ascii: "\\"),
491+
base[2] == UInt8(ascii: "."),
492+
base[3] == UInt8(ascii: "\\") {
493+
return try body(base)
494+
}
495+
496+
// 2. Canonicalize the path.
497+
// This will add the \\?\ prefix if needed based on the path's length.
498+
var pwszCanonicalPath: LPWSTR?
499+
let flags: ULONG = numericCast(PATHCCH_ALLOW_LONG_PATHS.rawValue)
500+
let result = PathAllocCanonicalize(pwszFullPath.baseAddress, flags, &pwszCanonicalPath)
501+
if let pwszCanonicalPath {
502+
defer { LocalFree(pwszCanonicalPath) }
503+
if result == S_OK {
504+
// 3. Perform the operation on the normalized path.
505+
return try body(pwszCanonicalPath)
506+
}
507+
}
508+
throw Errno(rawValue: _mapWindowsErrorToErrno(WIN32_FROM_HRESULT(result)))
509+
}
510+
}
511+
}
512+
513+
@inline(__always)
514+
fileprivate func HRESULT_CODE(_ hr: HRESULT) -> DWORD {
515+
DWORD(hr) & 0xffff
516+
}
517+
518+
@inline(__always)
519+
fileprivate func HRESULT_FACILITY(_ hr: HRESULT) -> DWORD {
520+
DWORD(hr << 16) & 0x1fff
521+
}
522+
523+
@inline(__always)
524+
fileprivate func SUCCEEDED(_ hr: HRESULT) -> Bool {
525+
hr >= 0
526+
}
527+
528+
// This is a non-standard extension to the Windows SDK that allows us to convert
529+
// an HRESULT to a Win32 error code.
530+
@inline(__always)
531+
fileprivate func WIN32_FROM_HRESULT(_ hr: HRESULT) -> DWORD {
532+
if SUCCEEDED(hr) { return DWORD(ERROR_SUCCESS) }
533+
if HRESULT_FACILITY(hr) == FACILITY_WIN32 {
534+
return HRESULT_CODE(hr)
535+
}
536+
return DWORD(hr)
537+
}
538+
#endif

Sources/System/Internals/Mocking.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ internal var mockingEnabled: Bool {
130130
@inline(__always)
131131
internal var forceWindowsPaths: Bool? {
132132
#if !ENABLE_MOCKING
133-
return false
133+
return nil
134134
#else
135135
return MockingDriver.forceWindowsPaths
136136
#endif

Sources/System/Internals/WindowsSyscallAdapters.swift

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,17 @@ internal func open(
3535
bInheritHandle: decodedFlags.bInheritHandle
3636
)
3737

38-
let hFile = CreateFileW(path,
39-
decodedFlags.dwDesiredAccess,
40-
DWORD(FILE_SHARE_DELETE
41-
| FILE_SHARE_READ
42-
| FILE_SHARE_WRITE),
43-
&saAttrs,
44-
decodedFlags.dwCreationDisposition,
45-
decodedFlags.dwFlagsAndAttributes,
46-
nil)
47-
48-
if hFile == INVALID_HANDLE_VALUE {
38+
guard let hFile = try? path.withCanonicalPathRepresentation({ path in
39+
CreateFileW(path,
40+
decodedFlags.dwDesiredAccess,
41+
DWORD(FILE_SHARE_DELETE
42+
| FILE_SHARE_READ
43+
| FILE_SHARE_WRITE),
44+
&saAttrs,
45+
decodedFlags.dwCreationDisposition,
46+
decodedFlags.dwFlagsAndAttributes,
47+
nil)
48+
}), hFile != INVALID_HANDLE_VALUE else {
4949
ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError()))
5050
return -1
5151
}
@@ -77,17 +77,17 @@ internal func open(
7777
bInheritHandle: decodedFlags.bInheritHandle
7878
)
7979

80-
let hFile = CreateFileW(path,
81-
decodedFlags.dwDesiredAccess,
82-
DWORD(FILE_SHARE_DELETE
83-
| FILE_SHARE_READ
84-
| FILE_SHARE_WRITE),
85-
&saAttrs,
86-
decodedFlags.dwCreationDisposition,
87-
decodedFlags.dwFlagsAndAttributes,
88-
nil)
89-
90-
if hFile == INVALID_HANDLE_VALUE {
80+
guard let hFile = try? path.withCanonicalPathRepresentation({ path in
81+
CreateFileW(path,
82+
decodedFlags.dwDesiredAccess,
83+
DWORD(FILE_SHARE_DELETE
84+
| FILE_SHARE_READ
85+
| FILE_SHARE_WRITE),
86+
&saAttrs,
87+
decodedFlags.dwCreationDisposition,
88+
decodedFlags.dwFlagsAndAttributes,
89+
nil)
90+
}), hFile != INVALID_HANDLE_VALUE else {
9191
ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError()))
9292
return -1
9393
}
@@ -242,7 +242,7 @@ internal func mkdir(
242242
bInheritHandle: false
243243
)
244244

245-
if !CreateDirectoryW(path, &saAttrs) {
245+
guard (try? path.withCanonicalPathRepresentation({ path in CreateDirectoryW(path, &saAttrs) })) == true else {
246246
ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError()))
247247
return -1
248248
}
@@ -254,7 +254,7 @@ internal func mkdir(
254254
internal func rmdir(
255255
_ path: UnsafePointer<CInterop.PlatformChar>
256256
) -> CInt {
257-
if !RemoveDirectoryW(path) {
257+
guard (try? path.withCanonicalPathRepresentation({ path in RemoveDirectoryW(path) })) == true else {
258258
ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError()))
259259
return -1
260260
}

Tests/SystemTests/FileOperationsTest.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import XCTest
1414
#else
1515
@testable import System
1616
#endif
17+
#if canImport(Android)
18+
import Android
19+
#endif
1720

1821
@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *)
1922
final class FileOperationsTest: XCTestCase {

Tests/SystemTests/FileOperationsTestWindows.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ final class FileOperationsTestWindows: XCTestCase {
174174

175175
/// Test that the umask works properly
176176
func testUmask() throws {
177+
// See https://learn.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/persistent-storage#permissions
178+
try XCTSkipIf(NSUserName() == "ContainerAdministrator", "containers use a different permission model")
179+
177180
// Default mask should be 0o022
178181
XCTAssertEqual(FilePermissions.creationMask, [.groupWrite, .otherWrite])
179182

@@ -205,6 +208,9 @@ final class FileOperationsTestWindows: XCTestCase {
205208

206209
/// Test that setting permissions on a file works as expected
207210
func testPermissions() throws {
211+
// See https://learn.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/persistent-storage#permissions
212+
try XCTSkipIf(NSUserName() == "ContainerAdministrator", "containers use a different permission model")
213+
208214
try FilePermissions.withCreationMask([]) {
209215
try withTemporaryFilePath(basename: "testPermissions") { path in
210216
let tests = [

Tests/SystemTests/FileTypesTest.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import SystemPackage
1414
#else
1515
import System
1616
#endif
17+
#if canImport(Android)
18+
import Android
19+
#endif
1720

1821
@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *)
1922
final class FileDescriptorTest: XCTestCase {

0 commit comments

Comments
 (0)