diff --git a/Package.swift b/Package.swift index b12c0f8357..6b8590674e 100644 --- a/Package.swift +++ b/Package.swift @@ -59,6 +59,7 @@ let package = Package( dependencies: [ "NIOConcurrencyHelpers", "_NIOBase64", + "CNIOOpenBSD", "CNIODarwin", "CNIOLinux", "CNIOWindows", @@ -91,6 +92,7 @@ let package = Package( .target( name: "NIOPosix", dependencies: [ + "CNIOOpenBSD", "CNIOLinux", "CNIODarwin", "CNIOWindows", @@ -147,6 +149,10 @@ let package = Package( name: "CNIOSHA1", dependencies: [] ), + .target( + name: "CNIOOpenBSD", + dependencies: [] + ), .target( name: "CNIOLinux", dependencies: [], @@ -482,6 +488,7 @@ let package = Package( "NIOTestUtils", "NIOConcurrencyHelpers", "NIOEmbedded", + "CNIOOpenBSD", "CNIOLinux", "CNIODarwin", "NIOTLS", diff --git a/README.md b/README.md index 5e858fd9bb..64da5c1a82 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ SwiftNIO aims to support all of the platforms where Swift is supported. Currentl * Ubuntu 18.04+ * macOS 10.9+, iOS 7+; (macOS 10.14+, iOS 12+, tvOS 12+ or watchOS 6+ with [swift-nio-transport-services][repo-nio-transport-services]) +SwiftNIO has experimental support on OpenBSD for all SwiftNIO libraries _except_ for NIOFileSystem, which is not yet supported. You can use all other SwiftNIO libraries on OpenBSD by adding them as dependencies in `Package.swift`. + ### Compatibility SwiftNIO follows [SemVer 2.0.0](https://semver.org/#semantic-versioning-200) with a separate document declaring [SwiftNIO's Public API](docs/public-api.md). diff --git a/Sources/CNIOOpenBSD/include/CNIOOpenBSD.h b/Sources/CNIOOpenBSD/include/CNIOOpenBSD.h new file mode 100644 index 0000000000..c2ce5da7b8 --- /dev/null +++ b/Sources/CNIOOpenBSD/include/CNIOOpenBSD.h @@ -0,0 +1,101 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2025 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +#ifndef C_NIO_OPENBSD_H +#define C_NIO_OPENBSD_H + +#if defined(__OpenBSD__) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Some explanation is required here. +// +// Due to SR-6772, we cannot get Swift code to directly see any of the mmsg structures or +// functions. However, we *can* get C code built by SwiftPM to see them. For this reason we +// elect to provide a selection of shims to enable Swift code to use recv_mmsg and send_mmsg. +// Mostly this is fine, but to minimise the overhead we want the Swift code to be able to +// create the msgvec directly without requiring further memory fussiness in our C shim. +// That requires us to also construct a C structure that has the same layout as struct mmsghdr. +// +// Conveniently glibc has pretty strict ABI stability rules, and this structure is part of the +// glibc ABI, so we can just reproduce the structure definition here and feel confident that it +// will be sufficient. +// +// If SR-6772 ever gets resolved we can remove this shim. +// +// https://bugs.swift.org/browse/SR-6772 + +typedef struct { + struct msghdr msg_hdr; + unsigned int msg_len; +} CNIOOpenBSD_mmsghdr; + +typedef struct { + struct in6_addr ipi6_addr; + unsigned int ipi6_ifindex; +} CNIOOpenBSD_in6_pktinfo; + +int CNIOOpenBSD_sendmmsg(int sockfd, CNIOOpenBSD_mmsghdr *msgvec, unsigned int vlen, int flags); +int CNIOOpenBSD_recvmmsg(int sockfd, CNIOOpenBSD_mmsghdr *msgvec, unsigned int vlen, int flags, struct timespec *timeout); + +int CNIOOpenBSD_pthread_set_name_np(pthread_t thread, const char *name); +int CNIOOpenBSD_pthread_get_name_np(pthread_t thread, char *name, size_t len); + +// Non-standard socket stuff. +int CNIOOpenBSD_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags); + +// cmsghdr handling +struct cmsghdr *CNIOOpenBSD_CMSG_FIRSTHDR(const struct msghdr *); +struct cmsghdr *CNIOOpenBSD_CMSG_NXTHDR(struct msghdr *, struct cmsghdr *); +const void *CNIOOpenBSD_CMSG_DATA(const struct cmsghdr *); +void *CNIOOpenBSD_CMSG_DATA_MUTABLE(struct cmsghdr *); +size_t CNIOOpenBSD_CMSG_LEN(size_t); +size_t CNIOOpenBSD_CMSG_SPACE(size_t); + +// awkward time_T pain +extern const int CNIOOpenBSD_SO_TIMESTAMP; +extern const int CNIOOpenBSD_SO_RCVTIMEO; + +int CNIOOpenBSD_system_info(struct utsname *uname_data); + +extern const unsigned long CNIOOpenBSD_IOCTL_VM_SOCKETS_GET_LOCAL_CID; + +const char* CNIOOpenBSD_dirent_dname(struct dirent *ent); + +extern const unsigned long CNIOOpenBSD_UTIME_OMIT; +extern const unsigned long CNIOOpenBSD_UTIME_NOW; + +extern const long CNIOOpenBSD_UDP_MAX_SEGMENTS; + +// A workaround for incorrect nullability annotations in the Android SDK. +// Probably unnecessary on BSD, but copying for consistency for now. +FTS *CNIOOpenBSD_fts_open(char * const *path_argv, int options, int (*compar)(const FTSENT **, const FTSENT **)); + +#endif +#endif diff --git a/Sources/CNIOOpenBSD/shim.c b/Sources/CNIOOpenBSD/shim.c new file mode 100644 index 0000000000..7f51abfac9 --- /dev/null +++ b/Sources/CNIOOpenBSD/shim.c @@ -0,0 +1,131 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2025 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// Xcode's Archive builds with Xcode's Package support struggle with empty .c files +// (https://bugs.swift.org/browse/SR-12939). +void CNIOOpenBSD_i_do_nothing_just_working_around_a_darwin_toolchain_bug(void) {} + +#if defined(__OpenBSD__) + +#include +#include +#include +#include + +int CNIOOpenBSD_pthread_set_name_np(pthread_t thread, const char *name) { + pthread_set_name_np(thread, name); + return 0; +} + +int CNIOOpenBSD_pthread_get_name_np(pthread_t thread, char *name, size_t len) { + pthread_get_name_np(thread, name, len); + return 0; +} + +int CNIOOpenBSD_sendmmsg(int sockfd, CNIOOpenBSD_mmsghdr *msgvec, unsigned int vlen, int flags) { + // This is technically undefined behaviour, but it's basically fine because these types are the same size, and we + // don't think the compiler is inclined to blow anything up here. + // This comment is from CNIOLinux, but I haven't reverified this applies for OpenBSD. + return sendmmsg(sockfd, (struct mmsghdr *)msgvec, vlen, flags); +} + +int CNIOOpenBSD_recvmmsg(int sockfd, CNIOOpenBSD_mmsghdr *msgvec, unsigned int vlen, int flags, struct timespec *timeout) { + // This is technically undefined behaviour, but it's basically fine because these types are the same size, and we + // don't think the compiler is inclined to blow anything up here. + // This comment is from CNIOLinux, but I haven't reverified this applies for OpenBSD. + return recvmmsg(sockfd, (struct mmsghdr *)msgvec, vlen, flags, timeout); +} + +int CNIOOpenBSD_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) { + return accept4(sockfd, addr, addrlen, flags); +} + +struct cmsghdr *CNIOOpenBSD_CMSG_FIRSTHDR(const struct msghdr *mhdr) { + assert(mhdr != NULL); + return CMSG_FIRSTHDR(mhdr); +} + +struct cmsghdr *CNIOOpenBSD_CMSG_NXTHDR(struct msghdr *mhdr, struct cmsghdr *cmsg) { + assert(mhdr != NULL); + assert(cmsg != NULL); + return CMSG_NXTHDR(mhdr, cmsg); +} + +const void *CNIOOpenBSD_CMSG_DATA(const struct cmsghdr *cmsg) { + assert(cmsg != NULL); + return CMSG_DATA(cmsg); +} + +void *CNIOOpenBSD_CMSG_DATA_MUTABLE(struct cmsghdr *cmsg) { + assert(cmsg != NULL); + return CMSG_DATA(cmsg); +} + +size_t CNIOOpenBSD_CMSG_LEN(size_t payloadSizeBytes) { + return CMSG_LEN(payloadSizeBytes); +} + +size_t CNIOOpenBSD_CMSG_SPACE(size_t payloadSizeBytes) { + return CMSG_SPACE(payloadSizeBytes); +} + +const int CNIOOpenBSD_SO_TIMESTAMP = SO_TIMESTAMP; +const int CNIOOpenBSD_SO_RCVTIMEO = SO_RCVTIMEO; + +bool supports_udp_sockopt(int opt, int value) { + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd == -1) { + return false; + } + int rc = setsockopt(fd, IPPROTO_UDP, opt, &value, sizeof(value)); + close(fd); + return rc == 0; +} + +bool CNIOOpenBSD_supports_udp_segment() { + #ifndef UDP_SEGMENT + return false; + #else + return supports_udp_sockopt(UDP_SEGMENT, 512); + #endif +} + +bool CNIOOpenBSD_supports_udp_gro() { + #ifndef UDP_GRO + return false; + #else + return supports_udp_sockopt(UDP_GRO, 1); + #endif +} + +int CNIOOpenBSD_system_info(struct utsname* uname_data) { + return uname(uname_data); +} + +const char* CNIOOpenBSD_dirent_dname(struct dirent* ent) { + return ent->d_name; +} + +const unsigned long CNIOOpenBSD_UTIME_OMIT = UTIME_OMIT; +const unsigned long CNIOOpenBSD_UTIME_NOW = UTIME_NOW; + +#ifdef UDP_MAX_SEGMENTS +const long CNIOOpenBSD_UDP_MAX_SEGMENTS = UDP_MAX_SEGMENTS; +#endif +const long CNIOOpenBSD_UDP_MAX_SEGMENTS = -1; + +FTS *CNIOOpenBSD_fts_open(char * const *path_argv, int options, int (*compar)(const FTSENT **, const FTSENT **)) { + return fts_open(path_argv, options, compar); +} +#endif diff --git a/Sources/CNIOSHA1/c_nio_sha1.c b/Sources/CNIOSHA1/c_nio_sha1.c index 73af9a813d..9d545d0ee0 100644 --- a/Sources/CNIOSHA1/c_nio_sha1.c +++ b/Sources/CNIOSHA1/c_nio_sha1.c @@ -54,7 +54,7 @@ #endif #ifdef __ANDROID__ #include -#elif defined(__linux__) || defined(__APPLE__) || defined(__wasm32__) +#elif defined(__linux__) || defined(__APPLE__) || defined(__wasm32__) || defined(__OpenBSD__) #include #elif defined(_WIN32) || defined(_WIN64) #ifndef LITTLE_ENDIAN diff --git a/Sources/NIOConcurrencyHelpers/NIOAtomic.swift b/Sources/NIOConcurrencyHelpers/NIOAtomic.swift index f2308ec929..4cc95915c3 100644 --- a/Sources/NIOConcurrencyHelpers/NIOAtomic.swift +++ b/Sources/NIOConcurrencyHelpers/NIOAtomic.swift @@ -213,6 +213,7 @@ public final class NIOAtomic { /// Create an atomic object with `value` @inlinable + @available(OpenBSD, unavailable, message: "malloc_size is unavailable.") public static func makeAtomic(value: T) -> NIOAtomic { let manager = Manager(bufferClass: self, minimumCapacity: 1) { _, _ in } manager.withUnsafeMutablePointerToElements { diff --git a/Sources/NIOConcurrencyHelpers/NIOLock.swift b/Sources/NIOConcurrencyHelpers/NIOLock.swift index e05c15bb6d..f57c8444bb 100644 --- a/Sources/NIOConcurrencyHelpers/NIOLock.swift +++ b/Sources/NIOConcurrencyHelpers/NIOLock.swift @@ -35,6 +35,9 @@ import wasi_pthread #if os(Windows) @usableFromInline typealias LockPrimitive = SRWLOCK +#elseif os(OpenBSD) +@usableFromInline +typealias LockPrimitive = pthread_mutex_t? #else @usableFromInline typealias LockPrimitive = pthread_mutex_t @@ -50,6 +53,11 @@ extension LockOperations { #if os(Windows) InitializeSRWLock(mutex) + #elseif os(OpenBSD) + var attr = pthread_mutexattr_t(bitPattern: 0) + pthread_mutexattr_init(&attr) + let err = pthread_mutex_init(mutex, &attr) + precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") #elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded)) var attr = pthread_mutexattr_t() pthread_mutexattr_init(&attr) diff --git a/Sources/NIOConcurrencyHelpers/atomics.swift b/Sources/NIOConcurrencyHelpers/atomics.swift index d472b083e8..2e2ea1fd74 100644 --- a/Sources/NIOConcurrencyHelpers/atomics.swift +++ b/Sources/NIOConcurrencyHelpers/atomics.swift @@ -463,6 +463,7 @@ extension UInt: AtomicPrimitive { deprecated, message: "AtomicBox is deprecated without replacement because the original implementation doesn't work." ) +@available(OpenBSD, unavailable, message: "malloc_size is unavailable.") public final class AtomicBox { private let storage: NIOAtomic @@ -608,4 +609,5 @@ public final class AtomicBox { } @available(*, deprecated) +@available(OpenBSD, unavailable, message: "malloc_size is unavailable.") extension AtomicBox: @unchecked Sendable where T: Sendable {} diff --git a/Sources/NIOConcurrencyHelpers/lock.swift b/Sources/NIOConcurrencyHelpers/lock.swift index 572232a78b..15242b5b87 100644 --- a/Sources/NIOConcurrencyHelpers/lock.swift +++ b/Sources/NIOConcurrencyHelpers/lock.swift @@ -43,6 +43,9 @@ public final class Lock { #if os(Windows) fileprivate let mutex: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: 1) + #elseif os(OpenBSD) + fileprivate let mutex: UnsafeMutablePointer = + UnsafeMutablePointer.allocate(capacity: 1) #else fileprivate let mutex: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: 1) @@ -52,6 +55,10 @@ public final class Lock { public init() { #if os(Windows) InitializeSRWLock(self.mutex) + #elseif os(OpenBSD) + var attr = pthread_mutexattr_t(bitPattern: 0) + let err = pthread_mutex_init(self.mutex, &attr) + precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") #elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded)) var attr = pthread_mutexattr_t() pthread_mutexattr_init(&attr) @@ -134,6 +141,9 @@ public final class ConditionLock { #if os(Windows) private let cond: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: 1) + #elseif os(OpenBSD) + private let cond: UnsafeMutablePointer = + UnsafeMutablePointer.allocate(capacity: 1) #elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded)) private let cond: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: 1) diff --git a/Sources/NIOCore/BSDSocketAPI.swift b/Sources/NIOCore/BSDSocketAPI.swift index 4b0972c755..590283911f 100644 --- a/Sources/NIOCore/BSDSocketAPI.swift +++ b/Sources/NIOCore/BSDSocketAPI.swift @@ -83,6 +83,14 @@ private let sysInet_ntop: inet_ntop private let sysInet_pton: @convention(c) (CInt, UnsafePointer?, UnsafeMutableRawPointer?) -> CInt = inet_pton #endif +#elseif os(OpenBSD) +@preconcurrency import Glibc +import CNIOOpenBSD + +private let sysInet_ntop: + @convention(c) (CInt, UnsafeRawPointer?, UnsafeMutablePointer?, socklen_t) -> UnsafePointer? = + inet_ntop +private let sysInet_pton: @convention(c) (CInt, UnsafePointer?, UnsafeMutableRawPointer?) -> CInt = inet_pton #elseif canImport(Darwin) import Darwin diff --git a/Sources/NIOCore/Interfaces.swift b/Sources/NIOCore/Interfaces.swift index 2f29e0c5cb..bb6c58c256 100644 --- a/Sources/NIOCore/Interfaces.swift +++ b/Sources/NIOCore/Interfaces.swift @@ -20,6 +20,8 @@ @preconcurrency import Bionic #endif import CNIOLinux +#elseif os(OpenBSD) +import CNIOOpenBSD #elseif canImport(Darwin) import Darwin #elseif canImport(WASILibc) @@ -51,7 +53,7 @@ extension ifaddrs { fileprivate var dstaddr: UnsafeMutablePointer? { #if os(Linux) || os(Android) return self.ifa_ifu.ifu_dstaddr - #elseif canImport(Darwin) + #elseif canImport(Darwin) || os(OpenBSD) return self.ifa_dstaddr #endif } @@ -59,7 +61,7 @@ extension ifaddrs { fileprivate var broadaddr: UnsafeMutablePointer? { #if os(Linux) || os(Android) return self.ifa_ifu.ifu_broadaddr - #elseif canImport(Darwin) + #elseif canImport(Darwin) || os(OpenBSD) return self.ifa_dstaddr #endif } diff --git a/Sources/NIOCore/SocketAddresses.swift b/Sources/NIOCore/SocketAddresses.swift index a78768b2e4..30bf3853e8 100644 --- a/Sources/NIOCore/SocketAddresses.swift +++ b/Sources/NIOCore/SocketAddresses.swift @@ -52,6 +52,9 @@ import Darwin @preconcurrency import Android #endif import CNIOLinux +#elseif os(OpenBSD) +@preconcurrency import Glibc +import CNIOOpenBSD #elseif canImport(WASILibc) @preconcurrency import WASILibc #else diff --git a/Sources/NIOCore/SocketOptionProvider.swift b/Sources/NIOCore/SocketOptionProvider.swift index b2f186cf43..2b6736bdff 100644 --- a/Sources/NIOCore/SocketOptionProvider.swift +++ b/Sources/NIOCore/SocketOptionProvider.swift @@ -22,6 +22,9 @@ import Darwin @preconcurrency import Bionic #endif import CNIOLinux +#elseif os(OpenBSD) +@preconcurrency import Glibc +import CNIOOpenBSD #elseif os(Windows) import WinSDK #elseif canImport(WASILibc) diff --git a/Sources/NIOCore/Utilities.swift b/Sources/NIOCore/Utilities.swift index 7970b83521..8259e337f3 100644 --- a/Sources/NIOCore/Utilities.swift +++ b/Sources/NIOCore/Utilities.swift @@ -20,6 +20,9 @@ import CNIOLinux #elseif canImport(Android) @preconcurrency import Android #endif +#elseif os(OpenBSD) +import CNIOOpenBSD +@preconcurrency import Glibc #elseif os(Windows) import ucrt import let WinSDK.RelationProcessorCore diff --git a/Sources/NIOMulticastChat/main.swift b/Sources/NIOMulticastChat/main.swift index 94316e5ecd..b71f6bb9a5 100644 --- a/Sources/NIOMulticastChat/main.swift +++ b/Sources/NIOMulticastChat/main.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// import CNIOLinux +import CNIOOpenBSD import NIOCore import NIOPosix diff --git a/Sources/NIOPosix/BSDSocketAPICommon.swift b/Sources/NIOPosix/BSDSocketAPICommon.swift index a77ac3f733..f0ab00c41d 100644 --- a/Sources/NIOPosix/BSDSocketAPICommon.swift +++ b/Sources/NIOPosix/BSDSocketAPICommon.swift @@ -97,6 +97,7 @@ extension NIOBSDSocket.SocketType { } // IPv4 Options +#if !os(OpenBSD) extension NIOBSDSocket.Option { /// Request that we are passed type of service details when receiving /// datagrams. @@ -116,6 +117,7 @@ extension NIOBSDSocket.Option { static let ip_recv_pktinfo: NIOBSDSocket.Option = NIOBSDSocket.Option(rawValue: Posix.IP_RECVPKTINFO) } +#endif // IPv6 Options extension NIOBSDSocket.Option { @@ -128,6 +130,7 @@ extension NIOBSDSocket.Option { static let ipv6_recv_tclass: NIOBSDSocket.Option = NIOBSDSocket.Option(rawValue: IPV6_RECVTCLASS) + #if !os(OpenBSD) /// Request that we are passed destination address and the receiving interface index when /// receiving datagrams. /// @@ -136,6 +139,7 @@ extension NIOBSDSocket.Option { /// IPv4 and IPv6. static let ipv6_recv_pktinfo: NIOBSDSocket.Option = NIOBSDSocket.Option(rawValue: Posix.IPV6_RECVPKTINFO) + #endif } extension NIOBSDSocket { diff --git a/Sources/NIOPosix/BSDSocketAPIPosix.swift b/Sources/NIOPosix/BSDSocketAPIPosix.swift index 76ae388c88..d8b5f01601 100644 --- a/Sources/NIOPosix/BSDSocketAPIPosix.swift +++ b/Sources/NIOPosix/BSDSocketAPIPosix.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import NIOCore -#if os(Linux) || os(Android) || os(FreeBSD) || canImport(Darwin) +#if os(Linux) || os(Android) || os(FreeBSD) || canImport(Darwin) || os(OpenBSD) extension Shutdown { internal var cValue: CInt { @@ -276,6 +276,14 @@ private let CMSG_DATA = CNIODarwin_CMSG_DATA private let CMSG_DATA_MUTABLE = CNIODarwin_CMSG_DATA_MUTABLE private let CMSG_SPACE = CNIODarwin_CMSG_SPACE private let CMSG_LEN = CNIODarwin_CMSG_LEN +#elseif os(OpenBSD) +import CNIOOpenBSD +private let CMSG_FIRSTHDR = CNIOOpenBSD_CMSG_FIRSTHDR +private let CMSG_NXTHDR = CNIOOpenBSD_CMSG_NXTHDR +private let CMSG_DATA = CNIOOpenBSD_CMSG_DATA +private let CMSG_DATA_MUTABLE = CNIOOpenBSD_CMSG_DATA_MUTABLE +private let CMSG_SPACE = CNIOOpenBSD_CMSG_SPACE +private let CMSG_LEN = CNIOOpenBSD_CMSG_LEN #else import CNIOLinux private let CMSG_FIRSTHDR = CNIOLinux_CMSG_FIRSTHDR diff --git a/Sources/NIOPosix/BaseSocket.swift b/Sources/NIOPosix/BaseSocket.swift index 5c5ab3ceb8..fe36a48610 100644 --- a/Sources/NIOPosix/BaseSocket.swift +++ b/Sources/NIOPosix/BaseSocket.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CNIOLinux +import CNIOOpenBSD import NIOConcurrencyHelpers import NIOCore diff --git a/Sources/NIOPosix/Bootstrap.swift b/Sources/NIOPosix/Bootstrap.swift index a79148ee6d..6768b1a729 100644 --- a/Sources/NIOPosix/Bootstrap.swift +++ b/Sources/NIOPosix/Bootstrap.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// import CNIOLinux +import CNIOOpenBSD import NIOCore #if os(Windows) diff --git a/Sources/NIOPosix/ControlMessage.swift b/Sources/NIOPosix/ControlMessage.swift index e882551b4d..34160e3202 100644 --- a/Sources/NIOPosix/ControlMessage.swift +++ b/Sources/NIOPosix/ControlMessage.swift @@ -17,6 +17,10 @@ import NIOCore import CNIODarwin #elseif os(Linux) || os(FreeBSD) || os(Android) import CNIOLinux +import Glibc +#elseif os(OpenBSD) +import CNIOOpenBSD +import Glibc #elseif os(Windows) import CNIOWindows #endif @@ -234,6 +238,7 @@ struct ControlMessageParser { self.ecnValue = .init(receivedValue: readValue) } } else if controlMessage.type == Posix.IP_PKTINFO { + #if !os(OpenBSD) if let data = controlMessage.data { let info = data.load(as: in_pktinfo.self) var addr = sockaddr_in() @@ -245,7 +250,7 @@ struct ControlMessageParser { interfaceIndex: Int(info.ipi_ifindex) ) } - + #endif } } diff --git a/Sources/NIOPosix/DatagramVectorReadManager.swift b/Sources/NIOPosix/DatagramVectorReadManager.swift index cf9f4f0771..2ede0bddc6 100644 --- a/Sources/NIOPosix/DatagramVectorReadManager.swift +++ b/Sources/NIOPosix/DatagramVectorReadManager.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CNIODarwin import CNIOLinux +import CNIOOpenBSD import NIOCore #if canImport(WinSDK) diff --git a/Sources/NIOPosix/NonBlockingFileIO.swift b/Sources/NIOPosix/NonBlockingFileIO.swift index 1dff63128a..1dc23e9849 100644 --- a/Sources/NIOPosix/NonBlockingFileIO.swift +++ b/Sources/NIOPosix/NonBlockingFileIO.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CNIOLinux +import CNIOOpenBSD import CNIOWindows import NIOConcurrencyHelpers import NIOCore @@ -829,8 +830,13 @@ public struct NonBlockingFileIO: Sendable { let ptr = pointer.baseAddress!.assumingMemoryBound(to: CChar.self) return String(cString: ptr) } + #if os(OpenBSD) + let ino = entry.pointee.d_fileno + #else + let ino = entry.pointee.d_ino + #endif entries.append( - NIODirectoryEntry(ino: UInt64(entry.pointee.d_ino), type: entry.pointee.d_type, name: name) + NIODirectoryEntry(ino: UInt64(ino), type: entry.pointee.d_type, name: name) ) } try? Posix.closedir(dir: dir) @@ -1278,8 +1284,13 @@ extension NonBlockingFileIO { let ptr = pointer.baseAddress!.assumingMemoryBound(to: CChar.self) return String(cString: ptr) } + #if os(OpenBSD) + let ino = entry.pointee.d_fileno + #else + let ino = entry.pointee.d_ino + #endif entries.append( - NIODirectoryEntry(ino: UInt64(entry.pointee.d_ino), type: entry.pointee.d_type, name: name) + NIODirectoryEntry(ino: UInt64(ino), type: entry.pointee.d_type, name: name) ) } try? Posix.closedir(dir: dir) diff --git a/Sources/NIOPosix/PendingDatagramWritesManager.swift b/Sources/NIOPosix/PendingDatagramWritesManager.swift index b7088584bd..6b995da28d 100644 --- a/Sources/NIOPosix/PendingDatagramWritesManager.swift +++ b/Sources/NIOPosix/PendingDatagramWritesManager.swift @@ -15,6 +15,7 @@ import Atomics import CNIODarwin import CNIOLinux +import CNIOOpenBSD import CNIOWindows import NIOCore diff --git a/Sources/NIOPosix/PendingWritesManager.swift b/Sources/NIOPosix/PendingWritesManager.swift index c6adbc093e..521b7cd5e7 100644 --- a/Sources/NIOPosix/PendingWritesManager.swift +++ b/Sources/NIOPosix/PendingWritesManager.swift @@ -14,6 +14,7 @@ import Atomics import CNIOLinux +import CNIOOpenBSD import NIOCore private struct PendingStreamWrite { diff --git a/Sources/NIOPosix/SelectorGeneric.swift b/Sources/NIOPosix/SelectorGeneric.swift index b538e3f4f5..bd19a0320b 100644 --- a/Sources/NIOPosix/SelectorGeneric.swift +++ b/Sources/NIOPosix/SelectorGeneric.swift @@ -185,7 +185,7 @@ internal class Selector { var selectorFD: CInt = -1 // -1 == we're closed // Here we add the stored properties that are used by the specific backends - #if canImport(Darwin) + #if canImport(Darwin) || os(OpenBSD) @usableFromInline typealias EventType = kevent #elseif os(Linux) || os(Android) diff --git a/Sources/NIOPosix/SelectorKqueue.swift b/Sources/NIOPosix/SelectorKqueue.swift index ad28ee343b..d25b8e0263 100644 --- a/Sources/NIOPosix/SelectorKqueue.swift +++ b/Sources/NIOPosix/SelectorKqueue.swift @@ -15,7 +15,7 @@ import NIOConcurrencyHelpers import NIOCore -#if canImport(Darwin) +#if canImport(Darwin) || os(OpenBSD) /// Represents the `kqueue` filters we might use: /// @@ -131,7 +131,13 @@ extension Selector: _SelectorBackendProtocol { // Clamp the timespec tv_sec value to the maximum supported by the Darwin-kernel. // Whilst this schedules the event far into the future (several years) it will still be triggered provided // the system stays up. + #if os(OpenBSD) + // Should double-check if this makes sense for OpenBSD, but + // let's just get this to compile for now. + ts.tv_sec = min(ts.tv_sec, time_t(sysIntervalTimerMaxSec)) + #else ts.tv_sec = min(ts.tv_sec, sysIntervalTimerMaxSec) + #endif return ts } @@ -388,6 +394,7 @@ extension kevent { // we do it with NOTE_LOWAT set to Int.max, which will ensure that there is never enough data // in the send buffer to trigger EVFILT_EXCEPT. Thanks to the sensible design of kqueue, // this only affects our EXCEPT filter: EVFILT_READ behaves separately. + #if canImport(Darwin) if filter == EVFILT_EXCEPT { self.fflags = CUnsignedInt(NOTE_LOWAT) self.data = Int.max @@ -395,6 +402,10 @@ extension kevent { self.fflags = 0 self.data = 0 } + #else + self.fflags = 0 + self.data = 0 + #endif } } diff --git a/Sources/NIOPosix/Socket.swift b/Sources/NIOPosix/Socket.swift index 723f0aa2a2..2e5408b047 100644 --- a/Sources/NIOPosix/Socket.swift +++ b/Sources/NIOPosix/Socket.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CNIOLinux +import CNIOOpenBSD import NIOCore #if canImport(WinSDK) diff --git a/Sources/NIOPosix/SocketChannel.swift b/Sources/NIOPosix/SocketChannel.swift index 504c2ad834..f52fcf0f44 100644 --- a/Sources/NIOPosix/SocketChannel.swift +++ b/Sources/NIOPosix/SocketChannel.swift @@ -28,6 +28,10 @@ import struct WinSDK.ipv6_mreq import struct WinSDK.socklen_t #endif +#if os(OpenBSD) +import CNIOOpenBSD +#endif + extension ByteBuffer { mutating func withMutableWritePointer( body: (UnsafeMutableRawBufferPointer) throws -> IOResult @@ -121,7 +125,7 @@ final class SocketChannel: BaseStreamSocketChannel, @unchecked Sendable case _ as ChannelOptions.Types.ConnectTimeoutOption: return connectTimeout as! Option.Value case _ as ChannelOptions.Types.LocalVsockContextID: - #if os(Windows) + #if os(Windows) || os(OpenBSD) fallthrough #else return try self.socket.getLocalVsockContextID() as! Option.Value @@ -280,7 +284,7 @@ final class ServerSocketChannel: BaseSocketChannel, @unchecked Sen case _ as ChannelOptions.Types.BacklogOption: return backlog as! Option.Value case _ as ChannelOptions.Types.LocalVsockContextID: - #if os(Windows) + #if os(Windows) || os(OpenBSD) fallthrough #else return try self.socket.getLocalVsockContextID() as! Option.Value @@ -319,7 +323,7 @@ final class ServerSocketChannel: BaseSocketChannel, @unchecked Sen switch target { case .socketAddress(let address): try socket.bind(to: address) - #if os(Windows) + #if os(Windows) || os(OpenBSD) case .vsockAddress: fatalError(vsockUnimplemented) #else @@ -575,12 +579,16 @@ final class DatagramChannel: BaseSocketChannel, @unchecked Sendable { let valueAsInt: CInt = value as! Bool ? 1 : 0 switch self.localAddress?.protocol { case .some(.inet): + #if os(OpenBSD) + throw ChannelError._operationUnsupported + #else self.reportExplicitCongestionNotifications = true try self.socket.setOption( level: .ip, name: .ip_recv_tos, value: valueAsInt ) + #endif case .some(.inet6): self.reportExplicitCongestionNotifications = true try self.socket.setOption( @@ -594,6 +602,9 @@ final class DatagramChannel: BaseSocketChannel, @unchecked Sendable { } case _ as ChannelOptions.Types.ReceivePacketInfo: let valueAsInt: CInt = value as! Bool ? 1 : 0 + #if os(OpenBSD) + throw ChannelError._operationUnsupported + #else switch self.localAddress?.protocol { case .some(.inet): self.receivePacketInfo = true @@ -613,6 +624,7 @@ final class DatagramChannel: BaseSocketChannel, @unchecked Sendable { // Receiving packet info is only supported for IP throw ChannelError._operationUnsupported } + #endif case _ as ChannelOptions.Types.DatagramSegmentSize: guard System.supportsUDPSegmentationOffload else { throw ChannelError._operationUnsupported @@ -647,11 +659,15 @@ final class DatagramChannel: BaseSocketChannel, @unchecked Sendable { case _ as ChannelOptions.Types.ExplicitCongestionNotificationsOption: switch self.localAddress?.protocol { case .some(.inet): + #if os(OpenBSD) + throw ChannelError._operationUnsupported + #else return try (self.socket.getOption( level: .ip, name: .ip_recv_tos ) != 0) as! Option.Value + #endif case .some(.inet6): return try (self.socket.getOption( @@ -663,6 +679,9 @@ final class DatagramChannel: BaseSocketChannel, @unchecked Sendable { throw ChannelError._operationUnsupported } case _ as ChannelOptions.Types.ReceivePacketInfo: + #if os(OpenBSD) + throw ChannelError._operationUnsupported + #else switch self.localAddress?.protocol { case .some(.inet): return try @@ -680,6 +699,7 @@ final class DatagramChannel: BaseSocketChannel, @unchecked Sendable { // Receiving packet info is only supported for IP throw ChannelError._operationUnsupported } + #endif case _ as ChannelOptions.Types.DatagramSegmentSize: guard System.supportsUDPSegmentationOffload else { throw ChannelError._operationUnsupported diff --git a/Sources/NIOPosix/SocketProtocols.swift b/Sources/NIOPosix/SocketProtocols.swift index 36b32a8e32..3996d8e570 100644 --- a/Sources/NIOPosix/SocketProtocols.swift +++ b/Sources/NIOPosix/SocketProtocols.swift @@ -77,7 +77,7 @@ protocol SocketProtocol: BaseSocketProtocol { func ignoreSIGPIPE() throws } -#if os(Linux) || os(Android) +#if os(Linux) || os(Android) || os(OpenBSD) // This is a lazily initialised global variable that when read for the first time, will ignore SIGPIPE. private let globallyIgnoredSIGPIPE: Bool = { // no F_SETNOSIGPIPE on Linux :( @@ -97,7 +97,7 @@ private let globallyIgnoredSIGPIPE: Bool = { extension BaseSocketProtocol { // used by `BaseSocket` and `PipePair`. internal static func ignoreSIGPIPE(descriptor fd: CInt) throws { - #if os(Linux) || os(Android) + #if os(Linux) || os(Android) || os(OpenBSD) let haveWeIgnoredSIGPIEThisIsHereToTriggerIgnoringIt = globallyIgnoredSIGPIPE guard haveWeIgnoredSIGPIEThisIsHereToTriggerIgnoringIt else { fatalError("BUG in NIO. We did not ignore SIGPIPE, this code path should definitely not be reachable.") diff --git a/Sources/NIOPosix/System.swift b/Sources/NIOPosix/System.swift index 576f75c674..d5b5ce939b 100644 --- a/Sources/NIOPosix/System.swift +++ b/Sources/NIOPosix/System.swift @@ -32,6 +32,11 @@ internal typealias MMsgHdr = CNIODarwin_mmsghdr import CNIOLinux internal typealias MMsgHdr = CNIOLinux_mmsghdr internal typealias in6_pktinfo = CNIOLinux_in6_pktinfo +#elseif os(OpenBSD) +@_exported @preconcurrency import Glibc +import CNIOOpenBSD +internal typealias MMsgHdr = CNIOOpenBSD_mmsghdr +let INADDR_ANY = UInt32(0) #elseif os(Windows) @_exported import ucrt @@ -151,7 +156,7 @@ private let sysSocketpair: @convention(c) (CInt, CInt, CInt, UnsafeMutablePointe private let sysSocketpair: @convention(c) (CInt, CInt, CInt, UnsafeMutablePointer?) -> CInt = socketpair #endif -#if os(Linux) || os(Android) || canImport(Darwin) +#if os(Linux) || os(Android) || canImport(Darwin) || os(OpenBSD) private let sysFstat = fstat private let sysStat = stat private let sysLstat = lstat @@ -168,6 +173,10 @@ private let sysRemove = remove #if os(Linux) || os(Android) private let sysSendMmsg = CNIOLinux_sendmmsg private let sysRecvMmsg = CNIOLinux_recvmmsg +#elseif os(OpenBSD) +private let sysKevent = kevent +private let sysSendMmsg = CNIOOpenBSD_sendmmsg +private let sysRecvMmsg = CNIOOpenBSD_recvmmsg #elseif canImport(Darwin) private let sysKevent = kevent private let sysMkpath = mkpath_np @@ -347,7 +356,7 @@ internal func syscall( } } } -#elseif os(Linux) || os(Android) +#elseif os(Linux) || os(Android) || os(OpenBSD) @inline(__always) @inlinable @discardableResult @@ -450,7 +459,7 @@ internal enum Posix: Sendable { static let SHUT_WR: CInt = CInt(Darwin.SHUT_WR) @usableFromInline static let SHUT_RDWR: CInt = CInt(Darwin.SHUT_RDWR) - #elseif os(Linux) || os(FreeBSD) || os(Android) + #elseif os(Linux) || os(FreeBSD) || os(Android) || os(OpenBSD) #if canImport(Glibc) @usableFromInline static let UIO_MAXIOV: Int = Int(Glibc.UIO_MAXIOV) @@ -514,6 +523,12 @@ internal enum Posix: Sendable { static let IPTOS_ECN_ECT0: CInt = CInt(CNIOLinux.IPTOS_ECN_ECT0) static let IPTOS_ECN_ECT1: CInt = CInt(CNIOLinux.IPTOS_ECN_ECT1) static let IPTOS_ECN_CE: CInt = CInt(CNIOLinux.IPTOS_ECN_CE) + #elseif os(OpenBSD) + static let IPTOS_ECN_NOTECT: CInt = CInt(CNIOOpenBSD.IPTOS_ECN_NOTECT) + static let IPTOS_ECN_MASK: CInt = CInt(CNIOOpenBSD.IPTOS_ECN_MASK) + static let IPTOS_ECN_ECT0: CInt = CInt(CNIOOpenBSD.IPTOS_ECN_ECT0) + static let IPTOS_ECN_ECT1: CInt = CInt(CNIOOpenBSD.IPTOS_ECN_ECT1) + static let IPTOS_ECN_CE: CInt = CInt(CNIOOpenBSD.IPTOS_ECN_CE) #elseif os(Windows) static let IPTOS_ECN_NOTECT: CInt = CInt(0x00) static let IPTOS_ECN_MASK: CInt = CInt(0x03) @@ -534,6 +549,11 @@ internal enum Posix: Sendable { static let IPV6_RECVPKTINFO: CInt = CInt(CNIOLinux.IPV6_RECVPKTINFO) static let IPV6_PKTINFO: CInt = CInt(CNIOLinux.IPV6_PKTINFO) + #elseif os(OpenBSD) + static let IP_PKTINFO: CInt = CInt(-1) // Not actually present. + + static let IPV6_RECVPKTINFO: CInt = CInt(CNIOOpenBSD.IPV6_RECVPKTINFO) + static let IPV6_PKTINFO: CInt = CInt(CNIOOpenBSD.IPV6_PKTINFO) #elseif os(Windows) static let IP_RECVPKTINFO: CInt = CInt(WinSDK.IP_PKTINFO) static let IP_PKTINFO: CInt = CInt(WinSDK.IP_PKTINFO) @@ -970,7 +990,7 @@ internal enum Posix: Sendable { sysClosedir(dir) } } - #elseif os(Linux) || os(FreeBSD) || os(Android) + #elseif os(Linux) || os(FreeBSD) || os(Android) || os(OpenBSD) @inline(never) public static func opendir(pathname: String) throws -> OpaquePointer { try syscall { @@ -1062,7 +1082,15 @@ extension Posix { } #endif +#if canImport(Darwin) || os(OpenBSD) #if canImport(Darwin) +internal typealias kevent_timespec = Darwin.timespec +#elseif os(OpenBSD) +internal typealias kevent_timespec = CNIOOpenBSD.timespec +#else +#error("implementation missing") +#endif + @usableFromInline internal enum KQueue: Sendable { @@ -1071,7 +1099,13 @@ internal enum KQueue: Sendable { @inline(never) public static func kqueue() throws -> CInt { try syscall(blocking: false) { + #if canImport(Darwin) Darwin.kqueue() + #elseif os(OpenBSD) + CNIOOpenBSD.kqueue() + #else + #error("implementation missing") + #endif }.result } @@ -1083,7 +1117,7 @@ internal enum KQueue: Sendable { nchanges: CInt, eventlist: UnsafeMutablePointer?, nevents: CInt, - timeout: UnsafePointer? + timeout: UnsafePointer? ) throws -> CInt { try syscall(blocking: false) { sysKevent(kq, changelist, nchanges, eventlist, nevents, timeout) diff --git a/Sources/NIOPosix/Thread.swift b/Sources/NIOPosix/Thread.swift index b730aba1b2..ea90f37d6b 100644 --- a/Sources/NIOPosix/Thread.swift +++ b/Sources/NIOPosix/Thread.swift @@ -16,6 +16,8 @@ import NIOConcurrencyHelpers #if os(Linux) || os(FreeBSD) || os(Android) import CNIOLinux +#elseif os(OpenBSD) +import CNIOOpenBSD #elseif os(Windows) import WinSDK #endif diff --git a/Sources/NIOPosix/ThreadPosix.swift b/Sources/NIOPosix/ThreadPosix.swift index d60dcec6f3..33b72fee22 100644 --- a/Sources/NIOPosix/ThreadPosix.swift +++ b/Sources/NIOPosix/ThreadPosix.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#if os(Linux) || os(Android) || os(FreeBSD) || canImport(Darwin) +#if os(Linux) || os(Android) || os(FreeBSD) || canImport(Darwin) || os(OpenBSD) #if os(Linux) || os(Android) import CNIOLinux @@ -24,6 +24,12 @@ private typealias ThreadDestructor = @convention(c) (UnsafeMutableRawPointer) -> #else private typealias ThreadDestructor = @convention(c) (UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? #endif +#elseif os(OpenBSD) +import CNIOOpenBSD + +private let sys_pthread_getname_np = CNIOOpenBSD_pthread_get_name_np +private let sys_pthread_setname_np = CNIOOpenBSD_pthread_set_name_np +private typealias ThreadDestructor = @convention(c) (UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? #elseif canImport(Darwin) private let sys_pthread_getname_np = pthread_getname_np // Emulate the same method signature as pthread_setname_np on Linux. @@ -49,6 +55,12 @@ private func sysPthread_create( let thread = pthread_create(handle, &attr, destructor, args) pthread_attr_destroy(&attr) return thread + #elseif os(OpenBSD) + var attr: pthread_attr_t? = .init(bitPattern: 0) + pthread_attr_init(&attr) + let thread = pthread_create(handle, &attr, destructor, args) + pthread_attr_destroy(&attr) + return thread #else #if canImport(Musl) var handleLinux: OpaquePointer? = nil