1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! Serial port configuration.
use crate::error::{VZError, VZResult};
use crate::ffi::{file_handle_for_fd, get_class};
use crate::{msg_send, msg_send_void};
use objc2::runtime::AnyObject;
use std::os::unix::io::RawFd;
/// Configuration for a serial port.
pub struct SerialPortConfiguration {
inner: *mut AnyObject,
/// File descriptors for the serial port (`read_fd`, `write_fd`).
fds: Option<(RawFd, RawFd)>,
}
// SAFETY: Inner ObjC pointer is only used via msg_send! which dispatches to the ObjC runtime.
unsafe impl Send for SerialPortConfiguration {}
impl SerialPortConfiguration {
/// Creates a `VirtIO` console serial port configuration using pipes.
///
/// This creates a serial port that appears as `hvc0` in the guest.
/// Returns the configuration and the file descriptors for reading/writing.
pub fn virtio_console() -> VZResult<Self> {
// Create pipes for bidirectional communication
let mut input_pipe: [libc::c_int; 2] = [0, 0];
let mut output_pipe: [libc::c_int; 2] = [0, 0];
// SAFETY: libc::pipe creates valid fd pairs. File handles are created from valid fds. ObjC objects follow alloc/init pattern with null checks.
unsafe {
if libc::pipe(input_pipe.as_mut_ptr()) != 0 {
return Err(VZError::OperationFailed(
"Failed to create input pipe".to_string(),
));
}
if libc::pipe(output_pipe.as_mut_ptr()) != 0 {
libc::close(input_pipe[0]);
libc::close(input_pipe[1]);
return Err(VZError::OperationFailed(
"Failed to create output pipe".to_string(),
));
}
// Create file handles
// For VZ: read from input_pipe[0], write to output_pipe[1]
let read_handle = file_handle_for_fd(input_pipe[0]);
let write_handle = file_handle_for_fd(output_pipe[1]);
if read_handle.is_null() || write_handle.is_null() {
libc::close(input_pipe[0]);
libc::close(input_pipe[1]);
libc::close(output_pipe[0]);
libc::close(output_pipe[1]);
return Err(VZError::OperationFailed(
"Failed to create file handles".to_string(),
));
}
// Create serial port attachment
let attachment = create_serial_port_attachment(read_handle, write_handle)?;
// Create VirtIO console serial port configuration
let cls =
get_class("VZVirtioConsoleDeviceSerialPortConfiguration").ok_or_else(|| {
VZError::Internal {
code: -1,
message: "VZVirtioConsoleDeviceSerialPortConfiguration class not found"
.into(),
}
})?;
let port = msg_send!(cls, new);
if port.is_null() {
libc::close(input_pipe[0]);
libc::close(input_pipe[1]);
libc::close(output_pipe[0]);
libc::close(output_pipe[1]);
return Err(VZError::Internal {
code: -1,
message: "Failed to create serial port configuration".into(),
});
}
msg_send_void!(port, setAttachment: attachment);
// Store FDs for user access:
// output_pipe[0] = read from VM
// input_pipe[1] = write to VM
Ok(Self {
inner: port,
fds: Some((output_pipe[0], input_pipe[1])),
})
}
}
/// Returns the file descriptor for reading output from the VM.
#[must_use]
pub fn read_fd(&self) -> Option<RawFd> {
self.fds.map(|(r, _)| r)
}
/// Returns the file descriptor for writing input to the VM.
#[must_use]
pub fn write_fd(&self) -> Option<RawFd> {
self.fds.map(|(_, w)| w)
}
/// Consumes the configuration and returns the raw pointer.
///
/// Note: The file descriptors are NOT closed when this is called.
/// The caller is responsible for managing them.
#[must_use]
pub fn into_ptr(self) -> *mut AnyObject {
let ptr = self.inner;
std::mem::forget(self);
ptr
}
}
impl Drop for SerialPortConfiguration {
fn drop(&mut self) {
if !self.inner.is_null() {
crate::ffi::release(self.inner);
}
// Note: We don't close fds here as they may still be in use
}
}
fn create_serial_port_attachment(
read_handle: *mut AnyObject,
write_handle: *mut AnyObject,
) -> VZResult<*mut AnyObject> {
// SAFETY: ObjC alloc/init on valid VZFileHandleSerialPortAttachment class with valid NSFileHandle objects.
unsafe {
let cls =
get_class("VZFileHandleSerialPortAttachment").ok_or_else(|| VZError::Internal {
code: -1,
message: "VZFileHandleSerialPortAttachment class not found".into(),
})?;
let obj = msg_send!(cls, alloc);
let attachment = msg_send!(obj, initWithFileHandleForReading: read_handle, fileHandleForWriting: write_handle);
if attachment.is_null() {
return Err(VZError::Internal {
code: -1,
message: "Failed to create serial port attachment".into(),
});
}
Ok(attachment)
}
}