Skip to content

[1.4.0] cherry-pick #158 & #176 #198

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

Merged
merged 12 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Sources/System/ErrnoWindows.swift
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 {
internal init(windowsError: DWORD) {
self.init(rawValue: _mapWindowsErrorToErrno(windowsError))
}
}

#endif
51 changes: 45 additions & 6 deletions Sources/System/FileOperations.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2020 Apple Inc. and the Swift System project authors
Copyright (c) 2020 - 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
Expand Down Expand Up @@ -135,8 +135,6 @@ extension FileDescriptor {
if let permissions = permissions {
return system_open(path, oFlag, permissions.rawValue)
}
precondition(!options.contains(.create),
"Create must be given permissions")
return system_open(path, oFlag)
}
return descOrError.map { FileDescriptor(rawValue: $0) }
Expand Down Expand Up @@ -369,6 +367,7 @@ extension FileDescriptor {
}
}

#if !os(WASI)
@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *)
extension FileDescriptor {
/// Duplicates this file descriptor and return the newly created copy.
Expand Down Expand Up @@ -433,8 +432,9 @@ extension FileDescriptor {
fatalError("Not implemented")
}
}
#endif

#if !os(Windows)
#if !os(WASI)
@available(/*System 1.1.0: macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4*/iOS 8, *)
extension FileDescriptor {
/// Creates a unidirectional data channel, which can be used for interprocess communication.
Expand Down Expand Up @@ -463,7 +463,6 @@ extension FileDescriptor {
}
#endif

#if !os(Windows)
@available(/*System 1.2.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *)
extension FileDescriptor {
/// Truncates or extends the file referenced by this file descriptor.
Expand Down Expand Up @@ -509,4 +508,44 @@ extension FileDescriptor {
}
}
}
#endif

extension FilePermissions {
/// The file creation permission mask (aka "umask").
///
/// Permissions set in this mask will be cleared by functions that create
/// files or directories. Note that this mask is process-wide, and that
/// *getting* it is not thread safe.
internal static var creationMask: FilePermissions {
get {
let oldMask = _umask(0o22)
_ = _umask(oldMask)
return FilePermissions(rawValue: oldMask)
}
set {
_ = _umask(newValue.rawValue)
}
}

/// Change the file creation permission mask, run some code, then
/// restore it to its original value.
///
/// - Parameters:
/// - permissions: The new permission mask.
///
/// This is more efficient than reading `creationMask` and restoring it
/// afterwards, because of the way reading the creation mask works.
internal static func withCreationMask<R>(
_ permissions: FilePermissions,
body: () throws -> R
) rethrows -> R {
let oldMask = _umask(permissions.rawValue)
defer {
_ = _umask(oldMask)
}
return try body()
}

internal static func _umask(_ mode: CModeT) -> CModeT {
return system_umask(mode)
}
}
10 changes: 9 additions & 1 deletion Sources/System/FilePath/FilePathParsing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ extension SystemString {
// `_prenormalizeWindowsRoots` and resume.
readIdx = _prenormalizeWindowsRoots()
writeIdx = readIdx

// Skip redundant separators
while readIdx < endIndex && isSeparator(self[readIdx]) {
self.formIndex(after: &readIdx)
}
} else {
assert(genericSeparator == platformSeparator)
}
Expand Down Expand Up @@ -330,10 +335,13 @@ extension FilePath {
// Whether we are providing Windows paths
@inline(__always)
internal var _windowsPaths: Bool {
if let forceWindowsPaths = forceWindowsPaths {
return forceWindowsPaths
}
#if os(Windows)
return true
#else
return forceWindowsPaths
return false
#endif
}

Expand Down
97 changes: 97 additions & 0 deletions Sources/System/FilePath/FilePathTemp.swift
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.
internal func withTemporaryFilePath<R>(
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 {
return true
}
let err = system_errno
if err == Errno.fileExists.rawValue {
return false
} else {
throw Errno(rawValue: err)
}
}
}

/// 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{
_ 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)

if try makeLockedDownDirectory(at: tempDir) {
return tempDir
}
}
}
153 changes: 153 additions & 0 deletions Sources/System/FilePath/FilePathTempPosix.swift
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)
Loading