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