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 _ = &mut (*storage).reserved;
132 reserved.cast::<T>()
133 }
134
135 unsafe extern "C" fn destroy(io: *mut zxio_sys::zxio_t) {
136 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 = &*Self::to_data(io);
147 match data.writev(std::slice::from_raw_parts(
148 vector.cast::<zx::sys::zx_iovec_t>(),
149 vector_count,
150 )) {
151 Ok(count) => {
152 *out_actual = count;
153 zx::sys::ZX_OK
154 }
155 Err(status) => status.into_raw(),
156 }
157 }
158
159 unsafe extern "C" fn isatty(
160 io: *mut zxio_sys::zxio_t,
161 tty: *mut bool,
162 ) -> zxio_sys::zx_status_t {
163 let data = &*Self::to_data(io);
164 match data.isatty() {
165 Ok(result) => {
166 *tty = result;
167 zx::sys::ZX_OK
168 }
169 Err(status) => status.into_raw(),
170 }
171 }
172
173 const OPS: zxio_sys::zxio_ops = zxio_sys::zxio_ops {
174 destroy: Some(Self::destroy),
175 writev: Some(Self::writev),
176 isatty: Some(Self::isatty),
177 ..DEFAULT_ZXIO_OPS
178 };
179 }
180
181 unsafe {
182 zxio_sys::zxio_init(storage.cast::<zxio_sys::zxio_tag>(), &Adapter::<T>::OPS);
183 }
184
185 let bound_fd = unsafe { fdio_sys::fdio_bind_to_fd(fdio, fd, 0) };
187 if bound_fd < 0 {
188 return Err(zx::Status::BAD_STATE);
189 }
190 assert_eq!(bound_fd, fd);
192 Ok(())
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
199 use std::sync::Arc;
200
201 struct MockFdOps {
202 dropped_counter: Arc<AtomicUsize>,
203 writev_cb: Option<Box<dyn Fn(&[zx::sys::zx_iovec_t]) + Send + 'static>>,
204 isatty_cb: Option<Box<dyn Fn() -> bool + Send + 'static>>,
205 }
206
207 impl Drop for MockFdOps {
208 fn drop(&mut self) {
209 self.dropped_counter.fetch_add(1, Ordering::Relaxed);
210 }
211 }
212
213 impl FdOps for MockFdOps {
214 fn writev(&self, iovecs: &[zx::sys::zx_iovec_t]) -> Result<usize, zx::Status> {
215 if let Some(cb) = self.writev_cb.as_ref() {
216 cb(iovecs);
217 }
218 Ok(iovecs.iter().map(|v| v.capacity).sum())
219 }
220
221 fn isatty(&self) -> Result<bool, zx::Status> {
222 if let Some(cb) = self.isatty_cb.as_ref() {
223 Ok(cb())
224 } else {
225 Ok(false)
226 }
227 }
228 }
229
230 #[fuchsia::test]
231 fn test_bind_to_fd_with_ops() {
232 let writev_called = Arc::new(AtomicBool::new(false));
233 let isatty_called = Arc::new(AtomicBool::new(false));
234 let dropped_counter = Arc::new(AtomicUsize::new(0));
235
236 {
237 let writev_called = writev_called.clone();
238 let isatty_called = isatty_called.clone();
239 let ops = MockFdOps {
240 dropped_counter: dropped_counter.clone(),
241 writev_cb: Some(Box::new(move |iovecs| {
242 writev_called.store(true, Ordering::Relaxed);
243 assert_eq!(iovecs.len(), 1);
244 let written_data = unsafe {
245 std::slice::from_raw_parts(
246 iovecs[0].buffer as *const u8,
247 iovecs[0].capacity,
248 )
249 };
250 assert_eq!(written_data, b"hello\n");
251 })),
252 isatty_cb: Some(Box::new(move || {
253 isatty_called.store(true, Ordering::Relaxed);
254 true
255 })),
256 };
257
258 bind_to_fd_with_ops(ops, 1).unwrap();
260 }
261
262 assert!(!writev_called.load(Ordering::Relaxed));
263 assert!(!isatty_called.load(Ordering::Relaxed));
264 println!("hello");
265 assert!(writev_called.load(Ordering::Relaxed));
266
267 assert_eq!(unsafe { libc::isatty(1) }, 1);
268 assert!(isatty_called.load(Ordering::Relaxed));
269
270 assert_eq!(dropped_counter.load(Ordering::Relaxed), 0);
271
272 bind_to_fd_with_ops(
275 MockFdOps {
276 dropped_counter: dropped_counter.clone(),
277 writev_cb: None,
278 isatty_cb: None,
279 },
280 1,
281 )
282 .unwrap();
283
284 assert_eq!(dropped_counter.load(Ordering::Relaxed), 1);
285 }
286
287 #[fuchsia::test]
288 fn test_bind_failure() {
289 let dropped_counter = Arc::new(AtomicUsize::new(0));
290 assert_eq!(
291 bind_to_fd_with_ops(
292 MockFdOps {
293 dropped_counter: dropped_counter.clone(),
294 writev_cb: None,
295 isatty_cb: None
296 },
297 -1
298 ),
299 Err(zx::Status::INVALID_ARGS)
300 );
301 assert_eq!(dropped_counter.load(Ordering::Relaxed), 1);
302
303 assert_eq!(
304 bind_to_fd_with_ops(
305 MockFdOps {
306 dropped_counter: dropped_counter.clone(),
307 writev_cb: None,
308 isatty_cb: None
309 },
310 1234
311 ),
312 Err(zx::Status::BAD_STATE)
313 );
314 assert_eq!(dropped_counter.load(Ordering::Relaxed), 2);
315 }
316}