Skip to content
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

Added socket support #65

Closed
wants to merge 5 commits into from
Closed
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
14 changes: 14 additions & 0 deletions Sources/System/FileDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ public struct FileDescriptor: RawRepresentable, Hashable, Codable {
public init(rawValue: CInt) { self.rawValue = rawValue }
}

internal extension FileDescriptor {

init(socket: CInterop.SocketDescriptor) {
// On Unix a file descriptor can be a file, pipe, socket, etc
// On windows file descriptors and sockets are different types
#if os(Windows)
#warning("Windows sockets not implemented")
fatalError()
#else
self.init(rawValue: socket)
#endif
}
}

// Standard file descriptors
extension FileDescriptor {
/// The standard input file descriptor, with a numeric value of 0.
Expand Down
179 changes: 179 additions & 0 deletions Sources/System/FileDescriptorSet.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2020 - 2021 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
*/

public extension FileDescriptor {

/// Set of file descriptors
@frozen
struct Set {

@_alwaysEmitIntoClient
internal private(set) var bytes: CInterop.FileDescriptorSet

@_alwaysEmitIntoClient
public init(_ bytes: CInterop.FileDescriptorSet) {
self.bytes = bytes
}

@_alwaysEmitIntoClient
public init() {
self.init(CInterop.FileDescriptorSet())
}
}
}

public extension FileDescriptor.Set {

@_alwaysEmitIntoClient
init<S>(_ sequence: S) where S: Sequence, S.Element == FileDescriptor {
self.init()
for element in sequence {
bytes.set(element.rawValue)
}
}

@_alwaysEmitIntoClient
mutating func append(_ element: FileDescriptor) {
bytes.set(element.rawValue)
}

@_alwaysEmitIntoClient
mutating func remove(_ element: FileDescriptor) {
bytes.clear(element.rawValue)
}

@_alwaysEmitIntoClient
mutating func contains(_ element: FileDescriptor) -> Bool {
bytes.isSet(element.rawValue)
}

@_alwaysEmitIntoClient
mutating func removeAll() {
self.bytes.zero()
}

#if os(Windows)
@_alwaysEmitIntoClient
var count: Int {
return numericCast(bytes.fd_count)
}
#endif
}

extension FileDescriptor.Set: ExpressibleByArrayLiteral {

public init(arrayLiteral elements: FileDescriptor...) {
assert(elements.count <= _fd_set_count,
"FileDescriptor.Set can only contain \(_fd_set_count) elements")
self.init(elements)
}
}

extension FileDescriptor.Set: CustomStringConvertible, CustomDebugStringConvertible {

@inline(never)
public var description: String {
return "FileDescriptor.Set()"
}

public var debugDescription: String {
return description
}
}

public extension FileDescriptor.Set {

@_alwaysEmitIntoClient
mutating func withUnsafeMutablePointer<T>(_ body: (UnsafeMutablePointer<Int32>) throws -> T) rethrows -> T {
return try bytes.withUnsafeMutablePointer(body)
}
}

internal extension CInterop.FileDescriptorSet {

@usableFromInline
mutating func zero() {
withUnsafeMutablePointer {
$0.initialize(repeating: 0, count: _fd_set_count)
}
}

///
/// Set an fd in an fd_set
///
/// - Parameter fd: The fd to add to the fd_set
///
@usableFromInline
mutating func set(_ fd: CInt) {
let (index, mask) = Self.offset(for: fd)
withUnsafeMutablePointer { $0[index] |= mask }
}

///
/// Clear an fd from an fd_set
///
/// - Parameter fd: The fd to clear from the fd_set
///
@usableFromInline
mutating func clear(_ fd: CInt) {
let (index, mask) = Self.offset(for: fd)
withUnsafeMutablePointer { $0[index] &= ~mask }
}

///
/// Check if an fd is present in an fd_set
///
/// - Parameter fd: The fd to check
///
/// - Returns: `True` if present, `false` otherwise.
///
@usableFromInline
mutating func isSet(_ fd: CInt) -> Bool {
let (index, mask) = Self.offset(for: fd)
return withUnsafeMutablePointer { $0[index] & mask != 0 }
}

@usableFromInline
static func offset(for fd: CInt) -> (offset: Int, mask: CInt) {
var intOffset = Int(fd) / _fd_set_count
#if _endian(big)
if intOffset % 2 == 0 {
intOffset += 1
} else {
intOffset -= 1
}
#endif
let bitOffset = Int(fd) % _fd_set_count
let mask = CInt(bitPattern: UInt32(1 << bitOffset))
return (intOffset, mask)
}

#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
@usableFromInline
mutating func withUnsafeMutablePointer<T>(_ body: (UnsafeMutablePointer<CInt>) throws -> T) rethrows -> T {
return try Swift.withUnsafeMutablePointer(to: &fds_bits) {
try body(UnsafeMutableRawPointer($0).assumingMemoryBound(to: CInt.self))
}
}
#elseif os(Linux) || os(FreeBSD) || os(Android)
@usableFromInline
mutating func withUnsafeMutablePointer<T>(_ body: (UnsafeMutablePointer<CInt>) throws -> T) rethrows -> T {
return try Swift.withUnsafeMutablePointer(to: &__fds_bits) {
try body(UnsafeMutableRawPointer($0).assumingMemoryBound(to: CInt.self))
}
}
#elseif os(Windows)
@usableFromInline
mutating func withUnsafeMutablePointer<T>(_ body: (UnsafeMutablePointer<CInt>) throws -> T) rethrows -> T {
return try Swift.withUnsafeMutablePointer(to: &fds_bits) {
try body(UnsafeMutableRawPointer($0).assumingMemoryBound(to: CInt.self))
}
}
#endif
}
76 changes: 76 additions & 0 deletions Sources/System/FileEvent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2021 - 2021 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
*/

/// File Events bitmask
@frozen
// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
public struct FileEvents: OptionSet, Hashable, Codable {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this name is a little misleading: we should probably call it PollEvents or something similar.


/// The raw C file events.
@_alwaysEmitIntoClient
public let rawValue: CInterop.FileEvent

/// Create a strongly-typed file events from a raw C value.
@_alwaysEmitIntoClient
public init(rawValue: CInterop.FileEvent) { self.rawValue = rawValue }

@_alwaysEmitIntoClient
private init(_ raw: CInt) { self.init(rawValue: numericCast(raw)) }
}

public extension FileEvents {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to be consistent about where we put extension access modifiers.


/// There is data to read.
@_alwaysEmitIntoClient
static var read: FileEvents { FileEvents(_POLLIN) }

/// There is urgent data to read (e.g., out-of-band data on TCP socket;
/// pseudoterminal master in packet mode has seen state change in slave).
@_alwaysEmitIntoClient
static var readUrgent: FileEvents { FileEvents(_POLLPRI) }

/// Writing now will not block.
@_alwaysEmitIntoClient
static var write: FileEvents { FileEvents(_POLLOUT) }

/// Error condition.
@_alwaysEmitIntoClient
static var error: FileEvents { FileEvents(_POLLERR) }

/// Hang up.
@_alwaysEmitIntoClient
static var hangup: FileEvents { FileEvents(_POLLHUP) }

/// Error condition.
@_alwaysEmitIntoClient
static var invalidRequest: FileEvents { FileEvents(_POLLNVAL) }
}


extension FileEvents
: CustomStringConvertible, CustomDebugStringConvertible
{
/// A textual representation of the file permissions.
@inline(never)
public var description: String {
let descriptions: [(Element, StaticString)] = [
(.read, ".read"),
(.readUrgent, ".readUrgent"),
(.write, ".write"),
(.error, ".error"),
(.hangup, ".hangup"),
(.invalidRequest, ".invalidRequest")
]

return _buildDescription(descriptions)
}

/// A textual representation of the file permissions, suitable for debugging.
public var debugDescription: String { self.description }
}
44 changes: 43 additions & 1 deletion Sources/System/FileHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
extension FileDescriptor {

/// Runs a closure and then closes the file descriptor, even if an error occurs.
///
/// - Parameter body: The closure to run.
Expand All @@ -32,7 +33,36 @@ extension FileDescriptor {
try self.close()
return result
}


/// Runs a closure and then closes the file descriptor if an error occurs.
///
/// - Parameter body: The closure to run.
/// If the closure throws an error,
/// this method closes the file descriptor before it rethrows that error.
///
/// - Returns: The value returned by the closure.
///
/// If `body` throws an error
/// this method rethrows that error.
@_alwaysEmitIntoClient
public func closeIfThrows<R>(_ body: () throws -> R) throws -> R {
do {
return try body()
} catch {
_ = self._close() // Squash close error and throw closure's
throw error
}
}

@usableFromInline
internal func _closeIfThrows<R>(_ body: () -> Result<R, Errno>) -> Result<R, Errno> {
return body().mapError {
// close if error is thrown
let _ = _close()
return $0
}
}

/// Writes a sequence of bytes to the current offset
/// and then updates the offset.
///
Expand Down Expand Up @@ -123,3 +153,15 @@ extension FileDescriptor {
}
}
}

