syncio/
lib.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::zxio::{
6    ZXIO_NODE_PROTOCOL_DIRECTORY, ZXIO_NODE_PROTOCOL_FILE, zxio_dirent_iterator_next,
7    zxio_dirent_iterator_t,
8};
9use bitflags::bitflags;
10use bstr::BString;
11use fidl::encoding::const_assert_eq;
12use fidl::endpoints::SynchronousProxy;
13use fidl_fuchsia_io as fio;
14use pin_weak::sync::PinWeak;
15use std::cell::OnceCell;
16use std::ffi::CStr;
17use std::marker::PhantomData;
18use std::mem::{MaybeUninit, size_of, size_of_val};
19use std::num::TryFromIntError;
20use std::os::raw::{c_char, c_int, c_uint, c_void};
21use std::pin::Pin;
22use std::sync::Arc;
23use zerocopy::{FromBytes, Immutable, IntoBytes, TryFromBytes};
24use zxio::{
25    ZXIO_SELINUX_CONTEXT_STATE_DATA, ZXIO_SHUTDOWN_OPTIONS_READ, ZXIO_SHUTDOWN_OPTIONS_WRITE,
26    msghdr, sockaddr, sockaddr_storage, socklen_t, zx_handle_t, zx_status_t, zxio_object_type_t,
27    zxio_seek_origin_t, zxio_socket_mark_t, zxio_storage_t,
28};
29
30pub mod zxio;
31
32pub use zxio::{
33    zxio_dirent_t, zxio_fsverity_descriptor, zxio_fsverity_descriptor_t,
34    zxio_node_attr_zxio_node_attr_has_t as zxio_node_attr_has_t, zxio_node_attributes_t,
35    zxio_signals_t,
36};
37
38// The inner mod is required because bitflags cannot pass the attribute through to the single
39// variant, and attributes cannot be applied to macro invocations.
40mod inner_signals {
41    // Part of the code for the NONE case that's produced by the macro triggers the lint, but as a
42    // whole, the produced code is still correct.
43    #![allow(clippy::bad_bit_mask)] // TODO(b/303500202) Remove once addressed in bitflags.
44    use super::{bitflags, zxio_signals_t};
45
46    bitflags! {
47        // These values should match the values in sdk/lib/zxio/include/lib/zxio/types.h
48        #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
49        pub struct ZxioSignals : zxio_signals_t {
50            const NONE            =      0;
51            const READABLE        = 1 << 0;
52            const WRITABLE        = 1 << 1;
53            const READ_DISABLED   = 1 << 2;
54            const WRITE_DISABLED  = 1 << 3;
55            const READ_THRESHOLD  = 1 << 4;
56            const WRITE_THRESHOLD = 1 << 5;
57            const OUT_OF_BAND     = 1 << 6;
58            const ERROR           = 1 << 7;
59            const PEER_CLOSED     = 1 << 8;
60        }
61    }
62}
63
64pub use inner_signals::ZxioSignals;
65
66bitflags! {
67    /// The flags for shutting down sockets.
68    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
69    pub struct ZxioShutdownFlags: u32 {
70        /// Further transmissions will be disallowed.
71        const WRITE = 1 << 0;
72
73        /// Further receptions will be disallowed.
74        const READ = 1 << 1;
75    }
76}
77
78const_assert_eq!(ZxioShutdownFlags::WRITE.bits(), ZXIO_SHUTDOWN_OPTIONS_WRITE);
79const_assert_eq!(ZxioShutdownFlags::READ.bits(), ZXIO_SHUTDOWN_OPTIONS_READ);
80
81pub enum SeekOrigin {
82    Start,
83    Current,
84    End,
85}
86
87impl From<SeekOrigin> for zxio_seek_origin_t {
88    fn from(origin: SeekOrigin) -> Self {
89        match origin {
90            SeekOrigin::Start => zxio::ZXIO_SEEK_ORIGIN_START,
91            SeekOrigin::Current => zxio::ZXIO_SEEK_ORIGIN_CURRENT,
92            SeekOrigin::End => zxio::ZXIO_SEEK_ORIGIN_END,
93        }
94    }
95}
96
97// TODO: We need a more comprehensive error strategy.
98// Our dependencies create elaborate error objects, but Starnix would prefer
99// this library produce zx::Status errors for easier conversion to Errno.
100
101#[derive(Default, Debug)]
102pub struct ZxioDirent {
103    pub protocols: Option<zxio::zxio_node_protocols_t>,
104    pub abilities: Option<zxio::zxio_abilities_t>,
105    pub id: Option<zxio::zxio_id_t>,
106    pub name: BString,
107}
108
109pub struct DirentIterator<'a> {
110    iterator: Box<zxio_dirent_iterator_t>,
111
112    // zxio_dirent_iterator_t holds pointers to the underlying directory, so we must keep it alive
113    // until we've destroyed it.
114    _directory: PhantomData<&'a Zxio>,
115
116    /// Whether the iterator has reached the end of dir entries.
117    /// This is necessary because the zxio API returns only once the error code
118    /// indicating the iterator has reached the end, where subsequent calls may
119    /// return other error codes.
120    finished: bool,
121}
122
123impl DirentIterator<'_> {
124    /// Rewind the iterator to the beginning.
125    pub fn rewind(&mut self) -> Result<(), zx::Status> {
126        #[allow(
127            clippy::undocumented_unsafe_blocks,
128            reason = "Force documented unsafe blocks in Starnix"
129        )]
130        let status = unsafe { zxio::zxio_dirent_iterator_rewind(&mut *self.iterator) };
131        zx::ok(status)?;
132        self.finished = false;
133        Ok(())
134    }
135}
136
137/// It is important that all methods here are &mut self, to require the client
138/// to obtain exclusive access to the object, externally locking it.
139impl Iterator for DirentIterator<'_> {
140    type Item = Result<ZxioDirent, zx::Status>;
141
142    /// Returns the next dir entry for this iterator.
143    fn next(&mut self) -> Option<Result<ZxioDirent, zx::Status>> {
144        if self.finished {
145            return None;
146        }
147        let mut entry = zxio_dirent_t::default();
148        let mut name_buffer = Vec::with_capacity(fio::MAX_NAME_LENGTH as usize);
149        // The FFI interface expects a pointer to c_char which is i8 on x86_64.
150        // The Rust str and OsStr types expect raw character data to be stored in a buffer u8 values.
151        // The types are equivalent for all practical purposes and Rust permits casting between the types,
152        // so we insert a type cast here in the FFI bindings.
153        entry.name = name_buffer.as_mut_ptr() as *mut c_char;
154        #[allow(
155            clippy::undocumented_unsafe_blocks,
156            reason = "Force documented unsafe blocks in Starnix"
157        )]
158        let status = unsafe { zxio_dirent_iterator_next(&mut *self.iterator.as_mut(), &mut entry) };
159        let result = match zx::ok(status) {
160            Ok(()) => {
161                let result = ZxioDirent::from(entry, name_buffer);
162                Ok(result)
163            }
164            Err(zx::Status::NOT_FOUND) => {
165                self.finished = true;
166                return None;
167            }
168            Err(e) => Err(e),
169        };
170        return Some(result);
171    }
172}
173
174impl Drop for DirentIterator<'_> {
175    fn drop(&mut self) {
176        #[allow(
177            clippy::undocumented_unsafe_blocks,
178            reason = "Force documented unsafe blocks in Starnix"
179        )]
180        unsafe {
181            zxio::zxio_dirent_iterator_destroy(&mut *self.iterator.as_mut());
182        }
183    }
184}
185
186#[allow(clippy::undocumented_unsafe_blocks, reason = "Force documented unsafe blocks in Starnix")]
187unsafe impl Send for DirentIterator<'_> {}
188#[allow(clippy::undocumented_unsafe_blocks, reason = "Force documented unsafe blocks in Starnix")]
189unsafe impl Sync for DirentIterator<'_> {}
190
191impl ZxioDirent {
192    fn from(dirent: zxio_dirent_t, name_buffer: Vec<u8>) -> ZxioDirent {
193        let protocols = if dirent.has.protocols { Some(dirent.protocols) } else { None };
194        let abilities = if dirent.has.abilities { Some(dirent.abilities) } else { None };
195        let id = if dirent.has.id { Some(dirent.id) } else { None };
196        let mut name = name_buffer;
197        #[allow(
198            clippy::undocumented_unsafe_blocks,
199            reason = "Force documented unsafe blocks in Starnix"
200        )]
201        unsafe {
202            name.set_len(dirent.name_length as usize)
203        };
204        ZxioDirent { protocols, abilities, id, name: name.into() }
205    }
206
207    pub fn is_dir(&self) -> bool {
208        self.protocols.map(|p| p & ZXIO_NODE_PROTOCOL_DIRECTORY > 0).unwrap_or(false)
209    }
210
211    pub fn is_file(&self) -> bool {
212        self.protocols.map(|p| p & ZXIO_NODE_PROTOCOL_FILE > 0).unwrap_or(false)
213    }
214}
215
216pub struct ZxioErrorCode(i16);
217impl ZxioErrorCode {
218    pub fn raw(&self) -> i16 {
219        self.0
220    }
221}
222
223#[derive(Debug, Copy, Clone, Eq, PartialEq)]
224pub enum ControlMessage {
225    IpTos(u8),
226    IpTtl(u8),
227    IpRecvOrigDstAddr([u8; size_of::<zxio::sockaddr_in>()]),
228    Ipv6Tclass(u8),
229    Ipv6HopLimit(u8),
230    Ipv6PacketInfo { iface: u32, local_addr: [u8; size_of::<zxio::in6_addr>()] },
231    Timestamp { sec: i64, usec: i64 },
232    TimestampNs { sec: i64, nsec: i64 },
233}
234
235const fn align_cmsg_size(len: usize) -> usize {
236    (len + size_of::<usize>() - 1) & !(size_of::<usize>() - 1)
237}
238
239const CMSG_HEADER_SIZE: usize = align_cmsg_size(size_of::<zxio::cmsghdr>());
240
241// Size of the buffer to allocate in recvmsg() for cmsgs buffer. We need a buffer that can fit
242// Ipv6Tclass, Ipv6HopLimit and Ipv6HopLimit messages.
243const MAX_CMSGS_BUFFER: usize =
244    CMSG_HEADER_SIZE * 3 + align_cmsg_size(1) * 2 + align_cmsg_size(size_of::<zxio::in6_pktinfo>());
245
246impl ControlMessage {
247    pub fn get_data_size(&self) -> usize {
248        match self {
249            ControlMessage::IpTos(_) => 1,
250            ControlMessage::IpTtl(_) => size_of::<c_int>(),
251            ControlMessage::IpRecvOrigDstAddr(addr) => size_of_val(&addr),
252            ControlMessage::Ipv6Tclass(_) => size_of::<c_int>(),
253            ControlMessage::Ipv6HopLimit(_) => size_of::<c_int>(),
254            ControlMessage::Ipv6PacketInfo { .. } => size_of::<zxio::in6_pktinfo>(),
255            ControlMessage::Timestamp { .. } => size_of::<zxio::timeval>(),
256            ControlMessage::TimestampNs { .. } => size_of::<zxio::timespec>(),
257        }
258    }
259
260    // Serializes the data in the format expected by ZXIO.
261    fn serialize<'a>(&'a self, out: &'a mut [u8]) -> usize {
262        let data = &mut out[CMSG_HEADER_SIZE..];
263        let (size, level, type_) = match self {
264            ControlMessage::IpTos(v) => {
265                v.write_to_prefix(data).unwrap();
266                (1, zxio::SOL_IP, zxio::IP_TOS)
267            }
268            ControlMessage::IpTtl(v) => {
269                (*v as c_int).write_to_prefix(data).unwrap();
270                (size_of::<c_int>(), zxio::SOL_IP, zxio::IP_TTL)
271            }
272            ControlMessage::IpRecvOrigDstAddr(v) => {
273                v.write_to_prefix(data).unwrap();
274                (size_of_val(&v), zxio::SOL_IP, zxio::IP_RECVORIGDSTADDR)
275            }
276            ControlMessage::Ipv6Tclass(v) => {
277                (*v as c_int).write_to_prefix(data).unwrap();
278                (size_of::<c_int>(), zxio::SOL_IPV6, zxio::IPV6_TCLASS)
279            }
280            ControlMessage::Ipv6HopLimit(v) => {
281                (*v as c_int).write_to_prefix(data).unwrap();
282                (size_of::<c_int>(), zxio::SOL_IPV6, zxio::IPV6_HOPLIMIT)
283            }
284            ControlMessage::Ipv6PacketInfo { iface, local_addr } => {
285                let pktinfo = zxio::in6_pktinfo {
286                    ipi6_addr: zxio::in6_addr {
287                        __in6_union: zxio::in6_addr__bindgen_ty_1 { __s6_addr: *local_addr },
288                    },
289                    ipi6_ifindex: *iface,
290                };
291                pktinfo.write_to_prefix(data).unwrap();
292                (size_of_val(&pktinfo), zxio::SOL_IPV6, zxio::IPV6_PKTINFO)
293            }
294            ControlMessage::Timestamp { sec, usec } => {
295                let timeval = zxio::timeval { tv_sec: *sec, tv_usec: *usec };
296                timeval.write_to_prefix(data).unwrap();
297                (size_of_val(&timeval), zxio::SOL_SOCKET, zxio::SO_TIMESTAMP)
298            }
299            ControlMessage::TimestampNs { sec, nsec } => {
300                let timespec = zxio::timespec { tv_sec: *sec, tv_nsec: *nsec };
301                timespec.write_to_prefix(data).unwrap();
302                (size_of_val(&timespec), zxio::SOL_SOCKET, zxio::SO_TIMESTAMPNS)
303            }
304        };
305        let total_size = CMSG_HEADER_SIZE + size;
306        let header = zxio::cmsghdr {
307            cmsg_len: total_size as c_uint,
308            cmsg_level: level as i32,
309            cmsg_type: type_ as i32,
310        };
311        header.write_to_prefix(&mut out[..]).unwrap();
312
313        total_size
314    }
315}
316
317fn serialize_control_messages(messages: &[ControlMessage]) -> Vec<u8> {
318    let size = messages
319        .iter()
320        .fold(0, |sum, x| sum + CMSG_HEADER_SIZE + align_cmsg_size(x.get_data_size()));
321    let mut buffer = vec![0u8; size];
322    let mut pos = 0;
323    for msg in messages {
324        pos += align_cmsg_size(msg.serialize(&mut buffer[pos..]));
325    }
326    assert_eq!(pos, buffer.len());
327    buffer
328}
329
330fn parse_control_messages(data: &[u8]) -> Vec<ControlMessage> {
331    let mut result = vec![];
332    let mut pos = 0;
333    loop {
334        if pos >= data.len() {
335            return result;
336        }
337        let header_data = &data[pos..];
338        let header = match zxio::cmsghdr::read_from_prefix(header_data) {
339            Ok((h, _)) if h.cmsg_len as usize > CMSG_HEADER_SIZE => h,
340            _ => return result,
341        };
342
343        let msg_data = &data[pos + CMSG_HEADER_SIZE..pos + header.cmsg_len as usize];
344        let msg = match (header.cmsg_level as u32, header.cmsg_type as u32) {
345            (zxio::SOL_IP, zxio::IP_TOS) => {
346                ControlMessage::IpTos(u8::read_from_prefix(msg_data).unwrap().0)
347            }
348            (zxio::SOL_IP, zxio::IP_TTL) => {
349                ControlMessage::IpTtl(c_int::read_from_prefix(msg_data).unwrap().0 as u8)
350            }
351            (zxio::SOL_IP, zxio::IP_RECVORIGDSTADDR) => ControlMessage::IpRecvOrigDstAddr(
352                <[u8; size_of::<zxio::sockaddr_in>()]>::read_from_prefix(msg_data).unwrap().0,
353            ),
354            (zxio::SOL_IPV6, zxio::IPV6_TCLASS) => {
355                ControlMessage::Ipv6Tclass(c_int::read_from_prefix(msg_data).unwrap().0 as u8)
356            }
357            (zxio::SOL_IPV6, zxio::IPV6_HOPLIMIT) => {
358                ControlMessage::Ipv6HopLimit(c_int::read_from_prefix(msg_data).unwrap().0 as u8)
359            }
360            (zxio::SOL_IPV6, zxio::IPV6_PKTINFO) => {
361                let pkt_info = zxio::in6_pktinfo::read_from_prefix(msg_data).unwrap().0;
362                #[allow(
363                    clippy::undocumented_unsafe_blocks,
364                    reason = "Force documented unsafe blocks in Starnix"
365                )]
366                ControlMessage::Ipv6PacketInfo {
367                    local_addr: unsafe { pkt_info.ipi6_addr.__in6_union.__s6_addr },
368                    iface: pkt_info.ipi6_ifindex,
369                }
370            }
371            (zxio::SOL_SOCKET, zxio::SO_TIMESTAMP) => {
372                let timeval = zxio::timeval::read_from_prefix(msg_data).unwrap().0;
373                ControlMessage::Timestamp { sec: timeval.tv_sec, usec: timeval.tv_usec }
374            }
375            (zxio::SOL_SOCKET, zxio::SO_TIMESTAMPNS) => {
376                let timespec = zxio::timespec::read_from_prefix(msg_data).unwrap().0;
377                ControlMessage::TimestampNs { sec: timespec.tv_sec, nsec: timespec.tv_nsec }
378            }
379            _ => panic!(
380                "ZXIO produced unexpected cmsg level={}, type={}",
381                header.cmsg_level, header.cmsg_type
382            ),
383        };
384        result.push(msg);
385
386        pos += align_cmsg_size(header.cmsg_len as usize);
387    }
388}
389
390pub struct RecvMessageInfo {
391    pub address: Vec<u8>,
392    pub bytes_read: usize,
393    pub message_length: usize,
394    pub control_messages: Vec<ControlMessage>,
395    pub flags: i32,
396}
397
398/// Holder type for the resulting context, ensuring that it
399pub struct SelinuxContextAttr<'a> {
400    buf: &'a mut MaybeUninit<[u8; fio::MAX_SELINUX_CONTEXT_ATTRIBUTE_LEN as usize]>,
401    size: OnceCell<usize>,
402}
403
404impl<'a> SelinuxContextAttr<'a> {
405    /// Creates a holder type for managing the buffer init, where the buffer is backed by the
406    /// provided `buf`.
407    pub fn new(
408        buf: &'a mut MaybeUninit<[u8; fio::MAX_SELINUX_CONTEXT_ATTRIBUTE_LEN as usize]>,
409    ) -> Self {
410        Self { buf, size: OnceCell::new() }
411    }
412
413    /// The number of bytes initialized.
414    fn init(&mut self, size: usize) {
415        let res = self.size.set(size);
416        debug_assert!(res.is_ok());
417    }
418
419    /// If a context string was recorded, this returns a slice to it.
420    pub fn get(&self) -> Option<&[u8]> {
421        let size = self.size.get()?;
422        // SAFETY: The OnceCell contains the number of bytes initialized.
423        Some(unsafe { self.buf.assume_init_ref()[..*size].as_ref() })
424    }
425}
426
427/// Options for Open3.
428#[derive(Default)]
429pub struct ZxioOpenOptions<'a, 'b> {
430    attributes: Option<&'a mut zxio_node_attributes_t>,
431
432    /// If an object is to be created, attributes that should be stored with the object at creation
433    /// time. Not all servers support all attributes.
434    create_attributes: Option<zxio::zxio_node_attr>,
435
436    /// If requesting the SELinux context as part of open, this manages the lifetime.
437    selinux_context_read: Option<&'a mut SelinuxContextAttr<'b>>,
438}
439
440impl<'a, 'b> ZxioOpenOptions<'a, 'b> {
441    /// Consumes the `create_attributes`` but the `attributes` is passed as a mutable ref, since the
442    /// retrieved attributes will be written back into it. If any pointer fields are non-null, this
443    /// will fail assertions.
444    pub fn new(
445        attributes: Option<&'a mut zxio_node_attributes_t>,
446        create_attributes: Option<zxio::zxio_node_attr>,
447    ) -> Self {
448        if let Some(attrs) = &attributes {
449            validate_pointer_fields(attrs);
450        }
451        if let Some(attrs) = &create_attributes {
452            validate_pointer_fields(attrs);
453        }
454        Self { attributes, create_attributes, selinux_context_read: None }
455    }
456
457    /// Attaches the provided selinux context buffer to the `create_attributes`
458    pub fn with_selinux_context_write(mut self, context: &'a [u8]) -> Result<Self, zx::Status> {
459        if context.len() > fio::MAX_SELINUX_CONTEXT_ATTRIBUTE_LEN as usize {
460            return Err(zx::Status::INVALID_ARGS);
461        }
462        // If this value increases we'll have to rethink the below conversions.
463        const_assert_eq!(fio::MAX_SELINUX_CONTEXT_ATTRIBUTE_LEN, 256);
464        {
465            let create_attributes = self.create_attributes.get_or_insert_with(Default::default);
466            create_attributes.selinux_context_length = context.len() as u16;
467            create_attributes.selinux_context_state = ZXIO_SELINUX_CONTEXT_STATE_DATA;
468            // SAFETY: In this context the pointer will only be read from, but the type is a
469            // mutable pointer.
470            create_attributes.selinux_context = context.as_ptr() as *mut u8;
471            create_attributes.has.selinux_context = true;
472        }
473        Ok(self)
474    }
475
476    /// Attaches the provided selinux context buffer to receive the context into. This call will
477    /// fail if no attributes query was attached, since the success of the fetch cannot be verified.
478    pub fn with_selinux_context_read(
479        mut self,
480        context: &'a mut SelinuxContextAttr<'b>,
481    ) -> Result<Self, zx::Status> {
482        if let Some(attributes_query) = &mut self.attributes {
483            attributes_query.selinux_context = context.buf.as_mut_ptr().cast::<u8>();
484            attributes_query.has.selinux_context = true;
485            self.selinux_context_read = Some(context);
486        } else {
487            // If the value attributes query wasn't passed in we can't populate it, and the caller
488            // can't get a response back to see if it worked anyways. Fail the request.
489            return Err(zx::Status::INVALID_ARGS);
490        }
491        Ok(self)
492    }
493
494    /// Called inside the open method to confirm that some bytes were recorded into the buffer.
495    fn init_context_from_read(&mut self) {
496        if let (Some(attributes), Some(context)) =
497            (&self.attributes, &mut self.selinux_context_read)
498        {
499            if attributes.selinux_context_state == ZXIO_SELINUX_CONTEXT_STATE_DATA {
500                context.init(attributes.selinux_context_length as usize);
501            }
502        }
503    }
504}
505
506/// Describes the mode of operation when setting an extended attribute.
507#[derive(Copy, Clone, Debug)]
508pub enum XattrSetMode {
509    /// Create the extended attribute if it doesn't exist, replace the value if it does.
510    Set = 1,
511    /// Create the extended attribute if it doesn't exist, failing if it does.
512    Create = 2,
513    /// Replace the value of the extended attribute, failing if it doesn't exist.
514    Replace = 3,
515}
516
517bitflags! {
518    /// Describes the mode of operation when allocating disk space using Allocate.
519    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
520    pub struct AllocateMode: u32 {
521        const KEEP_SIZE = 1 << 0;
522        const UNSHARE_RANGE = 1 << 1;
523        const PUNCH_HOLE = 1 << 2;
524        const COLLAPSE_RANGE = 1 << 3;
525        const ZERO_RANGE = 1 << 4;
526        const INSERT_RANGE = 1 << 5;
527    }
528}
529
530const_assert_eq!(AllocateMode::KEEP_SIZE.bits(), zxio::ZXIO_ALLOCATE_KEEP_SIZE);
531const_assert_eq!(AllocateMode::UNSHARE_RANGE.bits(), zxio::ZXIO_ALLOCATE_UNSHARE_RANGE);
532const_assert_eq!(AllocateMode::PUNCH_HOLE.bits(), zxio::ZXIO_ALLOCATE_PUNCH_HOLE);
533const_assert_eq!(AllocateMode::COLLAPSE_RANGE.bits(), zxio::ZXIO_ALLOCATE_COLLAPSE_RANGE);
534const_assert_eq!(AllocateMode::ZERO_RANGE.bits(), zxio::ZXIO_ALLOCATE_ZERO_RANGE);
535const_assert_eq!(AllocateMode::INSERT_RANGE.bits(), zxio::ZXIO_ALLOCATE_INSERT_RANGE);
536
537// `ZxioStorage` is marked as `PhantomPinned` in order to prevent unsafe moves
538// of the `zxio_storage_t`, because it may store self-referential types defined
539// in zxio.
540#[derive(Default)]
541struct ZxioStorage {
542    storage: zxio::zxio_storage_t,
543    token_resolver: Option<Arc<dyn ZxioTokenResolver>>,
544    _pin: std::marker::PhantomPinned,
545}
546
547/// A handle to a zxio object.
548///
549/// Note: the underlying storage backing the object is pinned on the heap
550/// because it can contain self referential data.
551pub struct Zxio {
552    inner: Pin<Arc<ZxioStorage>>,
553}
554
555impl Default for Zxio {
556    fn default() -> Self {
557        Self { inner: Arc::pin(ZxioStorage::default()) }
558    }
559}
560
561impl std::fmt::Debug for Zxio {
562    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
563        f.debug_struct("Zxio").finish()
564    }
565}
566
567pub struct ZxioWeak(PinWeak<ZxioStorage>);
568
569impl ZxioWeak {
570    pub fn upgrade(&self) -> Option<Zxio> {
571        Some(Zxio { inner: self.0.upgrade()? })
572    }
573}
574
575/// A trait that provides functionality to connect a channel to a FIDL service.
576//
577// TODO(https://github.com/rust-lang/rust/issues/44291): allow clients to pass
578// in a more general function pointer (`fn(&str, zx::Channel) -> zx::Status`)
579// rather than having to implement this trait.
580pub trait ServiceConnector {
581    /// Returns a channel to the service named by `service_name`.
582    fn connect(service_name: &str) -> Result<&'static zx::Channel, zx::Status>;
583}
584
585/// Sets `provider_handle` to a handle to the service named by `service_name`.
586///
587/// This function is intended to be passed to zxio_socket().
588///
589/// SAFETY: Dereferences the raw pointers `service_name` and `provider_handle`.
590unsafe extern "C" fn service_connector<S: ServiceConnector>(
591    service_name: *const c_char,
592    provider_handle: *mut zx_handle_t,
593) -> zx_status_t {
594    let status: zx::Status = (|| {
595        #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
596        let service_name = unsafe { CStr::from_ptr(service_name) }
597            .to_str()
598            .map_err(|std::str::Utf8Error { .. }| zx::Status::INVALID_ARGS)?;
599
600        S::connect(service_name).map(|channel| {
601            #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
602            unsafe {
603                *provider_handle = channel.raw_handle()
604            };
605        })
606    })()
607    .into();
608    status.into_raw()
609}
610
611/// Sets `out_storage` as the zxio_storage of `out_context`.
612///
613/// This function is intended to be passed to zxio_socket().
614///
615/// SAFETY: Dereferences the raw pointer `out_storage`.
616unsafe extern "C" fn storage_allocator(
617    _type: zxio_object_type_t,
618    out_storage: *mut *mut zxio_storage_t,
619    out_context: *mut *mut c_void,
620) -> zx_status_t {
621    let zxio_ptr_ptr = out_context as *mut *mut zxio_storage_t;
622    let status: zx::Status = (|| {
623        #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
624        if let Some(zxio_ptr) = unsafe { zxio_ptr_ptr.as_mut() } {
625            #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
626            if let Some(zxio) = unsafe { zxio_ptr.as_mut() } {
627                #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
628                unsafe {
629                    *out_storage = zxio
630                };
631                return Ok(());
632            }
633        }
634        Err(zx::Status::NO_MEMORY)
635    })()
636    .into();
637    status.into_raw()
638}
639
640/// Ensures that no pointer fields have been set. Used to enforce usage of code paths that can
641/// ensure safety. Without this, anything passing a `zxio_node_attributes_t` from the caller to
642/// zxio should be marked unsafe.
643fn validate_pointer_fields(attrs: &zxio_node_attributes_t) {
644    // If you're reading this, and the safe path you want doesn't exist, then add one!
645    assert!(
646        attrs.fsverity_root_hash.is_null(),
647        "Passed in a pointer for the fsverity_root_hash that could not assure the lifetime."
648    );
649    assert!(
650        attrs.selinux_context.is_null(),
651        "Passed in a pointer for the selinux_context that could not assure the lifetime."
652    );
653}
654
655/// To be called before letting any `zxio_node_attributes_t` get passed back to the caller, ensuring
656/// that any pointers used in the call are cleaned up.
657fn clean_pointer_fields(attrs: &mut zxio_node_attributes_t) {
658    attrs.fsverity_root_hash = std::ptr::null_mut();
659    attrs.selinux_context = std::ptr::null_mut();
660}
661
662pub const ZXIO_ROOT_HASH_LENGTH: usize = 64;
663
664/// A transparent wrapper around the bindgen type.
665#[repr(transparent)]
666#[derive(IntoBytes, TryFromBytes, Immutable)]
667pub struct ZxioSocketMark(zxio_socket_mark_t);
668
669impl ZxioSocketMark {
670    fn new(domain: u8, value: u32) -> Self {
671        ZxioSocketMark(zxio_socket_mark_t { is_present: true, domain, value, ..Default::default() })
672    }
673
674    /// Creates a new socket mark representing the SO_MARK domain.
675    pub fn so_mark(mark: u32) -> Self {
676        Self::new(fidl_fuchsia_net::MARK_DOMAIN_SO_MARK as u8, mark)
677    }
678
679    /// Creates a new socket mark representing the uid domain.
680    pub fn uid(uid: u32) -> Self {
681        Self::new(fidl_fuchsia_net::MARK_DOMAIN_SOCKET_UID as u8, uid)
682    }
683}
684
685/// A transparent wrapper around the zxio_wake_group_token_t bindgen type.
686#[repr(transparent)]
687pub struct ZxioWakeGroupToken(zx_handle_t);
688
689impl ZxioWakeGroupToken {
690    /// Creates a wake group token from the provided handle to an event.
691    pub fn new(token: Option<zx::Event>) -> Self {
692        ZxioWakeGroupToken(token.map(zx::Event::into_raw).unwrap_or(zx::sys::ZX_HANDLE_INVALID))
693    }
694}
695
696#[derive(Debug, Copy, Clone, PartialEq, Eq)]
697pub enum ZxioTokenType {
698    /// Sharing domain token for `setsockopt(SO_REUSEPORT)`.
699    SharingDomain,
700}
701
702impl TryFrom<zxio::zxio_token_type_t> for ZxioTokenType {
703    type Error = ();
704
705    fn try_from(value: zxio::zxio_token_type_t) -> Result<Self, Self::Error> {
706        match value {
707            zxio::ZXIO_TOKEN_TYPE_SHARING_DOMAIN => Ok(ZxioTokenType::SharingDomain),
708            _ => Err(()),
709        }
710    }
711}
712
713/// Trait for token resolvers. It's called to get access tokens and sharing
714/// domain tokens when handling operations that require these tokens.
715/// Token resolver can be attached to a `Zxio` object by calling
716/// `set_token_resolver`.
717pub trait ZxioTokenResolver: Send + Sync {
718    /// Returns a token of the specified type.
719    fn get_token(&self, token_type: ZxioTokenType) -> Option<zx::NullableHandle>;
720}
721
722/// `zxio_token_resolver` callback provided to ZXIO. Calls `ZxioTokenResolver`.
723unsafe extern "C" fn resolve_token(
724    io: *mut zxio::zxio_t,
725    token_type: zxio::zxio_token_type_t,
726) -> zx_handle_t {
727    #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
728    let io = unsafe { &mut *(io as *mut ZxioStorage) };
729    let Some(resolver) = io.token_resolver.as_ref() else {
730        return zx::sys::ZX_HANDLE_INVALID;
731    };
732
733    ZxioTokenType::try_from(token_type)
734        .ok()
735        .and_then(|type_| resolver.get_token(type_))
736        .map(zx::NullableHandle::into_raw)
737        .unwrap_or(zx::sys::ZX_HANDLE_INVALID)
738}
739
740/// Socket creation options that can be used.
741pub struct ZxioSocketCreationOptions<'a> {
742    pub marks: &'a mut [ZxioSocketMark],
743    pub wake_group: ZxioWakeGroupToken,
744}
745
746impl Zxio {
747    pub fn new_socket<S: ServiceConnector>(
748        domain: c_int,
749        socket_type: c_int,
750        protocol: c_int,
751        ZxioSocketCreationOptions { marks, wake_group }: ZxioSocketCreationOptions<'_>,
752    ) -> Result<Result<Self, ZxioErrorCode>, zx::Status> {
753        let zxio = Zxio::default();
754        let mut out_context = zxio.as_storage_ptr() as *mut c_void;
755        let mut out_code = 0;
756
757        let ZxioWakeGroupToken(wake_group) = wake_group;
758        let creation_opts = zxio::zxio_socket_creation_options {
759            num_marks: marks.len(),
760            marks: marks.as_mut_ptr() as *mut _,
761            wake_group,
762            ..Default::default()
763        };
764
765        #[allow(
766            clippy::undocumented_unsafe_blocks,
767            reason = "Force documented unsafe blocks in Starnix"
768        )]
769        let status = unsafe {
770            zxio::zxio_socket_with_options(
771                Some(service_connector::<S>),
772                domain,
773                socket_type,
774                protocol,
775                creation_opts,
776                Some(storage_allocator),
777                &mut out_context as *mut *mut c_void,
778                &mut out_code,
779            )
780        };
781        zx::ok(status)?;
782        match out_code {
783            0 => Ok(Ok(zxio)),
784            _ => Ok(Err(ZxioErrorCode(out_code))),
785        }
786    }
787
788    fn as_ptr(&self) -> *mut zxio::zxio_t {
789        &self.inner.storage.io as *const zxio::zxio_t as *mut zxio::zxio_t
790    }
791
792    fn as_storage_ptr(&self) -> *mut zxio::zxio_storage_t {
793        &self.inner.storage as *const zxio::zxio_storage_t as *mut zxio::zxio_storage_t
794    }
795
796    pub fn create(handle: zx::NullableHandle) -> Result<Zxio, zx::Status> {
797        let zxio = Zxio::default();
798        #[allow(
799            clippy::undocumented_unsafe_blocks,
800            reason = "Force documented unsafe blocks in Starnix"
801        )]
802        let status = unsafe { zxio::zxio_create(handle.into_raw(), zxio.as_storage_ptr()) };
803        zx::ok(status)?;
804        Ok(zxio)
805    }
806
807    pub fn release(self) -> Result<zx::NullableHandle, zx::Status> {
808        let mut handle = 0;
809        #[allow(
810            clippy::undocumented_unsafe_blocks,
811            reason = "Force documented unsafe blocks in Starnix"
812        )]
813        let status = unsafe { zxio::zxio_release(self.as_ptr(), &mut handle) };
814        zx::ok(status)?;
815        #[allow(
816            clippy::undocumented_unsafe_blocks,
817            reason = "Force documented unsafe blocks in Starnix"
818        )]
819        unsafe {
820            Ok(zx::NullableHandle::from_raw(handle))
821        }
822    }
823
824    /// Updates self with a new token resolver. Can be called only when there are no weak references
825    /// to self.
826    pub fn with_token_resolver(self, resolver: Arc<dyn ZxioTokenResolver>) -> Self {
827        // SAFETY: The resolver will be called with zxio_t passed as a pointer, which is valid for
828        // the lifetime of self.
829        let status = unsafe { zxio::zxio_set_token_resolver(self.as_ptr(), Some(resolve_token)) };
830        if status != zx::sys::ZX_OK && status != zx::sys::ZX_ERR_NOT_SUPPORTED {
831            panic!("Failed to set token resolver: {}", status);
832        }
833
834        // It's not possible to access `Arc::get_mut()` for `inner` while it is pinned.
835        // Destructure `self` and unpin `inner` temporarily.
836        let Zxio { inner } = self;
837        // SAFETY: The value is kept in an `Arc` and is pinned again below.
838        let mut inner = unsafe { Pin::into_inner_unchecked(inner) };
839
840        Arc::get_mut(&mut inner).expect("Expected unique ZxioStorage").token_resolver =
841            Some(resolver);
842
843        // SAFETY: The value was unpinned above.
844        let inner = unsafe { Pin::new_unchecked(inner) };
845
846        Zxio { inner }
847    }
848
849    pub fn open(
850        &self,
851        path: &str,
852        flags: fio::Flags,
853        mut options: ZxioOpenOptions<'_, '_>,
854    ) -> Result<Self, zx::Status> {
855        let zxio = Zxio::default();
856
857        let mut zxio_open_options = zxio::zxio_open_options::default();
858        zxio_open_options.inout_attr = match &mut options.attributes {
859            Some(a) => (*a) as *mut zxio_node_attributes_t,
860            None => std::ptr::null_mut(),
861        };
862        zxio_open_options.create_attr = match &options.create_attributes {
863            Some(a) => a as *const zxio_node_attributes_t,
864            None => std::ptr::null_mut(),
865        };
866
867        #[allow(
868            clippy::undocumented_unsafe_blocks,
869            reason = "Force documented unsafe blocks in Starnix"
870        )]
871        let status = unsafe {
872            zxio::zxio_open(
873                self.as_ptr(),
874                path.as_ptr() as *const c_char,
875                path.len(),
876                flags.bits(),
877                &zxio_open_options,
878                zxio.as_storage_ptr(),
879            )
880        };
881        options.init_context_from_read();
882        if let Some(attributes) = options.attributes {
883            clean_pointer_fields(attributes);
884        }
885        zx::ok(status)?;
886        Ok(zxio)
887    }
888
889    pub fn create_with_on_representation(
890        handle: zx::NullableHandle,
891        attributes: Option<&mut zxio_node_attributes_t>,
892    ) -> Result<Zxio, zx::Status> {
893        if let Some(attr) = &attributes {
894            validate_pointer_fields(attr);
895        }
896        let zxio = Zxio::default();
897        #[allow(
898            clippy::undocumented_unsafe_blocks,
899            reason = "Force documented unsafe blocks in Starnix"
900        )]
901        let status = unsafe {
902            zxio::zxio_create_with_on_representation(
903                handle.into_raw(),
904                attributes.map(|a| a as *mut _).unwrap_or(std::ptr::null_mut()),
905                zxio.as_storage_ptr(),
906            )
907        };
908        zx::ok(status)?;
909        Ok(zxio)
910    }
911
912    /// Opens a limited node connection (similar to O_PATH).
913    pub fn open_node(
914        &self,
915        path: &str,
916        flags: fio::Flags,
917        attributes: Option<&mut zxio_node_attributes_t>,
918    ) -> Result<Self, zx::Status> {
919        let zxio = Zxio::default();
920
921        #[allow(
922            clippy::undocumented_unsafe_blocks,
923            reason = "Force documented unsafe blocks in Starnix"
924        )]
925        let status = unsafe {
926            zxio::zxio_open(
927                self.as_ptr(),
928                path.as_ptr() as *const c_char,
929                path.len(),
930                // `PROTOCOL_NODE` takes precedence over over protocols. If other protocols are
931                // specified as well, it will be used to validate the target node type.
932                (flags | fio::Flags::PROTOCOL_NODE).bits(),
933                &zxio::zxio_open_options {
934                    inout_attr: attributes.map(|a| a as *mut _).unwrap_or(std::ptr::null_mut()),
935                    ..Default::default()
936                },
937                zxio.as_storage_ptr(),
938            )
939        };
940        zx::ok(status)?;
941        Ok(zxio)
942    }
943
944    pub fn unlink(&self, name: &str, flags: fio::UnlinkFlags) -> Result<(), zx::Status> {
945        let flags_bits = flags.bits().try_into().map_err(|_| zx::Status::INVALID_ARGS)?;
946        #[allow(
947            clippy::undocumented_unsafe_blocks,
948            reason = "Force documented unsafe blocks in Starnix"
949        )]
950        let status = unsafe {
951            zxio::zxio_unlink(self.as_ptr(), name.as_ptr() as *const c_char, name.len(), flags_bits)
952        };
953        zx::ok(status)
954    }
955
956    pub fn read(&self, data: &mut [u8]) -> Result<usize, zx::Status> {
957        let flags = zxio::zxio_flags_t::default();
958        let mut actual = 0usize;
959        #[allow(
960            clippy::undocumented_unsafe_blocks,
961            reason = "Force documented unsafe blocks in Starnix"
962        )]
963        let status = unsafe {
964            zxio::zxio_read(
965                self.as_ptr(),
966                data.as_ptr() as *mut c_void,
967                data.len(),
968                flags,
969                &mut actual,
970            )
971        };
972        zx::ok(status)?;
973        Ok(actual)
974    }
975
976    /// Performs a vectorized read, returning the number of bytes read to `data`.
977    ///
978    /// # Safety
979    ///
980    /// The caller must check the returned `Result` to make sure the buffers
981    /// provided are valid. The caller must provide pointers that are compatible
982    /// with the backing implementation of zxio.
983    ///
984    /// This call allows writing to arbitrary memory locations. It is up to the
985    /// caller to make sure that calling this method does not result in undefined
986    /// behaviour.
987    pub unsafe fn readv(&self, data: &[zxio::zx_iovec]) -> Result<usize, zx::Status> {
988        let flags = zxio::zxio_flags_t::default();
989        let mut actual = 0usize;
990        #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
991        let status = unsafe {
992            zxio::zxio_readv(
993                self.as_ptr(),
994                data.as_ptr() as *const zxio::zx_iovec,
995                data.len(),
996                flags,
997                &mut actual,
998            )
999        };
1000        zx::ok(status)?;
1001        Ok(actual)
1002    }
1003
1004    pub fn deep_clone(&self) -> Result<Zxio, zx::Status> {
1005        Zxio::create(self.clone_handle()?)
1006    }
1007
1008    pub fn clone_handle(&self) -> Result<zx::NullableHandle, zx::Status> {
1009        let mut handle = 0;
1010        #[allow(
1011            clippy::undocumented_unsafe_blocks,
1012            reason = "Force documented unsafe blocks in Starnix"
1013        )]
1014        let status = unsafe { zxio::zxio_clone(self.as_ptr(), &mut handle) };
1015        zx::ok(status)?;
1016        #[allow(
1017            clippy::undocumented_unsafe_blocks,
1018            reason = "Force documented unsafe blocks in Starnix"
1019        )]
1020        unsafe {
1021            Ok(zx::NullableHandle::from_raw(handle))
1022        }
1023    }
1024
1025    pub fn downgrade(&self) -> ZxioWeak {
1026        ZxioWeak(PinWeak::downgrade(self.inner.clone()))
1027    }
1028
1029    pub fn read_at(&self, offset: u64, data: &mut [u8]) -> Result<usize, zx::Status> {
1030        let flags = zxio::zxio_flags_t::default();
1031        let mut actual = 0usize;
1032        #[allow(
1033            clippy::undocumented_unsafe_blocks,
1034            reason = "Force documented unsafe blocks in Starnix"
1035        )]
1036        let status = unsafe {
1037            zxio::zxio_read_at(
1038                self.as_ptr(),
1039                offset,
1040                data.as_ptr() as *mut c_void,
1041                data.len(),
1042                flags,
1043                &mut actual,
1044            )
1045        };
1046        zx::ok(status)?;
1047        Ok(actual)
1048    }
1049
1050    /// Performs a vectorized read at an offset, returning the number of bytes
1051    /// read to `data`.
1052    ///
1053    /// # Safety
1054    ///
1055    /// The caller must check the returned `Result` to make sure the buffers
1056    /// provided are valid. The caller must provide pointers that are compatible
1057    /// with the backing implementation of zxio.
1058    ///
1059    /// This call allows writing to arbitrary memory locations. It is up to the
1060    /// caller to make sure that calling this method does not result in undefined
1061    /// behaviour.
1062    pub unsafe fn readv_at(
1063        &self,
1064        offset: u64,
1065        data: &[zxio::zx_iovec],
1066    ) -> Result<usize, zx::Status> {
1067        let flags = zxio::zxio_flags_t::default();
1068        let mut actual = 0usize;
1069        #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1070        let status = unsafe {
1071            zxio::zxio_readv_at(
1072                self.as_ptr(),
1073                offset,
1074                data.as_ptr() as *const zxio::zx_iovec,
1075                data.len(),
1076                flags,
1077                &mut actual,
1078            )
1079        };
1080        zx::ok(status)?;
1081        Ok(actual)
1082    }
1083
1084    pub fn write(&self, data: &[u8]) -> Result<usize, zx::Status> {
1085        let flags = zxio::zxio_flags_t::default();
1086        let mut actual = 0;
1087        #[allow(
1088            clippy::undocumented_unsafe_blocks,
1089            reason = "Force documented unsafe blocks in Starnix"
1090        )]
1091        let status = unsafe {
1092            zxio::zxio_write(
1093                self.as_ptr(),
1094                data.as_ptr() as *const c_void,
1095                data.len(),
1096                flags,
1097                &mut actual,
1098            )
1099        };
1100        zx::ok(status)?;
1101        Ok(actual)
1102    }
1103
1104    /// Performs a vectorized write, returning the number of bytes written from
1105    /// `data`.
1106    ///
1107    /// # Safety
1108    ///
1109    /// The caller must check the returned `Result` to make sure the buffers
1110    /// provided are valid. The caller must provide pointers that are compatible
1111    /// with the backing implementation of zxio.
1112    ///
1113    /// This call allows reading from arbitrary memory locations. It is up to the
1114    /// caller to make sure that calling this method does not result in undefined
1115    /// behaviour.
1116    pub unsafe fn writev(&self, data: &[zxio::zx_iovec]) -> Result<usize, zx::Status> {
1117        let flags = zxio::zxio_flags_t::default();
1118        let mut actual = 0;
1119        #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1120        let status = unsafe {
1121            zxio::zxio_writev(
1122                self.as_ptr(),
1123                data.as_ptr() as *const zxio::zx_iovec,
1124                data.len(),
1125                flags,
1126                &mut actual,
1127            )
1128        };
1129        zx::ok(status)?;
1130        Ok(actual)
1131    }
1132
1133    pub fn write_at(&self, offset: u64, data: &[u8]) -> Result<usize, zx::Status> {
1134        let flags = zxio::zxio_flags_t::default();
1135        let mut actual = 0;
1136        #[allow(
1137            clippy::undocumented_unsafe_blocks,
1138            reason = "Force documented unsafe blocks in Starnix"
1139        )]
1140        let status = unsafe {
1141            zxio::zxio_write_at(
1142                self.as_ptr(),
1143                offset,
1144                data.as_ptr() as *const c_void,
1145                data.len(),
1146                flags,
1147                &mut actual,
1148            )
1149        };
1150        zx::ok(status)?;
1151        Ok(actual)
1152    }
1153
1154    /// Performs a vectorized write at an offset, returning the number of bytes
1155    /// written from `data`.
1156    ///
1157    /// # Safety
1158    ///
1159    /// The caller must check the returned `Result` to make sure the buffers
1160    /// provided are valid. The caller must provide pointers that are compatible
1161    /// with the backing implementation of zxio.
1162    ///
1163    /// This call allows reading from arbitrary memory locations. It is up to the
1164    /// caller to make sure that calling this method does not result in undefined
1165    /// behaviour.
1166    pub unsafe fn writev_at(
1167        &self,
1168        offset: u64,
1169        data: &[zxio::zx_iovec],
1170    ) -> Result<usize, zx::Status> {
1171        let flags = zxio::zxio_flags_t::default();
1172        let mut actual = 0;
1173        #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1174        let status = unsafe {
1175            zxio::zxio_writev_at(
1176                self.as_ptr(),
1177                offset,
1178                data.as_ptr() as *const zxio::zx_iovec,
1179                data.len(),
1180                flags,
1181                &mut actual,
1182            )
1183        };
1184        zx::ok(status)?;
1185        Ok(actual)
1186    }
1187
1188    pub fn truncate(&self, length: u64) -> Result<(), zx::Status> {
1189        #[allow(
1190            clippy::undocumented_unsafe_blocks,
1191            reason = "Force documented unsafe blocks in Starnix"
1192        )]
1193        let status = unsafe { zxio::zxio_truncate(self.as_ptr(), length) };
1194        zx::ok(status)?;
1195        Ok(())
1196    }
1197
1198    pub fn seek(&self, seek_origin: SeekOrigin, offset: i64) -> Result<usize, zx::Status> {
1199        let mut result = 0;
1200        #[allow(
1201            clippy::undocumented_unsafe_blocks,
1202            reason = "Force documented unsafe blocks in Starnix"
1203        )]
1204        let status =
1205            unsafe { zxio::zxio_seek(self.as_ptr(), seek_origin.into(), offset, &mut result) };
1206        zx::ok(status)?;
1207        Ok(result)
1208    }
1209
1210    pub fn vmo_get(&self, flags: zx::VmarFlags) -> Result<zx::Vmo, zx::Status> {
1211        let mut vmo = 0;
1212        #[allow(
1213            clippy::undocumented_unsafe_blocks,
1214            reason = "Force documented unsafe blocks in Starnix"
1215        )]
1216        let status = unsafe { zxio::zxio_vmo_get(self.as_ptr(), flags.bits(), &mut vmo) };
1217        zx::ok(status)?;
1218        #[allow(
1219            clippy::undocumented_unsafe_blocks,
1220            reason = "Force documented unsafe blocks in Starnix"
1221        )]
1222        let handle = unsafe { zx::NullableHandle::from_raw(vmo) };
1223        Ok(zx::Vmo::from(handle))
1224    }
1225
1226    fn node_attributes_from_query(
1227        &self,
1228        query: zxio_node_attr_has_t,
1229        fsverity_root_hash: Option<&mut [u8; ZXIO_ROOT_HASH_LENGTH]>,
1230    ) -> zxio_node_attributes_t {
1231        if let Some(fsverity_root_hash) = fsverity_root_hash {
1232            zxio_node_attributes_t {
1233                has: query,
1234                fsverity_root_hash: fsverity_root_hash as *mut u8,
1235                ..Default::default()
1236            }
1237        } else {
1238            zxio_node_attributes_t { has: query, ..Default::default() }
1239        }
1240    }
1241
1242    pub fn attr_get(
1243        &self,
1244        query: zxio_node_attr_has_t,
1245    ) -> Result<zxio_node_attributes_t, zx::Status> {
1246        let mut attributes = self.node_attributes_from_query(query, None);
1247        #[allow(
1248            clippy::undocumented_unsafe_blocks,
1249            reason = "Force documented unsafe blocks in Starnix"
1250        )]
1251        let status = unsafe { zxio::zxio_attr_get(self.as_ptr(), &mut attributes) };
1252        zx::ok(status)?;
1253        Ok(attributes)
1254    }
1255
1256    // Call this function if access time needs to be updated before closing the node. For the case
1257    // where the underlying filesystem is unable to manage access time updates by itself (e.g.
1258    // because it cannot differentiate between a file read and write operation), Starnix is
1259    // responsible for informing the underlying filesystem that the node has been accessed and is
1260    // pending an access time update.
1261    pub fn close_and_update_access_time(self) -> Result<(), zx::Status> {
1262        let mut out_handle = zx::sys::ZX_HANDLE_INVALID;
1263        // SAFETY: This is okay as we have exclusive access to this Zxio object.
1264        let status = unsafe { zxio::zxio_release(self.as_ptr(), &mut out_handle) };
1265        zx::ok(status)?;
1266        let proxy = fio::NodeSynchronousProxy::from_channel(
1267            // SAFETY: `out_handle` extracted from `zxio_release` should be a valid handle.
1268            unsafe { zx::NullableHandle::from_raw(out_handle) }.into(),
1269        );
1270
1271        // Don't wait for response from `get_attributes` (by setting deadline to `INFINITE_PAST`).
1272        // We don't need to know any queried attributes, and ignore if this request fails.
1273        // Expect this to fail with a TIMED_OUT error. Timeouts are generally fatal and clients do
1274        // not expect to continue communications on a channel that is timing out.
1275        let _ = proxy.get_attributes(
1276            fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE,
1277            zx::MonotonicInstant::INFINITE_PAST,
1278        );
1279
1280        // The handle will be dropped when the proxy is dropped. `zxio_close` is called when self
1281        // is dropped.
1282        Ok(())
1283    }
1284
1285    /// Assumes that the caller has set `query.fsverity_root_hash` to true.
1286    pub fn attr_get_with_root_hash(
1287        &self,
1288        query: zxio_node_attr_has_t,
1289        fsverity_root_hash: &mut [u8; ZXIO_ROOT_HASH_LENGTH],
1290    ) -> Result<zxio_node_attributes_t, zx::Status> {
1291        let mut attributes = self.node_attributes_from_query(query, Some(fsverity_root_hash));
1292        #[allow(
1293            clippy::undocumented_unsafe_blocks,
1294            reason = "Force documented unsafe blocks in Starnix"
1295        )]
1296        let status = unsafe { zxio::zxio_attr_get(self.as_ptr(), &mut attributes) };
1297        clean_pointer_fields(&mut attributes);
1298        zx::ok(status)?;
1299        Ok(attributes)
1300    }
1301
1302    pub fn attr_set(&self, attributes: &zxio_node_attributes_t) -> Result<(), zx::Status> {
1303        validate_pointer_fields(attributes);
1304        #[allow(
1305            clippy::undocumented_unsafe_blocks,
1306            reason = "Force documented unsafe blocks in Starnix"
1307        )]
1308        let status = unsafe { zxio::zxio_attr_set(self.as_ptr(), attributes) };
1309        zx::ok(status)?;
1310        Ok(())
1311    }
1312
1313    pub fn enable_verity(&self, descriptor: &zxio_fsverity_descriptor_t) -> Result<(), zx::Status> {
1314        #[allow(
1315            clippy::undocumented_unsafe_blocks,
1316            reason = "Force documented unsafe blocks in Starnix"
1317        )]
1318        let status = unsafe { zxio::zxio_enable_verity(self.as_ptr(), descriptor) };
1319        zx::ok(status)?;
1320        Ok(())
1321    }
1322
1323    pub fn rename(
1324        &self,
1325        old_path: &str,
1326        new_directory: &Zxio,
1327        new_path: &str,
1328    ) -> Result<(), zx::Status> {
1329        let mut handle = zx::sys::ZX_HANDLE_INVALID;
1330        #[allow(
1331            clippy::undocumented_unsafe_blocks,
1332            reason = "Force documented unsafe blocks in Starnix"
1333        )]
1334        let status = unsafe { zxio::zxio_token_get(new_directory.as_ptr(), &mut handle) };
1335        zx::ok(status)?;
1336        #[allow(
1337            clippy::undocumented_unsafe_blocks,
1338            reason = "Force documented unsafe blocks in Starnix"
1339        )]
1340        let status = unsafe {
1341            zxio::zxio_rename(
1342                self.as_ptr(),
1343                old_path.as_ptr() as *const c_char,
1344                old_path.len(),
1345                handle,
1346                new_path.as_ptr() as *const c_char,
1347                new_path.len(),
1348            )
1349        };
1350        zx::ok(status)?;
1351        Ok(())
1352    }
1353
1354    pub fn wait_begin(
1355        &self,
1356        zxio_signals: zxio_signals_t,
1357    ) -> (zx::Unowned<'_, zx::NullableHandle>, zx::Signals) {
1358        let mut handle = zx::sys::ZX_HANDLE_INVALID;
1359        let mut zx_signals = zx::sys::ZX_SIGNAL_NONE;
1360        #[allow(
1361            clippy::undocumented_unsafe_blocks,
1362            reason = "Force documented unsafe blocks in Starnix"
1363        )]
1364        unsafe {
1365            zxio::zxio_wait_begin(self.as_ptr(), zxio_signals, &mut handle, &mut zx_signals)
1366        };
1367        #[allow(
1368            clippy::undocumented_unsafe_blocks,
1369            reason = "Force documented unsafe blocks in Starnix"
1370        )]
1371        let handle = unsafe { zx::Unowned::<zx::NullableHandle>::from_raw_handle(handle) };
1372        let signals = zx::Signals::from_bits_truncate(zx_signals);
1373        (handle, signals)
1374    }
1375
1376    pub fn wait_end(&self, signals: zx::Signals) -> zxio_signals_t {
1377        let mut zxio_signals = ZxioSignals::NONE.bits();
1378        #[allow(
1379            clippy::undocumented_unsafe_blocks,
1380            reason = "Force documented unsafe blocks in Starnix"
1381        )]
1382        unsafe {
1383            zxio::zxio_wait_end(self.as_ptr(), signals.bits(), &mut zxio_signals);
1384        }
1385        zxio_signals
1386    }
1387
1388    pub fn create_dirent_iterator(&self) -> Result<DirentIterator<'_>, zx::Status> {
1389        let mut zxio_iterator = Box::default();
1390        #[allow(
1391            clippy::undocumented_unsafe_blocks,
1392            reason = "Force documented unsafe blocks in Starnix"
1393        )]
1394        let status = unsafe { zxio::zxio_dirent_iterator_init(&mut *zxio_iterator, self.as_ptr()) };
1395        zx::ok(status)?;
1396        let iterator =
1397            DirentIterator { iterator: zxio_iterator, _directory: PhantomData, finished: false };
1398        Ok(iterator)
1399    }
1400
1401    pub fn connect(&self, addr: &[u8]) -> Result<Result<(), ZxioErrorCode>, zx::Status> {
1402        let mut out_code = 0;
1403        #[allow(
1404            clippy::undocumented_unsafe_blocks,
1405            reason = "Force documented unsafe blocks in Starnix"
1406        )]
1407        let status = unsafe {
1408            zxio::zxio_connect(
1409                self.as_ptr(),
1410                addr.as_ptr() as *const sockaddr,
1411                addr.len() as socklen_t,
1412                &mut out_code,
1413            )
1414        };
1415        zx::ok(status)?;
1416        match out_code {
1417            0 => Ok(Ok(())),
1418            _ => Ok(Err(ZxioErrorCode(out_code))),
1419        }
1420    }
1421
1422    pub fn bind(&self, addr: &[u8]) -> Result<Result<(), ZxioErrorCode>, zx::Status> {
1423        let mut out_code = 0;
1424        #[allow(
1425            clippy::undocumented_unsafe_blocks,
1426            reason = "Force documented unsafe blocks in Starnix"
1427        )]
1428        let status = unsafe {
1429            zxio::zxio_bind(
1430                self.as_ptr(),
1431                addr.as_ptr() as *const sockaddr,
1432                addr.len() as socklen_t,
1433                &mut out_code,
1434            )
1435        };
1436        zx::ok(status)?;
1437        match out_code {
1438            0 => Ok(Ok(())),
1439            _ => Ok(Err(ZxioErrorCode(out_code))),
1440        }
1441    }
1442
1443    pub fn listen(&self, backlog: i32) -> Result<Result<(), ZxioErrorCode>, zx::Status> {
1444        let mut out_code = 0;
1445        #[allow(
1446            clippy::undocumented_unsafe_blocks,
1447            reason = "Force documented unsafe blocks in Starnix"
1448        )]
1449        let status = unsafe { zxio::zxio_listen(self.as_ptr(), backlog as c_int, &mut out_code) };
1450        zx::ok(status)?;
1451        match out_code {
1452            0 => Ok(Ok(())),
1453            _ => Ok(Err(ZxioErrorCode(out_code))),
1454        }
1455    }
1456
1457    pub fn accept(&self) -> Result<Result<Zxio, ZxioErrorCode>, zx::Status> {
1458        let mut addrlen = std::mem::size_of::<sockaddr_storage>() as socklen_t;
1459        let mut addr = vec![0u8; addrlen as usize];
1460        let zxio = Zxio::default();
1461        let mut out_code = 0;
1462        #[allow(
1463            clippy::undocumented_unsafe_blocks,
1464            reason = "Force documented unsafe blocks in Starnix"
1465        )]
1466        let status = unsafe {
1467            zxio::zxio_accept(
1468                self.as_ptr(),
1469                addr.as_mut_ptr() as *mut sockaddr,
1470                &mut addrlen,
1471                zxio.as_storage_ptr(),
1472                &mut out_code,
1473            )
1474        };
1475        zx::ok(status)?;
1476        match out_code {
1477            0 => Ok(Ok(zxio)),
1478            _ => Ok(Err(ZxioErrorCode(out_code))),
1479        }
1480    }
1481
1482    pub fn getsockname(&self) -> Result<Result<Vec<u8>, ZxioErrorCode>, zx::Status> {
1483        let mut addrlen = std::mem::size_of::<sockaddr_storage>() as socklen_t;
1484        let mut addr = vec![0u8; addrlen as usize];
1485        let mut out_code = 0;
1486        #[allow(
1487            clippy::undocumented_unsafe_blocks,
1488            reason = "Force documented unsafe blocks in Starnix"
1489        )]
1490        let status = unsafe {
1491            zxio::zxio_getsockname(
1492                self.as_ptr(),
1493                addr.as_mut_ptr() as *mut sockaddr,
1494                &mut addrlen,
1495                &mut out_code,
1496            )
1497        };
1498        zx::ok(status)?;
1499        match out_code {
1500            0 => Ok(Ok(addr[..addrlen as usize].to_vec())),
1501            _ => Ok(Err(ZxioErrorCode(out_code))),
1502        }
1503    }
1504
1505    pub fn getpeername(&self) -> Result<Result<Vec<u8>, ZxioErrorCode>, zx::Status> {
1506        let mut addrlen = std::mem::size_of::<sockaddr_storage>() as socklen_t;
1507        let mut addr = vec![0u8; addrlen as usize];
1508        let mut out_code = 0;
1509        #[allow(
1510            clippy::undocumented_unsafe_blocks,
1511            reason = "Force documented unsafe blocks in Starnix"
1512        )]
1513        let status = unsafe {
1514            zxio::zxio_getpeername(
1515                self.as_ptr(),
1516                addr.as_mut_ptr() as *mut sockaddr,
1517                &mut addrlen,
1518                &mut out_code,
1519            )
1520        };
1521        zx::ok(status)?;
1522        match out_code {
1523            0 => Ok(Ok(addr[..addrlen as usize].to_vec())),
1524            _ => Ok(Err(ZxioErrorCode(out_code))),
1525        }
1526    }
1527
1528    pub fn getsockopt_slice(
1529        &self,
1530        level: u32,
1531        optname: u32,
1532        optval: &mut [u8],
1533    ) -> Result<Result<socklen_t, ZxioErrorCode>, zx::Status> {
1534        let mut optlen = optval.len() as socklen_t;
1535        let mut out_code = 0;
1536        #[allow(
1537            clippy::undocumented_unsafe_blocks,
1538            reason = "Force documented unsafe blocks in Starnix"
1539        )]
1540        let status = unsafe {
1541            zxio::zxio_getsockopt(
1542                self.as_ptr(),
1543                level as c_int,
1544                optname as c_int,
1545                optval.as_mut_ptr() as *mut c_void,
1546                &mut optlen,
1547                &mut out_code,
1548            )
1549        };
1550        zx::ok(status)?;
1551        match out_code {
1552            0 => Ok(Ok(optlen)),
1553            _ => Ok(Err(ZxioErrorCode(out_code))),
1554        }
1555    }
1556
1557    pub fn getsockopt(
1558        &self,
1559        level: u32,
1560        optname: u32,
1561        optlen: socklen_t,
1562    ) -> Result<Result<Vec<u8>, ZxioErrorCode>, zx::Status> {
1563        let mut optval = vec![0u8; optlen as usize];
1564        let result = self.getsockopt_slice(level, optname, &mut optval[..])?;
1565        Ok(result.map(|optlen| optval[..optlen as usize].to_vec()))
1566    }
1567
1568    pub fn setsockopt(
1569        &self,
1570        level: i32,
1571        optname: i32,
1572        optval: &[u8],
1573    ) -> Result<Result<(), ZxioErrorCode>, zx::Status> {
1574        let mut out_code = 0;
1575        #[allow(
1576            clippy::undocumented_unsafe_blocks,
1577            reason = "Force documented unsafe blocks in Starnix"
1578        )]
1579        let status = unsafe {
1580            zxio::zxio_setsockopt(
1581                self.as_ptr(),
1582                level,
1583                optname,
1584                optval.as_ptr() as *const c_void,
1585                optval.len() as socklen_t,
1586                &mut out_code,
1587            )
1588        };
1589        zx::ok(status)?;
1590        match out_code {
1591            0 => Ok(Ok(())),
1592            _ => Ok(Err(ZxioErrorCode(out_code))),
1593        }
1594    }
1595
1596    pub fn shutdown(
1597        &self,
1598        flags: ZxioShutdownFlags,
1599    ) -> Result<Result<(), ZxioErrorCode>, zx::Status> {
1600        let mut out_code = 0;
1601        #[allow(
1602            clippy::undocumented_unsafe_blocks,
1603            reason = "Force documented unsafe blocks in Starnix"
1604        )]
1605        let status = unsafe { zxio::zxio_shutdown(self.as_ptr(), flags.bits(), &mut out_code) };
1606        zx::ok(status)?;
1607        match out_code {
1608            0 => Ok(Ok(())),
1609            _ => Ok(Err(ZxioErrorCode(out_code))),
1610        }
1611    }
1612
1613    pub fn sendmsg(
1614        &self,
1615        addr: &mut [u8],
1616        buffer: &mut [zxio::iovec],
1617        cmsg: &[ControlMessage],
1618        flags: u32,
1619    ) -> Result<Result<usize, ZxioErrorCode>, zx::Status> {
1620        let mut msg = zxio::msghdr::default();
1621        msg.msg_name = match addr.len() {
1622            0 => std::ptr::null_mut() as *mut c_void,
1623            _ => addr.as_mut_ptr() as *mut c_void,
1624        };
1625        msg.msg_namelen = addr.len() as u32;
1626
1627        msg.msg_iovlen =
1628            i32::try_from(buffer.len()).map_err(|_: TryFromIntError| zx::Status::INVALID_ARGS)?;
1629        msg.msg_iov = buffer.as_mut_ptr();
1630
1631        let mut cmsg_buffer = serialize_control_messages(cmsg);
1632        msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void;
1633        msg.msg_controllen = cmsg_buffer.len() as u32;
1634
1635        let mut out_code = 0;
1636        let mut out_actual = 0;
1637
1638        #[allow(
1639            clippy::undocumented_unsafe_blocks,
1640            reason = "Force documented unsafe blocks in Starnix"
1641        )]
1642        let status = unsafe {
1643            zxio::zxio_sendmsg(self.as_ptr(), &msg, flags as c_int, &mut out_actual, &mut out_code)
1644        };
1645
1646        zx::ok(status)?;
1647        match out_code {
1648            0 => Ok(Ok(out_actual)),
1649            _ => Ok(Err(ZxioErrorCode(out_code))),
1650        }
1651    }
1652
1653    pub fn recvmsg(
1654        &self,
1655        buffer: &mut [zxio::iovec],
1656        flags: u32,
1657    ) -> Result<Result<RecvMessageInfo, ZxioErrorCode>, zx::Status> {
1658        let mut msg = msghdr::default();
1659        let mut addr = vec![0u8; std::mem::size_of::<sockaddr_storage>()];
1660        msg.msg_name = addr.as_mut_ptr() as *mut c_void;
1661        msg.msg_namelen = addr.len() as u32;
1662
1663        let max_buffer_capacity = buffer.iter().map(|v| v.iov_len).sum();
1664        msg.msg_iovlen =
1665            i32::try_from(buffer.len()).map_err(|_: TryFromIntError| zx::Status::INVALID_ARGS)?;
1666        msg.msg_iov = buffer.as_mut_ptr();
1667
1668        let mut cmsg_buffer = vec![0u8; MAX_CMSGS_BUFFER];
1669        msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void;
1670        msg.msg_controllen = cmsg_buffer.len() as u32;
1671
1672        let mut out_code = 0;
1673        let mut out_actual = 0;
1674        #[allow(
1675            clippy::undocumented_unsafe_blocks,
1676            reason = "Force documented unsafe blocks in Starnix"
1677        )]
1678        let status = unsafe {
1679            zxio::zxio_recvmsg(
1680                self.as_ptr(),
1681                &mut msg,
1682                flags as c_int,
1683                &mut out_actual,
1684                &mut out_code,
1685            )
1686        };
1687        zx::ok(status)?;
1688
1689        if out_code != 0 {
1690            return Ok(Err(ZxioErrorCode(out_code)));
1691        }
1692
1693        let control_messages = parse_control_messages(&cmsg_buffer[..msg.msg_controllen as usize]);
1694        Ok(Ok(RecvMessageInfo {
1695            address: addr[..msg.msg_namelen as usize].to_vec(),
1696            bytes_read: std::cmp::min(max_buffer_capacity, out_actual),
1697            message_length: out_actual,
1698            control_messages,
1699            flags: msg.msg_flags,
1700        }))
1701    }
1702
1703    pub fn read_link(&self) -> Result<&[u8], zx::Status> {
1704        let mut target = std::ptr::null();
1705        let mut target_len = 0;
1706        #[allow(
1707            clippy::undocumented_unsafe_blocks,
1708            reason = "Force documented unsafe blocks in Starnix"
1709        )]
1710        let status = unsafe { zxio::zxio_read_link(self.as_ptr(), &mut target, &mut target_len) };
1711        zx::ok(status)?;
1712        // SAFETY: target will live as long as the underlying zxio object lives.
1713        unsafe { Ok(std::slice::from_raw_parts(target, target_len)) }
1714    }
1715
1716    pub fn create_symlink(&self, name: &str, target: &[u8]) -> Result<Zxio, zx::Status> {
1717        let name = name.as_bytes();
1718        let zxio = Zxio::default();
1719        #[allow(
1720            clippy::undocumented_unsafe_blocks,
1721            reason = "Force documented unsafe blocks in Starnix"
1722        )]
1723        let status = unsafe {
1724            zxio::zxio_create_symlink(
1725                self.as_ptr(),
1726                name.as_ptr() as *const c_char,
1727                name.len(),
1728                target.as_ptr(),
1729                target.len(),
1730                zxio.as_storage_ptr(),
1731            )
1732        };
1733        zx::ok(status)?;
1734        Ok(zxio)
1735    }
1736
1737    pub fn xattr_list(&self) -> Result<Vec<Vec<u8>>, zx::Status> {
1738        unsafe extern "C" fn callback(context: *mut c_void, name: *const u8, name_len: usize) {
1739            #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1740            let out_names = unsafe { &mut *(context as *mut Vec<Vec<u8>>) };
1741            #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1742            let name_slice = unsafe { std::slice::from_raw_parts(name, name_len) };
1743            out_names.push(name_slice.to_vec());
1744        }
1745        let mut out_names = Vec::new();
1746        #[allow(
1747            clippy::undocumented_unsafe_blocks,
1748            reason = "Force documented unsafe blocks in Starnix"
1749        )]
1750        let status = unsafe {
1751            zxio::zxio_xattr_list(
1752                self.as_ptr(),
1753                Some(callback),
1754                &mut out_names as *mut _ as *mut c_void,
1755            )
1756        };
1757        zx::ok(status)?;
1758        Ok(out_names)
1759    }
1760
1761    pub fn xattr_get(&self, name: &[u8]) -> Result<Vec<u8>, zx::Status> {
1762        unsafe extern "C" fn callback(
1763            context: *mut c_void,
1764            data: zxio::zxio_xattr_data_t,
1765        ) -> zx_status_t {
1766            #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1767            let out_value = unsafe { &mut *(context as *mut Vec<u8>) };
1768            if data.data.is_null() {
1769                #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1770                let value_vmo = unsafe { zx::Unowned::<'_, zx::Vmo>::from_raw_handle(data.vmo) };
1771                match value_vmo.read_to_vec(0, data.len as u64) {
1772                    Ok(vec) => *out_value = vec,
1773                    Err(status) => return status.into_raw(),
1774                }
1775            } else {
1776                #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1777                let value_slice =
1778                    unsafe { std::slice::from_raw_parts(data.data as *mut u8, data.len) };
1779                out_value.extend_from_slice(value_slice);
1780            }
1781            zx::Status::OK.into_raw()
1782        }
1783        let mut out_value = Vec::new();
1784        #[allow(
1785            clippy::undocumented_unsafe_blocks,
1786            reason = "Force documented unsafe blocks in Starnix"
1787        )]
1788        let status = unsafe {
1789            zxio::zxio_xattr_get(
1790                self.as_ptr(),
1791                name.as_ptr(),
1792                name.len(),
1793                Some(callback),
1794                &mut out_value as *mut _ as *mut c_void,
1795            )
1796        };
1797        zx::ok(status)?;
1798        Ok(out_value)
1799    }
1800
1801    pub fn xattr_set(
1802        &self,
1803        name: &[u8],
1804        value: &[u8],
1805        mode: XattrSetMode,
1806    ) -> Result<(), zx::Status> {
1807        #[allow(
1808            clippy::undocumented_unsafe_blocks,
1809            reason = "Force documented unsafe blocks in Starnix"
1810        )]
1811        let status = unsafe {
1812            zxio::zxio_xattr_set(
1813                self.as_ptr(),
1814                name.as_ptr(),
1815                name.len(),
1816                value.as_ptr(),
1817                value.len(),
1818                mode as u32,
1819            )
1820        };
1821        zx::ok(status)
1822    }
1823
1824    pub fn xattr_remove(&self, name: &[u8]) -> Result<(), zx::Status> {
1825        #[allow(
1826            clippy::undocumented_unsafe_blocks,
1827            reason = "Force documented unsafe blocks in Starnix"
1828        )]
1829        zx::ok(unsafe { zxio::zxio_xattr_remove(self.as_ptr(), name.as_ptr(), name.len()) })
1830    }
1831
1832    pub fn link_into(&self, target_dir: &Zxio, name: &str) -> Result<(), zx::Status> {
1833        let mut handle = zx::sys::ZX_HANDLE_INVALID;
1834        #[allow(
1835            clippy::undocumented_unsafe_blocks,
1836            reason = "Force documented unsafe blocks in Starnix"
1837        )]
1838        zx::ok(unsafe { zxio::zxio_token_get(target_dir.as_ptr(), &mut handle) })?;
1839        #[allow(
1840            clippy::undocumented_unsafe_blocks,
1841            reason = "Force documented unsafe blocks in Starnix"
1842        )]
1843        zx::ok(unsafe {
1844            zxio::zxio_link_into(self.as_ptr(), handle, name.as_ptr() as *const c_char, name.len())
1845        })
1846    }
1847
1848    pub fn allocate(&self, offset: u64, len: u64, mode: AllocateMode) -> Result<(), zx::Status> {
1849        #[allow(
1850            clippy::undocumented_unsafe_blocks,
1851            reason = "Force documented unsafe blocks in Starnix"
1852        )]
1853        let status = unsafe { zxio::zxio_allocate(self.as_ptr(), offset, len, mode.bits()) };
1854        zx::ok(status)
1855    }
1856
1857    pub fn sync(&self) -> Result<(), zx::Status> {
1858        #[allow(
1859            clippy::undocumented_unsafe_blocks,
1860            reason = "Force documented unsafe blocks in Starnix"
1861        )]
1862        let status = unsafe { zxio::zxio_sync(self.as_ptr()) };
1863        zx::ok(status)
1864    }
1865
1866    pub fn close(&self) -> Result<(), zx::Status> {
1867        #[allow(
1868            clippy::undocumented_unsafe_blocks,
1869            reason = "Force documented unsafe blocks in Starnix"
1870        )]
1871        let status = unsafe { zxio::zxio_close(self.as_ptr()) };
1872        zx::ok(status)
1873    }
1874}
1875
1876impl Drop for ZxioStorage {
1877    fn drop(&mut self) {
1878        // `zxio_destroy` should be called once only when `ZxioStorage` is dropped, just before the
1879        // `zxio_storage_t` memory is deallocated. Operations on an upgraded `ZxioWeak`,
1880        // like WaitCanceler, will then always operate on a non-poisoned `zxio_t as long as the
1881        // `ZxioStorage` is alive.
1882        let zxio_ptr: *mut zxio::zxio_t = &mut self.storage.io;
1883        #[allow(
1884            clippy::undocumented_unsafe_blocks,
1885            reason = "Force documented unsafe blocks in Starnix"
1886        )]
1887        unsafe {
1888            zxio::zxio_destroy(zxio_ptr);
1889        };
1890    }
1891}
1892
1893impl Clone for Zxio {
1894    fn clone(&self) -> Self {
1895        Self { inner: self.inner.clone() }
1896    }
1897}
1898
1899enum NodeKind {
1900    File,
1901    Directory,
1902    Symlink,
1903    Unknown,
1904}
1905
1906impl From<fio::Representation> for NodeKind {
1907    fn from(representation: fio::Representation) -> Self {
1908        match representation {
1909            fio::Representation::File(_) => NodeKind::File,
1910            fio::Representation::Directory(_) => NodeKind::Directory,
1911            fio::Representation::Symlink(_) => NodeKind::Symlink,
1912            _ => NodeKind::Unknown,
1913        }
1914    }
1915}
1916
1917/// A fuchsia.io.Node along with its NodeInfoDeprecated.
1918///
1919/// The NodeInfoDeprecated provides information about the concrete protocol spoken by the
1920/// node.
1921struct DescribedNode {
1922    node: fio::NodeSynchronousProxy,
1923    kind: NodeKind,
1924}
1925
1926/// Open the given path in the given directory.
1927///
1928/// The semantics for the flags argument are defined by the
1929/// fuchsia.io/Directory.Open3 message.
1930///
1931/// This function adds FLAG_SEND_REPRESENTATION to the given flags and then blocks
1932/// until the directory describes the newly opened node.
1933///
1934/// Returns the opened Node, along with its NodeKind, or an error.
1935fn directory_open(
1936    directory: &fio::DirectorySynchronousProxy,
1937    path: &str,
1938    flags: fio::Flags,
1939    deadline: zx::MonotonicInstant,
1940) -> Result<DescribedNode, zx::Status> {
1941    let flags = flags | fio::Flags::FLAG_SEND_REPRESENTATION;
1942
1943    let (client_end, server_end) = zx::Channel::create();
1944    directory.open(path, flags, &Default::default(), server_end).map_err(|_| zx::Status::IO)?;
1945    let node = fio::NodeSynchronousProxy::new(client_end);
1946
1947    match node.wait_for_event(deadline).map_err(map_fidl_error)? {
1948        fio::NodeEvent::OnOpen_ { .. } => {
1949            panic!("Should never happen when sending FLAG_SEND_REPRESENTATION")
1950        }
1951        fio::NodeEvent::OnRepresentation { payload } => {
1952            Ok(DescribedNode { node, kind: payload.into() })
1953        }
1954        fio::NodeEvent::_UnknownEvent { .. } => Err(zx::Status::NOT_SUPPORTED),
1955    }
1956}
1957
1958/// Open a VMO at the given path in the given directory.
1959///
1960/// The semantics for the vmo_flags argument are defined by the
1961/// fuchsia.io/File.GetBackingMemory message (i.e., VmoFlags::*).
1962///
1963/// If the node at the given path is not a VMO, then this function returns
1964/// a zx::Status::IO error.
1965pub fn directory_open_vmo(
1966    directory: &fio::DirectorySynchronousProxy,
1967    path: &str,
1968    vmo_flags: fio::VmoFlags,
1969    deadline: zx::MonotonicInstant,
1970) -> Result<zx::Vmo, zx::Status> {
1971    let mut flags = fio::Flags::empty();
1972    if vmo_flags.contains(fio::VmoFlags::READ) {
1973        flags |= fio::PERM_READABLE;
1974    }
1975    if vmo_flags.contains(fio::VmoFlags::WRITE) {
1976        flags |= fio::PERM_WRITABLE;
1977    }
1978    if vmo_flags.contains(fio::VmoFlags::EXECUTE) {
1979        flags |= fio::PERM_EXECUTABLE;
1980    }
1981    let description = directory_open(directory, path, flags, deadline)?;
1982    let file = match description.kind {
1983        NodeKind::File => fio::FileSynchronousProxy::new(description.node.into_channel()),
1984        _ => return Err(zx::Status::IO),
1985    };
1986
1987    let vmo = file
1988        .get_backing_memory(vmo_flags, deadline)
1989        .map_err(map_fidl_error)?
1990        .map_err(zx::Status::from_raw)?;
1991    Ok(vmo)
1992}
1993
1994/// Read the content of the file at the given path in the given directory.
1995///
1996/// If the node at the given path is not a file, then this function returns
1997/// a zx::Status::IO error.
1998pub fn directory_read_file(
1999    directory: &fio::DirectorySynchronousProxy,
2000    path: &str,
2001    deadline: zx::MonotonicInstant,
2002) -> Result<Vec<u8>, zx::Status> {
2003    let description = directory_open(directory, path, fio::PERM_READABLE, deadline)?;
2004    let file = match description.kind {
2005        NodeKind::File => fio::FileSynchronousProxy::new(description.node.into_channel()),
2006        _ => return Err(zx::Status::IO),
2007    };
2008
2009    let mut result = Vec::new();
2010    loop {
2011        let mut data = file
2012            .read(fio::MAX_TRANSFER_SIZE, deadline)
2013            .map_err(map_fidl_error)?
2014            .map_err(zx::Status::from_raw)?;
2015        let finished = (data.len() as u64) < fio::MAX_TRANSFER_SIZE;
2016        result.append(&mut data);
2017        if finished {
2018            return Ok(result);
2019        }
2020    }
2021}
2022
2023/// Create an anonymous temp file in the given directory.
2024pub fn directory_create_tmp_file(
2025    directory: &fio::DirectorySynchronousProxy,
2026    flags: fio::Flags,
2027    deadline: zx::MonotonicInstant,
2028) -> Result<fio::FileSynchronousProxy, zx::Status> {
2029    let description = directory_open(
2030        directory,
2031        ".",
2032        flags
2033            | fio::PERM_WRITABLE
2034            | fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY
2035            | fio::Flags::PROTOCOL_FILE,
2036        deadline,
2037    )?;
2038    let file = match description.kind {
2039        NodeKind::File => fio::FileSynchronousProxy::new(description.node.into_channel()),
2040        _ => return Err(zx::Status::NOT_FILE),
2041    };
2042
2043    Ok(file)
2044}
2045
2046/// Open the given path in the given directory without blocking.
2047///
2048/// A zx::Channel to the opened node is returned (or an error).
2049///
2050/// It is an error to supply the FLAG_SEND_REPRESENTATION flag in flags.
2051///
2052/// This function will "succeed" even if the given path does not exist in the
2053/// given directory because this function does not wait for the directory to
2054/// confirm that the path exists.
2055pub fn directory_open_async(
2056    directory: &fio::DirectorySynchronousProxy,
2057    path: &str,
2058    flags: fio::Flags,
2059) -> Result<fio::DirectorySynchronousProxy, zx::Status> {
2060    if flags.intersects(fio::Flags::FLAG_SEND_REPRESENTATION) {
2061        return Err(zx::Status::INVALID_ARGS);
2062    }
2063
2064    let (proxy, server_end) = fidl::endpoints::create_sync_proxy::<fio::DirectoryMarker>();
2065    directory
2066        .open(path, flags, &Default::default(), server_end.into_channel())
2067        .map_err(|_| zx::Status::IO)?;
2068    Ok(proxy)
2069}
2070
2071/// Open a directory at the given path in the given directory without blocking.
2072///
2073/// This function adds the PROTOCOL_DIRECTORY flag to ensure that the open operation completes only
2074/// if the given path is actually a directory, which means clients can start using the returned
2075/// DirectorySynchronousProxy immediately without waiting for the server to complete the operation.
2076///
2077/// This function will "succeed" even if the given path does not exist in the
2078/// given directory or if the path is not a directory because this function
2079/// does not wait for the directory to confirm that the path exists and is a
2080/// directory.
2081pub fn directory_open_directory_async(
2082    directory: &fio::DirectorySynchronousProxy,
2083    path: &str,
2084    flags: fio::Flags,
2085) -> Result<fio::DirectorySynchronousProxy, zx::Status> {
2086    let flags = flags | fio::Flags::PROTOCOL_DIRECTORY;
2087    let proxy = directory_open_async(directory, path, flags)?;
2088    Ok(proxy)
2089}
2090
2091fn map_fidl_error(error: fidl::Error) -> zx::Status {
2092    match error {
2093        fidl::Error::ClientChannelClosed { status, .. } => status,
2094        _ => zx::Status::IO,
2095    }
2096}
2097
2098#[cfg(test)]
2099mod test {
2100    use super::*;
2101
2102    use anyhow::Error;
2103    use fidl::HandleBased;
2104    use fidl::endpoints::Proxy as _;
2105    use fuchsia_fs::directory;
2106    use {fidl_fuchsia_io as fio, fuchsia_async as fasync};
2107
2108    fn open_pkg() -> fio::DirectorySynchronousProxy {
2109        let pkg_proxy =
2110            directory::open_in_namespace("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
2111                .expect("failed to open /pkg");
2112        fio::DirectorySynchronousProxy::new(
2113            pkg_proxy
2114                .into_channel()
2115                .expect("failed to convert proxy into channel")
2116                .into_zx_channel(),
2117        )
2118    }
2119
2120    #[fasync::run_singlethreaded(test)]
2121    async fn test_directory_open() -> Result<(), Error> {
2122        let pkg = open_pkg();
2123        let description = directory_open(
2124            &pkg,
2125            "bin/syncio_lib_test",
2126            fio::PERM_READABLE,
2127            zx::MonotonicInstant::INFINITE,
2128        )?;
2129        assert!(match description.kind {
2130            NodeKind::File => true,
2131            _ => false,
2132        });
2133        Ok(())
2134    }
2135
2136    #[fasync::run_singlethreaded(test)]
2137    async fn test_directory_open_vmo() -> Result<(), Error> {
2138        let pkg = open_pkg();
2139        let vmo = directory_open_vmo(
2140            &pkg,
2141            "bin/syncio_lib_test",
2142            fio::VmoFlags::READ | fio::VmoFlags::EXECUTE,
2143            zx::MonotonicInstant::INFINITE,
2144        )?;
2145        assert!(!vmo.is_invalid_handle());
2146
2147        let info = vmo.basic_info()?;
2148        assert_eq!(zx::Rights::READ, info.rights & zx::Rights::READ);
2149        assert_eq!(zx::Rights::EXECUTE, info.rights & zx::Rights::EXECUTE);
2150        Ok(())
2151    }
2152
2153    #[fasync::run_singlethreaded(test)]
2154    async fn test_directory_read_file() -> Result<(), Error> {
2155        let pkg = open_pkg();
2156        let data =
2157            directory_read_file(&pkg, "bin/syncio_lib_test", zx::MonotonicInstant::INFINITE)?;
2158
2159        assert!(!data.is_empty());
2160        Ok(())
2161    }
2162
2163    #[fasync::run_singlethreaded(test)]
2164    async fn test_directory_open_directory_async() -> Result<(), Error> {
2165        let pkg = open_pkg();
2166        let bin =
2167            directory_open_directory_async(&pkg, "bin", fio::PERM_READABLE | fio::PERM_EXECUTABLE)?;
2168        let vmo = directory_open_vmo(
2169            &bin,
2170            "syncio_lib_test",
2171            fio::VmoFlags::READ | fio::VmoFlags::EXECUTE,
2172            zx::MonotonicInstant::INFINITE,
2173        )?;
2174        assert!(!vmo.is_invalid_handle());
2175
2176        let info = vmo.basic_info()?;
2177        assert_eq!(zx::Rights::READ, info.rights & zx::Rights::READ);
2178        assert_eq!(zx::Rights::EXECUTE, info.rights & zx::Rights::EXECUTE);
2179        Ok(())
2180    }
2181
2182    #[fasync::run_singlethreaded(test)]
2183    async fn test_directory_open_zxio_async() -> Result<(), Error> {
2184        let pkg_proxy =
2185            directory::open_in_namespace("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
2186                .expect("failed to open /pkg");
2187        let zx_channel = pkg_proxy
2188            .into_channel()
2189            .expect("failed to convert proxy into channel")
2190            .into_zx_channel();
2191        let storage = zxio::zxio_storage_t::default();
2192        #[allow(
2193            clippy::undocumented_unsafe_blocks,
2194            reason = "Force documented unsafe blocks in Starnix"
2195        )]
2196        let status = unsafe {
2197            zxio::zxio_create(
2198                zx_channel.into_raw(),
2199                &storage as *const zxio::zxio_storage_t as *mut zxio::zxio_storage_t,
2200            )
2201        };
2202        assert_eq!(status, zx::sys::ZX_OK);
2203        let io = &storage.io as *const zxio::zxio_t as *mut zxio::zxio_t;
2204        #[allow(
2205            clippy::undocumented_unsafe_blocks,
2206            reason = "Force documented unsafe blocks in Starnix"
2207        )]
2208        let close_status = unsafe { zxio::zxio_close(io) };
2209        assert_eq!(close_status, zx::sys::ZX_OK);
2210        #[allow(
2211            clippy::undocumented_unsafe_blocks,
2212            reason = "Force documented unsafe blocks in Starnix"
2213        )]
2214        unsafe {
2215            zxio::zxio_destroy(io);
2216        }
2217        Ok(())
2218    }
2219
2220    #[fuchsia::test]
2221    async fn test_directory_enumerate() -> Result<(), Error> {
2222        let pkg_dir_handle =
2223            directory::open_in_namespace("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
2224                .expect("failed to open /pkg")
2225                .into_channel()
2226                .expect("could not unwrap channel")
2227                .into_zx_channel()
2228                .into();
2229
2230        let io: Zxio = Zxio::create(pkg_dir_handle)?;
2231        let iter = io.create_dirent_iterator().expect("failed to create iterator");
2232        let expected_dir_names = vec![".", "bin", "lib", "meta"];
2233        let mut found_dir_names = iter
2234            .map(|e| {
2235                let dirent = e.expect("dirent");
2236                assert!(dirent.is_dir());
2237                std::str::from_utf8(&dirent.name).expect("name was not valid utf8").to_string()
2238            })
2239            .collect::<Vec<_>>();
2240        found_dir_names.sort();
2241        assert_eq!(expected_dir_names, found_dir_names);
2242
2243        // Check all entry inside bin are either "." or a file
2244        let bin_io = io
2245            .open("bin", fio::PERM_READABLE | fio::PERM_EXECUTABLE, Default::default())
2246            .expect("open");
2247        for entry in bin_io.create_dirent_iterator().expect("failed to create iterator") {
2248            let dirent = entry.expect("dirent");
2249            if dirent.name == "." {
2250                assert!(dirent.is_dir());
2251            } else {
2252                assert!(dirent.is_file());
2253            }
2254        }
2255
2256        Ok(())
2257    }
2258
2259    #[fuchsia::test]
2260    fn test_storage_allocator() {
2261        let mut out_storage = zxio_storage_t::default();
2262        let mut out_storage_ptr = &mut out_storage as *mut zxio_storage_t;
2263
2264        let mut out_context = Zxio::default();
2265        let mut out_context_ptr = &mut out_context as *mut Zxio;
2266
2267        #[allow(
2268            clippy::undocumented_unsafe_blocks,
2269            reason = "Force documented unsafe blocks in Starnix"
2270        )]
2271        let out = unsafe {
2272            storage_allocator(
2273                0 as zxio_object_type_t,
2274                &mut out_storage_ptr as *mut *mut zxio_storage_t,
2275                &mut out_context_ptr as *mut *mut Zxio as *mut *mut c_void,
2276            )
2277        };
2278        assert_eq!(out, zx::sys::ZX_OK);
2279    }
2280
2281    #[fuchsia::test]
2282    fn test_storage_allocator_bad_context() {
2283        let mut out_storage = zxio_storage_t::default();
2284        let mut out_storage_ptr = &mut out_storage as *mut zxio_storage_t;
2285
2286        let out_context = std::ptr::null_mut();
2287
2288        #[allow(
2289            clippy::undocumented_unsafe_blocks,
2290            reason = "Force documented unsafe blocks in Starnix"
2291        )]
2292        let out = unsafe {
2293            storage_allocator(
2294                0 as zxio_object_type_t,
2295                &mut out_storage_ptr as *mut *mut zxio_storage_t,
2296                out_context,
2297            )
2298        };
2299        assert_eq!(out, zx::sys::ZX_ERR_NO_MEMORY);
2300    }
2301}