Use an owning Softnet control file handle

This commit is contained in:
Fedor Korotkov 2026-07-21 17:12:27 -04:00
parent 311eec95f4
commit 4a018ce4bb
No known key found for this signature in database
2 changed files with 22 additions and 24 deletions

View File

@ -13,21 +13,20 @@ class Softnet: Network {
private let process = Process()
private var monitorTask: Task<Void, Error>? = nil
private let monitorTaskFinished = ManagedAtomic<Bool>(false)
private var controlFD: Int32?
let vmFD: Int32
init(vmMACAddress: String, extraArguments: [String] = [], controlFD: Int32? = nil) throws {
if let controlFD = controlFD {
do {
try Self.validateControlFD(controlFD)
} catch {
close(controlFD)
throw error
}
}
var controlFileHandle: FileHandle?
self.controlFD = controlFD
if let controlFD = controlFD {
guard controlFD > STDERR_FILENO else {
throw SoftnetError.InitializationFailed(why: "Softnet control file descriptor must be greater than 2")
}
controlFileHandle = FileHandle(fileDescriptor: controlFD, closeOnDealloc: true)
try Self.validateControlFD(controlFD)
}
let fds = UnsafeMutablePointer<Int32>.allocate(capacity: MemoryLayout<Int>.stride * 2)
@ -46,16 +45,12 @@ class Softnet: Network {
process.arguments = ["--vm-fd", String(STDIN_FILENO), "--vm-mac-address", vmMACAddress] + extraArguments
process.standardInput = FileHandle(fileDescriptor: softnetFD, closeOnDealloc: false)
if let controlFD = controlFD {
if let controlFileHandle = controlFileHandle {
process.arguments! += ["--control-fd", String(STDOUT_FILENO)]
process.standardOutput = FileHandle(fileDescriptor: controlFD, closeOnDealloc: false)
process.standardOutput = controlFileHandle
}
}
deinit {
closeControlFD()
}
static func validateControlFD(_ fd: Int32) throws {
guard fd > STDERR_FILENO else {
throw SoftnetError.InitializationFailed(why: "Softnet control file descriptor must be greater than 2")
@ -100,7 +95,7 @@ class Softnet: Network {
}
func run(_ sema: AsyncSemaphore) throws {
defer { closeControlFD() }
defer { try? (process.standardOutput as? FileHandle)?.close() }
try process.run()
@ -116,13 +111,6 @@ class Softnet: Network {
}
}
private func closeControlFD() {
if let controlFD = controlFD {
close(controlFD)
self.controlFD = nil
}
}
func stop() async throws {
if monitorTaskFinished.load(ordering: .sequentiallyConsistent) {
// Consume the monitor task's value to ensure the task has finished

View File

@ -51,6 +51,16 @@ final class SoftnetControlFDTests: XCTestCase {
XCTAssertThrowsError(try Softnet.validateControlFD(STDERR_FILENO))
}
func testStandardDescriptorsRemainOpenWhenInitializationFails() throws {
for fd in [STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO] {
let flags = fcntl(fd, F_GETFD)
XCTAssertNotEqual(flags, -1)
XCTAssertThrowsError(try Softnet(vmMACAddress: "02:00:00:00:00:01", controlFD: fd))
XCTAssertEqual(fcntl(fd, F_GETFD), flags)
}
}
func testControlChannelIsPassedToSoftnetAndVMFDRemainsDatagram() async throws {
let temporaryDirectory = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: false)