internal extension Result where Success == FileDescriptor, Failure == Errno {

@usableFromInline
func _closeIfThrows<R>(_ body: (FileDescriptor) -> Result<R, Errno>) -> Result<R, Errno> {
return flatMap { fileDescriptor in
fileDescriptor._closeIfThrows {
body(fileDescriptor)
}
}
}
}
41 changes: 40 additions & 1 deletion Sources/System/Internals/CInterop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public enum CInterop {
/// Windows, this is `UInt16` (a "wide" character).
public typealias PlatformChar = CInterop.Char
#endif

#if os(Windows)
/// The platform's preferred Unicode encoding. On Unix this is UTF-8 and on
/// Windows it is UTF-16. Native strings may contain invalid Unicode,
Expand All @@ -63,4 +63,43 @@ public enum CInterop {
/// on API.
public typealias PlatformUnicodeEncoding = UTF8
#endif

/// The platform file descriptor set.
public typealias FileDescriptorSet = fd_set

public typealias PollFileDescriptor = pollfd

public typealias FileDescriptorCount = nfds_t

public typealias FileEvent = Int16

#if os(Windows)
/// The platform socket descriptor.
public typealias SocketDescriptor = SOCKET
#else
/// The platform socket descriptor, which is the same as a file desciptor on Unix systems.
public typealias SocketDescriptor = CInt
#endif

/// The C `msghdr` type
public typealias MessageHeader = msghdr

/// The C `sa_family_t` type
public typealias SocketAddressFamily = sa_family_t

/// Socket Type
#if os(Linux)
public typealias SocketType = __socket_type
#else
public typealias SocketType = CInt
#endif

/// The C `addrinfo` type
public typealias AddressInfo = addrinfo

/// The C `sockaddr_in` type
public typealias SocketAddress = sockaddr

/// The C `sockaddr_in` type
public typealias UnixSocketAddress = sockaddr_un
}
Loading