1mod fdio_sys;
6mod zxio_sys;
7
8use std::marker::PhantomData;
9use std::os::fd::RawFd;
10
11pub trait FdOps: Sized + Send + 'static {
13 fn writev(&self, _iovecs: &[zx::sys::zx_iovec_t]) -> Result<usize, zx::Status> {
14 Err(zx::Status::WRONG_TYPE)
15 }
16
17 fn isatty(&self) -> Result<bool, zx::Status> {
18 Ok(false)
19 }
20
21 }
23
24const DEFAULT_ZXIO_OPS: zxio_sys::zxio_ops = zxio_sys::zxio_ops {
26 destroy: Some(zxio_sys::zxio_default_destroy),
27 close: Some(zxio_sys::zxio_default_close),
28 release: Some(zxio_sys::zxio_default_release),
29 borrow: Some(zxio_sys::zxio_default_borrow),
30 clone: Some(zxio_sys::zxio_default_clone),
31 wait_begin: Some(zxio_sys::zxio_default_wait_begin),
32 wait_end: Some(zxio_sys::zxio_default_wait_end),
33 sync: Some(zxio_sys::zxio_default_sync),
34 attr_get: Some(zxio_sys::zxio_default_attr_get),
35 attr_set: Some(zxio_sys::zxio_default_attr_set),
36 readv: Some(zxio_sys::zxio_default_readv),
37 readv_at: Some(zxio_sys::zxio_default_readv_at),
38 writev: Some(zxio_sys::zxio_default_writev),
39 writev_at: Some(zxio_sys::zxio_default_writev_at),
40 seek: Some(zxio_sys::zxio_default_seek),
41 truncate: Some(zxio_sys::zxio_default_truncate),
42 flags_get_deprecated: Some(zxio_sys::zxio_default_flags_get_deprecated),
43 flags_set_deprecated: Some(zxio_sys::zxio_default_flags_set_deprecated),
44 flags_get: Some(zxio_sys::zxio_default_flags_get),
45 flags_set: Some(zxio_sys::zxio_default_flags_set),
46 vmo_get: Some(zxio_sys::zxio_default_vmo_get),
47 on_mapped: Some(zxio_sys::zxio_default_on_mapped),
48 get_read_buffer_available: Some(zxio_sys::zxio_default_get_read_buffer_available),
49 shutdown: Some(zxio_sys::zxio_default_shutdown),
50 unlink: Some(zxio_sys::zxio_default_unlink),
51 token_get: Some(zxio_sys::zxio_default_token_get),
52 rename: Some(zxio_sys::zxio_default_rename),
53 link: Some(zxio_sys::zxio_default_link),
54 link_into: Some(zxio_sys::zxio_default_link_into),
55 dirent_iterator_init: Some(zxio_sys::zxio_default_dirent_iterator_init),
56 dirent_iterator_next: Some(zxio_sys::zxio_default_dirent_iterator_next),
57 dirent_iterator_rewind: Some(zxio_sys::zxio_default_dirent_iterator_rewind),
58 dirent_iterator_destroy: Some(zxio_sys::zxio_default_dirent_iterator_destroy),
59 isatty: Some(zxio_sys::zxio_default_isatty),
60 get_window_size: Some(zxio_sys::zxio_default_get_window_size),
61 set_window_size: Some(zxio_sys::zxio_default_set_window_size),
62 advisory_lock: Some(zxio_sys::zxio_default_advisory_lock),
63 watch_directory: Some(zxio_sys::zxio_default_watch_directory),
64 bind: Some(zxio_sys::zxio_default_bind),
65 connect: Some(zxio_sys::zxio_default_connect),
66 listen: Some(zxio_sys::zxio_default_listen),
67 accept: Some(zxio_sys::zxio_default_accept),
68 getsockname: Some(zxio_sys::zxio_default_getsockname),
69 getpeername: Some(zxio_sys::zxio_default_getpeername),
70 getsockopt: Some(zxio_sys::zxio_default_getsockopt),
71 setsockopt: Some(zxio_sys::zxio_default_setsockopt),
72 recvmsg: Some(zxio_sys::zxio_default_recvmsg),
73 sendmsg: Some(zxio_sys::zxio_default_sendmsg),
74 ioctl: Some(zxio_sys::zxio_default_ioctl),
75 read_link: Some(zxio_sys::zxio_default_read_link),
76 create_symlink: Some(zxio_sys::zxio_default_create_symlink),
77 xattr_list: Some(zxio_sys::zxio_default_xattr_list),
78 xattr_get: Some(zxio_sys::zxio_default_xattr_get),
79 xattr_set: Some(zxio_sys::zxio_default_xattr_set),
80 xattr_remove: Some(zxio_sys::zxio_default_xattr_remove),
81 open: Some(zxio_sys::zxio_default_open),
82 allocate: Some(zxio_sys::zxio_default_allocate),
83 enable_verity: Some(zxio_sys::zxio_default_enable_verity),
84};
85
86pub fn bind_to_fd_with_ops<T: FdOps>(ops: T, fd: RawFd) -> Result<(), zx::Status> {
90 struct AssertCompatible<T>(PhantomData<T>);
91
92 impl<T> AssertCompatible<T> {
93 const SIZE_OK: () = assert!(
94 std::mem::size_of::<T>() <= std::mem::size_of::<zxio_sys::zxio_private>(),
95 "bad size"
96 );
97 const ALIGNMENT_OK: () = assert!(
98 std::mem::align_of::<T>() <= std::mem::align_of::<zxio_sys::zxio_private>(),
99 "bad alignment"
100 );
101 }
102
103 let () = AssertCompatible::<T>::SIZE_OK;
104 let () = AssertCompatible::<T>::ALIGNMENT_OK;
105
106 if fd < 0 {
107 return Err(zx::Status::INVALID_ARGS);
111 }
112
113 let mut storage = std::ptr::null_mut();
114 let fdio = unsafe { fdio_sys::fdio_zxio_create(&mut storage) };
115
116 if fdio.is_null() {
117 return Err(zx::Status::INTERNAL);
118 }
119
120 unsafe {
123 let reserved: *mut _ = &mut (*storage.cast::<zxio_sys::zxio_storage>()).reserved;
124 reserved.cast::<T>().write(ops);
125 }
126
127 struct Adapter<T>(PhantomData<T>);
128 impl<T: FdOps> Adapter<T> {
129 unsafe fn to_data(zxio: *mut zxio_sys::zxio_t) -> *mut T {
130 let storage = zxio.cast::<zxio_sys::zxio_storage>();
131 let reserved: *mut _ = unsafe { &mut (*storage).reserved };
132 reserved.cast::<T>()
133 }
134
135 unsafe extern "C" fn destroy(io: *mut zxio_sys::zxio_t) {
136 unsafe { std::ptr::drop_in_place(Self::to_data(io)) };
137 }
138
139 unsafe extern "C" fn writev(
140 io: *mut zxio_sys::zxio_t,
141 vector: *const zxio_sys::zx_iovec_t,
142 vector_count: usize,
143 _flags: zxio_sys::zxio_flags_t,
144 out_actual: *mut usize,
145 ) -> zxio_sys::zx_status_t {
146 let data = unsafe { &*Self::to_data(io) };
147 match data.writev(unsafe {
148 std::slice::from_raw_parts(vector.cast::<zx::sys::zx_iovec_t>(), vector_count)
149 }) {
150 Ok(count) => {
151 unsafe { *out_actual = count };
152 zx::sys::ZX_OK
153 }
154 Err(status) => status.into_raw(),
155 }
156 }
157
158 unsafe extern "C" fn isatty(
159 io: *mut zxio_sys::zxio_t,
160 tty: *mut bool,
161 ) -> zxio_sys::zx_status_t {
162 let data = unsafe { &*Self::to_data(io) };
163 match data.isatty() {
164 Ok(result) => {
165 unsafe { *tty = result };
166 zx::sys::ZX_OK
167 }
168 Err(status) => status.into_raw(),
169 }
170 }
171
172 const OPS: zxio_sys::zxio_ops = zxio_sys::zxio_ops {
173 destroy: Some(Self::destroy),
174 writev: Some(Self::writev),
175 isatty: Some(Self::isatty),
176 ..DEFAULT_ZXIO_OPS
177 };
178 }
179
180 unsafe {
181 zxio_sys::zxio_init(storage.cast::<zxio_sys::zxio_tag>(), &Adapter::<T>::OPS);
182 }
183
184 let bound_fd = unsafe { fdio_sys::fdio_bind_to_fd(fdio, fd, 0) };
186 if bound_fd < 0 {
187 return Err(zx::Status::BAD_STATE);
188 }
189 assert_eq!(bound_fd, fd);
191 Ok(())
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use std::sync::Arc;
198 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
199
200 struct MockFdOps {
201 dropped_counter: Arc<AtomicUsize>,
202 writev_cb: Option<Box<dyn Fn(&[zx::sys::zx_iovec_t]) + Send + 'static>>,
203 isatty_cb: Option<Box<dyn Fn() -> bool + Send + 'static>>,
204 }
205
206 impl Drop for MockFdOps {
207 fn drop(&mut self) {
208 self.dropped_counter.fetch_add(1, Ordering::Relaxed);
209 }
210 }
211
212 impl FdOps for MockFdOps {
213 fn writev(&self, iovecs: &[zx::sys::zx_iovec_t]) -> Result<usize, zx::Status> {
214 if let Some(cb) = self.writev_cb.as_ref() {
215 cb(iovecs);
216 }
217 Ok(iovecs.iter().map(|v| v.capacity).sum())
218 }
219
220 fn isatty(&self) -> Result<bool, zx::Status> {
221 if let Some(cb) = self.isatty_cb.as_ref() { Ok(cb()) } else { Ok(false) }
222 }
223 }
224
225 #[fuchsia::test]
226 fn test_bind_to_fd_with_ops() {
227 let writev_called = Arc::new(AtomicBool::new(false));
228 let isatty_called = Arc::new(AtomicBool::new(false));
229 let dropped_counter = Arc::new(AtomicUsize::new(0));
230
231 {
232 let writev_called = writev_called.clone();
233 let isatty_called = isatty_called.clone();
234 let ops = MockFdOps {
235 dropped_counter: dropped_counter.clone(),
236 writev_cb: Some(Box::new(move |iovecs| {
237 writev_called.store(true, Ordering::Relaxed);
238 assert_eq!(iovecs.len(), 1);
239 let written_data = unsafe {
240 std::slice::from_raw_parts(
241 iovecs[0].buffer as *const u8,
242 iovecs[0].capacity,
243 )
244 };
245 assert_eq!(written_data, b"hello\n");
246 })),
247 isatty_cb: Some(Box::new(move || {
248 isatty_called.store(true, Ordering::Relaxed);
249 true
250 })),
251 };
252
253 bind_to_fd_with_ops(ops, 1).unwrap();
255 }
256
257 assert!(!writev_called.load(Ordering::Relaxed));
258 assert!(!isatty_called.load(Ordering::Relaxed));
259 println!("hello");
260 assert!(writev_called.load(Ordering::Relaxed));
261
262 assert_eq!(unsafe { libc::isatty(1) }, 1);
263 assert!(isatty_called.load(Ordering::Relaxed));
264
265 assert_eq!(dropped_counter.load(Ordering::Relaxed), 0);
266
267 bind_to_fd_with_ops(
270 MockFdOps {
271 dropped_counter: dropped_counter.clone(),
272 writev_cb: None,
273 isatty_cb: None,
274 },
275 1,
276 )
277 .unwrap();
278
279 assert_eq!(dropped_counter.load(Ordering::Relaxed), 1);
280 }
281
282 #[fuchsia::test]
283 fn test_bind_failure() {
284 let dropped_counter = Arc::new(AtomicUsize::new(0));
285 assert_eq!(
286 bind_to_fd_with_ops(
287 MockFdOps {
288 dropped_counter: dropped_counter.clone(),
289 writev_cb: None,
290 isatty_cb: None
291 },
292 -1
293 ),
294 Err(zx::Status::INVALID_ARGS)
295 );
296 assert_eq!(dropped_counter.load(Ordering::Relaxed), 1);
297
298 assert_eq!(
299 bind_to_fd_with_ops(
300 MockFdOps {
301 dropped_counter: dropped_counter.clone(),
302 writev_cb: None,
303 isatty_cb: None
304 },
305 1234
306 ),
307 Err(zx::Status::BAD_STATE)
308 );
309 assert_eq!(dropped_counter.load(Ordering::Relaxed), 2);
310 }
311}