Files
umn/Sources/UltraMeshCore/UnixIPC.swift
T
2026-08-31 15:55:09 -05:00

115 lines
4.7 KiB
Swift

import Foundation
import Darwin
public enum UnixIPC {
public static let defaultSocketPath = ProcessInfo.processInfo.environment["UMN_SOCKET"] ??
FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent("Library/Application Support/UltraMesh/umnd.sock").path
public static func connect(path: String = defaultSocketPath) throws -> Int32 {
let fd = socket(AF_UNIX, SOCK_STREAM, 0)
guard fd >= 0 else { throw POSIXError(.init(rawValue: errno) ?? .EIO) }
do {
var address = try unixAddress(path: path)
let result = withUnsafePointer(to: &address) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
Darwin.connect(fd, $0, unixAddressLength(path: path))
}
}
guard result == 0 else { throw POSIXError(.init(rawValue: errno) ?? .EIO) }
return fd
} catch {
Darwin.close(fd); throw error
}
}
public static func listen(path: String = defaultSocketPath) throws -> Int32 {
try FileManager.default.createDirectory(at: URL(fileURLWithPath: path).deletingLastPathComponent(),
withIntermediateDirectories: true)
unlink(path)
let fd = socket(AF_UNIX, SOCK_STREAM, 0)
guard fd >= 0 else { throw POSIXError(.init(rawValue: errno) ?? .EIO) }
var address = try unixAddress(path: path)
let bound = withUnsafePointer(to: &address) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
Darwin.bind(fd, $0, unixAddressLength(path: path))
}
}
guard bound == 0, Darwin.listen(fd, 64) == 0 else {
let code = errno; Darwin.close(fd); throw POSIXError(.init(rawValue: code) ?? .EIO)
}
chmod(path, 0o600)
return fd
}
public static func accept(_ server: Int32) throws -> Int32 {
let fd = Darwin.accept(server, nil, nil)
guard fd >= 0 else { throw POSIXError(.init(rawValue: errno) ?? .EIO) }
return fd
}
public static func send(_ message: IPCMessage, to fd: Int32) throws {
try writeAll(FrameCodec.encode(message), to: fd)
}
public static func receive(from fd: Int32) throws -> IPCMessage? {
guard let prefix = try readExact(4, from: fd) else { return nil }
let count = try FrameCodec.bodyLength(from: prefix)
guard let body = try readExact(count, from: fd) else { throw UMNError.invalidFrame }
return try FrameCodec.decode(IPCMessage.self, body: body)
}
public static func close(_ fd: Int32) { Darwin.shutdown(fd, SHUT_RDWR); Darwin.close(fd) }
private static func readExact(_ count: Int, from fd: Int32) throws -> Data? {
var output = Data(); output.reserveCapacity(count)
var buffer = [UInt8](repeating: 0, count: min(16_384, count))
while output.count < count {
let wanted = min(buffer.count, count - output.count)
let result = Darwin.read(fd, &buffer, wanted)
if result == 0 { return output.isEmpty ? nil : output }
if result < 0 {
if errno == EINTR { continue }
throw POSIXError(.init(rawValue: errno) ?? .EIO)
}
output.append(buffer, count: result)
}
return output
}
private static func writeAll(_ data: Data, to fd: Int32) throws {
try data.withUnsafeBytes { raw in
guard let base = raw.baseAddress else { return }
var offset = 0
while offset < data.count {
let result = Darwin.write(fd, base.advanced(by: offset), data.count - offset)
if result < 0 {
if errno == EINTR { continue }
throw POSIXError(.init(rawValue: errno) ?? .EIO)
}
offset += result
}
}
}
private static func unixAddress(path: String) throws -> sockaddr_un {
guard path.utf8.count < MemoryLayout.size(ofValue: sockaddr_un().sun_path) else {
throw UMNError.message("IPC socket path is too long")
}
var address = sockaddr_un(); address.sun_family = sa_family_t(AF_UNIX)
let capacity = MemoryLayout.size(ofValue: address.sun_path)
withUnsafeMutablePointer(to: &address.sun_path) { pointer in
pointer.withMemoryRebound(to: CChar.self, capacity: capacity) { destination in
path.withCString { source in
_ = strncpy(destination, source, capacity - 1)
}
}
}
return address
}
private static func unixAddressLength(path: String) -> socklen_t {
socklen_t(MemoryLayout<sa_family_t>.size + path.utf8.count + 1)
}
}