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};
83
84pub fn bind_to_fd_with_ops<T: FdOps>(ops: T, fd: RawFd) -> Result<(), zx::Status> {
88 struct AssertCompatible<T>(PhantomData<T>);
89
90 impl<T> AssertCompatible<T> {
91 const SIZE_OK: () = assert!(
92 std::mem::size_of::<T>() <= std::mem::size_of::<zxio_sys::zxio_private>(),
93 "bad size"
94 );
95 const ALIGNMENT_OK: () = assert!(
96 std::mem::align_of::<T>() <= std::mem::align_of::<zxio_sys::zxio_private>(),
97 "bad alignment"
98 );
99 }
100
101 let () = AssertCompatible::<T>::SIZE_OK;
102 let () = AssertCompatible::<T>::ALIGNMENT_OK;
103
104 if fd < 0 {
105 return Err(zx::Status::INVALID_ARGS);
109 }
110
111 let mut storage = std::ptr::null_mut();
112 let fdio = unsafe { fdio_sys::fdio_zxio_create(&mut storage) };
113
114 if fdio.is_null() {
115 return Err(zx::Status::INTERNAL);
116 }
117
118 unsafe {
121 let reserved: *mut _ = &mut (*storage.cast::<zxio_sys::zxio_storage>()).reserved;
122 reserved.cast::<T>().write(ops);
123 }
124
125 struct Adapter<T>(PhantomData<T>);
126 impl<T: FdOps> Adapter<T> {
127 unsafe fn to_data(zxio: *mut zxio_sys::zxio_t) -> *mut T {
128 let storage = zxio.cast::<zxio_sys::zxio_storage>();
129 let reserved: *mut _ = unsafe { &mut (*storage).reserved };
130 reserved.cast::<T>()
131 }
132
133 unsafe extern "C" fn destroy(io: *mut zxio_sys::zxio_t) {
134 unsafe { std::ptr::drop_in_place(Self::to_data(io)) };
135 }
136
137 unsafe extern "C" fn writev(
138 io: *mut zxio_sys::zxio_t,
139 vector: *const zxio_sys::zx_iovec_t,
140 vector_count: usize,
141 _flags: zxio_sys::zxio_flags_t,
142 out_actual: *mut usize,
143 ) -> zxio_sys::zx_status_t {
144 let data = unsafe { &*Self::to_data(io) };
145 match data.writev(unsafe {
146 std::slice::from_raw_parts(vector.cast::<zx::sys::zx_iovec_t>(), vector_count)
147 }) {
148 Ok(count) => {
149 unsafe { *out_actual = count };
150 zx::sys::ZX_OK
151 }
152 Err(status) => status.into_raw(),
153 }
154 }
155
156 unsafe extern "C" fn isatty(
157 io: *mut zxio_sys::zxio_t,
158 tty: *mut bool,
159 ) -> zxio_sys::zx_status_t {
160 let data = unsafe { &*Self::to_data(io) };
161 match data.isatty() {
162 Ok(result) => {
163 unsafe { *tty = result };
164 zx::sys::ZX_OK
165 }
166 Err(status) => status.into_raw(),
167 }
168 }
169
170 const OPS: zxio_sys::zxio_ops = zxio_sys::zxio_ops {
171 destroy: Some(Self::destroy),
172 writev: Some(Self::writev),
173 isatty: Some(Self::isatty),
174 ..DEFAULT_ZXIO_OPS
175 };
176 }
177
178 unsafe {
179 zxio_sys::zxio_init(storage.cast::<zxio_sys::zxio_tag>(), &Adapter::<T>::OPS);
180 }
181
182 let bound_fd = unsafe { fdio_sys::fdio_bind_to_fd(fdio, fd, 0) };
184 if bound_fd < 0 {
185 return Err(zx::Status::BAD_STATE);
186 }
187 assert_eq!(bound_fd, fd);
189 Ok(())
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use std::sync::Arc;
196 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
197
198 struct MockFdOps {
199 dropped_counter: Arc<AtomicUsize>,
200 writev_cb: Option<Box<dyn Fn(&[zx::sys::zx_iovec_t]) + Send + 'static>>,
201 isatty_cb: Option<Box<dyn Fn() -> bool + Send + 'static>>,
202 }
203
204 impl Drop for MockFdOps {
205 fn drop(&mut self) {
206 self.dropped_counter.fetch_add(1, Ordering::Relaxed);
207 }
208 }
209
210 impl FdOps for MockFdOps {
211 fn writev(&self, iovecs: &[zx::sys::zx_iovec_t]) -> Result<usize, zx::Status> {
212 if let Some(cb) = self.writev_cb.as_ref() {
213 cb(iovecs);
214 }
215 Ok(iovecs.iter().map(|v| v.capacity).sum())
216 }
217
218 fn isatty(&self) -> Result<bool, zx::Status> {
219 if let Some(cb) = self.isatty_cb.as_ref() { Ok(cb()) } else { Ok(false) }
220 }
221 }
222
223 #[fuchsia::test]
224 fn test_bind_to_fd_with_ops() {
225 let writev_called = Arc::new(AtomicBool::new(false));
226 let isatty_called = Arc::new(AtomicBool::new(false));
227 let dropped_counter = Arc::new(AtomicUsize::new(0));
228
229 {
230 let writev_called = writev_called.clone();
231 let isatty_called = isatty_called.clone();
232 let ops = MockFdOps {
233 dropped_counter: dropped_counter.clone(),
234 writev_cb: Some(Box::new(move |iovecs| {
235 writev_called.store(true, Ordering::Relaxed);
236 assert_eq!(iovecs.len(), 1);
237 let written_data = unsafe {
238 std::slice::from_raw_parts(
239 iovecs[0].buffer as *const u8,
240 iovecs[0].capacity,
241 )
242 };
243 assert_eq!(written_data, b"hello\n");
244 })),
245 isatty_cb: Some(Box::new(move || {
246 isatty_called.store(true, Ordering::Relaxed);
247 true
248 })),
249 };
250
251 bind_to_fd_with_ops(ops, 1).unwrap();
253 }
254
255 assert!(!writev_called.load(Ordering::Relaxed));
256 assert!(!isatty_called.load(Ordering::Relaxed));
257 println!("hello");
258 assert!(writev_called.load(Ordering::Relaxed));
259
260 assert_eq!(unsafe { libc::isatty(1) }, 1);
261 assert!(isatty_called.load(Ordering::Relaxed));
262
263 assert_eq!(dropped_counter.load(Ordering::Relaxed), 0);
264
265 bind_to_fd_with_ops(
268 MockFdOps {
269 dropped_counter: dropped_counter.clone(),
270 writev_cb: None,
271 isatty_cb: None,
272 },
273 1,
274 )
275 .unwrap();
276
277 assert_eq!(dropped_counter.load(Ordering::Relaxed), 1);
278 }
279
280 #[fuchsia::test]
281 fn test_bind_failure() {
282 let dropped_counter = Arc::new(AtomicUsize::new(0));
283 assert_eq!(
284 bind_to_fd_with_ops(
285 MockFdOps {
286 dropped_counter: dropped_counter.clone(),
287 writev_cb: None,
288 isatty_cb: None
289 },
290 -1
291 ),
292 Err(zx::Status::INVALID_ARGS)
293 );
294 assert_eq!(dropped_counter.load(Ordering::Relaxed), 1);
295
296 assert_eq!(
297 bind_to_fd_with_ops(
298 MockFdOps {
299 dropped_counter: dropped_counter.clone(),
300 writev_cb: None,
301 isatty_cb: None
302 },
303 1234
304 ),
305 Err(zx::Status::BAD_STATE)
306 );
307 assert_eq!(dropped_counter.load(Ordering::Relaxed), 2);
308 }
309}