//===----------------------------------------------------------------------===// // Copyright © 2025-2026 Apple Inc. and the Containerization project authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //===----------------------------------------------------------------------===// #if os(Linux) #if canImport(Musl) import Musl #elseif canImport(Glibc) import Glibc #endif import CShim import Foundation import Synchronization // On glibc, epoll constants are EPOLL_EVENTS enum values. On musl they're // plain UInt32. These helpers normalize them to UInt32/Int32. private func epollMask(_ value: UInt32) -> UInt32 { value } private func epollMask(_ value: Int32) -> UInt32 { UInt32(bitPattern: value) } #if canImport(Glibc) private func epollMask(_ value: EPOLL_EVENTS) -> UInt32 { value.rawValue } private func epollFlag(_ value: EPOLL_EVENTS) -> Int32 { Int32(bitPattern: value.rawValue) } #endif /// Register file descriptors to receive events via Linux's /// epoll syscall surface. public final class Epoll: Sendable { public typealias Mask = Int32 public typealias Handler = (@Sendable (Mask) -> Void) public static let maskIn: Int32 = Int32(bitPattern: epollMask(EPOLLIN)) public static let maskOut: Int32 = Int32(bitPattern: epollMask(EPOLLOUT)) public static let defaultMask: Int32 = maskIn | maskOut private let epollFD: Int32 private let handlers = SafeMap() private let pipe = Pipe() // to wake up a waiting epoll_wait public init() throws { let efd = epoll_create1(Int32(EPOLL_CLOEXEC)) guard efd > 0 else { throw POSIXError.fromErrno() } self.epollFD = efd try self.add(pipe.fileHandleForReading.fileDescriptor) { _ in } } public func add( _ fd: Int32, mask: Int32 = Epoll.defaultMask, handler: @escaping Handler ) throws { guard fcntl(fd, F_SETFL, O_NONBLOCK) == 0 else { throw POSIXError.fromErrno() } let events = epollMask(EPOLLET) | UInt32(bitPattern: mask) var event = epoll_event() event.events = events event.data.fd = fd try withUnsafeMutablePointer(to: &event) { ptr in while true { if epoll_ctl(self.epollFD, EPOLL_CTL_ADD, fd, ptr) == -1 { if errno == EAGAIN || errno == EINTR { continue } throw POSIXError.fromErrno() } break } } self.handlers.set(fd, handler) } /// Run the main epoll loop. /// /// max events to return in a single wait /// timeout in ms. /// -1 means block forever. /// 0 means return immediately if no events. public func run(maxEvents: Int = 128, timeout: Int32 = -1) throws { var events: [epoll_event] = .init( repeating: epoll_event(), count: maxEvents ) while true { let n = epoll_wait(self.epollFD, &events, Int32(events.count), timeout) guard n >= 0 else { if errno == EINTR || errno == EAGAIN { continue // go back to epoll_wait } throw POSIXError.fromErrno() } if n == 0 { return // if epoll wait times out, then n will be 0 } for i in 0.. Bool { errno == ENOENT || errno == EBADF || errno == EPERM } /// Shutdown the epoll handler. public func shutdown() throws { // wakes up epoll_wait and triggers a shutdown try self.pipe.fileHandleForWriting.close() } private final class SafeMap: Sendable { let dict = Mutex<[Key: Value]>([:]) func set(_ key: Key, _ value: Value) { dict.withLock { @Sendable in $0[key] = value } } func get(_ key: Key) -> Value? { dict.withLock { @Sendable in $0[key] } } func del(_ key: Key) { dict.withLock { @Sendable in _ = $0.removeValue(forKey: key) } } } } extension Epoll.Mask { public var isHangup: Bool { (self & Int32(bitPattern: epollMask(EPOLLHUP) | epollMask(EPOLLERR))) != 0 } public var isRhangup: Bool { (self & Int32(bitPattern: epollMask(EPOLLRDHUP))) != 0 } public var readyToRead: Bool { (self & Int32(bitPattern: epollMask(EPOLLIN))) != 0 } public var readyToWrite: Bool { (self & Int32(bitPattern: epollMask(EPOLLOUT))) != 0 } } #endif // os(Linux)