chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,419 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2026 Apple Inc. and the Containerization project authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ContainerizationError
|
||||
import Dispatch
|
||||
import Logging
|
||||
import Synchronization
|
||||
|
||||
#if canImport(Musl)
|
||||
import Musl
|
||||
#elseif canImport(Glibc)
|
||||
import Glibc
|
||||
#elseif canImport(Darwin)
|
||||
import Darwin
|
||||
#endif
|
||||
|
||||
#if canImport(FoundationEssentials)
|
||||
import FoundationEssentials
|
||||
#else
|
||||
import Foundation
|
||||
#endif
|
||||
|
||||
/// Manages bidirectional data relay between two file descriptors using `DispatchSource`.
|
||||
///
|
||||
/// Uses non-blocking I/O with backpressure: when a destination fd's buffer is full,
|
||||
/// the relay suspends reading from the source and installs a `DispatchSourceWrite`
|
||||
/// to resume once the destination is writable again. This prevents blocking the
|
||||
/// dispatch queue and avoids head-of-line blocking across connections.
|
||||
///
|
||||
/// ## Concurrency model
|
||||
///
|
||||
/// The class has two distinct synchronization domains:
|
||||
///
|
||||
/// - **Serial dispatch queue** — owns all I/O state: the `Direction` objects (`d1`, `d2`)
|
||||
/// and their read buffers (`buf1`, `buf2`). Every event handler, cancel handler, and
|
||||
/// `stop()` call runs on this queue. No locks are needed for that state because the
|
||||
/// queue is the exclusive executor. Fields in this domain are marked `nonisolated(unsafe)`.
|
||||
///
|
||||
/// - **Mutexes** — protect the two pieces of state that cross the queue boundary:
|
||||
/// `activeDirections` (written by `start()`, which may run off-queue) and
|
||||
/// `completionState` (read by `waitForCompletion()` from any async context).
|
||||
public final class BidirectionalRelay: Sendable {
|
||||
private let fd1: Int32
|
||||
private let fd2: Int32
|
||||
private let log: Logger?
|
||||
private let queue: DispatchQueue
|
||||
private static let queueKey = DispatchSpecificKey<Void>()
|
||||
|
||||
/// Owns one direction of the relay: its read source, optional write source, and
|
||||
/// any data buffered due to backpressure.
|
||||
///
|
||||
/// All methods must be called only from the relay's serial dispatch queue.
|
||||
private final class Direction {
|
||||
var readSource: DispatchSourceRead?
|
||||
var writeSource: DispatchSourceWrite?
|
||||
var pendingData: [UInt8] = []
|
||||
var pendingOffset: Int = 0
|
||||
private var readSuspended = false
|
||||
|
||||
func suspendRead() {
|
||||
guard let src = readSource, !src.isCancelled, !readSuspended else { return }
|
||||
readSuspended = true
|
||||
src.suspend()
|
||||
}
|
||||
|
||||
func resumeRead() {
|
||||
guard let src = readSource, !src.isCancelled, readSuspended else { return }
|
||||
readSuspended = false
|
||||
src.resume()
|
||||
}
|
||||
|
||||
/// Resumes the read source before cancelling it if it is suspended.
|
||||
/// GCD does not deliver a cancel handler for a suspended source until it is resumed.
|
||||
func cancelRead() {
|
||||
guard let src = readSource, !src.isCancelled else { return }
|
||||
if readSuspended {
|
||||
readSuspended = false
|
||||
src.resume()
|
||||
}
|
||||
src.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
private enum CompletionState {
|
||||
case pending
|
||||
case waiting(CheckedContinuation<Void, Never>)
|
||||
case completed
|
||||
}
|
||||
|
||||
private enum CopyResult {
|
||||
case ok
|
||||
case blocked
|
||||
case eof
|
||||
}
|
||||
|
||||
// Queue-owned state. Written by start() before activate(), so all subsequent
|
||||
// accesses from event/cancel handlers observe the initialized values without
|
||||
// additional synchronization. nonisolated(unsafe) declares that we are taking
|
||||
// responsibility for this; the serial queue is the enforcing mechanism.
|
||||
private nonisolated(unsafe) let d1 = Direction() // fd1 → fd2
|
||||
private nonisolated(unsafe) let d2 = Direction() // fd2 → fd1
|
||||
private nonisolated(unsafe) let buf1: UnsafeMutableBufferPointer<UInt8>
|
||||
private nonisolated(unsafe) let buf2: UnsafeMutableBufferPointer<UInt8>
|
||||
|
||||
// Counts active read sources. Set to 2 in start() (possibly off-queue) and
|
||||
// decremented in cancel handlers (always on the queue). The Mutex provides the
|
||||
// cross-thread visibility guarantee for the initial write from start(). Whichever
|
||||
// cancel handler drives the count to zero calls closeBothFds() exactly once —
|
||||
// no cross-source isCancelled checks, no possibility of double-close.
|
||||
private let activeDirections: Mutex<Int>
|
||||
|
||||
// May be read from any async context (waitForCompletion) and written from the
|
||||
// queue (closeBothFds), so it needs a Mutex rather than queue-only protection.
|
||||
private let completionState: Mutex<CompletionState>
|
||||
|
||||
/// Creates a new bidirectional relay between two file descriptors.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - fd1: The first file descriptor.
|
||||
/// - fd2: The second file descriptor.
|
||||
/// - queue: The dispatch queue to use for I/O operations. If nil, a new queue is created.
|
||||
/// - log: The optional logger for debugging.
|
||||
public init(
|
||||
fd1: Int32,
|
||||
fd2: Int32,
|
||||
queue: DispatchQueue? = nil,
|
||||
log: Logger? = nil
|
||||
) {
|
||||
self.fd1 = fd1
|
||||
self.fd2 = fd2
|
||||
self.queue = queue ?? DispatchQueue(label: "com.apple.containerization.bidirectional-relay")
|
||||
self.queue.setSpecific(key: Self.queueKey, value: ())
|
||||
self.log = log
|
||||
self.activeDirections = Mutex(0)
|
||||
self.completionState = Mutex(.pending)
|
||||
|
||||
let pageSize = Int(getpagesize())
|
||||
self.buf1 = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: pageSize)
|
||||
self.buf2 = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: pageSize)
|
||||
}
|
||||
|
||||
deinit {
|
||||
buf1.deallocate()
|
||||
buf2.deallocate()
|
||||
}
|
||||
|
||||
private static func setNonBlocking(_ fd: Int32) throws {
|
||||
let flags = fcntl(fd, F_GETFL)
|
||||
guard flags != -1 else {
|
||||
throw ContainerizationError(
|
||||
.internalError,
|
||||
message: "fcntl F_GETFL failed on fd \(fd): errno \(errno)"
|
||||
)
|
||||
}
|
||||
guard fcntl(fd, F_SETFL, flags | O_NONBLOCK) != -1 else {
|
||||
throw ContainerizationError(
|
||||
.internalError,
|
||||
message: "fcntl F_SETFL O_NONBLOCK failed on fd \(fd): errno \(errno)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts the bidirectional relay to copy data between fd1 and fd2.
|
||||
public func start() throws {
|
||||
try Self.setNonBlocking(fd1)
|
||||
try Self.setNonBlocking(fd2)
|
||||
|
||||
let src1 = DispatchSource.makeReadSource(fileDescriptor: fd1, queue: queue)
|
||||
let src2 = DispatchSource.makeReadSource(fileDescriptor: fd2, queue: queue)
|
||||
d1.readSource = src1
|
||||
d2.readSource = src2
|
||||
activeDirections.withLock { $0 = 2 }
|
||||
|
||||
src1.setEventHandler { [self] in handleRead(d1, from: fd1, to: fd2, buffer: buf1) }
|
||||
src2.setEventHandler { [self] in handleRead(d2, from: fd2, to: fd1, buffer: buf2) }
|
||||
|
||||
src1.setCancelHandler { [self] in
|
||||
d1.writeSource?.cancel()
|
||||
d1.writeSource = nil
|
||||
directionFinished()
|
||||
}
|
||||
src2.setCancelHandler { [self] in
|
||||
d2.writeSource?.cancel()
|
||||
d2.writeSource = nil
|
||||
directionFinished()
|
||||
}
|
||||
|
||||
src1.activate()
|
||||
src2.activate()
|
||||
}
|
||||
|
||||
/// Stops the relay and closes both file descriptors.
|
||||
public func stop() {
|
||||
runOnQueue {
|
||||
d1.cancelRead()
|
||||
d2.cancelRead()
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits for the relay to complete.
|
||||
public func waitForCompletion() async {
|
||||
await withCheckedContinuation { c in
|
||||
completionState.withLock { state in
|
||||
switch state {
|
||||
case .pending:
|
||||
state = .waiting(c)
|
||||
case .waiting:
|
||||
fatalError("waitForCompletion called multiple times")
|
||||
case .completed:
|
||||
c.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func runOnQueue(_ work: () -> Void) {
|
||||
if DispatchQueue.getSpecific(key: Self.queueKey) != nil {
|
||||
work()
|
||||
} else {
|
||||
queue.sync(execute: work)
|
||||
}
|
||||
}
|
||||
|
||||
private func directionFinished() {
|
||||
let remaining = activeDirections.withLock { count -> Int in
|
||||
count -= 1
|
||||
return count
|
||||
}
|
||||
if remaining == 0 {
|
||||
closeBothFds()
|
||||
}
|
||||
}
|
||||
|
||||
private func handleRead(
|
||||
_ dir: Direction,
|
||||
from srcFd: Int32,
|
||||
to dstFd: Int32,
|
||||
buffer: UnsafeMutableBufferPointer<UInt8>
|
||||
) {
|
||||
do {
|
||||
switch try Self.copy(buffer: buffer, from: srcFd, to: dstFd, direction: dir) {
|
||||
case .ok:
|
||||
break
|
||||
|
||||
case .eof:
|
||||
log?.debug(
|
||||
"source EOF",
|
||||
metadata: ["sourceFd": "\(srcFd)", "destinationFd": "\(dstFd)"]
|
||||
)
|
||||
dir.cancelRead()
|
||||
if shutdown(dstFd, Int32(SHUT_WR)) != 0 {
|
||||
log?.debug(
|
||||
"shutdown(SHUT_WR) failed",
|
||||
metadata: ["fd": "\(dstFd)", "errno": "\(errno)"]
|
||||
)
|
||||
}
|
||||
|
||||
case .blocked:
|
||||
log?.debug(
|
||||
"write blocked, applying backpressure",
|
||||
metadata: [
|
||||
"sourceFd": "\(srcFd)",
|
||||
"destinationFd": "\(dstFd)",
|
||||
"pendingBytes": "\(dir.pendingData.count)",
|
||||
]
|
||||
)
|
||||
dir.suspendRead()
|
||||
installWriteSource(for: dir, from: srcFd, to: dstFd)
|
||||
}
|
||||
} catch {
|
||||
log?.warning(
|
||||
"I/O error",
|
||||
metadata: [
|
||||
"sourceFd": "\(srcFd)",
|
||||
"destinationFd": "\(dstFd)",
|
||||
"error": "\(error)",
|
||||
]
|
||||
)
|
||||
dir.cancelRead()
|
||||
if shutdown(dstFd, Int32(SHUT_RDWR)) != 0 {
|
||||
log?.warning(
|
||||
"shutdown(SHUT_RDWR) failed",
|
||||
metadata: ["fd": "\(dstFd)", "errno": "\(errno)"]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func installWriteSource(for dir: Direction, from srcFd: Int32, to dstFd: Int32) {
|
||||
let ws = DispatchSource.makeWriteSource(fileDescriptor: dstFd, queue: queue)
|
||||
dir.writeSource = ws
|
||||
ws.setEventHandler { [self] in drainPending(dir: dir, from: srcFd, to: dstFd) }
|
||||
// No cancel handler: clearing pendingData from a cancel handler would race with
|
||||
// a newly installed write source if drainPending completes and the read source
|
||||
// immediately produces another blocked write, installing a fresh write source
|
||||
// before the old cancel handler fires. pendingData is cleared explicitly by
|
||||
// drainPending on success, and freed with Direction when the relay is torn down.
|
||||
ws.activate()
|
||||
}
|
||||
|
||||
private func drainPending(dir: Direction, from srcFd: Int32, to dstFd: Int32) {
|
||||
let remaining = dir.pendingData.count - dir.pendingOffset
|
||||
guard remaining > 0 else { return }
|
||||
|
||||
let n = dir.pendingData.withUnsafeBufferPointer { buf in
|
||||
write(dstFd, buf.baseAddress!.advanced(by: dir.pendingOffset), remaining)
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
dir.pendingOffset += n
|
||||
if dir.pendingOffset >= dir.pendingData.count {
|
||||
dir.writeSource?.cancel()
|
||||
dir.writeSource = nil
|
||||
dir.pendingData = []
|
||||
dir.pendingOffset = 0
|
||||
log?.debug(
|
||||
"backpressure relieved, resuming reads",
|
||||
metadata: ["sourceFd": "\(srcFd)", "destinationFd": "\(dstFd)"]
|
||||
)
|
||||
dir.resumeRead()
|
||||
}
|
||||
} else if n == -1 && errno == EAGAIN {
|
||||
// Spurious write-ready notification; wait for the next one.
|
||||
} else {
|
||||
log?.warning(
|
||||
"write error during pending drain",
|
||||
metadata: ["destinationFd": "\(dstFd)", "errno": "\(errno)"]
|
||||
)
|
||||
dir.writeSource?.cancel()
|
||||
dir.writeSource = nil
|
||||
dir.cancelRead()
|
||||
if shutdown(dstFd, Int32(SHUT_RDWR)) != 0 {
|
||||
log?.warning(
|
||||
"shutdown(SHUT_RDWR) failed after drain error",
|
||||
metadata: ["fd": "\(dstFd)", "errno": "\(errno)"]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Drains srcFd into dstFd in a loop until EAGAIN/EWOULDBLOCK or EOF.
|
||||
///
|
||||
/// Looping until EAGAIN is required on Linux: libdispatch uses FIONREAD to decide
|
||||
/// whether to fire the read event, so when the only remaining readable condition is
|
||||
/// EOF (FIONREAD == 0), the event is suppressed. Reading in a loop here ensures we
|
||||
/// observe read() == 0 on the same handler invocation that drained the last bytes.
|
||||
private static func copy(
|
||||
buffer: UnsafeMutableBufferPointer<UInt8>,
|
||||
from srcFd: Int32,
|
||||
to dstFd: Int32,
|
||||
direction: Direction
|
||||
) throws -> CopyResult {
|
||||
guard let base = buffer.baseAddress else {
|
||||
throw ContainerizationError(.invalidState, message: "buffer has no base address")
|
||||
}
|
||||
|
||||
readLoop: while true {
|
||||
let nr = read(srcFd, base, buffer.count)
|
||||
if nr == 0 { return .eof }
|
||||
if nr < 0 {
|
||||
if errno == EAGAIN || errno == EWOULDBLOCK { return .ok }
|
||||
if errno == EINTR { continue readLoop }
|
||||
throw ContainerizationError(
|
||||
.internalError,
|
||||
message: "read failed: fd \(srcFd), errno \(errno)"
|
||||
)
|
||||
}
|
||||
|
||||
var offset = 0
|
||||
while offset < nr {
|
||||
let nw = write(dstFd, base.advanced(by: offset), nr - offset)
|
||||
if nw > 0 {
|
||||
offset += nw
|
||||
} else if nw < 0 {
|
||||
if errno == EINTR { continue }
|
||||
if errno == EAGAIN || errno == EWOULDBLOCK {
|
||||
direction.pendingData = Array(
|
||||
UnsafeBufferPointer(start: base.advanced(by: offset), count: nr - offset)
|
||||
)
|
||||
direction.pendingOffset = 0
|
||||
return .blocked
|
||||
}
|
||||
throw ContainerizationError(
|
||||
.internalError,
|
||||
message: "write failed: fd \(dstFd), errno \(errno)"
|
||||
)
|
||||
} else {
|
||||
throw ContainerizationError(
|
||||
.internalError,
|
||||
message: "zero-byte write on fd \(dstFd)"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func closeBothFds() {
|
||||
log?.debug("closing fds", metadata: ["fd1": "\(fd1)", "fd2": "\(fd2)"])
|
||||
close(fd1)
|
||||
close(fd2)
|
||||
completionState.withLock { state in
|
||||
if case .waiting(let c) = state { c.resume() }
|
||||
state = .completed
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025-2026 Apple Inc. and the Containerization project authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import CShim
|
||||
import Foundation
|
||||
import Synchronization
|
||||
|
||||
#if canImport(Musl)
|
||||
import Musl
|
||||
#elseif canImport(Glibc)
|
||||
import Glibc
|
||||
#elseif canImport(Darwin)
|
||||
import Darwin
|
||||
#else
|
||||
#error("Socket not supported on this platform.")
|
||||
#endif
|
||||
|
||||
#if !os(Windows)
|
||||
let sysFchmod = fchmod
|
||||
let sysRead = read
|
||||
let sysUnlink = unlink
|
||||
let sysSend = send
|
||||
let sysClose = close
|
||||
let sysShutdown = shutdown
|
||||
let sysBind = bind
|
||||
let sysSocket = socket
|
||||
let sysSetsockopt = setsockopt
|
||||
let sysGetsockopt = getsockopt
|
||||
let sysListen = listen
|
||||
let sysAccept = accept
|
||||
let sysConnect = connect
|
||||
let sysIoctl: @convention(c) (CInt, CUnsignedLong, UnsafeMutableRawPointer) -> CInt = ioctl
|
||||
let sysRecvmsg = recvmsg
|
||||
#endif
|
||||
|
||||
/// Thread-safe socket wrapper.
|
||||
public final class Socket: Sendable {
|
||||
public enum TimeoutOption {
|
||||
case send
|
||||
case receive
|
||||
}
|
||||
|
||||
public enum ShutdownOption {
|
||||
case read
|
||||
case write
|
||||
case readWrite
|
||||
}
|
||||
|
||||
private enum SocketState {
|
||||
case created
|
||||
case connected
|
||||
case listening
|
||||
}
|
||||
|
||||
private struct State {
|
||||
let socketState: SocketState
|
||||
let handle: FileHandle?
|
||||
let type: SocketType
|
||||
let acceptSource: DispatchSourceRead?
|
||||
}
|
||||
|
||||
private let _closeOnDeinit: Bool
|
||||
private let _queue: DispatchQueue
|
||||
|
||||
private let state: Mutex<State>
|
||||
|
||||
public var fileDescriptor: Int32 {
|
||||
guard let handle = state.withLock({ $0.handle }) else {
|
||||
return -1
|
||||
}
|
||||
return handle.fileDescriptor
|
||||
}
|
||||
|
||||
public convenience init(type: SocketType, closeOnDeinit: Bool = true) throws {
|
||||
let sockFD = sysSocket(type.domain, type.type, 0)
|
||||
if sockFD < 0 {
|
||||
throw SocketError.withErrno("failed to create socket: \(sockFD)", errno: errno)
|
||||
}
|
||||
self.init(fd: sockFD, type: type, closeOnDeinit: closeOnDeinit)
|
||||
}
|
||||
|
||||
init(fd: Int32, type: SocketType, closeOnDeinit: Bool) {
|
||||
_queue = DispatchQueue(label: "com.apple.containerization.socket")
|
||||
_closeOnDeinit = closeOnDeinit
|
||||
let state = State(
|
||||
socketState: .created,
|
||||
handle: FileHandle(fileDescriptor: fd, closeOnDealloc: false),
|
||||
type: type,
|
||||
acceptSource: nil
|
||||
)
|
||||
self.state = Mutex(state)
|
||||
}
|
||||
|
||||
/// Internal initializer for wrapping already-connected file descriptors (e.g., from socketpair)
|
||||
/// Ideally we just get rid of the state machine in this class. Not sure how much value it provides..
|
||||
init(fd: Int32, type: SocketType, closeOnDeinit: Bool, connected: Bool) {
|
||||
_queue = DispatchQueue(label: "com.apple.containerization.socket")
|
||||
_closeOnDeinit = closeOnDeinit
|
||||
let state = State(
|
||||
socketState: connected ? .connected : .created,
|
||||
handle: FileHandle(fileDescriptor: fd, closeOnDealloc: false),
|
||||
type: type,
|
||||
acceptSource: nil
|
||||
)
|
||||
self.state = Mutex(state)
|
||||
}
|
||||
|
||||
deinit {
|
||||
if _closeOnDeinit {
|
||||
try? close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Socket {
|
||||
static func errnoToError(msg: String) -> SocketError {
|
||||
SocketError.withErrno("\(msg) (\(_errnoString(errno)))", errno: errno)
|
||||
}
|
||||
|
||||
public func connect() throws {
|
||||
try state.withLock { currentState in
|
||||
guard currentState.socketState == .created else {
|
||||
throw SocketError.invalidOperationOnSocket("connect")
|
||||
}
|
||||
guard let handle = currentState.handle else {
|
||||
throw SocketError.closed
|
||||
}
|
||||
|
||||
var res: Int32 = 0
|
||||
try currentState.type.withSockAddr { (ptr, length) in
|
||||
res = Syscall.retrying {
|
||||
sysConnect(handle.fileDescriptor, ptr, length)
|
||||
}
|
||||
}
|
||||
|
||||
if res == -1 {
|
||||
throw Socket.errnoToError(msg: "could not connect to socket \(currentState.type)")
|
||||
}
|
||||
|
||||
currentState = State(
|
||||
socketState: .connected,
|
||||
handle: handle,
|
||||
type: currentState.type,
|
||||
acceptSource: currentState.acceptSource
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public func listen() throws {
|
||||
try state.withLock { currentState in
|
||||
guard currentState.socketState == .created else {
|
||||
throw SocketError.invalidOperationOnSocket("listen")
|
||||
}
|
||||
guard let handle = currentState.handle else {
|
||||
throw SocketError.closed
|
||||
}
|
||||
|
||||
try currentState.type.beforeBind(fd: handle.fileDescriptor)
|
||||
|
||||
var rc: Int32 = 0
|
||||
try currentState.type.withSockAddr { (ptr, length) in
|
||||
rc = sysBind(handle.fileDescriptor, ptr, length)
|
||||
}
|
||||
|
||||
if rc < 0 {
|
||||
throw Socket.errnoToError(msg: "could not bind to \(currentState.type)")
|
||||
}
|
||||
|
||||
try currentState.type.beforeListen(fd: handle.fileDescriptor)
|
||||
|
||||
if sysListen(handle.fileDescriptor, SOMAXCONN) < 0 {
|
||||
throw Socket.errnoToError(msg: "listen failed on \(currentState.type)")
|
||||
}
|
||||
|
||||
currentState = State(
|
||||
socketState: .listening,
|
||||
handle: handle,
|
||||
type: currentState.type,
|
||||
acceptSource: currentState.acceptSource
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public func close() throws {
|
||||
try state.withLock { currentState in
|
||||
guard let handle = currentState.handle else {
|
||||
// Already closed.
|
||||
return
|
||||
}
|
||||
|
||||
let acceptSource = currentState.acceptSource
|
||||
|
||||
acceptSource?.cancel()
|
||||
try handle.close()
|
||||
|
||||
currentState = State(
|
||||
socketState: currentState.socketState,
|
||||
handle: nil,
|
||||
type: currentState.type,
|
||||
acceptSource: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public func write(data: any DataProtocol) throws -> Int {
|
||||
let handle = try state.withLock { currentState in
|
||||
guard currentState.socketState == .connected else {
|
||||
throw SocketError.invalidOperationOnSocket("write")
|
||||
}
|
||||
guard let handle = currentState.handle else {
|
||||
throw SocketError.closed
|
||||
}
|
||||
return handle
|
||||
}
|
||||
|
||||
if data.isEmpty {
|
||||
return 0
|
||||
}
|
||||
|
||||
try handle.write(contentsOf: data)
|
||||
return data.count
|
||||
}
|
||||
|
||||
public func acceptStream(closeOnDeinit: Bool = true) throws -> AsyncThrowingStream<Socket, Swift.Error> {
|
||||
let source = try state.withLock { currentState -> DispatchSourceRead in
|
||||
guard currentState.socketState == .listening else {
|
||||
throw SocketError.invalidOperationOnSocket("accept")
|
||||
}
|
||||
guard let handle = currentState.handle else {
|
||||
throw SocketError.closed
|
||||
}
|
||||
guard currentState.acceptSource == nil else {
|
||||
throw SocketError.acceptStreamExists
|
||||
}
|
||||
|
||||
let source = DispatchSource.makeReadSource(
|
||||
fileDescriptor: handle.fileDescriptor,
|
||||
queue: _queue
|
||||
)
|
||||
|
||||
currentState = State(
|
||||
socketState: currentState.socketState,
|
||||
handle: handle,
|
||||
type: currentState.type,
|
||||
acceptSource: source
|
||||
)
|
||||
|
||||
return source
|
||||
}
|
||||
|
||||
return AsyncThrowingStream { cont in
|
||||
source.setCancelHandler {
|
||||
cont.finish()
|
||||
}
|
||||
source.setEventHandler(handler: {
|
||||
if source.data == 0 {
|
||||
source.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let connection = try self.accept(closeOnDeinit: closeOnDeinit)
|
||||
cont.yield(connection)
|
||||
} catch SocketError.closed {
|
||||
source.cancel()
|
||||
} catch {
|
||||
cont.yield(with: .failure(error))
|
||||
source.cancel()
|
||||
}
|
||||
})
|
||||
source.activate()
|
||||
}
|
||||
}
|
||||
|
||||
public func accept(closeOnDeinit: Bool = true) throws -> Socket {
|
||||
let (handle, socketType) = try state.withLock { currentState in
|
||||
guard currentState.socketState == .listening else {
|
||||
throw SocketError.invalidOperationOnSocket("accept")
|
||||
}
|
||||
guard let handle = currentState.handle else {
|
||||
throw SocketError.closed
|
||||
}
|
||||
return (handle, currentState.type)
|
||||
}
|
||||
|
||||
let (clientFD, newSocketType) = try socketType.accept(fd: handle.fileDescriptor)
|
||||
return Socket(
|
||||
fd: clientFD,
|
||||
type: newSocketType,
|
||||
closeOnDeinit: closeOnDeinit,
|
||||
connected: true
|
||||
)
|
||||
}
|
||||
|
||||
/// Receive a file descriptor via SCM_RIGHTS control message.
|
||||
/// This is commonly used for passing file descriptors between processes via Unix domain sockets.
|
||||
public func receiveFileDescriptor() throws -> Int32 {
|
||||
let handle = try state.withLock { currentState in
|
||||
guard currentState.socketState == .connected else {
|
||||
throw SocketError.invalidOperationOnSocket("receiveFileDescriptor")
|
||||
}
|
||||
guard let handle = currentState.handle else {
|
||||
throw SocketError.closed
|
||||
}
|
||||
return handle
|
||||
}
|
||||
|
||||
var msg = msghdr()
|
||||
var iov = iovec()
|
||||
var buf: UInt8 = 0
|
||||
|
||||
iov.iov_base = withUnsafeMutablePointer(to: &buf) { UnsafeMutableRawPointer($0) }
|
||||
iov.iov_len = 1
|
||||
|
||||
msg.msg_iov = withUnsafeMutablePointer(to: &iov) { $0 }
|
||||
msg.msg_iovlen = 1
|
||||
|
||||
var cmsgBuf = [UInt8](repeating: 0, count: Int(CZ_CMSG_SPACE(Int(MemoryLayout<Int32>.size))))
|
||||
msg.msg_control = withUnsafeMutablePointer(to: &cmsgBuf[0]) { UnsafeMutableRawPointer($0) }
|
||||
msg.msg_controllen = numericCast(cmsgBuf.count)
|
||||
|
||||
let recvResult = withUnsafeMutablePointer(to: &msg) { msgPtr in
|
||||
sysRecvmsg(handle.fileDescriptor, msgPtr, 0)
|
||||
}
|
||||
|
||||
guard recvResult >= 0 else {
|
||||
throw Socket.errnoToError(msg: "recvmsg failed")
|
||||
}
|
||||
|
||||
// Extract file descriptor from control message
|
||||
let cmsgPtr = withUnsafeMutablePointer(to: &msg) { CZ_CMSG_FIRSTHDR($0) }
|
||||
guard let cmsg = cmsgPtr else {
|
||||
throw SocketError.invalidFileDescriptor
|
||||
}
|
||||
|
||||
guard cmsg.pointee.cmsg_level == SOL_SOCKET,
|
||||
cmsg.pointee.cmsg_type == SCM_RIGHTS
|
||||
else {
|
||||
throw SocketError.invalidFileDescriptor
|
||||
}
|
||||
|
||||
guard let dataPtr = CZ_CMSG_DATA(cmsg) else {
|
||||
throw SocketError.invalidFileDescriptor
|
||||
}
|
||||
|
||||
let fdPtr = dataPtr.assumingMemoryBound(to: Int32.self)
|
||||
let fd = fdPtr.pointee
|
||||
guard fd >= 0 else {
|
||||
throw SocketError.invalidFileDescriptor
|
||||
}
|
||||
|
||||
return fd
|
||||
}
|
||||
|
||||
public func read(buffer: inout Data) throws -> Int {
|
||||
let handle = try state.withLock { currentState in
|
||||
guard currentState.socketState == .connected else {
|
||||
throw SocketError.invalidOperationOnSocket("read")
|
||||
}
|
||||
guard let handle = currentState.handle else {
|
||||
throw SocketError.closed
|
||||
}
|
||||
return handle
|
||||
}
|
||||
|
||||
var bytesRead = 0
|
||||
let bufferSize = buffer.count
|
||||
try buffer.withUnsafeMutableBytes { pointer in
|
||||
guard let baseAddress = pointer.baseAddress else {
|
||||
throw SocketError.missingBaseAddress
|
||||
}
|
||||
|
||||
bytesRead = Syscall.retrying {
|
||||
sysRead(handle.fileDescriptor, baseAddress, bufferSize)
|
||||
}
|
||||
if bytesRead < 0 {
|
||||
throw Socket.errnoToError(msg: "error reading from connection")
|
||||
} else if bytesRead == 0 {
|
||||
throw SocketError.closed
|
||||
}
|
||||
}
|
||||
return bytesRead
|
||||
}
|
||||
|
||||
public func shutdown(how: ShutdownOption) throws {
|
||||
let handle = try state.withLock { currentState in
|
||||
guard let handle = currentState.handle else {
|
||||
throw SocketError.closed
|
||||
}
|
||||
return handle
|
||||
}
|
||||
|
||||
var howOpt: Int32 = 0
|
||||
switch how {
|
||||
case .read:
|
||||
howOpt = Int32(SHUT_RD)
|
||||
case .write:
|
||||
howOpt = Int32(SHUT_WR)
|
||||
case .readWrite:
|
||||
howOpt = Int32(SHUT_RDWR)
|
||||
}
|
||||
|
||||
if sysShutdown(handle.fileDescriptor, howOpt) < 0 {
|
||||
throw Socket.errnoToError(msg: "shutdown failed")
|
||||
}
|
||||
}
|
||||
|
||||
public func setSockOpt(sockOpt: Int32 = 0, ptr: UnsafeRawPointer, stride: UInt32) throws {
|
||||
let handle = try state.withLock { currentState in
|
||||
guard let handle = currentState.handle else {
|
||||
throw SocketError.closed
|
||||
}
|
||||
return handle
|
||||
}
|
||||
|
||||
if setsockopt(handle.fileDescriptor, SOL_SOCKET, sockOpt, ptr, stride) < 0 {
|
||||
throw Socket.errnoToError(msg: "failed to set sockopt")
|
||||
}
|
||||
}
|
||||
|
||||
public func setTimeout(option: TimeoutOption, seconds: Int) throws {
|
||||
let handle = try state.withLock { currentState in
|
||||
guard let handle = currentState.handle else {
|
||||
throw SocketError.closed
|
||||
}
|
||||
return handle
|
||||
}
|
||||
|
||||
var sockOpt: Int32 = 0
|
||||
switch option {
|
||||
case .receive:
|
||||
sockOpt = SO_RCVTIMEO
|
||||
case .send:
|
||||
sockOpt = SO_SNDTIMEO
|
||||
}
|
||||
|
||||
var timer = timeval()
|
||||
timer.tv_sec = seconds
|
||||
timer.tv_usec = 0
|
||||
|
||||
if setsockopt(
|
||||
handle.fileDescriptor,
|
||||
SOL_SOCKET,
|
||||
sockOpt,
|
||||
&timer,
|
||||
socklen_t(MemoryLayout<timeval>.size)
|
||||
) < 0 {
|
||||
throw Socket.errnoToError(msg: "failed to set read timeout")
|
||||
}
|
||||
}
|
||||
|
||||
static func _errnoString(_ err: Int32?) -> String {
|
||||
String(validatingCString: strerror(errno)) ?? "error: \(errno)"
|
||||
}
|
||||
}
|
||||
|
||||
public enum SocketError: Error, Equatable, CustomStringConvertible {
|
||||
case closed
|
||||
case acceptStreamExists
|
||||
case invalidOperationOnSocket(String)
|
||||
case missingBaseAddress
|
||||
case withErrno(_ msg: String, errno: Int32)
|
||||
case invalidFileDescriptor
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .closed:
|
||||
return "socket: closed"
|
||||
case .acceptStreamExists:
|
||||
return "accept stream already exists"
|
||||
case .invalidOperationOnSocket(let operation):
|
||||
return "socket: invalid operation on socket '\(operation)'"
|
||||
case .missingBaseAddress:
|
||||
return "socket: missing base address"
|
||||
case .withErrno(let msg, _):
|
||||
return "socket: error \(msg)"
|
||||
case .invalidFileDescriptor:
|
||||
return "socket: invalid file descriptor received"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025-2026 Apple Inc. and the Containerization project authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#if canImport(Musl)
|
||||
import Musl
|
||||
#elseif canImport(Glibc)
|
||||
import Glibc
|
||||
#elseif canImport(Darwin)
|
||||
import Darwin
|
||||
#else
|
||||
#error("SocketType not supported on this platform.")
|
||||
#endif
|
||||
|
||||
/// Protocol used to describe the family of socket to be created with `Socket`.
|
||||
public protocol SocketType: Sendable, CustomStringConvertible {
|
||||
/// The domain for the socket (AF_UNIX, AF_VSOCK etc.)
|
||||
var domain: Int32 { get }
|
||||
/// The type of socket (SOCK_STREAM).
|
||||
var type: Int32 { get }
|
||||
|
||||
/// Actions to perform before calling bind(2).
|
||||
func beforeBind(fd: Int32) throws
|
||||
/// Actions to perform before calling listen(2).
|
||||
func beforeListen(fd: Int32) throws
|
||||
|
||||
/// Handle accept(2) for an implementation of a socket type.
|
||||
func accept(fd: Int32) throws -> (Int32, SocketType)
|
||||
/// Provide a sockaddr pointer (by casting a socket specific type like sockaddr_un for example).
|
||||
func withSockAddr(_ closure: (_ ptr: UnsafePointer<sockaddr>, _ len: UInt32) throws -> Void) throws
|
||||
}
|
||||
|
||||
extension SocketType {
|
||||
public func beforeBind(fd: Int32) {}
|
||||
public func beforeListen(fd: Int32) {}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025-2026 Apple Inc. and the Containerization project authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#if canImport(Musl)
|
||||
import Musl
|
||||
let _SOCK_STREAM = SOCK_STREAM
|
||||
#elseif canImport(Glibc)
|
||||
import Glibc
|
||||
let _SOCK_STREAM = Int32(SOCK_STREAM.rawValue)
|
||||
#elseif canImport(Darwin)
|
||||
import Darwin
|
||||
let _SOCK_STREAM = SOCK_STREAM
|
||||
#else
|
||||
#error("UnixType not supported on this platform.")
|
||||
#endif
|
||||
|
||||
/// Unix domain socket variant of `SocketType`.
|
||||
public struct UnixType: SocketType, Sendable, CustomStringConvertible {
|
||||
public var domain: Int32 { AF_UNIX }
|
||||
public var type: Int32 { _SOCK_STREAM }
|
||||
public var description: String {
|
||||
path
|
||||
}
|
||||
|
||||
public let path: String
|
||||
public let perms: mode_t?
|
||||
private let _addr: sockaddr_un
|
||||
private let _unlinkExisting: Bool
|
||||
|
||||
private init(sockaddr: sockaddr_un) {
|
||||
let pathname: String = withUnsafePointer(to: sockaddr.sun_path) { ptr in
|
||||
let charPtr = UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self)
|
||||
return String(cString: charPtr)
|
||||
}
|
||||
self._addr = sockaddr
|
||||
self.path = pathname
|
||||
self._unlinkExisting = false
|
||||
self.perms = nil
|
||||
}
|
||||
|
||||
/// Mode and unlinkExisting only used if the socket is going to be a listening socket.
|
||||
public init(
|
||||
path: String,
|
||||
perms: mode_t? = nil,
|
||||
unlinkExisting: Bool = false
|
||||
) throws {
|
||||
self.path = path
|
||||
self.perms = perms
|
||||
self._unlinkExisting = unlinkExisting
|
||||
var addr = sockaddr_un()
|
||||
addr.sun_family = sa_family_t(AF_UNIX)
|
||||
|
||||
let socketName = path
|
||||
let nameLength = socketName.utf8.count
|
||||
|
||||
#if os(macOS)
|
||||
// Funnily enough, this isn't limited by sun path on macOS even though
|
||||
// it's stated as so.
|
||||
let lengthLimit = 253
|
||||
#elseif os(Linux)
|
||||
let lengthLimit = MemoryLayout.size(ofValue: addr.sun_path)
|
||||
#endif
|
||||
|
||||
guard nameLength < lengthLimit else {
|
||||
throw Error.nameTooLong(path)
|
||||
}
|
||||
|
||||
_ = withUnsafeMutablePointer(to: &addr.sun_path.0) { ptr in
|
||||
socketName.withCString { strncpy(ptr, $0, nameLength) }
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
addr.sun_len = UInt8(MemoryLayout<UInt8>.size + MemoryLayout<sa_family_t>.size + socketName.utf8.count + 1)
|
||||
#endif
|
||||
self._addr = addr
|
||||
}
|
||||
|
||||
public func accept(fd: Int32) throws -> (Int32, SocketType) {
|
||||
var clientFD: Int32 = -1
|
||||
var addr = sockaddr_un()
|
||||
|
||||
clientFD = Syscall.retrying {
|
||||
var size = socklen_t(MemoryLayout<sockaddr_un>.stride)
|
||||
return withUnsafeMutablePointer(to: &addr) { pointer in
|
||||
pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { pointer in
|
||||
sysAccept(fd, pointer, &size)
|
||||
}
|
||||
}
|
||||
}
|
||||
if clientFD < 0 {
|
||||
throw Socket.errnoToError(msg: "accept failed")
|
||||
}
|
||||
|
||||
return (clientFD, UnixType(sockaddr: addr))
|
||||
}
|
||||
|
||||
public func beforeBind(fd: Int32) throws {
|
||||
#if os(Linux)
|
||||
// Only Linux supports setting the mode of a socket before binding.
|
||||
if let perms = self.perms {
|
||||
guard fchmod(fd, perms) == 0 else {
|
||||
throw Socket.errnoToError(msg: "fchmod failed")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
var rc: Int32 = 0
|
||||
if self._unlinkExisting {
|
||||
rc = sysUnlink(self.path)
|
||||
if rc != 0 && errno != ENOENT {
|
||||
throw Socket.errnoToError(msg: "failed to remove old socket at \(self.path)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func beforeListen(fd: Int32) throws {
|
||||
#if os(macOS)
|
||||
if let perms = self.perms {
|
||||
guard chmod(self.path, perms) == 0 else {
|
||||
throw Socket.errnoToError(msg: "chmod failed")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public func withSockAddr(_ closure: (UnsafePointer<sockaddr>, UInt32) throws -> Void) throws {
|
||||
var addr = self._addr
|
||||
try withUnsafePointer(to: &addr) {
|
||||
let addrBytes = UnsafeRawPointer($0).assumingMemoryBound(to: sockaddr.self)
|
||||
try closure(addrBytes, UInt32(MemoryLayout<sockaddr_un>.stride))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UnixType {
|
||||
/// `UnixType` errors.
|
||||
public enum Error: Swift.Error, CustomStringConvertible {
|
||||
case nameTooLong(_: String)
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .nameTooLong(let name):
|
||||
return "\(name) is too long for a Unix Domain Socket path"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025-2026 Apple Inc. and the Containerization project authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import CShim
|
||||
|
||||
#if canImport(Musl)
|
||||
import Musl
|
||||
#elseif canImport(Glibc)
|
||||
import Glibc
|
||||
#elseif canImport(Darwin)
|
||||
import Darwin
|
||||
#else
|
||||
#error("VsockType not supported on this platform.")
|
||||
#endif
|
||||
|
||||
/// Vsock variant of `SocketType`.
|
||||
public struct VsockType: SocketType, Sendable {
|
||||
public var domain: Int32 { AF_VSOCK }
|
||||
public var type: Int32 { _SOCK_STREAM }
|
||||
public var description: String {
|
||||
"\(cid):\(port)"
|
||||
}
|
||||
|
||||
public static let anyCID: UInt32 = UInt32(bitPattern: -1)
|
||||
public static let hypervisorCID: UInt32 = 0x0
|
||||
// Supported on Linux 5.6+; otherwise, will need to use getLocalCID().
|
||||
public static let localCID: UInt32 = 0x1
|
||||
public static let hostCID: UInt32 = 0x2
|
||||
|
||||
// socketFD is unused on Linux.
|
||||
public static func getLocalCID(socketFD: Int32) throws -> UInt32 {
|
||||
let request = VsockLocalCIDIoctl
|
||||
#if os(Linux)
|
||||
let fd = open("/dev/vsock", O_RDONLY | O_CLOEXEC)
|
||||
if fd == -1 {
|
||||
throw Socket.errnoToError(msg: "failed to open /dev/vsock")
|
||||
}
|
||||
defer { close(fd) }
|
||||
#else
|
||||
let fd = socketFD
|
||||
#endif
|
||||
var cid: UInt32 = 0
|
||||
guard sysIoctl(fd, numericCast(request), &cid) != -1 else {
|
||||
throw Socket.errnoToError(msg: "failed to get local cid")
|
||||
}
|
||||
return cid
|
||||
}
|
||||
|
||||
public let port: UInt32
|
||||
public let cid: UInt32
|
||||
|
||||
private let _addr: sockaddr_vm
|
||||
|
||||
public init(port: UInt32, cid: UInt32) {
|
||||
self.cid = cid
|
||||
self.port = port
|
||||
var sockaddr = sockaddr_vm()
|
||||
sockaddr.svm_family = sa_family_t(AF_VSOCK)
|
||||
sockaddr.svm_cid = cid
|
||||
sockaddr.svm_port = port
|
||||
self._addr = sockaddr
|
||||
}
|
||||
|
||||
private init(sockaddr: sockaddr_vm) {
|
||||
self._addr = sockaddr
|
||||
self.cid = sockaddr.svm_cid
|
||||
self.port = sockaddr.svm_port
|
||||
}
|
||||
|
||||
public func accept(fd: Int32) throws -> (Int32, SocketType) {
|
||||
var clientFD: Int32 = -1
|
||||
var addr = sockaddr_vm()
|
||||
|
||||
while clientFD < 0 {
|
||||
var size = socklen_t(MemoryLayout<sockaddr_vm>.stride)
|
||||
clientFD = withUnsafeMutablePointer(to: &addr) { pointer in
|
||||
pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { pointer in
|
||||
sysAccept(fd, pointer, &size)
|
||||
}
|
||||
}
|
||||
if clientFD < 0 && errno != EINTR {
|
||||
throw Socket.errnoToError(msg: "accept failed")
|
||||
}
|
||||
}
|
||||
return (clientFD, VsockType(sockaddr: addr))
|
||||
}
|
||||
|
||||
public func withSockAddr(_ closure: (UnsafePointer<sockaddr>, UInt32) throws -> Void) throws {
|
||||
var addr = self._addr
|
||||
try withUnsafePointer(to: &addr) {
|
||||
let addrBytes = UnsafeRawPointer($0).assumingMemoryBound(to: sockaddr.self)
|
||||
try closure(addrBytes, UInt32(MemoryLayout<sockaddr_vm>.stride))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user