diff --git a/Package.swift b/Package.swift index b12c0f8357..fd6aec5143 100644 --- a/Package.swift +++ b/Package.swift @@ -21,7 +21,7 @@ let swiftSystem: PackageDescription.Target.Dependency = .product(name: "SystemPa // These platforms require a dependency on `NIOPosix` from `NIOHTTP1` to maintain backward // compatibility with previous NIO versions. -let historicalNIOPosixDependencyRequired: [Platform] = [.macOS, .iOS, .tvOS, .watchOS, .linux, .android] +let historicalNIOPosixDependencyRequired: [Platform] = [.macOS, .iOS, .tvOS, .watchOS, .linux, .android, .custom("freebsd"), .custom("FreeBSD")] let swiftSettings: [SwiftSetting] = [] @@ -60,6 +60,7 @@ let package = Package( "NIOConcurrencyHelpers", "_NIOBase64", "CNIODarwin", + "CNIOFreeBSD", "CNIOLinux", "CNIOWindows", "CNIOWASI", @@ -93,6 +94,7 @@ let package = Package( dependencies: [ "CNIOLinux", "CNIODarwin", + "CNIOFreeBSD", "CNIOWindows", "NIOConcurrencyHelpers", "NIOCore", @@ -161,6 +163,10 @@ let package = Package( .define("__APPLE_USE_RFC_3542") ] ), + .target( + name: "CNIOFreeBSD", + dependencies: [] + ), .target( name: "CNIOWindows", dependencies: [] @@ -260,6 +266,7 @@ let package = Package( "NIOPosix", "CNIOLinux", "CNIODarwin", + "CNIOFreeBSD", swiftAtomics, swiftCollections, swiftSystem, @@ -484,6 +491,7 @@ let package = Package( "NIOEmbedded", "CNIOLinux", "CNIODarwin", + "CNIOFreeBSD", "NIOTLS", ], swiftSettings: swiftSettings diff --git a/Sources/CNIOFreeBSD/include/CNIOFreeBSD.h b/Sources/CNIOFreeBSD/include/CNIOFreeBSD.h new file mode 100644 index 0000000000..d40e258721 --- /dev/null +++ b/Sources/CNIOFreeBSD/include/CNIOFreeBSD.h @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2018 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_FREEBSD_H +#define C_NIO_FREEBSD_H + +#ifdef __FreeBSD__ +#include +#include +#include +#include +#include +#include +#include +#include + +extern const int CNIOFreeBSD_AT_EMPTY_PATH; + +extern const int CNIOFreeBSD_IPTOS_ECN_NOTECT; +extern const int CNIOFreeBSD_IPTOS_ECN_MASK; +extern const int CNIOFreeBSD_IPTOS_ECN_ECT0; +extern const int CNIOFreeBSD_IPTOS_ECN_ECT1; +extern const int CNIOFreeBSD_IPTOS_ECN_CE; +extern const int CNIOFreeBSD_IPV6_RECVPKTINFO; +extern const int CNIOFreeBSD_IPV6_PKTINFO; + +int CNIOFreeBSD_sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, int flags); +int CNIOFreeBSD_recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, int flags, struct timespec *timeout); + +// cmsghdr handling +struct cmsghdr *CNIOFreeBSD_CMSG_FIRSTHDR(const struct msghdr *); +struct cmsghdr *CNIOFreeBSD_CMSG_NXTHDR(const struct msghdr *, const struct cmsghdr *); +const void *CNIOFreeBSD_CMSG_DATA(const struct cmsghdr *); +void *CNIOFreeBSD_CMSG_DATA_MUTABLE(struct cmsghdr *); +size_t CNIOFreeBSD_CMSG_LEN(size_t); +size_t CNIOFreeBSD_CMSG_SPACE(size_t); + +const char* CNIOFreeBSD_dirent_dname(struct dirent* ent); + +#endif // __FreeBSD__ +#endif // C_NIO_FREEBSD_H diff --git a/Sources/CNIOFreeBSD/shim.c b/Sources/CNIOFreeBSD/shim.c new file mode 100644 index 0000000000..bb8f3b5499 --- /dev/null +++ b/Sources/CNIOFreeBSD/shim.c @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2018 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 +// +//===----------------------------------------------------------------------===// +#ifdef __FreeBSD__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dirent.h" + +int CNIOFreeBSD_sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, int flags) { + return sendmmsg(sockfd, msgvec, vlen, flags); +} + +int CNIOFreeBSD_recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, int flags, struct timespec *timeout) { + return recvmmsg(sockfd, msgvec, vlen, flags, timeout); +} + +struct cmsghdr *CNIOFreeBSD_CMSG_FIRSTHDR(const struct msghdr *mhdr) { + assert(mhdr != NULL); + return CMSG_FIRSTHDR(mhdr); +} + +struct cmsghdr *CNIOFreeBSD_CMSG_NXTHDR(const struct msghdr *mhdr, const struct cmsghdr *cmsg) { + assert(mhdr != NULL); + assert(cmsg != NULL); // Not required by Darwin but Linux needs this so we should match. + return CMSG_NXTHDR(mhdr, cmsg); +} + +const void *CNIOFreeBSD_CMSG_DATA(const struct cmsghdr *cmsg) { + assert(cmsg != NULL); + return CMSG_DATA(cmsg); +} + +void *CNIOFreeBSD_CMSG_DATA_MUTABLE(struct cmsghdr *cmsg) { + assert(cmsg != NULL); + return CMSG_DATA(cmsg); +} + +size_t CNIOFreeBSD_CMSG_LEN(size_t payloadSizeBytes) { + return CMSG_LEN(payloadSizeBytes); +} + +size_t CNIOFreeBSD_CMSG_SPACE(size_t payloadSizeBytes) { + return CMSG_SPACE(payloadSizeBytes); +} + +const int CNIOFreeBSD_IPTOS_ECN_NOTECT = IPTOS_ECN_NOTECT; +const int CNIOFreeBSD_IPTOS_ECN_MASK = IPTOS_ECN_MASK; +const int CNIOFreeBSD_IPTOS_ECN_ECT0 = IPTOS_ECN_ECT0; +const int CNIOFreeBSD_IPTOS_ECN_ECT1 = IPTOS_ECN_ECT1; +const int CNIOFreeBSD_IPTOS_ECN_CE = IPTOS_ECN_CE; +const int CNIOFreeBSD_IPV6_RECVPKTINFO = IPV6_RECVPKTINFO; +const int CNIOFreeBSD_IPV6_PKTINFO = IPV6_PKTINFO; +const int CNIOFreeBSD_AT_EMPTY_PATH = AT_EMPTY_PATH; + +const char* CNIOFreeBSD_dirent_dname(struct dirent* ent) { + return ent->d_name; +} + +#endif // __APPLE__ diff --git a/Sources/CNIOSHA1/c_nio_sha1.c b/Sources/CNIOSHA1/c_nio_sha1.c index 73af9a813d..0a1bc8b1a4 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(__FreeBSD__) || defined(__wasm32__) #include #elif defined(_WIN32) || defined(_WIN64) #ifndef LITTLE_ENDIAN diff --git a/Sources/NIOConcurrencyHelpers/NIOLock.swift b/Sources/NIOConcurrencyHelpers/NIOLock.swift index e05c15bb6d..38fedfb6c4 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(FreeBSD) || os(OpenBSD) +@usableFromInline +typealias LockPrimitive = pthread_mutex_t? #else @usableFromInline typealias LockPrimitive = pthread_mutex_t @@ -51,10 +54,18 @@ extension LockOperations { #if os(Windows) InitializeSRWLock(mutex) #elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded)) + #if os(FreeBSD) + var attr: pthread_mutexattr_t? = nil + #else var attr = pthread_mutexattr_t() + #endif pthread_mutexattr_init(&attr) debugOnly { + #if os(FreeBSD) + pthread_mutexattr_settype(&attr, Int32(PTHREAD_MUTEX_ERRORCHECK.rawValue)) + #else pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK)) + #endif } let err = pthread_mutex_init(mutex, &attr) diff --git a/Sources/NIOConcurrencyHelpers/lock.swift b/Sources/NIOConcurrencyHelpers/lock.swift index 572232a78b..482199b1ee 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(FreeBSD) || os(OpenBSD) + fileprivate let mutex: UnsafeMutablePointer = + UnsafeMutablePointer.allocate(capacity: 1) #else fileprivate let mutex: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: 1) @@ -53,10 +56,18 @@ public final class Lock { #if os(Windows) InitializeSRWLock(self.mutex) #elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded)) + #if os(FreeBSD) + var attr: pthread_mutexattr_t? = nil + #else var attr = pthread_mutexattr_t() + #endif pthread_mutexattr_init(&attr) debugOnly { + #if os(FreeBSD) + pthread_mutexattr_settype(&attr, Int32(PTHREAD_MUTEX_ERRORCHECK.rawValue)) + #else pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK)) + #endif } let err = pthread_mutex_init(self.mutex, &attr) @@ -134,6 +145,9 @@ public final class ConditionLock { #if os(Windows) private let cond: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: 1) + #elseif (os(FreeBSD) || os(OpenBSD)) && ((compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))) + 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..b0a9cb2413 100644 --- a/Sources/NIOCore/BSDSocketAPI.swift +++ b/Sources/NIOCore/BSDSocketAPI.swift @@ -97,6 +97,13 @@ 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 os(FreeBSD) +import Glibc + +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 #else #error("The BSD Socket module was unable to identify your C library.") #endif diff --git a/Sources/NIOCore/Interfaces.swift b/Sources/NIOCore/Interfaces.swift index 2f29e0c5cb..979284a5d4 100644 --- a/Sources/NIOCore/Interfaces.swift +++ b/Sources/NIOCore/Interfaces.swift @@ -51,7 +51,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(FreeBSD) return self.ifa_dstaddr #endif } @@ -59,7 +59,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(FreeBSD) return self.ifa_dstaddr #endif } diff --git a/Sources/NIOCore/SocketOptionProvider.swift b/Sources/NIOCore/SocketOptionProvider.swift index b2f186cf43..337a06a8cc 100644 --- a/Sources/NIOCore/SocketOptionProvider.swift +++ b/Sources/NIOCore/SocketOptionProvider.swift @@ -26,6 +26,8 @@ import CNIOLinux import WinSDK #elseif canImport(WASILibc) @preconcurrency import WASILibc +#elseif os(FreeBSD) +import Glibc #else #error("The Socket Option provider module was unable to identify your C library.") #endif diff --git a/Sources/NIOFS/DirectoryEntries.swift b/Sources/NIOFS/DirectoryEntries.swift index 38267dc744..4c9d88aba8 100644 --- a/Sources/NIOFS/DirectoryEntries.swift +++ b/Sources/NIOFS/DirectoryEntries.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CNIODarwin +import CNIOFreeBSD import CNIOLinux import NIOConcurrencyHelpers import NIOCore @@ -556,6 +557,8 @@ private struct DirectoryEnumerator: Sendable { // Empty is checked for above, root can't exist within a directory, and directory // items must be a single path component. name = FilePath.Component(platformString: CNIODarwin_dirent_dname(entry))! + #elseif os(FreeBSD) + name = FilePath.Component(platformString: CNIOFreeBSD_dirent_dname(entry))! #else name = FilePath.Component(platformString: CNIOLinux_dirent_dname(entry))! #endif @@ -598,7 +601,11 @@ private struct DirectoryEnumerator: Sendable { while entries.count < count { switch Libc.ftsRead(fts) { case .success(.some(let entry)): + #if os(FreeBSD) + let info = FTSInfo(rawValue: UInt16(entry.pointee.fts_info)) + #else let info = FTSInfo(rawValue: entry.pointee.fts_info) + #endif switch info { case .directoryPreOrder: let entry = DirectoryEntry(path: NIOFilePath(entry.path), type: .directory)! diff --git a/Sources/NIOFS/FileInfo.swift b/Sources/NIOFS/FileInfo.swift index ff6cfb0547..7dcec2de0e 100644 --- a/Sources/NIOFS/FileInfo.swift +++ b/Sources/NIOFS/FileInfo.swift @@ -16,6 +16,8 @@ import SystemPackage #if canImport(Darwin) import Darwin +#elseif os(FreeBSD) +import Glibc #elseif canImport(Glibc) @preconcurrency import Glibc import CNIOLinux @@ -149,7 +151,7 @@ extension FileInfo { /// A time interval consisting of whole seconds and nanoseconds. public struct Timespec: Hashable, Sendable { - #if canImport(Darwin) + #if canImport(Darwin) || os(FreeBSD) private static let utimeOmit = Int(UTIME_OMIT) private static let utimeNow = Int(UTIME_NOW) #elseif canImport(Glibc) || canImport(Musl) || canImport(Android) diff --git a/Sources/NIOFS/FileSystem.swift b/Sources/NIOFS/FileSystem.swift index b54e6cb28b..d0219156c8 100644 --- a/Sources/NIOFS/FileSystem.swift +++ b/Sources/NIOFS/FileSystem.swift @@ -1284,6 +1284,25 @@ extension FileSystem { while offset < sourceInfo.size { // sendfile(2) limits writes to 0x7ffff000 in size let size = min(Int(sourceInfo.size) - offset, 0x7fff_f000) + #if os(FreeBSD) + var _offset: Int? = offset + var _dOffset: Int? = nil + let result = Syscall.copy_file_range( + from: sourceFD, + offset: &_offset, + to: destinationFD, + destOffset: &_dOffset, + size: size, + flags: 0 + ).mapError { errno in + FileSystemError.copy_file_range( + errno: errno, + from: sourcePath, + to: destinationPath, + location: .here() + ) + } + #else let result = Syscall.sendfile( to: destinationFD, from: sourceFD, @@ -1297,7 +1316,7 @@ extension FileSystem { location: .here() ) } - + #endif switch result { case let .success(bytesCopied): offset += bytesCopied diff --git a/Sources/NIOFS/FileSystemError+Syscall.swift b/Sources/NIOFS/FileSystemError+Syscall.swift index 280041e5c6..2066344996 100644 --- a/Sources/NIOFS/FileSystemError+Syscall.swift +++ b/Sources/NIOFS/FileSystemError+Syscall.swift @@ -129,6 +129,34 @@ extension FileSystemError { ) } + @_spi(Testing) + public static func extattr_get_fd( + attribute name: String, + errno: Errno, + path: FilePath, + location: SourceLocation + ) -> Self { + let code: FileSystemError.Code + let message: String + + switch errno { + case .badFileDescriptor: + code = .closed + message = "Could not get value for extended attribute ('\(name)'), '\(path)' is closed." + default: + code = .unknown + message = "Could not get value for extended attribute ('\(name)') for '\(path)'" + } + + return FileSystemError( + code: code, + message: message, + systemCall: "extattr_get_fd", + errno: errno, + location: location + ) + } + @_spi(Testing) public static func flistxattr(errno: Errno, path: FilePath, location: SourceLocation) -> Self { let code: FileSystemError.Code @@ -1117,6 +1145,55 @@ extension FileSystemError { ) } + #if os(FreeBSD) + @_spi(Testing) + public static func copy_file_range( + errno: Errno, + from sourcePath: FilePath, + to destinationPath: FilePath, + location: SourceLocation + ) -> FileSystemError { + let code: FileSystemError.Code + let message: String + + switch errno { + case .invalidArgument: + code = .invalidArgument + message = """ + """ + case .fileTooLarge: + code = .resourceExhausted + message = """ + The copy exceeds process's file size limit or maximum file size for \ + the file system '\(destinationPath)' resides on. + """ + case .ioError: + code = .io + message = """ + An I/O error occurred while reading from '\(sourcePath)', can't copy to \ + '\(destinationPath)'. + """ + case .noMemory: + code = .io + message = """ + Insufficient memory to read from '\(sourcePath)', can't copy to \ + '\(destinationPath)'. + """ + default: + code = .unknown + message = "Can't copy file from '\(sourcePath)' to '\(destinationPath)'." + } + + return FileSystemError( + code: code, + message: message, + systemCall: "copy_file_range", + errno: errno, + location: location + ) + } + #endif + @_spi(Testing) public static func futimens( errno: Errno, diff --git a/Sources/NIOFS/FileType.swift b/Sources/NIOFS/FileType.swift index ba0c63407d..10b45fab41 100644 --- a/Sources/NIOFS/FileType.swift +++ b/Sources/NIOFS/FileType.swift @@ -137,7 +137,7 @@ extension FileType { /// Initializes a file type from the `d_type` from `dirent`. @_spi(Testing) public init(direntType: UInt8) { - #if canImport(Darwin) || canImport(Musl) || os(Android) + #if canImport(Darwin) || canImport(Musl) || os(Android) || os(FreeBSD) let value = Int32(direntType) #elseif canImport(Glibc) let value = Int(direntType) @@ -158,7 +158,7 @@ extension FileType { self = .symlink case DT_SOCK: self = .socket - #if canImport(Darwin) + #if canImport(Darwin) || os(FreeBSD) case DT_WHT: self = .whiteout #endif diff --git a/Sources/NIOFS/Internal/System Calls/CInterop.swift b/Sources/NIOFS/Internal/System Calls/CInterop.swift index 2235af8a61..c0e6867ab0 100644 --- a/Sources/NIOFS/Internal/System Calls/CInterop.swift +++ b/Sources/NIOFS/Internal/System Calls/CInterop.swift @@ -17,6 +17,9 @@ import SystemPackage #if canImport(Darwin) import Darwin import CNIODarwin +#elseif os(FreeBSD) +import Glibc +import CNIOFreeBSD #elseif canImport(Glibc) @preconcurrency import Glibc import CNIOLinux @@ -73,6 +76,9 @@ extension CInterop { #if canImport(Darwin) typealias FTS = CNIODarwin.FTS typealias FTSEnt = CNIODarwin.FTSENT + #elseif os(FreeBSD) + typealias FTS = CNIOFreeBSD.FTS + typealias FTSEnt = CNIOFreeBSD.FTSENT #elseif canImport(Glibc) || canImport(Musl) || canImport(Android) typealias FTS = CNIOLinux.FTS typealias FTSEnt = CNIOLinux.FTSENT diff --git a/Sources/NIOFS/Internal/System Calls/FileDescriptor+Syscalls.swift b/Sources/NIOFS/Internal/System Calls/FileDescriptor+Syscalls.swift index db13935c72..473e31421e 100644 --- a/Sources/NIOFS/Internal/System Calls/FileDescriptor+Syscalls.swift +++ b/Sources/NIOFS/Internal/System Calls/FileDescriptor+Syscalls.swift @@ -17,6 +17,9 @@ import SystemPackage #if canImport(Darwin) import Darwin +#elseif os(FreeBSD) +import Glibc +import CNIOFreeBSD #elseif canImport(Glibc) @preconcurrency import Glibc import CNIOLinux @@ -111,7 +114,11 @@ extension FileDescriptor { _ buffer: UnsafeMutableBufferPointer? ) -> Result { valueOrErrno(retryOnInterrupt: false) { + #if os(FreeBSD) + system_extattr_list_fd(self.rawValue, EXTATTR_NAMESPACE_USER, buffer?.baseAddress, buffer?.count ?? 0) + #else system_flistxattr(self.rawValue, buffer?.baseAddress, buffer?.count ?? 0) + #endif } } @@ -132,7 +139,11 @@ extension FileDescriptor { ) -> Result { valueOrErrno(retryOnInterrupt: false) { name.withPlatformString { + #if os(FreeBSD) + system_extattr_get_fd(self.rawValue, EXTATTR_NAMESPACE_USER, $0, buffer?.baseAddress, buffer?.count ?? 0) + #else system_fgetxattr(self.rawValue, $0, buffer?.baseAddress, buffer?.count ?? 0) + #endif } } } @@ -151,7 +162,11 @@ extension FileDescriptor { ) -> Result { nothingOrErrno(retryOnInterrupt: false) { name.withPlatformString { namePointer in + #if os(FreeBSD) + system_extattr_set_fd(self.rawValue, EXTATTR_NAMESPACE_USER, namePointer, value?.baseAddress, value?.count ?? 0) + #else system_fsetxattr(self.rawValue, namePointer, value?.baseAddress, value?.count ?? 0) + #endif } } } @@ -166,7 +181,11 @@ extension FileDescriptor { public func removeExtendedAttribute(_ name: String) -> Result { nothingOrErrno(retryOnInterrupt: false) { name.withPlatformString { + #if os(FreeBSD) + system_extattr_delete_fd(self.rawValue, EXTATTR_NAMESPACE_USER, $0) + #else system_fremovexattr(self.rawValue, $0) + #endif } } } @@ -314,11 +333,14 @@ extension FileDescriptor { } #if canImport(Glibc) || canImport(Musl) || canImport(Bionic) + +#if !os(FreeBSD) extension FileDescriptor.OpenOptions { static var temporaryFile: Self { Self(rawValue: CNIOLinux_O_TMPFILE) } } +#endif extension FileDescriptor { static var currentWorkingDirectory: Self { diff --git a/Sources/NIOFS/Internal/System Calls/Syscall.swift b/Sources/NIOFS/Internal/System Calls/Syscall.swift index 7b672096bd..cfa6a46ff6 100644 --- a/Sources/NIOFS/Internal/System Calls/Syscall.swift +++ b/Sources/NIOFS/Internal/System Calls/Syscall.swift @@ -17,6 +17,9 @@ import SystemPackage #if canImport(Darwin) import Darwin import CNIODarwin +#elseif os(FreeBSD) +import Glibc +import CNIOFreeBSD #elseif canImport(Glibc) @preconcurrency import Glibc import CNIOLinux @@ -120,6 +123,14 @@ public enum Syscall: Sendable { nothingOrErrno(retryOnInterrupt: false) { old.withPlatformString { oldPath in new.withPlatformString { newPath in + #if os(FreeBSD) + system_renameat( + oldFD.rawValue, + oldPath, + newFD.rawValue, + newPath + ) + #else system_renameat2( oldFD.rawValue, oldPath, @@ -127,6 +138,7 @@ public enum Syscall: Sendable { newPath, flags.rawValue ) + #endif } } } @@ -140,6 +152,7 @@ public enum Syscall: Sendable { self.rawValue = rawValue } + #if !os(FreeBSD) public static var exclusive: Self { Self(rawValue: CNIOLinux_RENAME_NOREPLACE) } @@ -147,6 +160,7 @@ public enum Syscall: Sendable { public static var swap: Self { Self(rawValue: CNIOLinux_RENAME_EXCHANGE) } + #endif } #endif @@ -163,13 +177,24 @@ public enum Syscall: Sendable { @_spi(Testing) public static var emptyPath: Self { + #if os(FreeBSD) + Self(rawValue: AT_EMPTY_PATH) + #else Self(rawValue: CNIOLinux_AT_EMPTY_PATH) + #endif } @_spi(Testing) public static var followSymbolicLinks: Self { Self(rawValue: AT_SYMLINK_FOLLOW) } + + #if os(FreeBSD) + @_spi(Testing) + public static var resolveBeneath: Self { + Self(rawValue: AT_RESOLVE_BENEATH) + } + #endif } @_spi(Testing) @@ -255,7 +280,7 @@ public enum Syscall: Sendable { } } - #if canImport(Glibc) || canImport(Musl) || canImport(Bionic) + #if !os(FreeBSD) && (canImport(Glibc) || canImport(Musl) || canImport(Bionic)) @_spi(Testing) public static func sendfile( to output: FileDescriptor, @@ -269,6 +294,31 @@ public enum Syscall: Sendable { } #endif + #if os(FreeBSD) + @_spi(Testing) + public static func copy_file_range( + from sourceFD: FileDescriptor, + offset: inout Int?, + to destinationFD: FileDescriptor, + destOffset: inout Int?, + size: Int, + flags: UInt + ) -> Result { + valueOrErrno(retryOnInterrupt: false) { + switch (offset, destOffset) { + case (nil, nil): + return system_copy_file_range(sourceFD.rawValue, nil, destinationFD.rawValue, nil, size, UInt32(flags)) + case (.some, nil): + return system_copy_file_range(sourceFD.rawValue, &offset!, destinationFD.rawValue, nil, size, UInt32(flags)) + case (nil, .some): + return system_copy_file_range(sourceFD.rawValue, nil, destinationFD.rawValue, &destOffset!, size, UInt32(flags)) + case (.some, .some): + return system_copy_file_range(sourceFD.rawValue, &offset!, destinationFD.rawValue, &destOffset!, size, UInt32(flags)) + } + } + } + #endif + @_spi(Testing) public static func futimens( fileDescriptor fd: FileDescriptor, diff --git a/Sources/NIOFS/Internal/System Calls/Syscalls.swift b/Sources/NIOFS/Internal/System Calls/Syscalls.swift index a871876025..80ff296c3c 100644 --- a/Sources/NIOFS/Internal/System Calls/Syscalls.swift +++ b/Sources/NIOFS/Internal/System Calls/Syscalls.swift @@ -17,6 +17,9 @@ import SystemPackage #if canImport(Darwin) import Darwin import CNIODarwin +#elseif os(FreeBSD) +import Glibc +import CNIOFreeBSD #elseif canImport(Glibc) @preconcurrency import Glibc import CNIOLinux @@ -163,6 +166,64 @@ internal func system_readlink( return readlink(path, buffer, bufferSize) } +#if os(FreeBSD) +internal func system_extattr_list_fd( + _ fd: FileDescriptor.RawValue, + _ namespace: CInt, + _ namebuf: UnsafeMutablePointer?, + _ size: Int +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { + return mockInt(fd, namespace, namebuf, size) + } + #endif + return extattr_list_fd(fd, namespace, namebuf, size) +} + +internal func system_extattr_get_fd( + _ fd: FileDescriptor.RawValue, + _ namespace: CInt, + _ name: UnsafePointer, + _ value: UnsafeMutableRawPointer?, + _ size: Int +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { + return mockInt(fd, namespace, name, value, size) + } + #endif + return extattr_get_fd(fd, namespace, name, value, size) +} + +internal func system_extattr_set_fd( + _ fd: FileDescriptor.RawValue, + _ namespace: CInt, + _ name: UnsafePointer, + _ buf: UnsafeRawPointer?, + _ size: Int +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { + return mockInt(fd, namespace, name, buf, size) + } + #endif + return extattr_set_fd(fd, namespace, name, buf, size) +} + +internal func system_extattr_delete_fd( + _ fd: FileDescriptor.RawValue, + _ namespace: CInt, + _ name: UnsafePointer +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { + return mockInt(fd, namespace, name) + } + #endif + return Int(extattr_delete_fd(fd, namespace, name)) +} +#else /// flistxattr(2): List extended attribute names internal func system_flistxattr( _ fd: FileDescriptor.RawValue, @@ -244,6 +305,7 @@ internal func system_fremovexattr( return fremovexattr(fd, name) #endif } +#endif /// rename(2): Change the name of a file internal func system_rename( @@ -273,7 +335,23 @@ internal func system_renamex_np( } #endif -#if canImport(Glibc) || canImport(Musl) || canImport(Android) +#if os(FreeBSD) +internal func system_renameat( + _ oldFD: FileDescriptor.RawValue, + _ old: UnsafePointer, + _ newFD: FileDescriptor.RawValue, + _ new: UnsafePointer +) -> CInt { + #if ENABLE_MOCKING + if mockingEnabled { + return mock(oldFD, old, newFD, new) + } + #endif + return renameat(oldFD, old, newFD, new) +} +#endif + +#if !os(FreeBSD) && (canImport(Glibc) || canImport(Musl) || canImport(Android)) internal func system_renameat2( _ oldFD: FileDescriptor.RawValue, _ old: UnsafePointer, @@ -333,7 +411,8 @@ internal func system_unlink( return unlink(path) } -#if canImport(Glibc) || canImport(Musl) || canImport(Android) + +#if !os(FreeBSD) && (canImport(Glibc) || canImport(Musl) || canImport(Android)) /// sendfile(2): Transfer data between descriptors internal func system_sendfile( _ outFD: CInt, @@ -351,6 +430,25 @@ internal func system_sendfile( } #endif +#if os(FreeBSD) +internal func system_copy_file_range( + _ inFD: CInt, + _ inOff: UnsafeMutablePointer?, + _ outFD: CInt, + _ outOff: UnsafeMutablePointer?, + _ size: Int, + _ flags: UInt32 +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { + return mockInt(inFD, inOff, outFD, outOff, size, flags) + } + #endif + return copy_file_range(inFD, inOff, outFD, outOff, size, flags) + +} +#endif + internal func system_futimens( _ fd: CInt, _ times: UnsafePointer? diff --git a/Sources/NIOFS/Internal/SystemFileHandle.swift b/Sources/NIOFS/Internal/SystemFileHandle.swift index 34f5fb6660..e26b5e405b 100644 --- a/Sources/NIOFS/Internal/SystemFileHandle.swift +++ b/Sources/NIOFS/Internal/SystemFileHandle.swift @@ -543,21 +543,32 @@ extension SystemFileHandle.SendableView { named: name ).flatMapError { errno -> Result<[UInt8], FileSystemError> in switch errno { - #if canImport(Darwin) + #if canImport(Darwin) || os(FreeBSD) case .attributeNotFound: // Okay, return empty value. return .success([]) #endif + #if !os(FreeBSD) case .noData: // Okay, return empty value. return .success([]) + #endif default: + #if os(FreeBSD) + let error = FileSystemError.extattr_get_fd( + attribute: name, + errno: errno, + path: self.path, + location: .here() + ) + #else let error = FileSystemError.fgetxattr( attribute: name, errno: errno, path: self.path, location: .here() ) + #endif return .failure(error) } }.get() @@ -832,6 +843,15 @@ extension SystemFileHandle.SendableView { to: desiredPath, options: materialization.exclusive ? [.exclusive] : [] ) + #elseif os(FreeBSD) + renameFunction = "renameat" + renameResult = Syscall.rename( + from: createdPath, + relativeTo: .currentWorkingDirectory, + to: desiredPath, + relativeTo: .currentWorkingDirectory, + flags: [] + ) #elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic) // The created and desired paths are absolute, so the relative descriptors are // ignored. However, they must still be provided to 'rename' in order to pass @@ -1480,7 +1500,7 @@ extension SystemFileHandle { let materializationMode: Materialization.Mode let options: FileDescriptor.OpenOptions - #if canImport(Glibc) || canImport(Musl) || canImport(Bionic) + #if !os(FreeBSD) && (canImport(Glibc) || canImport(Musl) || canImport(Bionic)) if useTemporaryFileIfPossible { options = [.temporaryFile] materializationMode = .link diff --git a/Sources/NIOPosix/BSDSocketAPICommon.swift b/Sources/NIOPosix/BSDSocketAPICommon.swift index a77ac3f733..5948d05d6c 100644 --- a/Sources/NIOPosix/BSDSocketAPICommon.swift +++ b/Sources/NIOPosix/BSDSocketAPICommon.swift @@ -106,7 +106,7 @@ extension NIOBSDSocket.Option { /// IPv4 and IPv6. static let ip_recv_tos: NIOBSDSocket.Option = NIOBSDSocket.Option(rawValue: IP_RECVTOS) - +#if !os(FreeBSD) /// Request that we are passed destination address and the receiving interface index when /// receiving datagrams. /// @@ -115,8 +115,15 @@ extension NIOBSDSocket.Option { /// IPv4 and IPv6. static let ip_recv_pktinfo: NIOBSDSocket.Option = NIOBSDSocket.Option(rawValue: Posix.IP_RECVPKTINFO) +#else + /// Request that we are passed receiving interface index when receiving datagrams. + static let ip_recv_if: NIOBSDSocket.Option = + NIOBSDSocket.Option(rawValue: Posix.IP_RECVIF) + /// Request that we are passed destination address when receiving datagrams. + static let ip_orig_dstaddr: NIOBSDSocket.Option = + NIOBSDSocket.Option(rawValue: Posix.IP_ORIGDSTADDR) +#endif } - // IPv6 Options extension NIOBSDSocket.Option { /// Request that we are passed traffic class details when receiving diff --git a/Sources/NIOPosix/BSDSocketAPIPosix.swift b/Sources/NIOPosix/BSDSocketAPIPosix.swift index 76ae388c88..911a17c74b 100644 --- a/Sources/NIOPosix/BSDSocketAPIPosix.swift +++ b/Sources/NIOPosix/BSDSocketAPIPosix.swift @@ -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(FreeBSD) +import CNIOFreeBSD +private let CMSG_FIRSTHDR = CNIOFreeBSD_CMSG_FIRSTHDR +private let CMSG_NXTHDR = CNIOFreeBSD_CMSG_NXTHDR +private let CMSG_DATA = CNIOFreeBSD_CMSG_DATA +private let CMSG_DATA_MUTABLE = CNIOFreeBSD_CMSG_DATA_MUTABLE +private let CMSG_SPACE = CNIOFreeBSD_CMSG_SPACE +private let CMSG_LEN = CNIOFreeBSD_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..391638da61 100644 --- a/Sources/NIOPosix/BaseSocket.swift +++ b/Sources/NIOPosix/BaseSocket.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CNIOLinux +import CNIOFreeBSD import NIOConcurrencyHelpers import NIOCore diff --git a/Sources/NIOPosix/ControlMessage.swift b/Sources/NIOPosix/ControlMessage.swift index e882551b4d..3bef80956b 100644 --- a/Sources/NIOPosix/ControlMessage.swift +++ b/Sources/NIOPosix/ControlMessage.swift @@ -15,10 +15,12 @@ import NIOCore #if canImport(Darwin) import CNIODarwin -#elseif os(Linux) || os(FreeBSD) || os(Android) +#elseif os(Linux) || os(Android) import CNIOLinux #elseif os(Windows) import CNIOWindows +#elseif os(FreeBSD) +import CNIOFreeBSD #endif #if os(Windows) @@ -195,10 +197,23 @@ struct ControlMessageParser { var ecnValue: NIOExplicitCongestionNotificationState = .transportNotCapable // Default var packetInfo: NIOPacketInfo? = nil + #if os(FreeBSD) + var destinationAddress: SocketAddress? = nil + var interfaceIndex: Int? = nil + #endif + init(parsing controlMessagesReceived: UnsafeControlMessageCollection) { for controlMessage in controlMessagesReceived { self.receiveMessage(controlMessage) } + #if os(FreeBSD) + if let destinationAddress, let interfaceIndex { + self.packetInfo = NIOPacketInfo( + destinationAddress: destinationAddress, + interfaceIndex: interfaceIndex + ) + } + #endif } #if canImport(Darwin) @@ -226,6 +241,26 @@ struct ControlMessageParser { } private mutating func receiveIPv4Message(_ controlMessage: UnsafeControlMessage) { +#if os(FreeBSD) + if controlMessage.type == ControlMessageParser.ipv4TosType { + if let data = controlMessage.data { + assert(data.count == 1) + precondition(data.count >= 1) + let readValue = CInt(data[0]) + self.ecnValue = .init(receivedValue: readValue) + } + } else if controlMessage.type == Posix.IP_ORIGDSTADDR { + if let data = controlMessage.data { + let addr = data.load(as: sockaddr_in.self) + self.destinationAddress = SocketAddress(addr, host: "") + } + } else if controlMessage.type == Posix.IP_RECVIF { + if let data = controlMessage.data { + let addr = data.load(as: sockaddr_dl.self) + self.interfaceIndex = Int(addr.sdl_index) + } + } +#else if controlMessage.type == ControlMessageParser.ipv4TosType { if let data = controlMessage.data { assert(data.count == 1) @@ -247,6 +282,7 @@ struct ControlMessageParser { } } +#endif } private mutating func receiveIPv6Message(_ controlMessage: UnsafeControlMessage) { diff --git a/Sources/NIOPosix/DatagramVectorReadManager.swift b/Sources/NIOPosix/DatagramVectorReadManager.swift index cf9f4f0771..f2ca0837df 100644 --- a/Sources/NIOPosix/DatagramVectorReadManager.swift +++ b/Sources/NIOPosix/DatagramVectorReadManager.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// import CNIODarwin +import CNIOFreeBSD import CNIOLinux import NIOCore diff --git a/Sources/NIOPosix/NonBlockingFileIO.swift b/Sources/NIOPosix/NonBlockingFileIO.swift index 1dff63128a..b7bf44a5b8 100644 --- a/Sources/NIOPosix/NonBlockingFileIO.swift +++ b/Sources/NIOPosix/NonBlockingFileIO.swift @@ -14,6 +14,7 @@ import CNIOLinux import CNIOWindows +import CNIOFreeBSD 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(FreeBSD) + 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(FreeBSD) + let ino = UInt64(entry.pointee.d_fileno) +#else + let ino = UInt64(entry.pointee.d_ino) +#endif entries.append( - NIODirectoryEntry(ino: UInt64(entry.pointee.d_ino), type: entry.pointee.d_type, name: name) + NIODirectoryEntry(ino: 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..bdadee940a 100644 --- a/Sources/NIOPosix/PendingDatagramWritesManager.swift +++ b/Sources/NIOPosix/PendingDatagramWritesManager.swift @@ -16,6 +16,7 @@ import Atomics import CNIODarwin import CNIOLinux import CNIOWindows +import CNIOFreeBSD import NIOCore #if canImport(WinSDK) diff --git a/Sources/NIOPosix/PendingWritesManager.swift b/Sources/NIOPosix/PendingWritesManager.swift index c6adbc093e..607fce7038 100644 --- a/Sources/NIOPosix/PendingWritesManager.swift +++ b/Sources/NIOPosix/PendingWritesManager.swift @@ -14,6 +14,7 @@ import Atomics import CNIOLinux +import CNIOFreeBSD import NIOCore private struct PendingStreamWrite { diff --git a/Sources/NIOPosix/SelectorGeneric.swift b/Sources/NIOPosix/SelectorGeneric.swift index b538e3f4f5..2a27dacb45 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(FreeBSD) @usableFromInline typealias EventType = kevent #elseif os(Linux) || os(Android) diff --git a/Sources/NIOPosix/SelectorKqueue.swift b/Sources/NIOPosix/SelectorKqueue.swift index ad28ee343b..b8a860163a 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(FreeBSD) /// Represents the `kqueue` filters we might use: /// @@ -82,9 +82,17 @@ extension KQueueEventFilterSet { return UInt16(self.contains(event) ? EV_ADD : EV_DELETE) } - for (event, filter) in [ + #if os(FreeBSD) + let eventFilterPairs = [ + (KQueueEventFilterSet.read, EVFILT_READ), (.write, EVFILT_WRITE) + ] + #else + let eventFilterPairs = [ (KQueueEventFilterSet.read, EVFILT_READ), (.write, EVFILT_WRITE), (.except, EVFILT_EXCEPT), - ] { + ] + #endif + + for (event, filter) in eventFilterPairs { if let flags = calculateKQueueChange(event: event) { kevents.appendEvent( fileDescriptor: fileDescriptor, @@ -278,6 +286,9 @@ extension Selector: _SelectorBackendProtocol { } loopStart() + #if os(FreeBSD) + let EVFILT_EXCEPT = EVFILT_READ + #endif for i in 0.., @unchecked Sendable case _ as ChannelOptions.Types.ConnectTimeoutOption: return connectTimeout as! Option.Value case _ as ChannelOptions.Types.LocalVsockContextID: - #if os(Windows) + #if os(Windows) || os(FreeBSD) fallthrough #else return try self.socket.getLocalVsockContextID() as! Option.Value @@ -157,7 +158,7 @@ final class SocketChannel: BaseStreamSocketChannel, @unchecked Sendable self.scheduleConnectTimeout() return false } - +#if !os(FreeBSD) override func connectSocket(to address: VsockAddress) throws -> Bool { if try self.socket.connect(to: address) { return true @@ -165,7 +166,7 @@ final class SocketChannel: BaseStreamSocketChannel, @unchecked Sendable self.scheduleConnectTimeout() return false } - +#endif override func finishConnectSocket() throws { if let scheduled = self.connectTimeoutScheduled { // Connection established so cancel the previous scheduled timeout. @@ -280,7 +281,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(FreeBSD) fallthrough #else return try self.socket.getLocalVsockContextID() as! Option.Value @@ -319,7 +320,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(FreeBSD) case .vsockAddress: fatalError(vsockUnimplemented) #else @@ -453,8 +454,10 @@ final class ServerSocketChannel: BaseSocketChannel, @unchecked Sen override func triggerUserOutboundEvent0(_ event: Any, promise: EventLoopPromise?) { switch event { +#if !os(FreeBSD) case let event as VsockChannelEvents.BindToAddress: self.bind0(to: .vsockAddress(event.address), promise: promise) +#endif default: promise?.fail(ChannelError._operationUnsupported) } @@ -597,11 +600,24 @@ final class DatagramChannel: BaseSocketChannel, @unchecked Sendable { switch self.localAddress?.protocol { case .some(.inet): self.receivePacketInfo = true + #if os(FreeBSD) + try self.socket.setOption( + level: .ip, + name: .ip_recv_if, + value: valueAsInt + ) + try self.socket.setOption( + level: .ip, + name: .ip_orig_dstaddr, + value: valueAsInt + ) + #else try self.socket.setOption( level: .ip, name: .ip_recv_pktinfo, value: valueAsInt ) + #endif case .some(.inet6): self.receivePacketInfo = true try self.socket.setOption( @@ -665,11 +681,23 @@ final class DatagramChannel: BaseSocketChannel, @unchecked Sendable { case _ as ChannelOptions.Types.ReceivePacketInfo: switch self.localAddress?.protocol { case .some(.inet): + #if os(FreeBSD) + return try + ((self.socket.getOption( + level: .ip, + name: .ip_recv_if + ) != 0) && + (self.socket.getOption( + level: .ip, + name: .ip_orig_dstaddr + ) != 0)) as! Option.Value + #else return try (self.socket.getOption( level: .ip, name: .ip_recv_pktinfo ) != 0) as! Option.Value + #endif case .some(.inet6): return try (self.socket.getOption( @@ -723,11 +751,11 @@ final class DatagramChannel: BaseSocketChannel, @unchecked Sendable { preconditionFailure("Connect of datagram socket did not complete synchronously.") } } - +#if !os(FreeBSD) override func connectSocket(to address: VsockAddress) throws -> Bool { throw ChannelError._operationUnsupported } - +#endif override func finishConnectSocket() throws { // This is not required for connected datagram channels connect is a synchronous operation. throw ChannelError._operationUnsupported diff --git a/Sources/NIOPosix/SocketProtocols.swift b/Sources/NIOPosix/SocketProtocols.swift index 36b32a8e32..29320d2fa5 100644 --- a/Sources/NIOPosix/SocketProtocols.swift +++ b/Sources/NIOPosix/SocketProtocols.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// import NIOCore +import CNIOFreeBSD #if canImport(WinSDK) import struct WinSDK.socklen_t @@ -107,7 +108,13 @@ extension BaseSocketProtocol { #else assert(fd >= 0, "illegal file descriptor \(fd)") do { +#if os(FreeBSD) + var yes: CInt = 1 + let size = socklen_t(MemoryLayout.size) + setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &yes, size) +#else try Posix.fcntl(descriptor: fd, command: F_SETNOSIGPIPE, value: 1) +#endif } catch let error as IOError { try? Posix.close(descriptor: fd) // don't care about failure here if error.errnoCode == EINVAL { diff --git a/Sources/NIOPosix/System.swift b/Sources/NIOPosix/System.swift index 576f75c674..06d87d9949 100644 --- a/Sources/NIOPosix/System.swift +++ b/Sources/NIOPosix/System.swift @@ -21,7 +21,7 @@ import NIOCore @_exported import Darwin.C import CNIODarwin internal typealias MMsgHdr = CNIODarwin_mmsghdr -#elseif os(Linux) || os(FreeBSD) || os(Android) +#elseif os(Linux) || os(Android) #if canImport(Glibc) @_exported @preconcurrency import Glibc #elseif canImport(Musl) @@ -38,6 +38,11 @@ internal typealias in6_pktinfo = CNIOLinux_in6_pktinfo import CNIOWindows internal typealias MMsgHdr = CNIOWindows_mmsghdr +#elseif os(FreeBSD) +@_exported import Glibc +import CNIOFreeBSD + +internal typealias MMsgHdr = mmsghdr #else #error("The POSIX system module was unable to identify your C library.") #endif @@ -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(FreeBSD) private let sysFstat = fstat private let sysStat = stat private let sysLstat = lstat @@ -173,6 +178,10 @@ private let sysKevent = kevent private let sysMkpath = mkpath_np private let sysSendMmsg = CNIODarwin_sendmmsg private let sysRecvMmsg = CNIODarwin_recvmmsg +#elseif os(FreeBSD) +private let sysKevent = kevent +private let sysSendMmsg = CNIOFreeBSD_sendmmsg +private let sysRecvMmsg = CNIOFreeBSD_recvmmsg #endif #if !os(Windows) private let sysIoctl: @convention(c) (CInt, CUnsignedLong, UnsafeMutableRawPointer) -> CInt = ioctl @@ -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(Android) #if canImport(Glibc) @usableFromInline static let UIO_MAXIOV: Int = Int(Glibc.UIO_MAXIOV) @@ -479,6 +488,15 @@ internal enum Posix: Sendable { @usableFromInline static let SHUT_RDWR: CInt = CInt(Android.SHUT_RDWR) #endif + #elseif os(FreeBSD) + @usableFromInline + static let UIO_MAXIOV: Int = 1024 + @usableFromInline + static let SHUT_RD: CInt = CInt(Glibc.SHUT_RD) + @usableFromInline + static let SHUT_WR: CInt = CInt(Glibc.SHUT_WR) + @usableFromInline + static let SHUT_RDWR: CInt = CInt(Glibc.SHUT_RDWR) #else @usableFromInline static var UIO_MAXIOV: Int { @@ -504,7 +522,7 @@ internal enum Posix: Sendable { static let IPTOS_ECN_ECT0: CInt = CNIODarwin_IPTOS_ECN_ECT0 static let IPTOS_ECN_ECT1: CInt = CNIODarwin_IPTOS_ECN_ECT1 static let IPTOS_ECN_CE: CInt = CNIODarwin_IPTOS_ECN_CE - #elseif os(Linux) || os(FreeBSD) || os(Android) + #elseif os(Linux) || os(Android) #if os(Android) static let IPTOS_ECN_NOTECT: CInt = CInt(CNIOLinux.IPTOS_ECN_NOTECT) #else @@ -520,6 +538,12 @@ internal enum Posix: Sendable { static let IPTOS_ECN_ECT0: CInt = CInt(0x02) static let IPTOS_ECN_ECT1: CInt = CInt(0x01) static let IPTOS_ECN_CE: CInt = CInt(0x03) + #elseif os(FreeBSD) + static let IPTOS_ECN_NOTECT: CInt = CNIOFreeBSD_IPTOS_ECN_NOTECT + static let IPTOS_ECN_MASK: CInt = CNIOFreeBSD_IPTOS_ECN_MASK + static let IPTOS_ECN_ECT0: CInt = CNIOFreeBSD_IPTOS_ECN_ECT0 + static let IPTOS_ECN_ECT1: CInt = CNIOFreeBSD_IPTOS_ECN_ECT1 + static let IPTOS_ECN_CE: CInt = CNIOFreeBSD_IPTOS_ECN_CE #endif #if canImport(Darwin) @@ -528,7 +552,7 @@ internal enum Posix: Sendable { static let IPV6_RECVPKTINFO: CInt = CNIODarwin_IPV6_RECVPKTINFO static let IPV6_PKTINFO: CInt = CNIODarwin_IPV6_PKTINFO - #elseif os(Linux) || os(FreeBSD) || os(Android) + #elseif os(Linux) || os(Android) static let IP_RECVPKTINFO: CInt = CInt(CNIOLinux.IP_PKTINFO) static let IP_PKTINFO: CInt = CInt(CNIOLinux.IP_PKTINFO) @@ -540,6 +564,12 @@ internal enum Posix: Sendable { static let IPV6_RECVPKTINFO: CInt = CInt(WinSDK.IPV6_PKTINFO) static let IPV6_PKTINFO: CInt = CInt(WinSDK.IPV6_PKTINFO) + #elseif os(FreeBSD) + static let IPV6_RECVPKTINFO: CInt = Glibc.IPV6_RECVPKTINFO + static let IPV6_PKTINFO: CInt = Glibc.IPV6_PKTINFO + + static let IP_RECVIF: CInt = Glibc.IP_RECVIF + static let IP_ORIGDSTADDR: CInt = Glibc.IP_ORIGDSTADDR #endif #if !os(Windows) @@ -798,7 +828,12 @@ internal enum Posix: Sendable { let result: CInt = Darwin.sendfile(fd, descriptor, offset, &w, nil, 0) written = w return ssize_t(result) - #elseif os(Linux) || os(FreeBSD) || os(Android) + #elseif os(FreeBSD) + var w: off_t = off_t(0) + let result: CInt = Glibc.sendfile(fd, descriptor, offset, count, nil, &w, 0) + written = w + return ssize_t(result) + #elseif os(Linux) || os(Android) var off: off_t = offset #if canImport(Glibc) let result: ssize_t = Glibc.sendfile(descriptor, fd, &off, count) @@ -973,9 +1008,13 @@ internal enum Posix: Sendable { #elseif os(Linux) || os(FreeBSD) || os(Android) @inline(never) public static func opendir(pathname: String) throws -> OpaquePointer { +#if os(FreeBSD) + sysOpendir(pathname)! +#else try syscall { sysOpendir(pathname) } +#endif } @inline(never) @@ -1062,16 +1101,26 @@ extension Posix { } #endif -#if canImport(Darwin) +#if canImport(Darwin) || os(FreeBSD) @usableFromInline internal enum KQueue: Sendable { + #if os(FreeBSD) + public typealias Timespec = Glibc.timespec + #else + public typealias Timespec = Darwin.timespec + #endif + // TODO: Figure out how to specify a typealias to the kevent struct without run into trouble with the swift compiler @inline(never) public static func kqueue() throws -> CInt { try syscall(blocking: false) { + #if os(FreeBSD) + Glibc.kqueue() + #else Darwin.kqueue() + #endif }.result } @@ -1083,7 +1132,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/ThreadPosix.swift b/Sources/NIOPosix/ThreadPosix.swift index d60dcec6f3..8124b35f4c 100644 --- a/Sources/NIOPosix/ThreadPosix.swift +++ b/Sources/NIOPosix/ThreadPosix.swift @@ -34,7 +34,10 @@ private func sys_pthread_setname_np(_ p: pthread_t, _ pointer: UnsafePointer UnsafeMutableRawPointer? - +#elseif os(FreeBSD) +private let sys_pthread_getname_np = pthread_getname_np +private let sys_pthread_setname_np = pthread_setname_np +private typealias ThreadDestructor = @convention(c) (UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? #endif private func sysPthread_create( @@ -49,6 +52,8 @@ private func sysPthread_create( let thread = pthread_create(handle, &attr, destructor, args) pthread_attr_destroy(&attr) return thread + #elseif os(FreeBSD) + return pthread_create(handle, nil, destructor, args) #else #if canImport(Musl) var handleLinux: OpaquePointer? = nil diff --git a/Sources/_NIOFileSystem/Array+FileSystem.swift b/Sources/_NIOFileSystem/Array+FileSystem.swift index 59c10f1b9f..81fa8fb0f3 100644 --- a/Sources/_NIOFileSystem/Array+FileSystem.swift +++ b/Sources/_NIOFileSystem/Array+FileSystem.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#if canImport(Darwin) || os(Linux) || os(Android) +#if canImport(Darwin) || os(Linux) || os(Android) || os(FreeBSD) import NIOCore @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) diff --git a/Sources/_NIOFileSystem/ArraySlice+FileSystem.swift b/Sources/_NIOFileSystem/ArraySlice+FileSystem.swift index e319f6f777..095ef76d3a 100644 --- a/Sources/_NIOFileSystem/ArraySlice+FileSystem.swift +++ b/Sources/_NIOFileSystem/ArraySlice+FileSystem.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#if canImport(Darwin) || os(Linux) || os(Android) +#if canImport(Darwin) || os(Linux) || os(Android) || os(FreeBSD) import NIOCore @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) diff --git a/Sources/_NIOFileSystem/DirectoryEntries.swift b/Sources/_NIOFileSystem/DirectoryEntries.swift index f14e4bdea1..d06c67835f 100644 --- a/Sources/_NIOFileSystem/DirectoryEntries.swift +++ b/Sources/_NIOFileSystem/DirectoryEntries.swift @@ -14,6 +14,7 @@ import CNIODarwin import CNIOLinux +import CNIOFreeBSD import NIOConcurrencyHelpers import NIOCore import NIOPosix @@ -382,7 +383,13 @@ private struct DirectoryEnumerator: Sendable { case symbolicLink case symbolicLinkToNonExistentTarget - init?(rawValue: UInt16) { + #if os(FreeBSD) + typealias RawValue = Int32 + #else + typealias RawValue = UInt16 + #endif + + init?(rawValue: RawValue) { switch Int32(rawValue) { case FTS_D: self = .directoryPreOrder @@ -556,6 +563,8 @@ private struct DirectoryEnumerator: Sendable { // Empty is checked for above, root can't exist within a directory, and directory // items must be a single path component. name = FilePath.Component(platformString: CNIODarwin_dirent_dname(entry))! + #elseif os(FreeBSD) + name = FilePath.Component(platformString: CNIOFreeBSD_dirent_dname(entry))! #else name = FilePath.Component(platformString: CNIOLinux_dirent_dname(entry))! #endif diff --git a/Sources/_NIOFileSystem/FileInfo.swift b/Sources/_NIOFileSystem/FileInfo.swift index e5769a8e5e..7c58cae51f 100644 --- a/Sources/_NIOFileSystem/FileInfo.swift +++ b/Sources/_NIOFileSystem/FileInfo.swift @@ -18,7 +18,11 @@ import SystemPackage import Darwin #elseif canImport(Glibc) @preconcurrency import Glibc +#if os(FreeBSD) +import CNIOFreeBSD +#else import CNIOLinux +#endif #elseif canImport(Musl) @preconcurrency import Musl import CNIOLinux @@ -149,7 +153,7 @@ extension FileInfo { /// A time interval consisting of whole seconds and nanoseconds. public struct Timespec: Hashable, Sendable { - #if canImport(Darwin) + #if canImport(Darwin) || os(FreeBSD) private static let utimeOmit = Int(UTIME_OMIT) private static let utimeNow = Int(UTIME_NOW) #elseif canImport(Glibc) || canImport(Musl) || canImport(Android) diff --git a/Sources/_NIOFileSystem/FileSystem.swift b/Sources/_NIOFileSystem/FileSystem.swift index 8b7df2bb1b..2ce418ee8b 100644 --- a/Sources/_NIOFileSystem/FileSystem.swift +++ b/Sources/_NIOFileSystem/FileSystem.swift @@ -1231,8 +1231,7 @@ extension FileSystem { location: .here() ) } - - #elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic) + #elseif os(FreeBSD) || canImport(Glibc) || canImport(Musl) || canImport(Bionic) let openSourceResult = self._openFile( forReadingAt: sourcePath, @@ -1298,6 +1297,25 @@ extension FileSystem { while offset < sourceInfo.size { // sendfile(2) limits writes to 0x7ffff000 in size let size = min(Int(sourceInfo.size) - offset, 0x7fff_f000) + #if os(FreeBSD) + var _offset: Int? = offset + var _dOffset: Int? = nil + let result = Syscall.copy_file_range( + from: sourceFD, + offset: &_offset, + to: destinationFD, + destOffset: &_dOffset, + size: size, + flags: 0 + ).mapError { errno in + FileSystemError.copy_file_range( + errno: errno, + from: sourcePath, + to: destinationPath, + location: .here() + ) + } + #else let result = Syscall.sendfile( to: destinationFD, from: sourceFD, @@ -1311,7 +1329,7 @@ extension FileSystem { location: .here() ) } - + #endif switch result { case let .success(bytesCopied): offset += bytesCopied diff --git a/Sources/_NIOFileSystem/FileSystemError+Syscall.swift b/Sources/_NIOFileSystem/FileSystemError+Syscall.swift index 280041e5c6..2066344996 100644 --- a/Sources/_NIOFileSystem/FileSystemError+Syscall.swift +++ b/Sources/_NIOFileSystem/FileSystemError+Syscall.swift @@ -129,6 +129,34 @@ extension FileSystemError { ) } + @_spi(Testing) + public static func extattr_get_fd( + attribute name: String, + errno: Errno, + path: FilePath, + location: SourceLocation + ) -> Self { + let code: FileSystemError.Code + let message: String + + switch errno { + case .badFileDescriptor: + code = .closed + message = "Could not get value for extended attribute ('\(name)'), '\(path)' is closed." + default: + code = .unknown + message = "Could not get value for extended attribute ('\(name)') for '\(path)'" + } + + return FileSystemError( + code: code, + message: message, + systemCall: "extattr_get_fd", + errno: errno, + location: location + ) + } + @_spi(Testing) public static func flistxattr(errno: Errno, path: FilePath, location: SourceLocation) -> Self { let code: FileSystemError.Code @@ -1117,6 +1145,55 @@ extension FileSystemError { ) } + #if os(FreeBSD) + @_spi(Testing) + public static func copy_file_range( + errno: Errno, + from sourcePath: FilePath, + to destinationPath: FilePath, + location: SourceLocation + ) -> FileSystemError { + let code: FileSystemError.Code + let message: String + + switch errno { + case .invalidArgument: + code = .invalidArgument + message = """ + """ + case .fileTooLarge: + code = .resourceExhausted + message = """ + The copy exceeds process's file size limit or maximum file size for \ + the file system '\(destinationPath)' resides on. + """ + case .ioError: + code = .io + message = """ + An I/O error occurred while reading from '\(sourcePath)', can't copy to \ + '\(destinationPath)'. + """ + case .noMemory: + code = .io + message = """ + Insufficient memory to read from '\(sourcePath)', can't copy to \ + '\(destinationPath)'. + """ + default: + code = .unknown + message = "Can't copy file from '\(sourcePath)' to '\(destinationPath)'." + } + + return FileSystemError( + code: code, + message: message, + systemCall: "copy_file_range", + errno: errno, + location: location + ) + } + #endif + @_spi(Testing) public static func futimens( errno: Errno, diff --git a/Sources/_NIOFileSystem/FileType.swift b/Sources/_NIOFileSystem/FileType.swift index ba0c63407d..3ca72bdcf0 100644 --- a/Sources/_NIOFileSystem/FileType.swift +++ b/Sources/_NIOFileSystem/FileType.swift @@ -137,7 +137,7 @@ extension FileType { /// Initializes a file type from the `d_type` from `dirent`. @_spi(Testing) public init(direntType: UInt8) { - #if canImport(Darwin) || canImport(Musl) || os(Android) + #if canImport(Darwin) || canImport(Musl) || os(Android) || os(FreeBSD) let value = Int32(direntType) #elseif canImport(Glibc) let value = Int(direntType) diff --git a/Sources/_NIOFileSystem/Internal/System Calls/CInterop.swift b/Sources/_NIOFileSystem/Internal/System Calls/CInterop.swift index 2235af8a61..b76215cec8 100644 --- a/Sources/_NIOFileSystem/Internal/System Calls/CInterop.swift +++ b/Sources/_NIOFileSystem/Internal/System Calls/CInterop.swift @@ -19,7 +19,11 @@ import Darwin import CNIODarwin #elseif canImport(Glibc) @preconcurrency import Glibc +#if os(FreeBSD) +import CNIOFreeBSD +#else import CNIOLinux +#endif #elseif canImport(Musl) @preconcurrency import Musl import CNIOLinux @@ -73,6 +77,9 @@ extension CInterop { #if canImport(Darwin) typealias FTS = CNIODarwin.FTS typealias FTSEnt = CNIODarwin.FTSENT + #elseif os(FreeBSD) + typealias FTS = CNIOFreeBSD.FTS + typealias FTSEnt = CNIOFreeBSD.FTSENT #elseif canImport(Glibc) || canImport(Musl) || canImport(Android) typealias FTS = CNIOLinux.FTS typealias FTSEnt = CNIOLinux.FTSENT diff --git a/Sources/_NIOFileSystem/Internal/System Calls/FileDescriptor+Syscalls.swift b/Sources/_NIOFileSystem/Internal/System Calls/FileDescriptor+Syscalls.swift index 0d824fca34..c72a5c2491 100644 --- a/Sources/_NIOFileSystem/Internal/System Calls/FileDescriptor+Syscalls.swift +++ b/Sources/_NIOFileSystem/Internal/System Calls/FileDescriptor+Syscalls.swift @@ -19,7 +19,11 @@ import SystemPackage import Darwin #elseif canImport(Glibc) @preconcurrency import Glibc +#if os(FreeBSD) +import CNIOFreeBSD +#else import CNIOLinux +#endif #elseif canImport(Musl) @preconcurrency import Musl import CNIOLinux @@ -111,7 +115,11 @@ extension FileDescriptor { _ buffer: UnsafeMutableBufferPointer? ) -> Result { valueOrErrno(retryOnInterrupt: false) { + #if os(FreeBSD) + system_extattr_list_fd(self.rawValue, EXTATTR_NAMESPACE_USER, buffer?.baseAddress, buffer?.count ?? 0) + #else system_flistxattr(self.rawValue, buffer?.baseAddress, buffer?.count ?? 0) + #endif } } @@ -132,7 +140,11 @@ extension FileDescriptor { ) -> Result { valueOrErrno(retryOnInterrupt: false) { name.withPlatformString { + #if os(FreeBSD) + system_extattr_get_fd(self.rawValue, EXTATTR_NAMESPACE_USER, $0, buffer?.baseAddress, buffer?.count ?? 0) + #else system_fgetxattr(self.rawValue, $0, buffer?.baseAddress, buffer?.count ?? 0) + #endif } } } @@ -151,7 +163,11 @@ extension FileDescriptor { ) -> Result { nothingOrErrno(retryOnInterrupt: false) { name.withPlatformString { namePointer in + #if os(FreeBSD) + system_extattr_set_fd(self.rawValue, EXTATTR_NAMESPACE_USER, namePointer, value?.baseAddress, value?.count ?? 0) + #else system_fsetxattr(self.rawValue, namePointer, value?.baseAddress, value?.count ?? 0) + #endif } } } @@ -166,7 +182,11 @@ extension FileDescriptor { public func removeExtendedAttribute(_ name: String) -> Result { nothingOrErrno(retryOnInterrupt: false) { name.withPlatformString { + #if os(FreeBSD) + system_extattr_delete_fd(self.rawValue, EXTATTR_NAMESPACE_USER, $0) + #else system_fremovexattr(self.rawValue, $0) + #endif } } } @@ -313,13 +333,15 @@ extension FileDescriptor { } } -#if canImport(Glibc) || canImport(Musl) || canImport(Bionic) +#if !os(FreeBSD) && (canImport(Glibc) || canImport(Musl) || canImport(Bionic)) extension FileDescriptor.OpenOptions { static var temporaryFile: Self { Self(rawValue: CNIOLinux_O_TMPFILE) } } +#endif +#if canImport(Glibc) || canImport(Musl) || canImport(Bionic) extension FileDescriptor { static var currentWorkingDirectory: Self { Self(rawValue: AT_FDCWD) diff --git a/Sources/_NIOFileSystem/Internal/System Calls/Syscall.swift b/Sources/_NIOFileSystem/Internal/System Calls/Syscall.swift index 1520586458..e9bb60cbf8 100644 --- a/Sources/_NIOFileSystem/Internal/System Calls/Syscall.swift +++ b/Sources/_NIOFileSystem/Internal/System Calls/Syscall.swift @@ -17,6 +17,9 @@ import SystemPackage #if canImport(Darwin) import Darwin import CNIODarwin +#elseif os(FreeBSD) +import Glibc +import CNIOFreeBSD #elseif canImport(Glibc) @preconcurrency import Glibc import CNIOLinux @@ -120,6 +123,14 @@ public enum Syscall: Sendable { nothingOrErrno(retryOnInterrupt: false) { old.withPlatformString { oldPath in new.withPlatformString { newPath in + #if os(FreeBSD) + system_renameat( + oldFD.rawValue, + oldPath, + newFD.rawValue, + newPath + ) + #else system_renameat2( oldFD.rawValue, oldPath, @@ -127,6 +138,7 @@ public enum Syscall: Sendable { newPath, flags.rawValue ) + #endif } } } @@ -140,6 +152,7 @@ public enum Syscall: Sendable { self.rawValue = rawValue } + #if !os(FreeBSD) public static var exclusive: Self { Self(rawValue: CNIOLinux_RENAME_NOREPLACE) } @@ -147,6 +160,7 @@ public enum Syscall: Sendable { public static var swap: Self { Self(rawValue: CNIOLinux_RENAME_EXCHANGE) } + #endif } #endif @@ -163,13 +177,24 @@ public enum Syscall: Sendable { @_spi(Testing) public static var emptyPath: Self { + #if os(FreeBSD) + Self(rawValue: AT_EMPTY_PATH) + #else Self(rawValue: CNIOLinux_AT_EMPTY_PATH) + #endif } @_spi(Testing) public static var followSymbolicLinks: Self { Self(rawValue: AT_SYMLINK_FOLLOW) } + + #if os(FreeBSD) + @_spi(Testing) + public static var resolveBeneath: Self { + Self(rawValue: AT_RESOLVE_BENEATH) + } + #endif } @_spi(Testing) @@ -255,7 +280,7 @@ public enum Syscall: Sendable { } } - #if canImport(Glibc) || canImport(Musl) || canImport(Bionic) + #if !os(FreeBSD) && (canImport(Glibc) || canImport(Musl) || canImport(Bionic)) @_spi(Testing) public static func sendfile( to output: FileDescriptor, @@ -269,6 +294,31 @@ public enum Syscall: Sendable { } #endif + #if os(FreeBSD) + @_spi(Testing) + public static func copy_file_range( + from sourceFD: FileDescriptor, + offset: inout Int?, + to destinationFD: FileDescriptor, + destOffset: inout Int?, + size: Int, + flags: UInt + ) -> Result { + valueOrErrno(retryOnInterrupt: false) { + switch (offset, destOffset) { + case (nil, nil): + return system_copy_file_range(sourceFD.rawValue, nil, destinationFD.rawValue, nil, size, UInt32(flags)) + case (.some, nil): + return system_copy_file_range(sourceFD.rawValue, &offset!, destinationFD.rawValue, nil, size, UInt32(flags)) + case (nil, .some): + return system_copy_file_range(sourceFD.rawValue, nil, destinationFD.rawValue, &destOffset!, size, UInt32(flags)) + case (.some, .some): + return system_copy_file_range(sourceFD.rawValue, &offset!, destinationFD.rawValue, &destOffset!, size, UInt32(flags)) + } + } + } + #endif + @_spi(Testing) public static func futimens( fileDescriptor fd: FileDescriptor, diff --git a/Sources/_NIOFileSystem/Internal/System Calls/Syscalls.swift b/Sources/_NIOFileSystem/Internal/System Calls/Syscalls.swift index a871876025..80ff296c3c 100644 --- a/Sources/_NIOFileSystem/Internal/System Calls/Syscalls.swift +++ b/Sources/_NIOFileSystem/Internal/System Calls/Syscalls.swift @@ -17,6 +17,9 @@ import SystemPackage #if canImport(Darwin) import Darwin import CNIODarwin +#elseif os(FreeBSD) +import Glibc +import CNIOFreeBSD #elseif canImport(Glibc) @preconcurrency import Glibc import CNIOLinux @@ -163,6 +166,64 @@ internal func system_readlink( return readlink(path, buffer, bufferSize) } +#if os(FreeBSD) +internal func system_extattr_list_fd( + _ fd: FileDescriptor.RawValue, + _ namespace: CInt, + _ namebuf: UnsafeMutablePointer?, + _ size: Int +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { + return mockInt(fd, namespace, namebuf, size) + } + #endif + return extattr_list_fd(fd, namespace, namebuf, size) +} + +internal func system_extattr_get_fd( + _ fd: FileDescriptor.RawValue, + _ namespace: CInt, + _ name: UnsafePointer, + _ value: UnsafeMutableRawPointer?, + _ size: Int +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { + return mockInt(fd, namespace, name, value, size) + } + #endif + return extattr_get_fd(fd, namespace, name, value, size) +} + +internal func system_extattr_set_fd( + _ fd: FileDescriptor.RawValue, + _ namespace: CInt, + _ name: UnsafePointer, + _ buf: UnsafeRawPointer?, + _ size: Int +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { + return mockInt(fd, namespace, name, buf, size) + } + #endif + return extattr_set_fd(fd, namespace, name, buf, size) +} + +internal func system_extattr_delete_fd( + _ fd: FileDescriptor.RawValue, + _ namespace: CInt, + _ name: UnsafePointer +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { + return mockInt(fd, namespace, name) + } + #endif + return Int(extattr_delete_fd(fd, namespace, name)) +} +#else /// flistxattr(2): List extended attribute names internal func system_flistxattr( _ fd: FileDescriptor.RawValue, @@ -244,6 +305,7 @@ internal func system_fremovexattr( return fremovexattr(fd, name) #endif } +#endif /// rename(2): Change the name of a file internal func system_rename( @@ -273,7 +335,23 @@ internal func system_renamex_np( } #endif -#if canImport(Glibc) || canImport(Musl) || canImport(Android) +#if os(FreeBSD) +internal func system_renameat( + _ oldFD: FileDescriptor.RawValue, + _ old: UnsafePointer, + _ newFD: FileDescriptor.RawValue, + _ new: UnsafePointer +) -> CInt { + #if ENABLE_MOCKING + if mockingEnabled { + return mock(oldFD, old, newFD, new) + } + #endif + return renameat(oldFD, old, newFD, new) +} +#endif + +#if !os(FreeBSD) && (canImport(Glibc) || canImport(Musl) || canImport(Android)) internal func system_renameat2( _ oldFD: FileDescriptor.RawValue, _ old: UnsafePointer, @@ -333,7 +411,8 @@ internal func system_unlink( return unlink(path) } -#if canImport(Glibc) || canImport(Musl) || canImport(Android) + +#if !os(FreeBSD) && (canImport(Glibc) || canImport(Musl) || canImport(Android)) /// sendfile(2): Transfer data between descriptors internal func system_sendfile( _ outFD: CInt, @@ -351,6 +430,25 @@ internal func system_sendfile( } #endif +#if os(FreeBSD) +internal func system_copy_file_range( + _ inFD: CInt, + _ inOff: UnsafeMutablePointer?, + _ outFD: CInt, + _ outOff: UnsafeMutablePointer?, + _ size: Int, + _ flags: UInt32 +) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { + return mockInt(inFD, inOff, outFD, outOff, size, flags) + } + #endif + return copy_file_range(inFD, inOff, outFD, outOff, size, flags) + +} +#endif + internal func system_futimens( _ fd: CInt, _ times: UnsafePointer? diff --git a/Sources/_NIOFileSystem/Internal/SystemFileHandle.swift b/Sources/_NIOFileSystem/Internal/SystemFileHandle.swift index 1e765a3387..ce4aa637b4 100644 --- a/Sources/_NIOFileSystem/Internal/SystemFileHandle.swift +++ b/Sources/_NIOFileSystem/Internal/SystemFileHandle.swift @@ -543,21 +543,32 @@ extension SystemFileHandle.SendableView { named: name ).flatMapError { errno -> Result<[UInt8], FileSystemError> in switch errno { - #if canImport(Darwin) + #if canImport(Darwin) || os(FreeBSD) case .attributeNotFound: // Okay, return empty value. return .success([]) #endif + #if !os(FreeBSD) case .noData: // Okay, return empty value. return .success([]) + #endif default: + #if os(FreeBSD) + let error = FileSystemError.extattr_get_fd( + attribute: name, + errno: errno, + path: self.path, + location: .here() + ) + #else let error = FileSystemError.fgetxattr( attribute: name, errno: errno, path: self.path, location: .here() ) + #endif return .failure(error) } }.get() @@ -832,6 +843,15 @@ extension SystemFileHandle.SendableView { to: desiredPath, options: materialization.exclusive ? [.exclusive] : [] ) + #elseif os(FreeBSD) + renameFunction = "renameat" + renameResult = Syscall.rename( + from: createdPath, + relativeTo: .currentWorkingDirectory, + to: desiredPath, + relativeTo: .currentWorkingDirectory, + flags: [] + ) #elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic) // The created and desired paths are absolute, so the relative descriptors are // ignored. However, they must still be provided to 'rename' in order to pass @@ -1480,7 +1500,7 @@ extension SystemFileHandle { let materializationMode: Materialization.Mode let options: FileDescriptor.OpenOptions - #if canImport(Glibc) || canImport(Musl) || canImport(Bionic) + #if !os(FreeBSD) && (canImport(Glibc) || canImport(Musl) || canImport(Bionic)) if useTemporaryFileIfPossible { options = [.temporaryFile] materializationMode = .link diff --git a/Sources/_NIOFileSystemFoundationCompat/Data+FileSystem.swift b/Sources/_NIOFileSystemFoundationCompat/Data+FileSystem.swift index 069a450a69..a2338caf28 100644 --- a/Sources/_NIOFileSystemFoundationCompat/Data+FileSystem.swift +++ b/Sources/_NIOFileSystemFoundationCompat/Data+FileSystem.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#if canImport(Darwin) || os(Linux) || os(Android) +#if canImport(Darwin) || os(Linux) || os(Android) || os(FreeBSD) import _NIOFileSystem import NIOCore import NIOFoundationCompat diff --git a/Tests/NIOFSTests/FileHandleTests.swift b/Tests/NIOFSTests/FileHandleTests.swift index 06d9b32e80..4edc0e7a72 100644 --- a/Tests/NIOFSTests/FileHandleTests.swift +++ b/Tests/NIOFSTests/FileHandleTests.swift @@ -154,8 +154,13 @@ internal final class FileHandleTests: XCTestCase { } func testListAttributeNames() async throws { + #if os(FreeBSD) + let expectedSystemCall = "extattr_list_fd" + #else + let expectedSystemCall = "flistxattr" + #endif let testCase = MockHandleTest( - expectedSystemCall: "flistxattr", + expectedSystemCall: expectedSystemCall, knownErrnos: [ .notSupported: .unsupported, .notPermitted: .unsupported, @@ -170,15 +175,25 @@ internal final class FileHandleTests: XCTestCase { } func testValueForAttribute() async throws { - var nonThrowingErrnos: [Errno] = [.noData] + var nonThrowingErrnos: [Errno] = [] var knownErrnos: [Errno: FileSystemError.Code] = [.notSupported: .unsupported] - #if canImport(Darwin) + #if canImport(Darwin) || os(FreeBSD) nonThrowingErrnos.append(.attributeNotFound) knownErrnos[.fileNameTooLong] = .invalidArgument #endif + #if !os(FreeBSD) + nonThrowingErrnos.append(.noData) + #endif + + #if os(FreeBSD) + let expectedSystemCall = "extattr_get_fd" + #else + let expectedSystemCall = "fgetxattr" + #endif + let testCase = MockHandleTest( - expectedSystemCall: "fgetxattr", + expectedSystemCall: expectedSystemCall, nonThrowingErrnos: nonThrowingErrnos, knownErrnos: knownErrnos, unknownErrnos: [.deadlock, .ioError] @@ -210,8 +225,14 @@ internal final class FileHandleTests: XCTestCase { knownErrnos[.fileNameTooLong] = .invalidArgument #endif + #if os(FreeBSD) + let expectedSystemCall = "extattr_set_fd" + #else + let expectedSystemCall = "fsetxattr" + #endif + let testCase = MockHandleTest( - expectedSystemCall: "fsetxattr", + expectedSystemCall: expectedSystemCall, knownErrnos: knownErrnos, unknownErrnos: [.deadlock, .ioError] ) { handle in @@ -227,8 +248,14 @@ internal final class FileHandleTests: XCTestCase { knownErrnos[.fileNameTooLong] = .invalidArgument #endif + #if os(FreeBSD) + let expectedSystemCall = "extattr_delete_fd" + #else + let expectedSystemCall = "fremovexattr" + #endif + let testCase = MockHandleTest( - expectedSystemCall: "fremovexattr", + expectedSystemCall: expectedSystemCall, knownErrnos: knownErrnos, unknownErrnos: [.deadlock, .ioError] ) { handle in diff --git a/Tests/NIOFSTests/FileInfoTests.swift b/Tests/NIOFSTests/FileInfoTests.swift index 5aa7454734..f63203c6ee 100644 --- a/Tests/NIOFSTests/FileInfoTests.swift +++ b/Tests/NIOFSTests/FileInfoTests.swift @@ -14,10 +14,16 @@ import CNIOLinux import NIOFS +import CNIOFreeBSD import XCTest #if canImport(Darwin) import Darwin +#elseif os(FreeBSD) +import Glibc +import NIOCore +import NIOConcurrencyHelpers +import CDispatch #elseif canImport(Glibc) import Glibc #elseif canImport(Android) @@ -26,6 +32,8 @@ import Android #if canImport(Darwin) private let S_IFREG = Darwin.S_IFREG +#elseif os(FreeBSD) +private let S_IFREG = Glibc.S_IFREG #elseif canImport(Glibc) private let S_IFREG = Glibc.S_IFREG #elseif canImport(Musl) @@ -53,6 +61,11 @@ final class FileInfoTests: XCTestCase { status.st_birthtimespec = timespec(tv_sec: 3, tv_nsec: 0) status.st_flags = 11 status.st_gen = 12 + #elseif os(FreeBSD) + status.st_atim = timespec(tv_sec: 0, tv_nsec: 0) + status.st_mtim = timespec(tv_sec: 1, tv_nsec: 0) + status.st_ctim = timespec(tv_sec: 2, tv_nsec: 0) + status.st_birthtim = timespec(tv_sec: 3, tv_nsec: 0) #elseif canImport(Glibc) || canImport(Android) status.st_atim = timespec(tv_sec: 0, tv_nsec: 0) status.st_mtim = timespec(tv_sec: 1, tv_nsec: 0) @@ -111,6 +124,11 @@ final class FileInfoTests: XCTestCase { assertNotEqualAfterMutation { $0.platformSpecificStatus!.st_atim.tv_sec += 1 } assertNotEqualAfterMutation { $0.platformSpecificStatus!.st_mtim.tv_sec += 1 } assertNotEqualAfterMutation { $0.platformSpecificStatus!.st_ctim.tv_sec += 1 } + #if os(FreeBSD) + assertNotEqualAfterMutation { $0.platformSpecificStatus!.st_birthtim.tv_sec += 1 } + assertNotEqualAfterMutation { $0.platformSpecificStatus!.st_flags += 1 } + assertNotEqualAfterMutation { $0.platformSpecificStatus!.st_gen += 1 } + #endif #endif } @@ -145,7 +163,7 @@ final class FileInfoTests: XCTestCase { assertDifferentHashValueAfterMutation { $0.platformSpecificStatus!.st_blocks += 1 } assertDifferentHashValueAfterMutation { $0.platformSpecificStatus!.st_blksize += 1 } - #if canImport(Darwin) + #if canImport(Darwin) || os(FreeBSD) assertDifferentHashValueAfterMutation { $0.platformSpecificStatus!.st_atimespec.tv_sec += 1 } diff --git a/Tests/NIOFSTests/Internal/SyscallTests.swift b/Tests/NIOFSTests/Internal/SyscallTests.swift index 451575f7b7..d93bf9369b 100644 --- a/Tests/NIOFSTests/Internal/SyscallTests.swift +++ b/Tests/NIOFSTests/Internal/SyscallTests.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CNIOLinux +import CNIOFreeBSD @_spi(Testing) import NIOFS import SystemPackage import XCTest @@ -306,7 +307,7 @@ final class SyscallTests: XCTestCase { } func test_renameat2() throws { - #if canImport(Glibc) || canImport(Bionic) + #if !os(FreeBSD) && (canImport(Glibc) || canImport(Bionic)) let fd1 = FileDescriptor(rawValue: 13) let fd2 = FileDescriptor(rawValue: 42) @@ -355,7 +356,7 @@ final class SyscallTests: XCTestCase { } func test_sendfile() throws { - #if canImport(Glibc) || canImport(Bionic) + #if !os(FreeBSD) && (canImport(Glibc) || canImport(Bionic)) let input = FileDescriptor(rawValue: 42) let output = FileDescriptor(rawValue: 1) diff --git a/Tests/NIOPosixTests/ControlMessageTests.swift b/Tests/NIOPosixTests/ControlMessageTests.swift index 26b6634bb0..8e7e2866cc 100644 --- a/Tests/NIOPosixTests/ControlMessageTests.swift +++ b/Tests/NIOPosixTests/ControlMessageTests.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CNIOLinux +import CNIOFreeBSD import XCTest @testable import NIOPosix diff --git a/Tests/NIOPosixTests/EchoServerClientTest.swift b/Tests/NIOPosixTests/EchoServerClientTest.swift index 9f93a120b9..9be87f00ae 100644 --- a/Tests/NIOPosixTests/EchoServerClientTest.swift +++ b/Tests/NIOPosixTests/EchoServerClientTest.swift @@ -273,6 +273,9 @@ class EchoServerClientTest: XCTestCase { let connectAddress = VsockAddress(cid: .any, port: port) #elseif os(Linux) || os(Android) let connectAddress = VsockAddress(cid: .local, port: port) + #else + // this platform does not support vsock loopback + let connectAddress: VsockAddress! #endif let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group).connect(to: connectAddress).wait()) diff --git a/Tests/NIOPosixTests/NonBlockingFileIOTest.swift b/Tests/NIOPosixTests/NonBlockingFileIOTest.swift index 7e69975b4b..0c84e85a29 100644 --- a/Tests/NIOPosixTests/NonBlockingFileIOTest.swift +++ b/Tests/NIOPosixTests/NonBlockingFileIOTest.swift @@ -15,6 +15,7 @@ import Atomics import CNIOLinux import NIOConcurrencyHelpers +import CNIOFreeBSD import NIOCore import XCTest diff --git a/Tests/NIOPosixTests/PendingDatagramWritesManagerTests.swift b/Tests/NIOPosixTests/PendingDatagramWritesManagerTests.swift index 8771e21b27..3a1da71932 100644 --- a/Tests/NIOPosixTests/PendingDatagramWritesManagerTests.swift +++ b/Tests/NIOPosixTests/PendingDatagramWritesManagerTests.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CNIODarwin +import CNIOFreeBSD import CNIOLinux import NIOEmbedded import XCTest diff --git a/Tests/NIOPosixTests/SocketAddressTest.swift b/Tests/NIOPosixTests/SocketAddressTest.swift index e08ebbe743..9c43df33a0 100644 --- a/Tests/NIOPosixTests/SocketAddressTest.swift +++ b/Tests/NIOPosixTests/SocketAddressTest.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CNIOLinux +import CNIOFreeBSD import XCTest @testable import NIOCore diff --git a/Tests/NIOPosixTests/SocketOptionProviderTest.swift b/Tests/NIOPosixTests/SocketOptionProviderTest.swift index 32d2c704ec..a34c132e61 100644 --- a/Tests/NIOPosixTests/SocketOptionProviderTest.swift +++ b/Tests/NIOPosixTests/SocketOptionProviderTest.swift @@ -332,8 +332,12 @@ final class SocketOptionProviderTest: XCTestCase { let tcpInfo = try assertNoThrowWithValue(channel.getTCPInfo().wait()) // We just need to soundness check something here to ensure that the data is vaguely reasonable. + #if os(FreeBSD) + XCTAssertEqual(tcpInfo.tcpi_state, UInt8(TCPS_ESTABLISHED)) + #else XCTAssertEqual(tcpInfo.tcpi_state, UInt8(TCP_ESTABLISHED)) #endif + #endif } func testTCPConnectionInfo() throws { diff --git a/Tests/NIOPosixTests/SyscallAbstractionLayer.swift b/Tests/NIOPosixTests/SyscallAbstractionLayer.swift index 09b0a720e6..93d5160c3d 100644 --- a/Tests/NIOPosixTests/SyscallAbstractionLayer.swift +++ b/Tests/NIOPosixTests/SyscallAbstractionLayer.swift @@ -49,6 +49,7 @@ // the calling thread in an UnsafeTransfer. import CNIOLinux +import CNIOFreeBSD import NIOConcurrencyHelpers import NIOCore import XCTest diff --git a/Tests/NIOPosixTests/SystemTest.swift b/Tests/NIOPosixTests/SystemTest.swift index 390f26a094..31ff604146 100644 --- a/Tests/NIOPosixTests/SystemTest.swift +++ b/Tests/NIOPosixTests/SystemTest.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CNIOLinux +import CNIOFreeBSD import NIOCore import XCTest @@ -54,7 +55,7 @@ class SystemTest: XCTestCase { } } - #if canImport(Darwin) + #if canImport(Darwin) || os(FreeBSD) // Example twin data options captured on macOS private static let cmsghdrExample: [UInt8] = [ 0x10, 0x00, 0x00, 0x00, // Length 16 including header diff --git a/Tests/NIOPosixTests/VsockAddressTest.swift b/Tests/NIOPosixTests/VsockAddressTest.swift index 6c9f44c101..ea81564d44 100644 --- a/Tests/NIOPosixTests/VsockAddressTest.swift +++ b/Tests/NIOPosixTests/VsockAddressTest.swift @@ -16,6 +16,7 @@ import XCTest @testable import NIOCore @testable import NIOPosix +#if !os(FreeBSD) class VsockAddressTest: XCTestCase { func testDescriptionWorks() throws { @@ -81,3 +82,4 @@ class VsockAddressTest: XCTestCase { XCTAssertEqual(try channel.getOption(.localVsockContextID).wait(), localCID) } } +#endif