-
Notifications
You must be signed in to change notification settings - Fork 123
Fix swift-system to work on Windows. #158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cfd3f9d
c6456a9
ec7d406
21638a0
33773a8
2357d69
f5ad5e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/* | ||
This source file is part of the Swift System open source project | ||
|
||
Copyright (c) 2024 Apple Inc. and the Swift System project authors | ||
Licensed under Apache License v2.0 with Runtime Library Exception | ||
|
||
See https://swift.org/LICENSE.txt for license information | ||
*/ | ||
|
||
#if os(Windows) | ||
|
||
import WinSDK | ||
|
||
extension Errno { | ||
public init(windowsError: DWORD) { | ||
self.init(rawValue: _mapWindowsErrorToErrno(windowsError)) | ||
} | ||
} | ||
|
||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
This source file is part of the Swift System open source project | ||
|
||
Copyright (c) 2024 Apple Inc. and the Swift System project authors | ||
Licensed under Apache License v2.0 with Runtime Library Exception | ||
|
||
See https://swift.org/LICENSE.txt for license information | ||
*/ | ||
|
||
// MARK: - API | ||
|
||
/// Create a temporary path for the duration of the closure. | ||
/// | ||
/// - Parameters: | ||
/// - basename: The base name for the temporary path. | ||
/// - body: The closure to execute. | ||
/// | ||
/// Creates a temporary directory with a name based on the given `basename`, | ||
/// executes `body`, passing in the path of the created directory, then | ||
/// deletes the directory and all of its contents before returning. | ||
public func withTemporaryFilePath<R>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make sure that any API additions go through some kind of review, ideally on the forums. At the very least, a dedicated PR that highlights it's an API addition PR as opposed to just a bug-fix PR for Windows. For this function, would a static method on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it would. My only reservation is that (to me) things like I only needed this for the tests, so for now I think the thing to do is to make this non-public (I'll raise a PR to fix that). I'll check through to make sure I didn't leak any other new API and do the same there. |
||
basename: FilePath.Component, | ||
_ body: (FilePath) throws -> R | ||
) throws -> R { | ||
let temporaryDir = try createUniqueTemporaryDirectory(basename: basename) | ||
defer { | ||
try? _recursiveRemove(at: temporaryDir) | ||
} | ||
|
||
return try body(temporaryDir) | ||
} | ||
|
||
// MARK: - Internals | ||
|
||
fileprivate let base64 = Array<UInt8>( | ||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".utf8 | ||
) | ||
|
||
/// Create a directory that is only accessible to the current user. | ||
/// | ||
/// - Parameters: | ||
/// - path: The path of the directory to create. | ||
/// - Returns: `true` if a new directory was created. | ||
/// | ||
/// This function will throw if there is an error, except if the error | ||
/// is that the directory exists, in which case it returns `false`. | ||
fileprivate func makeLockedDownDirectory(at path: FilePath) throws -> Bool { | ||
return try path.withPlatformString { | ||
if system_mkdir($0, 0o700) == 0 { | ||
al45tair marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return true | ||
} | ||
let err = system_errno | ||
if err == Errno.fileExists.rawValue { | ||
return false | ||
} else { | ||
throw Errno(rawValue: err) | ||
} | ||
al45tair marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
/// Generate a random string of base64 filename safe characters. | ||
/// | ||
/// - Parameters: | ||
/// - length: The number of characters in the returned string. | ||
/// - Returns: A random string of length `length`. | ||
fileprivate func createRandomString(length: Int) -> String { | ||
return String( | ||
decoding: (0..<length).map{ | ||
al45tair marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_ in base64[Int.random(in: 0..<64)] | ||
}, | ||
as: UTF8.self | ||
) | ||
} | ||
|
||
/// Given a base name, create a uniquely named temporary directory. | ||
/// | ||
/// - Parameters: | ||
/// - basename: The base name for the new directory. | ||
/// - Returns: The path to the new directory. | ||
/// | ||
/// Creates a directory in the system temporary directory whose name | ||
/// starts with `basename`, followed by a `.` and then a random | ||
/// string of characters. | ||
fileprivate func createUniqueTemporaryDirectory( | ||
basename: FilePath.Component | ||
) throws -> FilePath { | ||
var tempDir = try _getTemporaryDirectory() | ||
tempDir.append(basename) | ||
|
||
while true { | ||
tempDir.extension = createRandomString(length: 16) | ||
al45tair marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if try makeLockedDownDirectory(at: tempDir) { | ||
return tempDir | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/* | ||
This source file is part of the Swift System open source project | ||
|
||
Copyright (c) 2024 Apple Inc. and the Swift System project authors | ||
Licensed under Apache License v2.0 with Runtime Library Exception | ||
|
||
See https://swift.org/LICENSE.txt for license information | ||
*/ | ||
|
||
#if !os(Windows) | ||
|
||
/// Get the path to the system temporary directory. | ||
internal func _getTemporaryDirectory() throws -> FilePath { | ||
guard let tmp = system_getenv("TMPDIR") else { | ||
return "/tmp" | ||
} | ||
|
||
return FilePath(SystemString(platformString: tmp)) | ||
} | ||
|
||
/// Delete the entire contents of a directory, including its subdirectories. | ||
/// | ||
/// - Parameters: | ||
/// - path: The directory to be deleted. | ||
/// | ||
/// Removes a directory completely, including all of its contents. | ||
internal func _recursiveRemove( | ||
at path: FilePath | ||
) throws { | ||
let dirfd = try FileDescriptor.open(path, .readOnly, options: .directory) | ||
defer { | ||
try? dirfd.close() | ||
} | ||
|
||
let dot: (CInterop.PlatformChar, CInterop.PlatformChar) = (46, 0) | ||
try withUnsafeBytes(of: dot) { | ||
try recursiveRemove( | ||
in: dirfd.rawValue, | ||
name: $0.assumingMemoryBound(to: CInterop.PlatformChar.self).baseAddress! | ||
) | ||
} | ||
|
||
try path.withPlatformString { | ||
if system_rmdir($0) != 0 { | ||
throw Errno.current | ||
} | ||
} | ||
} | ||
|
||
/// Open a directory by reference to its parent and name. | ||
/// | ||
/// - Parameters: | ||
/// - dirfd: An open file descriptor for the parent directory. | ||
/// - name: The name of the directory to open. | ||
/// - Returns: A pointer to a `DIR` structure. | ||
/// | ||
/// This is like `opendir()`, but instead of taking a path, it uses a | ||
/// file descriptor pointing at the parent, thus avoiding path length | ||
/// limits. | ||
fileprivate func impl_opendirat( | ||
_ dirfd: CInt, | ||
_ name: UnsafePointer<CInterop.PlatformChar> | ||
) -> system_DIRPtr? { | ||
let fd = system_openat(dirfd, name, | ||
FileDescriptor.AccessMode.readOnly.rawValue | ||
| FileDescriptor.OpenOptions.directory.rawValue) | ||
if fd < 0 { | ||
return nil | ||
} | ||
return system_fdopendir(fd) | ||
} | ||
|
||
/// Invoke a closure for each file within a particular directory. | ||
/// | ||
/// - Parameters: | ||
/// - dirfd: The parent of the directory to be enumerated. | ||
/// - subdir: The subdirectory to be enumerated. | ||
/// - body: The closure that will be invoked. | ||
/// | ||
/// We skip the `.` and `..` pseudo-entries. | ||
fileprivate func forEachFile( | ||
in dirfd: CInt, | ||
subdir: UnsafePointer<CInterop.PlatformChar>, | ||
_ body: (system_dirent) throws -> () | ||
) throws { | ||
guard let dir = impl_opendirat(dirfd, subdir) else { | ||
throw Errno.current | ||
} | ||
defer { | ||
_ = system_closedir(dir) | ||
} | ||
|
||
while let dirent = system_readdir(dir) { | ||
// Skip . and .. | ||
if dirent.pointee.d_name.0 == 46 | ||
&& (dirent.pointee.d_name.1 == 0 | ||
|| (dirent.pointee.d_name.1 == 46 | ||
&& dirent.pointee.d_name.2 == 0)) { | ||
continue | ||
} | ||
|
||
try body(dirent.pointee) | ||
} | ||
} | ||
|
||
/// Delete the entire contents of a directory, including its subdirectories. | ||
/// | ||
/// - Parameters: | ||
/// - dirfd: The parent of the directory to be removed. | ||
/// - name: The name of the directory to be removed. | ||
/// | ||
/// Removes a directory completely, including all of its contents. | ||
fileprivate func recursiveRemove( | ||
in dirfd: CInt, | ||
name: UnsafePointer<CInterop.PlatformChar> | ||
) throws { | ||
// First, deal with subdirectories | ||
try forEachFile(in: dirfd, subdir: name) { dirent in | ||
if dirent.d_type == SYSTEM_DT_DIR { | ||
try withUnsafeBytes(of: dirent.d_name) { | ||
try recursiveRemove( | ||
in: dirfd, | ||
name: $0.assumingMemoryBound(to: CInterop.PlatformChar.self) | ||
.baseAddress! | ||
) | ||
} | ||
} | ||
} | ||
|
||
// Now delete the contents of this directory | ||
try forEachFile(in: dirfd, subdir: name) { dirent in | ||
let flag: CInt | ||
|
||
if dirent.d_type == SYSTEM_DT_DIR { | ||
flag = SYSTEM_AT_REMOVE_DIR | ||
} else { | ||
flag = 0 | ||
} | ||
|
||
let result = withUnsafeBytes(of: dirent.d_name) { | ||
system_unlinkat(dirfd, | ||
$0.assumingMemoryBound(to: CInterop.PlatformChar.self) | ||
.baseAddress!, | ||
flag) | ||
} | ||
|
||
if result != 0 { | ||
throw Errno.current | ||
} | ||
} | ||
} | ||
|
||
#endif // !os(Windows) |
Uh oh!
There was an error while loading. Please reload this page.