Compare commits

...

5 Commits

Author SHA1 Message Date
Saleem Abdulrasool
1485a1f66f
Merge 438bb9ac66d622f8f562bfd9c1aecef525c939b7 into fa43c96f5f91baaa0a86a1c44f9158ee36861c93 2025-03-19 08:43:54 +09:00
Christopher Thielen
fa43c96f5f
Move temporarily relocated FileManager extensions back to their original locations (#1213) 2025-03-18 11:29:53 -07:00
michael-yuji
2f65d91455
Fix _copyDirectoryMetadata compilation error on FreeBSD (#1121)
* Fix FreeBSD build

* address comments
2025-03-17 14:00:41 -07:00
Tony Parker
117041b61f
ISO8601ComponentStyle proposal (#1211)
* Add ISO8601ComponentStyle proposal

* Update Proposals/NNNN-ISO8601ComponentsStyle.md

* Update Proposals/NNNN-ISO8601ComponentsStyle.md

* Rename NNNN-ISO8601ComponentsStyle.md to 0021-ISO8601ComponentsStyle.md

* Update Proposals/0021-ISO8601ComponentsStyle.md

* Update Proposals/0021-ISO8601ComponentsStyle.md

---------

Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com>
2025-03-14 11:06:05 -07:00
Daniel Eggert
b95199858b
[Proposal] URI Templating (#1186)
* Proposal: URI Templating

* throwing → fallible

* Clarifications, typos, grammar.

* Add links to implementation and pitch.

* Change status to “awaiting review”.

* Use failing initializer on URL instead of makeURL()

* Update metadata

* Rename
2025-03-13 17:42:40 -07:00
6 changed files with 551 additions and 61 deletions

View File

@ -0,0 +1,256 @@
# URI Templating
* Proposal: [SF-00020](00020-uri-templating.md)
* Authors: [Daniel Eggert](https://github.com/danieleggert)
* Review Manager: [Tina L](https://github.com/itingliu)
* Status: **Review: March 14, 2025...March 21, 2025**
* Implementation: [swiftlang/swift-foundation#1198](https://github.com/swiftlang/swift-foundation/pull/1198)
* Review: ([pitch](https://forums.swift.org/t/pitch-uri-templating/78030))
## Introduction
This proposal adds support for [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) _URI templates_ to the Swift URL type.
Although there are multiple levels of expansion, the core concept is that you can define a _template_ such as
```
http://example.com/~{username}/
http://example.com/dictionary/{term:1}/{term}
http://example.com/search{?q,lang}
```
and then _expand_ these using named values (i.e. a dictionary) into a `URL`.
The templating has a rich set of options for substituting various parts of URLs. [RFC 6570 section 1.2](https://datatracker.ietf.org/doc/html/rfc6570#section-1.2) lists all 4 levels of increasing complexity.
## Motivation
[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) provides a simple, yet powerful way to allow for variable expansion in URLs.
This provides a mechanism for a server to convey to clients how to construct URLs for specific resources. In the [RFC 8620 JMAP protocol](https://datatracker.ietf.org/doc/html/rfc8620) for example, the server sends it client a template such as
```
https://jmap.example.com/download/{accountId}/{blobId}/{name}?accept={type}
```
and the client can then use variable expansion to construct a URL for resources. The API contract between the server and the client defines which variables this specific template has, and which ones are optional.
Since URI templates provide a powerful way to define URL patterns with placeholders, they are adopted in various standards.
## Proposed solution
```swift
guard
let template = URL.Template("http://www.example.com/foo{?query,number}"),
let url = URL(
template: template,
variables: [
"query": "bar baz",
"number": "234",
]
)
else { return }
```
The RFC 6570 template gets parsed as part of the `URL.Template(_:)` initializer. It will return `nil` if the passed in string is not a valid template.
The `Template` can then be expanded with _variables_ to create a URL:
```swift
extension URL {
public init?(
template: URL.Template,
variables: [URL.Template.VariableName: URL.Template.Value]
)
}
```
## Detailed design
### Templates and Expansion
[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) defines 8 different kinds of expansions:
* Simple String Expansion: `{var}`
* Reserved Expansion: `{+var}`
* Fragment Expansion: `{#var}`
* Label Expansion with Dot-Prefix: `{.var}`
* Path Segment Expansion: `{/var}`
* Path-Style Parameter Expansion: `{;var}`
* Form-Style Query Expansion: `{?var}`
* Form-Style Query Continuation: `{&var}`
Additionally, RFC 6570 allows for _prefix values_ and _composite values_.
Prefix values allow for e.g. `{var:3}` which would result in (up to) the first 3 characters of `var`.
Composite values allow `/mapper{?address*}` to e.g. expand into `/mapper?city=Newport%20Beach&state=CA`.
This implementation covers all levels and expression types defined in the RFC.
### API Details
There are 3 new types:
* `URL.Template`
* `URL.Template.VariableName`
* `URL.Template.Value`
All new API is guarded by `@available(FoundationPreview 6.2, *)`.
#### `Template`
`URL.Template` represents a parsed template that can be used to create a `URL` from it by _expanding_ variables according to RFC 6570.
Its sole API is its initializer:
```swift
extension URL {
/// A template for constructing a URL from variable expansions.
///
/// This is an template that can be expanded into
/// a ``URL`` by calling ``URL(template:variables:)``.
///
/// Templating has a rich set of options for substituting various parts of URLs. See
/// [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) for
/// details.
public struct Template: Sendable, Hashable {}
}
extension URL.Template {
/// Creates a new template from its text form.
///
/// The template string needs to be a valid RFC 6570 template.
///
/// If parsing the template fails, this will return `nil`.
public init?(_ template: String)
}
```
It will return `nil` if the provided string can not be parsed as a valid template.
#### Variables
Variables are represented as a `[URL.Template.VariableName: URL.Template.Value]`.
#### `VariableName`
The `URL.Template.VariableName` type is a type-safe wrapper around `String`
```swift
extension URL.Template {
/// The name of a variable used for expanding a template.
public struct VariableName: Sendable, Hashable {
public init(_ key: String)
}
}
```
The following extensions and conformances make it easy to convert between `VariableName` and `String`:
```swift
extension String {
public init(_ key: URL.Template.VariableName)
}
extension URL.Template.VariableName: CustomStringConvertible {
public var description: String
}
extension URL.Template.VariableName: ExpressibleByStringLiteral {
public init(stringLiteral value: String)
}
```
#### `Value`
The `URL.Template.Value` type can represent the 3 different kinds of values that RFC 6570 supports:
```swift
extension URL.Template {
/// The value of a variable used for expanding a template.
///
/// A ``Value`` can be one of 3 kinds:
/// 1. "text": a single `String`
/// 2. "list": an array of `String`
/// 3. "associative list": an ordered array of key-value pairs of `String` (similar to `Dictionary`, but ordered).
public struct Value: Sendable, Hashable {}
}
extension URL.Template.Value {
/// A text value to be used with a ``URL.Template``.
public static func text(_ text: String) -> URL.Template.Value
/// A list value (an array of `String`s) to be used with a ``URL.Template``.
public static func list(_ list: some Sequence<String>) -> URL.Template.Value
/// An associative list value (ordered key-value pairs) to be used with a ``URL.Template``.
public static func associativeList(_ list: some Sequence<(key: String, value: String)>) -> URL.Template.Value
}
```
To make it easier to use hard-coded values, the following `ExpressibleBy…` conformances are provided:
```swift
extension URL.Template.Value: ExpressibleByStringLiteral {
public init(stringLiteral value: String)
}
extension URL.Template.Value: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: String...)
}
extension URL.Template.Value: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, String)...)
}
```
#### Expansion to `URL`
Finally, `URL.Template` has this factory method:
```swift
extension URL {
/// Creates a new `URL` by expanding the RFC 6570 template and variables.
///
/// This will fail if variable expansion does not produce a valid,
/// well-formed URL.
///
/// All text will be converted to NFC (Unicode Normalization Form C) and UTF-8
/// before being percent-encoded if needed.
///
/// - Parameters:
/// - template: The RFC 6570 template to be expanded.
/// - variables: Variables to expand in the template.
public init?(
template: URL.Template,
variables: [URL.Template.VariableName: URL.Template.Value]
)
}
```
This will only fail (return `nil`) if `URL.init?(string:)` fails.
It may seem counterintuitive when and how this could fail, but a string such as `http://example.com:bad%port/` would cause `URL.init?(string:)` to fail, and URI Templates do not provide a way to prevent this. It is also worth noting that it is valid to not provide values for all variables in the template. Expansion will still succeed, generating a string. If this string is a valid URL, depends on the exact details of the template. Determining which variables exist in a template, which are required for expansion, and whether the resulting URL is valid is part of the API contract between the server providing the template and the client generating the URL.
Additionally, the new types `URL.Template`, `URL.Template.VariableName`, and `URL.Template.Value` all conform to `CustomStringConvertible`.
### Unicode
The _expansion_ that happens as part of calling `URL(template:variables:)` will
* convert text to NFC (Unicode Normalization Form C)
* convert text to UTF-8 before being percent-encoded (if needed).
as per [RFC 6570 section 1.6](https://datatracker.ietf.org/doc/html/rfc6570#section-1.6).
## Source compatibility
These changes are additive only and are not expected to have an impact on source compatibility.
## Implications on adoption
This feature can be freely adopted and un-adopted in source code with no deployment constraints and without affecting source compatibility.
## Future directions
Since this proposal covers all of RFC 6570, the current expectation is for it to not be extended further.
## Alternatives considered
Instead of `URL.init?(template:variables)`, the API could have used a method on `URL.Template`, e.g. `URL.Template.makeURL(variables:)`. The `URL.init?` approach would be less discoverable. There was some feedback to the initial pitch, though, that preferred the `URL.init?` method which aligns with the existing `URL.init?(string:)` initializer. This initializer approach aligns more with existing `URL.init?(string:)` and `String.init(format:)`.
Additionally, the API _could_ expose a (non-failing!) `URL.Template.expand(variables:)` (or other naming) that returns a `String`. But since the purpose is very clearly to create URLs, it feels like that would just add noise.
Using a DSL (domain-specific language) for `URL.Template` could improve type safety. However, because servers typically send templates as strings for client-side processing and request generation, the added complexity of a DSL outweighs its benefits. The proposed implementation is string-based (_stringly typed_) because that is what the RFC 6570 mandates.
There was a lot of interest during the pitch to have an API that lends itself to _routing_, providing a way to go back-and-forth between a route and its variables. But thats a very different use case than the RFC 6570 templates provide, and it would be better suited to have a web server routing specific API, either in Foundation or in a web server specific package. [pointfreeco/swift-url-routing](https://github.com/pointfreeco/swift-url-routing) is one such example.
Instead of using the `text`, `list` and `associativeList` names (which are _terms of art_ in RFC 6570), the names `string`, `array`, and `orderedDictioary` would align better with normal Swift naming conventions. The proposal favors the _terms of art_, but there was some interest in using changing this.

View File

@ -0,0 +1,212 @@
# ISO8601 Components Formatting and Parsing
* Proposal: SF-0021
* Author(s): Tony Parker <anthony.parker@apple.com>
* Status: **Review: March 17, 2025...March 24, 2025**
* Intended Release: _Swift 6.2_
* Review: ([pitch](https://forums.swift.org/t/pitch-iso8601-components-format-style/77990))
*_Related issues_*
* https://github.com/swiftlang/swift-foundation/issues/323
* https://github.com/swiftlang/swift-foundation/issues/967
* https://github.com/swiftlang/swift-foundation/issues/1159
## Revision history
* **v1** Initial version
## Introduction
Based upon feedback from adoption of `ISO8601FormatStyle`, we propose two changes and one addition to the API:
- Change the behavior of the `includingFractionalSeconds` flag with respect to parsing. `ISO8601FormatStyle` will now always allow fractional seconds regardless of the setting.
- Change the behavior of the time zone flag with respect to parsing. `ISO8601FormatStyle` will now always allow hours-only time zone offsets.
- Add a _components_ style, which formats `DateComponents` into ISO8601 and parses ISO8601-formatted `String`s into `DateComponents`.
## Motivation
The existing `Date.ISO8601FormatStyle` type has one property for controlling fractional seconds, and it is also settable in the initializer. The existing behavior is that parsing _requires_ presence of fractional seconds if set, and _requires_ absence of fractional seconds if not set.
If the caller does not know if the string contains the fractional seconds or not, they are forced to parse the string twice:
```swift
let str = "2022-01-28T15:35:46Z"
var result = try? Date.ISO8601FormatStyle(includingFractionalSeconds: false).parse(str)
if result == nil {
result = try? Date.ISO8601FormatStyle(includingFractionalSeconds: true).parse(str)
}
```
In most cases, the caller simply does not care if the fractional seconds are present or not. Therefore, we propose changing the behavior of the parser to **always** allow fractional seconds, regardless of the setting of the `includeFractionalSeconds` flag. The flag is still used for formatting.
With respect to time zone offsets, the parser has always allowed the optional presence of seconds, as well the optional presence of `:`. We propose extending this behavior to allow optional minutes as well. The following are considered well-formed by the parser:
```
2022-01-28T15:35:46 +08
2022-01-28T15:35:46 +0800
2022-01-28T15:35:46 +080000
2022-01-28T15:35:46 +08
2022-01-28T15:35:46 +08:00
2022-01-28T15:35:46 +08:00:00
```
In order to provide an alternative for cases where strict parsing is required, a new parser is provided that returns the _components_ of the parsed date instead of the resolved `Date` itself. This new parser also provides a mechanism to retrieve the time zone from an ISO8601-formatted string. Following parsing of the components, the caller can resolve them into a `Date` using the regular `Calendar` and `DateComponents` API.
## Proposed solution and example
In addition to the behavior change above, we propose introducing a new `DateComponents.ISO8601FormatStyle`. The API surface is nearly identical to `Date.ISO8601FormatStyle`, with the exception of the output type. It reuses the same inner types, and they share a common implementation. The full API surface is in the detailed design, below.
Formatting ISO8601 components is just as straightforward as formatting a `Date`.
```swift
let components = DateComponents(year: 1999, month: 12, day: 31, hour: 23, minute: 59, second: 59)
let formatted = components.formatted(.iso8601Components)
print(formatted) // 1999-12-31T23:59:59Z
```
Parsing ISO8601 components follows the same pattern as other parse strategies:
```swift
let components = try DateComponents.ISO8601FormatStyle().parse("2022-01-28T15:35:46Z")
// components are: DateComponents(timeZone: .gmt, year: 2022, month: 1, day: 28, hour: 15, minute: 35, second: 46))
```
If further conversion to a `Date` is required, the existing `Calendar` API can be used:
```swift
let date = components.date // optional result, date may be invalid
```
## Detailed design
The full API surface of the new style is:
```swift
@available(FoundationPreview 6.2, *)
extension DateComponents {
/// Options for generating and parsing string representations of dates following the ISO 8601 standard.
public struct ISO8601FormatStyle : Sendable, Codable, Hashable {
public var timeSeparator: Date.ISO8601FormatStyle.TimeSeparator { get }
/// If set, fractional seconds will be present in formatted output. Fractional seconds may be present in parsing regardless of the setting of this property.
public var includingFractionalSeconds: Bool { get }
public var timeZoneSeparator: Date.ISO8601FormatStyle.TimeZoneSeparator { get }
public var dateSeparator: Date.ISO8601FormatStyle.DateSeparator { get }
public var dateTimeSeparator: Date.ISO8601FormatStyle.DateTimeSeparator { get }
public init(from decoder: any Decoder) throws
public func encode(to encoder: any Encoder) throws
public func hash(into hasher: inout Hasher)
public static func ==(lhs: ISO8601FormatStyle, rhs: ISO8601FormatStyle) -> Bool
public var timeZone: TimeZone = TimeZone(secondsFromGMT: 0)!
// The default is the format of RFC 3339 with no fractional seconds: "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
public init(dateSeparator: Date.ISO8601FormatStyle.DateSeparator = .dash, dateTimeSeparator: Date.ISO8601FormatStyle.DateTimeSeparator = .standard, timeSeparator: Date.ISO8601FormatStyle.TimeSeparator = .colon, timeZoneSeparator: Date.ISO8601FormatStyle.TimeZoneSeparator = .omitted, includingFractionalSeconds: Bool = false, timeZone: TimeZone = TimeZone(secondsFromGMT: 0)!)
}
}
@available(FoundationPreview 6.2, *)
extension DateComponents.ISO8601FormatStyle {
public func year() -> Self
public func weekOfYear() -> Self
public func month() -> Self
public func day() -> Self
public func time(includingFractionalSeconds: Bool) -> Self
public func timeZone(separator: Date.ISO8601FormatStyle.TimeZoneSeparator) -> Self
public func dateSeparator(_ separator: Date.ISO8601FormatStyle.DateSeparator) -> Self
public func dateTimeSeparator(_ separator: Date.ISO8601FormatStyle.DateTimeSeparator) -> Self
public func timeSeparator(_ separator: Date.ISO8601FormatStyle.TimeSeparator) -> Self
public func timeZoneSeparator(_ separator: Date.ISO8601FormatStyle.TimeZoneSeparator) -> Self
}
@available(FoundationPreview 6.2, *)
extension DateComponents.ISO8601FormatStyle : FormatStyle {
public func format(_ value: DateComponents) -> String
}
@available(FoundationPreview 6.2, *)
public extension FormatStyle where Self == DateComponents.ISO8601FormatStyle {
static var iso8601Components: Self
}
@available(FoundationPreview 6.2, *)
public extension ParseableFormatStyle where Self == DateComponents.ISO8601FormatStyle {
static var iso8601Components: Self
}
@available(FoundationPreview 6.2, *)
public extension ParseStrategy where Self == DateComponents.ISO8601FormatStyle {
@_disfavoredOverload
static var iso8601Components: Self
}
@available(FoundationPreview 6.2, *)
extension DateComponents.ISO8601FormatStyle : ParseStrategy {
public func parse(_ value: String) throws -> DateComponents
}
@available(FoundationPreview 6.2, *)
extension DateComponents.ISO8601FormatStyle: ParseableFormatStyle {
public var parseStrategy: Self
}
@available(FoundationPreview 6.2, *)
extension DateComponents.ISO8601FormatStyle : CustomConsumingRegexComponent {
public typealias RegexOutput = DateComponents
public func consuming(_ input: String, startingAt index: String.Index, in bounds: Range<String.Index>) throws -> (upperBound: String.Index, output: DateComponents)?
}
@available(FoundationPreview 6.2, *)
extension RegexComponent where Self == DateComponents.ISO8601FormatStyle {
@_disfavoredOverload
public static var iso8601Components: DateComponents.ISO8601FormatStyle
public static func iso8601ComponentsWithTimeZone(includingFractionalSeconds: Bool = false, dateSeparator: Date.ISO8601FormatStyle.DateSeparator = .dash, dateTimeSeparator: Date.ISO8601FormatStyle.DateTimeSeparator = .standard, timeSeparator: Date.ISO8601FormatStyle.TimeSeparator = .colon, timeZoneSeparator: Date.ISO8601FormatStyle.TimeZoneSeparator = .omitted) -> Self
public static func iso8601Components(timeZone: TimeZone, includingFractionalSeconds: Bool = false, dateSeparator: Date.ISO8601FormatStyle.DateSeparator = .dash, dateTimeSeparator: Date.ISO8601FormatStyle.DateTimeSeparator = .standard, timeSeparator: Date.ISO8601FormatStyle.TimeSeparator = .colon) -> Self
public static func iso8601Components(timeZone: TimeZone, dateSeparator: Date.ISO8601FormatStyle.DateSeparator = .dash) -> Self
}
```
Unlike the `Date` format style, formatting with a `DateComponents` style can have a mismatch between the specified output fields and the contents of the `DateComponents` struct. In the case where the input `DateComponents` is missing required values, then the formatter will fill in default values to ensure correct output.
```swift
let components = DateComponents(year: 1999, month: 12, day: 31)
let formatted = components.formatted(.iso8601Components) // 1999-12-31T00:00:00Z
```
## Impact on existing code
The change to always allow fractional seconds will affect existing code. As described above, we believe the improvement in the API surface is worth the risk of introducing unexpected behavior for the rare case that a parser truly needs to specify the exact presence or absence of frational seconds.
If code depending on this new behavior must be backdeployed before Swift 6.2, then Swift's `if #available` checks may be used to parse twice on older releases of the OS or Swift.
## Alternatives considered
### "Allowing" Option
We considered adding a new flag to `Date.ISO8601FormatStyle` to control the optional parsing of fractional seconds. However, the truth table quickly became confusing:
#### Formatting
| `includingFractionalSeconds` | `allowingFractionalSeconds` | Fractional Seconds |
| -- | -- | -- |
| `true` | `true` | Included |
| `true` | `false` | Included |
| `false` | `true` | Excluded |
| `false` | `true` | Excluded |
#### Parsing
| `includingFractionalSeconds` | `allowingFractionalSeconds` | Fractional Seconds |
| -- | -- | -- |
| `true` | `true` | Required Present |
| `true` | `false` | Required Present |
| `false` | `true` | ? |
| `false` | `true` | Allow Present or Missing |
In addition, all the initializers needed to be duplicated to add the new option.
In practice, the additional complexity did not seem worth the tradeoff with the potential for a compatibility issue with the existing style. This does require callers to be aware that the behavior has changed in the release in which this feature ships. Therefore, we will be clear in the documentation about the change.

View File

@ -201,7 +201,40 @@ extension stat {
} }
} }
#if FOUNDATION_FRAMEWORK
extension FileProtectionType {
var intValue: Int32? {
switch self {
case .complete: PROTECTION_CLASS_A
case .init(rawValue: "NSFileProtectionWriteOnly"), .completeUnlessOpen: PROTECTION_CLASS_B
case .init(rawValue: "NSFileProtectionCompleteUntilUserAuthentication"), .completeUntilFirstUserAuthentication: PROTECTION_CLASS_C
case .none: PROTECTION_CLASS_D
#if !os(macOS)
case .completeWhenUserInactive: PROTECTION_CLASS_CX
#endif
default: nil
}
}
init?(intValue value: Int32) {
switch value {
case PROTECTION_CLASS_A: self = .complete
case PROTECTION_CLASS_B: self = .completeUnlessOpen
case PROTECTION_CLASS_C: self = .completeUntilFirstUserAuthentication
case PROTECTION_CLASS_D: self = .none
#if !os(macOS)
case PROTECTION_CLASS_CX: self = .completeWhenUserInactive
#endif
default: return nil
}
}
}
#endif #endif
#endif
extension FileAttributeKey {
fileprivate static var _extendedAttributes: Self { Self("NSFileExtendedAttributes") }
}
extension _FileManagerImpl { extension _FileManagerImpl {
func createFile( func createFile(

View File

@ -69,6 +69,12 @@ extension stat {
} }
#endif #endif
#if FOUNDATION_FRAMEWORK && os(macOS)
extension URLResourceKey {
static var _finderInfoKey: Self { URLResourceKey("_NSURLFinderInfoKey") }
}
#endif
extension _FileManagerImpl { extension _FileManagerImpl {
#if os(macOS) && FOUNDATION_FRAMEWORK #if os(macOS) && FOUNDATION_FRAMEWORK
private struct _HFSFinderInfo { private struct _HFSFinderInfo {

View File

@ -960,6 +960,49 @@ enum _FileOperations {
private static func _copyDirectoryMetadata(srcFD: CInt, srcPath: @autoclosure () -> String, dstFD: CInt, dstPath: @autoclosure () -> String, delegate: some LinkOrCopyDelegate) throws { private static func _copyDirectoryMetadata(srcFD: CInt, srcPath: @autoclosure () -> String, dstFD: CInt, dstPath: @autoclosure () -> String, delegate: some LinkOrCopyDelegate) throws {
#if !os(WASI) && !os(Android) #if !os(WASI) && !os(Android)
// Copy extended attributes // Copy extended attributes
#if os(FreeBSD)
// FreeBSD uses the `extattr_*` calls for setting extended attributes. Unlike like, the namespace for the extattrs are not determined by prefix of the attribute
for namespace in [EXTATTR_NAMESPACE_SYSTEM, EXTATTR_NAMESPACE_USER] {
// if we don't have permission to list attributes in system namespace, this returns -1 and skips it
var size = extattr_list_fd(srcFD, namespace, nil, 0)
if size > 0 {
// we are allocating size + 1 bytes here such that we have room for the last null terminator
try withUnsafeTemporaryAllocation(of: CChar.self, capacity: size + 1) { keyList in
// The list of entry returns by `extattr_list_*` contains the length(1 byte) of the attribute name, follow by the Non-NULL terminated attribute name. (See exattr(2))
size = extattr_list_fd(srcFD, namespace, keyList.baseAddress!, size)
guard size > 0 else { continue }
var keyLength = Int(keyList.baseAddress!.pointee)
var current = keyList.baseAddress!.advanced(by: 1)
let end = keyList.baseAddress!.advanced(by: size)
keyList.baseAddress!.advanced(by: size).pointee = 0
while current < end {
let nextEntry = current.advanced(by: keyLength)
// get the length of next key, if this is the last entry, this points to the explicitly zerod byte at `end`.
keyLength = Int(nextEntry.pointee)
// zero the length field of the next name, so current name can pass in as a null-terminated string
nextEntry.pointee = 0
// this also set `current` to `end` after iterating all entries
defer { current = nextEntry.advanced(by: 1) }
var valueSize = extattr_get_fd(srcFD, namespace, current, nil, 0)
if valueSize >= 0 {
try withUnsafeTemporaryAllocation(of: UInt8.self, capacity: valueSize) { valueBuffer in
valueSize = extattr_get_fd(srcFD, namespace, current, valueBuffer.baseAddress!, valueSize)
if valueSize >= 0 {
if extattr_set_fd(srcFD, namespace, current, valueBuffer.baseAddress!, valueSize) != 0 {
try delegate.throwIfNecessary(errno, srcPath(), dstPath())
}
}
}
}
}
}
}
}
#else
var size = flistxattr(srcFD, nil, 0) var size = flistxattr(srcFD, nil, 0)
if size > 0 { if size > 0 {
try withUnsafeTemporaryAllocation(of: CChar.self, capacity: size) { keyList in try withUnsafeTemporaryAllocation(of: CChar.self, capacity: size) { keyList in
@ -985,6 +1028,7 @@ enum _FileOperations {
} }
} }
#endif #endif
#endif
var statInfo = stat() var statInfo = stat()
if fstat(srcFD, &statInfo) == 0 { if fstat(srcFD, &statInfo) == 0 {
#if !os(WASI) // WASI doesn't have fchown for now #if !os(WASI) // WASI doesn't have fchown for now

View File

@ -271,65 +271,4 @@ extension BidirectionalCollection where Element == Unicode.Scalar, Index == Stri
} }
// START: Workaround for https://github.com/swiftlang/swift/pull/78697
// The extensions below are temporarily relocated to work around a compiler crash.
// Once that crash is resolved, they should be moved back to their original files.
#if !NO_FILESYSTEM
// Relocated from FileManager+Utilities.swift
#if FOUNDATION_FRAMEWORK && os(macOS)
extension URLResourceKey {
static var _finderInfoKey: Self { URLResourceKey("_NSURLFinderInfoKey") }
}
#endif #endif
// Relocated from FileManager+Files.swift
#if FOUNDATION_FRAMEWORK
internal import DarwinPrivate.sys.content_protection
#endif
#if !os(Windows)
#if FOUNDATION_FRAMEWORK
extension FileProtectionType {
var intValue: Int32? {
switch self {
case .complete: PROTECTION_CLASS_A
case .init(rawValue: "NSFileProtectionWriteOnly"), .completeUnlessOpen: PROTECTION_CLASS_B
case .init(rawValue: "NSFileProtectionCompleteUntilUserAuthentication"), .completeUntilFirstUserAuthentication: PROTECTION_CLASS_C
case .none: PROTECTION_CLASS_D
#if !os(macOS)
case .completeWhenUserInactive: PROTECTION_CLASS_CX
#endif
default: nil
}
}
init?(intValue value: Int32) {
switch value {
case PROTECTION_CLASS_A: self = .complete
case PROTECTION_CLASS_B: self = .completeUnlessOpen
case PROTECTION_CLASS_C: self = .completeUntilFirstUserAuthentication
case PROTECTION_CLASS_D: self = .none
#if !os(macOS)
case PROTECTION_CLASS_CX: self = .completeWhenUserInactive
#endif
default: return nil
}
}
}
#endif
#endif
#endif
#endif
#if !NO_FILESYSTEM
// Relocated from FileManager+Files.swift. Originally fileprivate.
extension FileAttributeKey {
internal static var _extendedAttributes: Self { Self("NSFileExtendedAttributes") }
}
#endif
// END: Workaround for https://github.com/swiftlang/swift/pull/78697