Skip to main content

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    _pin: std::marker::PhantomPinned,
544}
545
546/// A handle to a zxio object.
547///
548/// Note: the underlying storage backing the object is pinned on the heap
549/// because it can contain self referential data.
550pub struct Zxio {
551    inner: Pin<Arc<ZxioStorage>>,
552}
553
554impl Default for Zxio {
555    fn default() -> Self {
556        Self { inner: Arc::pin(ZxioStorage::default()) }
557    }
558}
559
560impl std::fmt::Debug for Zxio {
561    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
562        f.debug_struct("Zxio").finish()
563    }
564}
565
566pub struct ZxioWeak(PinWeak<ZxioStorage>);
567
568impl ZxioWeak {
569    pub fn upgrade(&self) -> Option<Zxio> {
570        Some(Zxio { inner: self.0.upgrade()? })
571    }
572}
573
574/// A trait that provides functionality to connect a channel to a FIDL service.
575//
576// TODO(https://github.com/rust-lang/rust/issues/44291): allow clients to pass
577// in a more general function pointer (`fn(&str, zx::Channel) -> zx::Status`)
578// rather than having to implement this trait.
579pub trait ServiceConnector {
580    /// Returns a channel to the service named by `service_name`.
581    fn connect(service_name: &str) -> Result<&'static zx::Channel, zx::Status>;
582}
583
584/// Sets `provider_handle` to a handle to the service named by `service_name`.
585///
586/// This function is intended to be passed to zxio_socket().
587///
588/// SAFETY: Dereferences the raw pointers `service_name` and `provider_handle`.
589unsafe extern "C" fn service_connector<S: ServiceConnector>(
590    service_name: *const c_char,
591    provider_handle: *mut zx_handle_t,
592) -> zx_status_t {
593    let status: zx::Status = (|| {
594        #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
595        let service_name = unsafe { CStr::from_ptr(service_name) }
596            .to_str()
597            .map_err(|std::str::Utf8Error { .. }| zx::Status::INVALID_ARGS)?;
598
599        S::connect(service_name).map(|channel| {
600            #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
601            unsafe {
602                *provider_handle = channel.raw_handle()
603            };
604        })
605    })()
606    .into();
607    status.into_raw()
608}
609
610/// Sets `out_storage` as the zxio_storage of `out_context`.
611///
612/// This function is intended to be passed to zxio_socket().
613///
614/// SAFETY: Dereferences the raw pointer `out_storage`.
615unsafe extern "C" fn storage_allocator(
616    _type: zxio_object_type_t,
617    out_storage: *mut *mut zxio_storage_t,
618    out_context: *mut *mut c_void,
619) -> zx_status_t {
620    let zxio_ptr_ptr = out_context as *mut *mut zxio_storage_t;
621    let status: zx::Status = (|| {
622        #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
623        if let Some(zxio_ptr) = unsafe { zxio_ptr_ptr.as_mut() } {
624            #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
625            if let Some(zxio) = unsafe { zxio_ptr.as_mut() } {
626                #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
627                unsafe {
628                    *out_storage = zxio
629                };
630                return Ok(());
631            }
632        }
633        Err(zx::Status::NO_MEMORY)
634    })()
635    .into();
636    status.into_raw()
637}
638
639/// Ensures that no pointer fields have been set. Used to enforce usage of code paths that can
640/// ensure safety. Without this, anything passing a `zxio_node_attributes_t` from the caller to
641/// zxio should be marked unsafe.
642fn validate_pointer_fields(attrs: &zxio_node_attributes_t) {
643    // If you're reading this, and the safe path you want doesn't exist, then add one!
644    assert!(
645        attrs.fsverity_root_hash.is_null(),
646        "Passed in a pointer for the fsverity_root_hash that could not assure the lifetime."
647    );
648    assert!(
649        attrs.selinux_context.is_null(),
650        "Passed in a pointer for the selinux_context that could not assure the lifetime."
651    );
652}
653
654/// To be called before letting any `zxio_node_attributes_t` get passed back to the caller, ensuring
655/// that any pointers used in the call are cleaned up.
656fn clean_pointer_fields(attrs: &mut zxio_node_attributes_t) {
657    attrs.fsverity_root_hash = std::ptr::null_mut();
658    attrs.selinux_context = std::ptr::null_mut();
659}
660
661pub const ZXIO_ROOT_HASH_LENGTH: usize = 64;
662
663/// A transparent wrapper around the bindgen type.
664#[repr(transparent)]
665#[derive(IntoBytes, TryFromBytes, Immutable)]
666pub struct ZxioSocketMark(zxio_socket_mark_t);
667
668impl ZxioSocketMark {
669    fn new(domain: u8, value: u32) -> Self {
670        ZxioSocketMark(zxio_socket_mark_t { is_present: true, domain, value, ..Default::default() })
671    }
672
673    /// Creates a new socket mark representing the SO_MARK domain.
674    pub fn so_mark(mark: u32) -> Self {
675        Self::new(fidl_fuchsia_net::MARK_DOMAIN_SO_MARK as u8, mark)
676    }
677
678    /// Creates a new socket mark representing the uid domain.
679    pub fn uid(uid: u32) -> Self {
680        Self::new(fidl_fuchsia_net::MARK_DOMAIN_SOCKET_UID as u8, uid)
681    }
682}
683
684/// A transparent wrapper around the zxio_wake_group_token_t bindgen type.
685#[repr(transparent)]
686pub struct ZxioWakeGroupToken(zx_handle_t);
687
688impl ZxioWakeGroupToken {
689    /// Creates a wake group token from the provided handle to an event.
690    pub fn new(token: Option<zx::Event>) -> Self {
691        ZxioWakeGroupToken(token.map(zx::Event::into_raw).unwrap_or(zx::sys::ZX_HANDLE_INVALID))
692    }
693}
694
695/// Socket creation options that can be used.
696pub struct ZxioSocketCreationOptions<'a> {
697    pub marks: &'a mut [ZxioSocketMark],
698    pub wake_group: ZxioWakeGroupToken,
699}
700
701impl Zxio {
702    pub fn new_socket<S: ServiceConnector>(
703        domain: c_int,
704        socket_type: c_int,
705        protocol: c_int,
706        ZxioSocketCreationOptions { marks, wake_group }: ZxioSocketCreationOptions<'_>,
707    ) -> Result<Result<Self, ZxioErrorCode>, zx::Status> {
708        let zxio = Zxio::default();
709        let mut out_context = zxio.as_storage_ptr() as *mut c_void;
710        let mut out_code = 0;
711
712        let ZxioWakeGroupToken(wake_group) = wake_group;
713        let creation_opts = zxio::zxio_socket_creation_options {
714            num_marks: marks.len(),
715            marks: marks.as_mut_ptr() as *mut _,
716            wake_group,
717            ..Default::default()
718        };
719
720        #[allow(
721            clippy::undocumented_unsafe_blocks,
722            reason = "Force documented unsafe blocks in Starnix"
723        )]
724        let status = unsafe {
725            zxio::zxio_socket_with_options(
726                Some(service_connector::<S>),
727                domain,
728                socket_type,
729                protocol,
730                creation_opts,
731                Some(storage_allocator),
732                &mut out_context as *mut *mut c_void,
733                &mut out_code,
734            )
735        };
736        zx::ok(status)?;
737        match out_code {
738            0 => Ok(Ok(zxio)),
739            _ => Ok(Err(ZxioErrorCode(out_code))),
740        }
741    }
742
743    fn as_ptr(&self) -> *mut zxio::zxio_t {
744        &self.inner.storage.io as *const zxio::zxio_t as *mut zxio::zxio_t
745    }
746
747    fn as_storage_ptr(&self) -> *mut zxio::zxio_storage_t {
748        &self.inner.storage as *const zxio::zxio_storage_t as *mut zxio::zxio_storage_t
749    }
750
751    pub fn create(handle: zx::NullableHandle) -> Result<Zxio, zx::Status> {
752        let zxio = Zxio::default();
753        #[allow(
754            clippy::undocumented_unsafe_blocks,
755            reason = "Force documented unsafe blocks in Starnix"
756        )]
757        let status = unsafe { zxio::zxio_create(handle.into_raw(), zxio.as_storage_ptr()) };
758        zx::ok(status)?;
759        Ok(zxio)
760    }
761
762    pub fn release(self) -> Result<zx::NullableHandle, zx::Status> {
763        let mut handle = 0;
764        #[allow(
765            clippy::undocumented_unsafe_blocks,
766            reason = "Force documented unsafe blocks in Starnix"
767        )]
768        let status = unsafe { zxio::zxio_release(self.as_ptr(), &mut handle) };
769        zx::ok(status)?;
770        #[allow(
771            clippy::undocumented_unsafe_blocks,
772            reason = "Force documented unsafe blocks in Starnix"
773        )]
774        unsafe {
775            Ok(zx::NullableHandle::from_raw(handle))
776        }
777    }
778
779    pub fn open(
780        &self,
781        path: &str,
782        flags: fio::Flags,
783        mut options: ZxioOpenOptions<'_, '_>,
784    ) -> Result<Self, zx::Status> {
785        let zxio = Zxio::default();
786
787        let mut zxio_open_options = zxio::zxio_open_options::default();
788        zxio_open_options.inout_attr = match &mut options.attributes {
789            Some(a) => (*a) as *mut zxio_node_attributes_t,
790            None => std::ptr::null_mut(),
791        };
792        zxio_open_options.create_attr = match &options.create_attributes {
793            Some(a) => a as *const zxio_node_attributes_t,
794            None => std::ptr::null_mut(),
795        };
796
797        #[allow(
798            clippy::undocumented_unsafe_blocks,
799            reason = "Force documented unsafe blocks in Starnix"
800        )]
801        let status = unsafe {
802            zxio::zxio_open(
803                self.as_ptr(),
804                path.as_ptr() as *const c_char,
805                path.len(),
806                flags.bits(),
807                &zxio_open_options,
808                zxio.as_storage_ptr(),
809            )
810        };
811        options.init_context_from_read();
812        if let Some(attributes) = options.attributes {
813            clean_pointer_fields(attributes);
814        }
815        zx::ok(status)?;
816        Ok(zxio)
817    }
818
819    pub fn create_with_on_representation(
820        handle: zx::NullableHandle,
821        attributes: Option<&mut zxio_node_attributes_t>,
822    ) -> Result<Zxio, zx::Status> {
823        if let Some(attr) = &attributes {
824            validate_pointer_fields(attr);
825        }
826        let zxio = Zxio::default();
827        #[allow(
828            clippy::undocumented_unsafe_blocks,
829            reason = "Force documented unsafe blocks in Starnix"
830        )]
831        let status = unsafe {
832            zxio::zxio_create_with_on_representation(
833                handle.into_raw(),
834                attributes.map(|a| a as *mut _).unwrap_or(std::ptr::null_mut()),
835                zxio.as_storage_ptr(),
836            )
837        };
838        zx::ok(status)?;
839        Ok(zxio)
840    }
841
842    /// Opens a limited node connection (similar to O_PATH).
843    pub fn open_node(
844        &self,
845        path: &str,
846        flags: fio::Flags,
847        attributes: Option<&mut zxio_node_attributes_t>,
848    ) -> Result<Self, zx::Status> {
849        let zxio = Zxio::default();
850
851        #[allow(
852            clippy::undocumented_unsafe_blocks,
853            reason = "Force documented unsafe blocks in Starnix"
854        )]
855        let status = unsafe {
856            zxio::zxio_open(
857                self.as_ptr(),
858                path.as_ptr() as *const c_char,
859                path.len(),
860                // `PROTOCOL_NODE` takes precedence over over protocols. If other protocols are
861                // specified as well, it will be used to validate the target node type.
862                (flags | fio::Flags::PROTOCOL_NODE).bits(),
863                &zxio::zxio_open_options {
864                    inout_attr: attributes.map(|a| a as *mut _).unwrap_or(std::ptr::null_mut()),
865                    ..Default::default()
866                },
867                zxio.as_storage_ptr(),
868            )
869        };
870        zx::ok(status)?;
871        Ok(zxio)
872    }
873
874    pub fn unlink(&self, name: &str, flags: fio::UnlinkFlags) -> Result<(), zx::Status> {
875        let flags_bits = flags.bits().try_into().map_err(|_| zx::Status::INVALID_ARGS)?;
876        #[allow(
877            clippy::undocumented_unsafe_blocks,
878            reason = "Force documented unsafe blocks in Starnix"
879        )]
880        let status = unsafe {
881            zxio::zxio_unlink(self.as_ptr(), name.as_ptr() as *const c_char, name.len(), flags_bits)
882        };
883        zx::ok(status)
884    }
885
886    pub fn read(&self, data: &mut [u8]) -> Result<usize, zx::Status> {
887        let flags = zxio::zxio_flags_t::default();
888        let mut actual = 0usize;
889        #[allow(
890            clippy::undocumented_unsafe_blocks,
891            reason = "Force documented unsafe blocks in Starnix"
892        )]
893        let status = unsafe {
894            zxio::zxio_read(
895                self.as_ptr(),
896                data.as_ptr() as *mut c_void,
897                data.len(),
898                flags,
899                &mut actual,
900            )
901        };
902        zx::ok(status)?;
903        Ok(actual)
904    }
905
906    /// Performs a vectorized read, returning the number of bytes read to `data`.
907    ///
908    /// # Safety
909    ///
910    /// The caller must check the returned `Result` to make sure the buffers
911    /// provided are valid. The caller must provide pointers that are compatible
912    /// with the backing implementation of zxio.
913    ///
914    /// This call allows writing to arbitrary memory locations. It is up to the
915    /// caller to make sure that calling this method does not result in undefined
916    /// behaviour.
917    pub unsafe fn readv(&self, data: &[zxio::zx_iovec]) -> Result<usize, zx::Status> {
918        let flags = zxio::zxio_flags_t::default();
919        let mut actual = 0usize;
920        #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
921        let status = unsafe {
922            zxio::zxio_readv(
923                self.as_ptr(),
924                data.as_ptr() as *const zxio::zx_iovec,
925                data.len(),
926                flags,
927                &mut actual,
928            )
929        };
930        zx::ok(status)?;
931        Ok(actual)
932    }
933
934    pub fn deep_clone(&self) -> Result<Zxio, zx::Status> {
935        Zxio::create(self.clone_handle()?)
936    }
937
938    pub fn clone_handle(&self) -> Result<zx::NullableHandle, zx::Status> {
939        let mut handle = 0;
940        #[allow(
941            clippy::undocumented_unsafe_blocks,
942            reason = "Force documented unsafe blocks in Starnix"
943        )]
944        let status = unsafe { zxio::zxio_clone(self.as_ptr(), &mut handle) };
945        zx::ok(status)?;
946        #[allow(
947            clippy::undocumented_unsafe_blocks,
948            reason = "Force documented unsafe blocks in Starnix"
949        )]
950        unsafe {
951            Ok(zx::NullableHandle::from_raw(handle))
952        }
953    }
954
955    pub fn downgrade(&self) -> ZxioWeak {
956        ZxioWeak(PinWeak::downgrade(self.inner.clone()))
957    }
958
959    pub fn read_at(&self, offset: u64, data: &mut [u8]) -> Result<usize, zx::Status> {
960        let flags = zxio::zxio_flags_t::default();
961        let mut actual = 0usize;
962        #[allow(
963            clippy::undocumented_unsafe_blocks,
964            reason = "Force documented unsafe blocks in Starnix"
965        )]
966        let status = unsafe {
967            zxio::zxio_read_at(
968                self.as_ptr(),
969                offset,
970                data.as_ptr() as *mut c_void,
971                data.len(),
972                flags,
973                &mut actual,
974            )
975        };
976        zx::ok(status)?;
977        Ok(actual)
978    }
979
980    /// Performs a vectorized read at an offset, returning the number of bytes
981    /// read to `data`.
982    ///
983    /// # Safety
984    ///
985    /// The caller must check the returned `Result` to make sure the buffers
986    /// provided are valid. The caller must provide pointers that are compatible
987    /// with the backing implementation of zxio.
988    ///
989    /// This call allows writing to arbitrary memory locations. It is up to the
990    /// caller to make sure that calling this method does not result in undefined
991    /// behaviour.
992    pub unsafe fn readv_at(
993        &self,
994        offset: u64,
995        data: &[zxio::zx_iovec],
996    ) -> Result<usize, zx::Status> {
997        let flags = zxio::zxio_flags_t::default();
998        let mut actual = 0usize;
999        #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1000        let status = unsafe {
1001            zxio::zxio_readv_at(
1002                self.as_ptr(),
1003                offset,
1004                data.as_ptr() as *const zxio::zx_iovec,
1005                data.len(),
1006                flags,
1007                &mut actual,
1008            )
1009        };
1010        zx::ok(status)?;
1011        Ok(actual)
1012    }
1013
1014    pub fn write(&self, data: &[u8]) -> Result<usize, zx::Status> {
1015        let flags = zxio::zxio_flags_t::default();
1016        let mut actual = 0;
1017        #[allow(
1018            clippy::undocumented_unsafe_blocks,
1019            reason = "Force documented unsafe blocks in Starnix"
1020        )]
1021        let status = unsafe {
1022            zxio::zxio_write(
1023                self.as_ptr(),
1024                data.as_ptr() as *const c_void,
1025                data.len(),
1026                flags,
1027                &mut actual,
1028            )
1029        };
1030        zx::ok(status)?;
1031        Ok(actual)
1032    }
1033
1034    /// Performs a vectorized write, returning the number of bytes written from
1035    /// `data`.
1036    ///
1037    /// # Safety
1038    ///
1039    /// The caller must check the returned `Result` to make sure the buffers
1040    /// provided are valid. The caller must provide pointers that are compatible
1041    /// with the backing implementation of zxio.
1042    ///
1043    /// This call allows reading from arbitrary memory locations. It is up to the
1044    /// caller to make sure that calling this method does not result in undefined
1045    /// behaviour.
1046    pub unsafe fn writev(&self, data: &[zxio::zx_iovec]) -> Result<usize, zx::Status> {
1047        let flags = zxio::zxio_flags_t::default();
1048        let mut actual = 0;
1049        #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1050        let status = unsafe {
1051            zxio::zxio_writev(
1052                self.as_ptr(),
1053                data.as_ptr() as *const zxio::zx_iovec,
1054                data.len(),
1055                flags,
1056                &mut actual,
1057            )
1058        };
1059        zx::ok(status)?;
1060        Ok(actual)
1061    }
1062
1063    pub fn write_at(&self, offset: u64, data: &[u8]) -> Result<usize, zx::Status> {
1064        let flags = zxio::zxio_flags_t::default();
1065        let mut actual = 0;
1066        #[allow(
1067            clippy::undocumented_unsafe_blocks,
1068            reason = "Force documented unsafe blocks in Starnix"
1069        )]
1070        let status = unsafe {
1071            zxio::zxio_write_at(
1072                self.as_ptr(),
1073                offset,
1074                data.as_ptr() as *const c_void,
1075                data.len(),
1076                flags,
1077                &mut actual,
1078            )
1079        };
1080        zx::ok(status)?;
1081        Ok(actual)
1082    }
1083
1084    /// Performs a vectorized write at an offset, returning the number of bytes
1085    /// written from `data`.
1086    ///
1087    /// # Safety
1088    ///
1089    /// The caller must check the returned `Result` to make sure the buffers
1090    /// provided are valid. The caller must provide pointers that are compatible
1091    /// with the backing implementation of zxio.
1092    ///
1093    /// This call allows reading from arbitrary memory locations. It is up to the
1094    /// caller to make sure that calling this method does not result in undefined
1095    /// behaviour.
1096    pub unsafe fn writev_at(
1097        &self,
1098        offset: u64,
1099        data: &[zxio::zx_iovec],
1100    ) -> Result<usize, zx::Status> {
1101        let flags = zxio::zxio_flags_t::default();
1102        let mut actual = 0;
1103        #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1104        let status = unsafe {
1105            zxio::zxio_writev_at(
1106                self.as_ptr(),
1107                offset,
1108                data.as_ptr() as *const zxio::zx_iovec,
1109                data.len(),
1110                flags,
1111                &mut actual,
1112            )
1113        };
1114        zx::ok(status)?;
1115        Ok(actual)
1116    }
1117
1118    pub fn truncate(&self, length: u64) -> Result<(), zx::Status> {
1119        #[allow(
1120            clippy::undocumented_unsafe_blocks,
1121            reason = "Force documented unsafe blocks in Starnix"
1122        )]
1123        let status = unsafe { zxio::zxio_truncate(self.as_ptr(), length) };
1124        zx::ok(status)?;
1125        Ok(())
1126    }
1127
1128    pub fn seek(&self, seek_origin: SeekOrigin, offset: i64) -> Result<usize, zx::Status> {
1129        let mut result = 0;
1130        #[allow(
1131            clippy::undocumented_unsafe_blocks,
1132            reason = "Force documented unsafe blocks in Starnix"
1133        )]
1134        let status =
1135            unsafe { zxio::zxio_seek(self.as_ptr(), seek_origin.into(), offset, &mut result) };
1136        zx::ok(status)?;
1137        Ok(result)
1138    }
1139
1140    pub fn vmo_get(&self, flags: zx::VmarFlags) -> Result<zx::Vmo, zx::Status> {
1141        let mut vmo = 0;
1142        #[allow(
1143            clippy::undocumented_unsafe_blocks,
1144            reason = "Force documented unsafe blocks in Starnix"
1145        )]
1146        let status = unsafe { zxio::zxio_vmo_get(self.as_ptr(), flags.bits(), &mut vmo) };
1147        zx::ok(status)?;
1148        #[allow(
1149            clippy::undocumented_unsafe_blocks,
1150            reason = "Force documented unsafe blocks in Starnix"
1151        )]
1152        let handle = unsafe { zx::NullableHandle::from_raw(vmo) };
1153        Ok(zx::Vmo::from(handle))
1154    }
1155
1156    fn node_attributes_from_query(
1157        &self,
1158        query: zxio_node_attr_has_t,
1159        fsverity_root_hash: Option<&mut [u8; ZXIO_ROOT_HASH_LENGTH]>,
1160    ) -> zxio_node_attributes_t {
1161        if let Some(fsverity_root_hash) = fsverity_root_hash {
1162            zxio_node_attributes_t {
1163                has: query,
1164                fsverity_root_hash: fsverity_root_hash as *mut u8,
1165                ..Default::default()
1166            }
1167        } else {
1168            zxio_node_attributes_t { has: query, ..Default::default() }
1169        }
1170    }
1171
1172    pub fn attr_get(
1173        &self,
1174        query: zxio_node_attr_has_t,
1175    ) -> Result<zxio_node_attributes_t, zx::Status> {
1176        let mut attributes = self.node_attributes_from_query(query, None);
1177        #[allow(
1178            clippy::undocumented_unsafe_blocks,
1179            reason = "Force documented unsafe blocks in Starnix"
1180        )]
1181        let status = unsafe { zxio::zxio_attr_get(self.as_ptr(), &mut attributes) };
1182        zx::ok(status)?;
1183        Ok(attributes)
1184    }
1185
1186    // Call this function if access time needs to be updated before closing the node. For the case
1187    // where the underlying filesystem is unable to manage access time updates by itself (e.g.
1188    // because it cannot differentiate between a file read and write operation), Starnix is
1189    // responsible for informing the underlying filesystem that the node has been accessed and is
1190    // pending an access time update.
1191    pub fn close_and_update_access_time(self) -> Result<(), zx::Status> {
1192        let mut out_handle = zx::sys::ZX_HANDLE_INVALID;
1193        // SAFETY: This is okay as we have exclusive access to this Zxio object.
1194        let status = unsafe { zxio::zxio_release(self.as_ptr(), &mut out_handle) };
1195        zx::ok(status)?;
1196        let proxy = fio::NodeSynchronousProxy::from_channel(
1197            // SAFETY: `out_handle` extracted from `zxio_release` should be a valid handle.
1198            unsafe { zx::NullableHandle::from_raw(out_handle) }.into(),
1199        );
1200
1201        // Don't wait for response from `get_attributes` (by setting deadline to `INFINITE_PAST`).
1202        // We don't need to know any queried attributes, and ignore if this request fails.
1203        // Expect this to fail with a TIMED_OUT error. Timeouts are generally fatal and clients do
1204        // not expect to continue communications on a channel that is timing out.
1205        let _ = proxy.get_attributes(
1206            fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE,
1207            zx::MonotonicInstant::INFINITE_PAST,
1208        );
1209
1210        // The handle will be dropped when the proxy is dropped. `zxio_close` is called when self
1211        // is dropped.
1212        Ok(())
1213    }
1214
1215    /// Assumes that the caller has set `query.fsverity_root_hash` to true.
1216    pub fn attr_get_with_root_hash(
1217        &self,
1218        query: zxio_node_attr_has_t,
1219        fsverity_root_hash: &mut [u8; ZXIO_ROOT_HASH_LENGTH],
1220    ) -> Result<zxio_node_attributes_t, zx::Status> {
1221        let mut attributes = self.node_attributes_from_query(query, Some(fsverity_root_hash));
1222        #[allow(
1223            clippy::undocumented_unsafe_blocks,
1224            reason = "Force documented unsafe blocks in Starnix"
1225        )]
1226        let status = unsafe { zxio::zxio_attr_get(self.as_ptr(), &mut attributes) };
1227        clean_pointer_fields(&mut attributes);
1228        zx::ok(status)?;
1229        Ok(attributes)
1230    }
1231
1232    pub fn attr_set(&self, attributes: &zxio_node_attributes_t) -> Result<(), zx::Status> {
1233        validate_pointer_fields(attributes);
1234        #[allow(
1235            clippy::undocumented_unsafe_blocks,
1236            reason = "Force documented unsafe blocks in Starnix"
1237        )]
1238        let status = unsafe { zxio::zxio_attr_set(self.as_ptr(), attributes) };
1239        zx::ok(status)?;
1240        Ok(())
1241    }
1242
1243    pub fn enable_verity(&self, descriptor: &zxio_fsverity_descriptor_t) -> Result<(), zx::Status> {
1244        #[allow(
1245            clippy::undocumented_unsafe_blocks,
1246            reason = "Force documented unsafe blocks in Starnix"
1247        )]
1248        let status = unsafe { zxio::zxio_enable_verity(self.as_ptr(), descriptor) };
1249        zx::ok(status)?;
1250        Ok(())
1251    }
1252
1253    pub fn rename(
1254        &self,
1255        old_path: &str,
1256        new_directory: &Zxio,
1257        new_path: &str,
1258    ) -> Result<(), zx::Status> {
1259        let mut handle = zx::sys::ZX_HANDLE_INVALID;
1260        #[allow(
1261            clippy::undocumented_unsafe_blocks,
1262            reason = "Force documented unsafe blocks in Starnix"
1263        )]
1264        let status = unsafe { zxio::zxio_token_get(new_directory.as_ptr(), &mut handle) };
1265        zx::ok(status)?;
1266        #[allow(
1267            clippy::undocumented_unsafe_blocks,
1268            reason = "Force documented unsafe blocks in Starnix"
1269        )]
1270        let status = unsafe {
1271            zxio::zxio_rename(
1272                self.as_ptr(),
1273                old_path.as_ptr() as *const c_char,
1274                old_path.len(),
1275                handle,
1276                new_path.as_ptr() as *const c_char,
1277                new_path.len(),
1278            )
1279        };
1280        zx::ok(status)?;
1281        Ok(())
1282    }
1283
1284    pub fn wait_begin(
1285        &self,
1286        zxio_signals: zxio_signals_t,
1287    ) -> (zx::Unowned<'_, zx::NullableHandle>, zx::Signals) {
1288        let mut handle = zx::sys::ZX_HANDLE_INVALID;
1289        let mut zx_signals = zx::sys::ZX_SIGNAL_NONE;
1290        #[allow(
1291            clippy::undocumented_unsafe_blocks,
1292            reason = "Force documented unsafe blocks in Starnix"
1293        )]
1294        unsafe {
1295            zxio::zxio_wait_begin(self.as_ptr(), zxio_signals, &mut handle, &mut zx_signals)
1296        };
1297        #[allow(
1298            clippy::undocumented_unsafe_blocks,
1299            reason = "Force documented unsafe blocks in Starnix"
1300        )]
1301        let handle = unsafe { zx::Unowned::<zx::NullableHandle>::from_raw_handle(handle) };
1302        let signals = zx::Signals::from_bits_truncate(zx_signals);
1303        (handle, signals)
1304    }
1305
1306    pub fn wait_end(&self, signals: zx::Signals) -> zxio_signals_t {
1307        let mut zxio_signals = ZxioSignals::NONE.bits();
1308        #[allow(
1309            clippy::undocumented_unsafe_blocks,
1310            reason = "Force documented unsafe blocks in Starnix"
1311        )]
1312        unsafe {
1313            zxio::zxio_wait_end(self.as_ptr(), signals.bits(), &mut zxio_signals);
1314        }
1315        zxio_signals
1316    }
1317
1318    pub fn create_dirent_iterator(&self) -> Result<DirentIterator<'_>, zx::Status> {
1319        let mut zxio_iterator = Box::default();
1320        #[allow(
1321            clippy::undocumented_unsafe_blocks,
1322            reason = "Force documented unsafe blocks in Starnix"
1323        )]
1324        let status = unsafe { zxio::zxio_dirent_iterator_init(&mut *zxio_iterator, self.as_ptr()) };
1325        zx::ok(status)?;
1326        let iterator =
1327            DirentIterator { iterator: zxio_iterator, _directory: PhantomData, finished: false };
1328        Ok(iterator)
1329    }
1330
1331    pub fn connect(&self, addr: &[u8]) -> Result<Result<(), ZxioErrorCode>, zx::Status> {
1332        let mut out_code = 0;
1333        #[allow(
1334            clippy::undocumented_unsafe_blocks,
1335            reason = "Force documented unsafe blocks in Starnix"
1336        )]
1337        let status = unsafe {
1338            zxio::zxio_connect(
1339                self.as_ptr(),
1340                addr.as_ptr() as *const sockaddr,
1341                addr.len() as socklen_t,
1342                &mut out_code,
1343            )
1344        };
1345        zx::ok(status)?;
1346        match out_code {
1347            0 => Ok(Ok(())),
1348            _ => Ok(Err(ZxioErrorCode(out_code))),
1349        }
1350    }
1351
1352    pub fn bind(&self, addr: &[u8]) -> Result<Result<(), ZxioErrorCode>, zx::Status> {
1353        let mut out_code = 0;
1354        #[allow(
1355            clippy::undocumented_unsafe_blocks,
1356            reason = "Force documented unsafe blocks in Starnix"
1357        )]
1358        let status = unsafe {
1359            zxio::zxio_bind(
1360                self.as_ptr(),
1361                addr.as_ptr() as *const sockaddr,
1362                addr.len() as socklen_t,
1363                &mut out_code,
1364            )
1365        };
1366        zx::ok(status)?;
1367        match out_code {
1368            0 => Ok(Ok(())),
1369            _ => Ok(Err(ZxioErrorCode(out_code))),
1370        }
1371    }
1372
1373    pub fn listen(&self, backlog: i32) -> Result<Result<(), ZxioErrorCode>, zx::Status> {
1374        let mut out_code = 0;
1375        #[allow(
1376            clippy::undocumented_unsafe_blocks,
1377            reason = "Force documented unsafe blocks in Starnix"
1378        )]
1379        let status = unsafe { zxio::zxio_listen(self.as_ptr(), backlog as c_int, &mut out_code) };
1380        zx::ok(status)?;
1381        match out_code {
1382            0 => Ok(Ok(())),
1383            _ => Ok(Err(ZxioErrorCode(out_code))),
1384        }
1385    }
1386
1387    pub fn accept(&self) -> Result<Result<Zxio, ZxioErrorCode>, zx::Status> {
1388        let mut addrlen = std::mem::size_of::<sockaddr_storage>() as socklen_t;
1389        let mut addr = vec![0u8; addrlen as usize];
1390        let zxio = Zxio::default();
1391        let mut out_code = 0;
1392        #[allow(
1393            clippy::undocumented_unsafe_blocks,
1394            reason = "Force documented unsafe blocks in Starnix"
1395        )]
1396        let status = unsafe {
1397            zxio::zxio_accept(
1398                self.as_ptr(),
1399                addr.as_mut_ptr() as *mut sockaddr,
1400                &mut addrlen,
1401                zxio.as_storage_ptr(),
1402                &mut out_code,
1403            )
1404        };
1405        zx::ok(status)?;
1406        match out_code {
1407            0 => Ok(Ok(zxio)),
1408            _ => Ok(Err(ZxioErrorCode(out_code))),
1409        }
1410    }
1411
1412    pub fn getsockname(&self) -> Result<Result<Vec<u8>, ZxioErrorCode>, zx::Status> {
1413        let mut addrlen = std::mem::size_of::<sockaddr_storage>() as socklen_t;
1414        let mut addr = vec![0u8; addrlen as usize];
1415        let mut out_code = 0;
1416        #[allow(
1417            clippy::undocumented_unsafe_blocks,
1418            reason = "Force documented unsafe blocks in Starnix"
1419        )]
1420        let status = unsafe {
1421            zxio::zxio_getsockname(
1422                self.as_ptr(),
1423                addr.as_mut_ptr() as *mut sockaddr,
1424                &mut addrlen,
1425                &mut out_code,
1426            )
1427        };
1428        zx::ok(status)?;
1429        match out_code {
1430            0 => Ok(Ok(addr[..addrlen as usize].to_vec())),
1431            _ => Ok(Err(ZxioErrorCode(out_code))),
1432        }
1433    }
1434
1435    pub fn getpeername(&self) -> Result<Result<Vec<u8>, ZxioErrorCode>, zx::Status> {
1436        let mut addrlen = std::mem::size_of::<sockaddr_storage>() as socklen_t;
1437        let mut addr = vec![0u8; addrlen as usize];
1438        let mut out_code = 0;
1439        #[allow(
1440            clippy::undocumented_unsafe_blocks,
1441            reason = "Force documented unsafe blocks in Starnix"
1442        )]
1443        let status = unsafe {
1444            zxio::zxio_getpeername(
1445                self.as_ptr(),
1446                addr.as_mut_ptr() as *mut sockaddr,
1447                &mut addrlen,
1448                &mut out_code,
1449            )
1450        };
1451        zx::ok(status)?;
1452        match out_code {
1453            0 => Ok(Ok(addr[..addrlen as usize].to_vec())),
1454            _ => Ok(Err(ZxioErrorCode(out_code))),
1455        }
1456    }
1457
1458    pub fn getsockopt_slice(
1459        &self,
1460        level: u32,
1461        optname: u32,
1462        optval: &mut [u8],
1463    ) -> Result<Result<socklen_t, ZxioErrorCode>, zx::Status> {
1464        let mut optlen = optval.len() as socklen_t;
1465        let mut out_code = 0;
1466        #[allow(
1467            clippy::undocumented_unsafe_blocks,
1468            reason = "Force documented unsafe blocks in Starnix"
1469        )]
1470        let status = unsafe {
1471            zxio::zxio_getsockopt(
1472                self.as_ptr(),
1473                level as c_int,
1474                optname as c_int,
1475                optval.as_mut_ptr() as *mut c_void,
1476                &mut optlen,
1477                &mut out_code,
1478            )
1479        };
1480        zx::ok(status)?;
1481        match out_code {
1482            0 => Ok(Ok(optlen)),
1483            _ => Ok(Err(ZxioErrorCode(out_code))),
1484        }
1485    }
1486
1487    pub fn getsockopt(
1488        &self,
1489        level: u32,
1490        optname: u32,
1491        optlen: socklen_t,
1492    ) -> Result<Result<Vec<u8>, ZxioErrorCode>, zx::Status> {
1493        let mut optval = vec![0u8; optlen as usize];
1494        let result = self.getsockopt_slice(level, optname, &mut optval[..])?;
1495        Ok(result.map(|optlen| optval[..optlen as usize].to_vec()))
1496    }
1497
1498    pub fn setsockopt(
1499        &self,
1500        level: i32,
1501        optname: i32,
1502        optval: &[u8],
1503        access_token: Option<zx::NullableHandle>,
1504    ) -> Result<Result<(), ZxioErrorCode>, zx::Status> {
1505        let mut out_code = 0;
1506        let access_token_handle =
1507            access_token.map(zx::NullableHandle::into_raw).unwrap_or(zx::sys::ZX_HANDLE_INVALID);
1508
1509        #[allow(
1510            clippy::undocumented_unsafe_blocks,
1511            reason = "Force documented unsafe blocks in Starnix"
1512        )]
1513        let status = unsafe {
1514            zxio::zxio_setsockopt(
1515                self.as_ptr(),
1516                level,
1517                optname,
1518                optval.as_ptr() as *const c_void,
1519                optval.len() as socklen_t,
1520                access_token_handle,
1521                &mut out_code,
1522            )
1523        };
1524        zx::ok(status)?;
1525        match out_code {
1526            0 => Ok(Ok(())),
1527            _ => Ok(Err(ZxioErrorCode(out_code))),
1528        }
1529    }
1530
1531    pub fn shutdown(
1532        &self,
1533        flags: ZxioShutdownFlags,
1534    ) -> Result<Result<(), ZxioErrorCode>, zx::Status> {
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 { zxio::zxio_shutdown(self.as_ptr(), flags.bits(), &mut out_code) };
1541        zx::ok(status)?;
1542        match out_code {
1543            0 => Ok(Ok(())),
1544            _ => Ok(Err(ZxioErrorCode(out_code))),
1545        }
1546    }
1547
1548    pub fn sendmsg(
1549        &self,
1550        addr: &mut [u8],
1551        buffer: &mut [zxio::iovec],
1552        cmsg: &[ControlMessage],
1553        flags: u32,
1554    ) -> Result<Result<usize, ZxioErrorCode>, zx::Status> {
1555        let mut msg = zxio::msghdr::default();
1556        msg.msg_name = match addr.len() {
1557            0 => std::ptr::null_mut() as *mut c_void,
1558            _ => addr.as_mut_ptr() as *mut c_void,
1559        };
1560        msg.msg_namelen = addr.len() as u32;
1561
1562        msg.msg_iovlen =
1563            i32::try_from(buffer.len()).map_err(|_: TryFromIntError| zx::Status::INVALID_ARGS)?;
1564        msg.msg_iov = buffer.as_mut_ptr();
1565
1566        let mut cmsg_buffer = serialize_control_messages(cmsg);
1567        msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void;
1568        msg.msg_controllen = cmsg_buffer.len() as u32;
1569
1570        let mut out_code = 0;
1571        let mut out_actual = 0;
1572
1573        #[allow(
1574            clippy::undocumented_unsafe_blocks,
1575            reason = "Force documented unsafe blocks in Starnix"
1576        )]
1577        let status = unsafe {
1578            zxio::zxio_sendmsg(self.as_ptr(), &msg, flags as c_int, &mut out_actual, &mut out_code)
1579        };
1580
1581        zx::ok(status)?;
1582        match out_code {
1583            0 => Ok(Ok(out_actual)),
1584            _ => Ok(Err(ZxioErrorCode(out_code))),
1585        }
1586    }
1587
1588    pub fn recvmsg(
1589        &self,
1590        buffer: &mut [zxio::iovec],
1591        flags: u32,
1592    ) -> Result<Result<RecvMessageInfo, ZxioErrorCode>, zx::Status> {
1593        let mut msg = msghdr::default();
1594        let mut addr = vec![0u8; std::mem::size_of::<sockaddr_storage>()];
1595        msg.msg_name = addr.as_mut_ptr() as *mut c_void;
1596        msg.msg_namelen = addr.len() as u32;
1597
1598        let max_buffer_capacity = buffer.iter().map(|v| v.iov_len).sum();
1599        msg.msg_iovlen =
1600            i32::try_from(buffer.len()).map_err(|_: TryFromIntError| zx::Status::INVALID_ARGS)?;
1601        msg.msg_iov = buffer.as_mut_ptr();
1602
1603        let mut cmsg_buffer = vec![0u8; MAX_CMSGS_BUFFER];
1604        msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void;
1605        msg.msg_controllen = cmsg_buffer.len() as u32;
1606
1607        let mut out_code = 0;
1608        let mut out_actual = 0;
1609        #[allow(
1610            clippy::undocumented_unsafe_blocks,
1611            reason = "Force documented unsafe blocks in Starnix"
1612        )]
1613        let status = unsafe {
1614            zxio::zxio_recvmsg(
1615                self.as_ptr(),
1616                &mut msg,
1617                flags as c_int,
1618                &mut out_actual,
1619                &mut out_code,
1620            )
1621        };
1622        zx::ok(status)?;
1623
1624        if out_code != 0 {
1625            return Ok(Err(ZxioErrorCode(out_code)));
1626        }
1627
1628        let control_messages = parse_control_messages(&cmsg_buffer[..msg.msg_controllen as usize]);
1629        Ok(Ok(RecvMessageInfo {
1630            address: addr[..msg.msg_namelen as usize].to_vec(),
1631            bytes_read: std::cmp::min(max_buffer_capacity, out_actual),
1632            message_length: out_actual,
1633            control_messages,
1634            flags: msg.msg_flags,
1635        }))
1636    }
1637
1638    pub fn read_link(&self) -> Result<&[u8], zx::Status> {
1639        let mut target = std::ptr::null();
1640        let mut target_len = 0;
1641        #[allow(
1642            clippy::undocumented_unsafe_blocks,
1643            reason = "Force documented unsafe blocks in Starnix"
1644        )]
1645        let status = unsafe { zxio::zxio_read_link(self.as_ptr(), &mut target, &mut target_len) };
1646        zx::ok(status)?;
1647        // SAFETY: target will live as long as the underlying zxio object lives.
1648        unsafe { Ok(std::slice::from_raw_parts(target, target_len)) }
1649    }
1650
1651    pub fn create_symlink(&self, name: &str, target: &[u8]) -> Result<Zxio, zx::Status> {
1652        let name = name.as_bytes();
1653        let zxio = Zxio::default();
1654        #[allow(
1655            clippy::undocumented_unsafe_blocks,
1656            reason = "Force documented unsafe blocks in Starnix"
1657        )]
1658        let status = unsafe {
1659            zxio::zxio_create_symlink(
1660                self.as_ptr(),
1661                name.as_ptr() as *const c_char,
1662                name.len(),
1663                target.as_ptr(),
1664                target.len(),
1665                zxio.as_storage_ptr(),
1666            )
1667        };
1668        zx::ok(status)?;
1669        Ok(zxio)
1670    }
1671
1672    pub fn xattr_list(&self) -> Result<Vec<Vec<u8>>, zx::Status> {
1673        unsafe extern "C" fn callback(context: *mut c_void, name: *const u8, name_len: usize) {
1674            #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1675            let out_names = unsafe { &mut *(context as *mut Vec<Vec<u8>>) };
1676            #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1677            let name_slice = unsafe { std::slice::from_raw_parts(name, name_len) };
1678            out_names.push(name_slice.to_vec());
1679        }
1680        let mut out_names = Vec::new();
1681        #[allow(
1682            clippy::undocumented_unsafe_blocks,
1683            reason = "Force documented unsafe blocks in Starnix"
1684        )]
1685        let status = unsafe {
1686            zxio::zxio_xattr_list(
1687                self.as_ptr(),
1688                Some(callback),
1689                &mut out_names as *mut _ as *mut c_void,
1690            )
1691        };
1692        zx::ok(status)?;
1693        Ok(out_names)
1694    }
1695
1696    pub fn xattr_get(&self, name: &[u8]) -> Result<Vec<u8>, zx::Status> {
1697        unsafe extern "C" fn callback(
1698            context: *mut c_void,
1699            data: zxio::zxio_xattr_data_t,
1700        ) -> zx_status_t {
1701            #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1702            let out_value = unsafe { &mut *(context as *mut Vec<u8>) };
1703            if data.data.is_null() {
1704                #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1705                let value_vmo = unsafe { zx::Unowned::<'_, zx::Vmo>::from_raw_handle(data.vmo) };
1706                match value_vmo.read_to_vec(0, data.len as u64) {
1707                    Ok(vec) => *out_value = vec,
1708                    Err(status) => return status.into_raw(),
1709                }
1710            } else {
1711                #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
1712                let value_slice =
1713                    unsafe { std::slice::from_raw_parts(data.data as *mut u8, data.len) };
1714                out_value.extend_from_slice(value_slice);
1715            }
1716            zx::Status::OK.into_raw()
1717        }
1718        let mut out_value = Vec::new();
1719        #[allow(
1720            clippy::undocumented_unsafe_blocks,
1721            reason = "Force documented unsafe blocks in Starnix"
1722        )]
1723        let status = unsafe {
1724            zxio::zxio_xattr_get(
1725                self.as_ptr(),
1726                name.as_ptr(),
1727                name.len(),
1728                Some(callback),
1729                &mut out_value as *mut _ as *mut c_void,
1730            )
1731        };
1732        zx::ok(status)?;
1733        Ok(out_value)
1734    }
1735
1736    pub fn xattr_set(
1737        &self,
1738        name: &[u8],
1739        value: &[u8],
1740        mode: XattrSetMode,
1741    ) -> Result<(), zx::Status> {
1742        #[allow(
1743            clippy::undocumented_unsafe_blocks,
1744            reason = "Force documented unsafe blocks in Starnix"
1745        )]
1746        let status = unsafe {
1747            zxio::zxio_xattr_set(
1748                self.as_ptr(),
1749                name.as_ptr(),
1750                name.len(),
1751                value.as_ptr(),
1752                value.len(),
1753                mode as u32,
1754            )
1755        };
1756        zx::ok(status)
1757    }
1758
1759    pub fn xattr_remove(&self, name: &[u8]) -> Result<(), zx::Status> {
1760        #[allow(
1761            clippy::undocumented_unsafe_blocks,
1762            reason = "Force documented unsafe blocks in Starnix"
1763        )]
1764        zx::ok(unsafe { zxio::zxio_xattr_remove(self.as_ptr(), name.as_ptr(), name.len()) })
1765    }
1766
1767    pub fn link_into(&self, target_dir: &Zxio, name: &str) -> Result<(), zx::Status> {
1768        let mut handle = zx::sys::ZX_HANDLE_INVALID;
1769        #[allow(
1770            clippy::undocumented_unsafe_blocks,
1771            reason = "Force documented unsafe blocks in Starnix"
1772        )]
1773        zx::ok(unsafe { zxio::zxio_token_get(target_dir.as_ptr(), &mut handle) })?;
1774        #[allow(
1775            clippy::undocumented_unsafe_blocks,
1776            reason = "Force documented unsafe blocks in Starnix"
1777        )]
1778        zx::ok(unsafe {
1779            zxio::zxio_link_into(self.as_ptr(), handle, name.as_ptr() as *const c_char, name.len())
1780        })
1781    }
1782
1783    pub fn allocate(&self, offset: u64, len: u64, mode: AllocateMode) -> Result<(), zx::Status> {
1784        #[allow(
1785            clippy::undocumented_unsafe_blocks,
1786            reason = "Force documented unsafe blocks in Starnix"
1787        )]
1788        let status = unsafe { zxio::zxio_allocate(self.as_ptr(), offset, len, mode.bits()) };
1789        zx::ok(status)
1790    }
1791
1792    pub fn sync(&self) -> Result<(), zx::Status> {
1793        #[allow(
1794            clippy::undocumented_unsafe_blocks,
1795            reason = "Force documented unsafe blocks in Starnix"
1796        )]
1797        let status = unsafe { zxio::zxio_sync(self.as_ptr()) };
1798        zx::ok(status)
1799    }
1800
1801    pub fn close(&self) -> Result<(), zx::Status> {
1802        #[allow(
1803            clippy::undocumented_unsafe_blocks,
1804            reason = "Force documented unsafe blocks in Starnix"
1805        )]
1806        let status = unsafe { zxio::zxio_close(self.as_ptr()) };
1807        zx::ok(status)
1808    }
1809}
1810
1811impl Drop for ZxioStorage {
1812    fn drop(&mut self) {
1813        // `zxio_destroy` should be called once only when `ZxioStorage` is dropped, just before the
1814        // `zxio_storage_t` memory is deallocated. Operations on an upgraded `ZxioWeak`,
1815        // like WaitCanceler, will then always operate on a non-poisoned `zxio_t as long as the
1816        // `ZxioStorage` is alive.
1817        let zxio_ptr: *mut zxio::zxio_t = &mut self.storage.io;
1818        #[allow(
1819            clippy::undocumented_unsafe_blocks,
1820            reason = "Force documented unsafe blocks in Starnix"
1821        )]
1822        unsafe {
1823            zxio::zxio_destroy(zxio_ptr);
1824        };
1825    }
1826}
1827
1828impl Clone for Zxio {
1829    fn clone(&self) -> Self {
1830        Self { inner: self.inner.clone() }
1831    }
1832}
1833
1834enum NodeKind {
1835    File,
1836    Directory,
1837    Symlink,
1838    Unknown,
1839}
1840
1841impl From<fio::Representation> for NodeKind {
1842    fn from(representation: fio::Representation) -> Self {
1843        match representation {
1844            fio::Representation::File(_) => NodeKind::File,
1845            fio::Representation::Directory(_) => NodeKind::Directory,
1846            fio::Representation::Symlink(_) => NodeKind::Symlink,
1847            _ => NodeKind::Unknown,
1848        }
1849    }
1850}
1851
1852/// A fuchsia.io.Node along with its NodeInfoDeprecated.
1853///
1854/// The NodeInfoDeprecated provides information about the concrete protocol spoken by the
1855/// node.
1856struct DescribedNode {
1857    node: fio::NodeSynchronousProxy,
1858    kind: NodeKind,
1859}
1860
1861/// Open the given path in the given directory.
1862///
1863/// The semantics for the flags argument are defined by the
1864/// fuchsia.io/Directory.Open3 message.
1865///
1866/// This function adds FLAG_SEND_REPRESENTATION to the given flags and then blocks
1867/// until the directory describes the newly opened node.
1868///
1869/// Returns the opened Node, along with its NodeKind, or an error.
1870fn directory_open(
1871    directory: &fio::DirectorySynchronousProxy,
1872    path: &str,
1873    flags: fio::Flags,
1874    deadline: zx::MonotonicInstant,
1875) -> Result<DescribedNode, zx::Status> {
1876    let flags = flags | fio::Flags::FLAG_SEND_REPRESENTATION;
1877
1878    let (client_end, server_end) = zx::Channel::create();
1879    directory.open(path, flags, &Default::default(), server_end).map_err(|_| zx::Status::IO)?;
1880    let node = fio::NodeSynchronousProxy::new(client_end);
1881
1882    match node.wait_for_event(deadline).map_err(map_fidl_error)? {
1883        fio::NodeEvent::OnOpen_ { .. } => {
1884            panic!("Should never happen when sending FLAG_SEND_REPRESENTATION")
1885        }
1886        fio::NodeEvent::OnRepresentation { payload } => {
1887            Ok(DescribedNode { node, kind: payload.into() })
1888        }
1889        fio::NodeEvent::_UnknownEvent { .. } => Err(zx::Status::NOT_SUPPORTED),
1890    }
1891}
1892
1893/// Open a VMO at the given path in the given directory.
1894///
1895/// The semantics for the vmo_flags argument are defined by the
1896/// fuchsia.io/File.GetBackingMemory message (i.e., VmoFlags::*).
1897///
1898/// If the node at the given path is not a VMO, then this function returns
1899/// a zx::Status::IO error.
1900pub fn directory_open_vmo(
1901    directory: &fio::DirectorySynchronousProxy,
1902    path: &str,
1903    vmo_flags: fio::VmoFlags,
1904    deadline: zx::MonotonicInstant,
1905) -> Result<zx::Vmo, zx::Status> {
1906    let mut flags = fio::Flags::empty();
1907    if vmo_flags.contains(fio::VmoFlags::READ) {
1908        flags |= fio::PERM_READABLE;
1909    }
1910    if vmo_flags.contains(fio::VmoFlags::WRITE) {
1911        flags |= fio::PERM_WRITABLE;
1912    }
1913    if vmo_flags.contains(fio::VmoFlags::EXECUTE) {
1914        flags |= fio::PERM_EXECUTABLE;
1915    }
1916    let description = directory_open(directory, path, flags, deadline)?;
1917    let file = match description.kind {
1918        NodeKind::File => fio::FileSynchronousProxy::new(description.node.into_channel()),
1919        _ => return Err(zx::Status::IO),
1920    };
1921
1922    let vmo = file
1923        .get_backing_memory(vmo_flags, deadline)
1924        .map_err(map_fidl_error)?
1925        .map_err(zx::Status::from_raw)?;
1926    Ok(vmo)
1927}
1928
1929/// Read the content of the file at the given path in the given directory.
1930///
1931/// If the node at the given path is not a file, then this function returns
1932/// a zx::Status::IO error.
1933pub fn directory_read_file(
1934    directory: &fio::DirectorySynchronousProxy,
1935    path: &str,
1936    deadline: zx::MonotonicInstant,
1937) -> Result<Vec<u8>, zx::Status> {
1938    let description = directory_open(directory, path, fio::PERM_READABLE, deadline)?;
1939    let file = match description.kind {
1940        NodeKind::File => fio::FileSynchronousProxy::new(description.node.into_channel()),
1941        _ => return Err(zx::Status::IO),
1942    };
1943
1944    let mut result = Vec::new();
1945    loop {
1946        let mut data = file
1947            .read(fio::MAX_TRANSFER_SIZE, deadline)
1948            .map_err(map_fidl_error)?
1949            .map_err(zx::Status::from_raw)?;
1950        let finished = (data.len() as u64) < fio::MAX_TRANSFER_SIZE;
1951        result.append(&mut data);
1952        if finished {
1953            return Ok(result);
1954        }
1955    }
1956}
1957
1958/// Create an anonymous temp file in the given directory.
1959pub fn directory_create_tmp_file(
1960    directory: &fio::DirectorySynchronousProxy,
1961    flags: fio::Flags,
1962    deadline: zx::MonotonicInstant,
1963) -> Result<fio::FileSynchronousProxy, zx::Status> {
1964    let description = directory_open(
1965        directory,
1966        ".",
1967        flags
1968            | fio::PERM_WRITABLE
1969            | fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY
1970            | fio::Flags::PROTOCOL_FILE,
1971        deadline,
1972    )?;
1973    let file = match description.kind {
1974        NodeKind::File => fio::FileSynchronousProxy::new(description.node.into_channel()),
1975        _ => return Err(zx::Status::NOT_FILE),
1976    };
1977
1978    Ok(file)
1979}
1980
1981/// Open the given path in the given directory without blocking.
1982///
1983/// A zx::Channel to the opened node is returned (or an error).
1984///
1985/// It is an error to supply the FLAG_SEND_REPRESENTATION flag in flags.
1986///
1987/// This function will "succeed" even if the given path does not exist in the
1988/// given directory because this function does not wait for the directory to
1989/// confirm that the path exists.
1990pub fn directory_open_async(
1991    directory: &fio::DirectorySynchronousProxy,
1992    path: &str,
1993    flags: fio::Flags,
1994) -> Result<fio::DirectorySynchronousProxy, zx::Status> {
1995    if flags.intersects(fio::Flags::FLAG_SEND_REPRESENTATION) {
1996        return Err(zx::Status::INVALID_ARGS);
1997    }
1998
1999    let (proxy, server_end) = fidl::endpoints::create_sync_proxy::<fio::DirectoryMarker>();
2000    directory
2001        .open(path, flags, &Default::default(), server_end.into_channel())
2002        .map_err(|_| zx::Status::IO)?;
2003    Ok(proxy)
2004}
2005
2006/// Open a directory at the given path in the given directory without blocking.
2007///
2008/// This function adds the PROTOCOL_DIRECTORY flag to ensure that the open operation completes only
2009/// if the given path is actually a directory, which means clients can start using the returned
2010/// DirectorySynchronousProxy immediately without waiting for the server to complete the operation.
2011///
2012/// This function will "succeed" even if the given path does not exist in the
2013/// given directory or if the path is not a directory because this function
2014/// does not wait for the directory to confirm that the path exists and is a
2015/// directory.
2016pub fn directory_open_directory_async(
2017    directory: &fio::DirectorySynchronousProxy,
2018    path: &str,
2019    flags: fio::Flags,
2020) -> Result<fio::DirectorySynchronousProxy, zx::Status> {
2021    let flags = flags | fio::Flags::PROTOCOL_DIRECTORY;
2022    let proxy = directory_open_async(directory, path, flags)?;
2023    Ok(proxy)
2024}
2025
2026fn map_fidl_error(error: fidl::Error) -> zx::Status {
2027    match error {
2028        fidl::Error::ClientChannelClosed { status, .. } => status,
2029        _ => zx::Status::IO,
2030    }
2031}
2032
2033#[cfg(test)]
2034mod test {
2035    use super::*;
2036
2037    use anyhow::Error;
2038    use fidl::endpoints::Proxy as _;
2039    use fidl_fuchsia_io as fio;
2040    use fuchsia_async as fasync;
2041    use fuchsia_fs::directory;
2042
2043    fn open_pkg() -> fio::DirectorySynchronousProxy {
2044        let pkg_proxy =
2045            directory::open_in_namespace("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
2046                .expect("failed to open /pkg");
2047        fio::DirectorySynchronousProxy::new(
2048            pkg_proxy
2049                .into_channel()
2050                .expect("failed to convert proxy into channel")
2051                .into_zx_channel(),
2052        )
2053    }
2054
2055    #[fasync::run_singlethreaded(test)]
2056    async fn test_directory_open() -> Result<(), Error> {
2057        let pkg = open_pkg();
2058        let description = directory_open(
2059            &pkg,
2060            "bin/syncio_lib_test",
2061            fio::PERM_READABLE,
2062            zx::MonotonicInstant::INFINITE,
2063        )?;
2064        assert!(match description.kind {
2065            NodeKind::File => true,
2066            _ => false,
2067        });
2068        Ok(())
2069    }
2070
2071    #[fasync::run_singlethreaded(test)]
2072    async fn test_directory_open_vmo() -> Result<(), Error> {
2073        let pkg = open_pkg();
2074        let vmo = directory_open_vmo(
2075            &pkg,
2076            "bin/syncio_lib_test",
2077            fio::VmoFlags::READ | fio::VmoFlags::EXECUTE,
2078            zx::MonotonicInstant::INFINITE,
2079        )?;
2080        assert!(!vmo.is_invalid());
2081
2082        let info = vmo.basic_info()?;
2083        assert_eq!(zx::Rights::READ, info.rights & zx::Rights::READ);
2084        assert_eq!(zx::Rights::EXECUTE, info.rights & zx::Rights::EXECUTE);
2085        Ok(())
2086    }
2087
2088    #[fasync::run_singlethreaded(test)]
2089    async fn test_directory_read_file() -> Result<(), Error> {
2090        let pkg = open_pkg();
2091        let data =
2092            directory_read_file(&pkg, "bin/syncio_lib_test", zx::MonotonicInstant::INFINITE)?;
2093
2094        assert!(!data.is_empty());
2095        Ok(())
2096    }
2097
2098    #[fasync::run_singlethreaded(test)]
2099    async fn test_directory_open_directory_async() -> Result<(), Error> {
2100        let pkg = open_pkg();
2101        let bin =
2102            directory_open_directory_async(&pkg, "bin", fio::PERM_READABLE | fio::PERM_EXECUTABLE)?;
2103        let vmo = directory_open_vmo(
2104            &bin,
2105            "syncio_lib_test",
2106            fio::VmoFlags::READ | fio::VmoFlags::EXECUTE,
2107            zx::MonotonicInstant::INFINITE,
2108        )?;
2109        assert!(!vmo.is_invalid());
2110
2111        let info = vmo.basic_info()?;
2112        assert_eq!(zx::Rights::READ, info.rights & zx::Rights::READ);
2113        assert_eq!(zx::Rights::EXECUTE, info.rights & zx::Rights::EXECUTE);
2114        Ok(())
2115    }
2116
2117    #[fasync::run_singlethreaded(test)]
2118    async fn test_directory_open_zxio_async() -> Result<(), Error> {
2119        let pkg_proxy =
2120            directory::open_in_namespace("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
2121                .expect("failed to open /pkg");
2122        let zx_channel = pkg_proxy
2123            .into_channel()
2124            .expect("failed to convert proxy into channel")
2125            .into_zx_channel();
2126        let storage = zxio::zxio_storage_t::default();
2127        #[allow(
2128            clippy::undocumented_unsafe_blocks,
2129            reason = "Force documented unsafe blocks in Starnix"
2130        )]
2131        let status = unsafe {
2132            zxio::zxio_create(
2133                zx_channel.into_raw(),
2134                &storage as *const zxio::zxio_storage_t as *mut zxio::zxio_storage_t,
2135            )
2136        };
2137        assert_eq!(status, zx::sys::ZX_OK);
2138        let io = &storage.io as *const zxio::zxio_t as *mut zxio::zxio_t;
2139        #[allow(
2140            clippy::undocumented_unsafe_blocks,
2141            reason = "Force documented unsafe blocks in Starnix"
2142        )]
2143        let close_status = unsafe { zxio::zxio_close(io) };
2144        assert_eq!(close_status, zx::sys::ZX_OK);
2145        #[allow(
2146            clippy::undocumented_unsafe_blocks,
2147            reason = "Force documented unsafe blocks in Starnix"
2148        )]
2149        unsafe {
2150            zxio::zxio_destroy(io);
2151        }
2152        Ok(())
2153    }
2154
2155    #[fuchsia::test]
2156    async fn test_directory_enumerate() -> Result<(), Error> {
2157        let pkg_dir_handle =
2158            directory::open_in_namespace("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
2159                .expect("failed to open /pkg")
2160                .into_channel()
2161                .expect("could not unwrap channel")
2162                .into_zx_channel()
2163                .into();
2164
2165        let io: Zxio = Zxio::create(pkg_dir_handle)?;
2166        let iter = io.create_dirent_iterator().expect("failed to create iterator");
2167        let expected_dir_names = vec![".", "bin", "lib", "meta"];
2168        let mut found_dir_names = iter
2169            .map(|e| {
2170                let dirent = e.expect("dirent");
2171                assert!(dirent.is_dir());
2172                std::str::from_utf8(&dirent.name).expect("name was not valid utf8").to_string()
2173            })
2174            .collect::<Vec<_>>();
2175        found_dir_names.sort();
2176        assert_eq!(expected_dir_names, found_dir_names);
2177
2178        // Check all entry inside bin are either "." or a file
2179        let bin_io = io
2180            .open("bin", fio::PERM_READABLE | fio::PERM_EXECUTABLE, Default::default())
2181            .expect("open");
2182        for entry in bin_io.create_dirent_iterator().expect("failed to create iterator") {
2183            let dirent = entry.expect("dirent");
2184            if dirent.name == "." {
2185                assert!(dirent.is_dir());
2186            } else {
2187                assert!(dirent.is_file());
2188            }
2189        }
2190
2191        Ok(())
2192    }
2193
2194    #[fuchsia::test]
2195    fn test_storage_allocator() {
2196        let mut out_storage = zxio_storage_t::default();
2197        let mut out_storage_ptr = &mut out_storage as *mut zxio_storage_t;
2198
2199        let mut out_context = Zxio::default();
2200        let mut out_context_ptr = &mut out_context as *mut Zxio;
2201
2202        #[allow(
2203            clippy::undocumented_unsafe_blocks,
2204            reason = "Force documented unsafe blocks in Starnix"
2205        )]
2206        let out = unsafe {
2207            storage_allocator(
2208                0 as zxio_object_type_t,
2209                &mut out_storage_ptr as *mut *mut zxio_storage_t,
2210                &mut out_context_ptr as *mut *mut Zxio as *mut *mut c_void,
2211            )
2212        };
2213        assert_eq!(out, zx::sys::ZX_OK);
2214    }
2215
2216    #[fuchsia::test]
2217    fn test_storage_allocator_bad_context() {
2218        let mut out_storage = zxio_storage_t::default();
2219        let mut out_storage_ptr = &mut out_storage as *mut zxio_storage_t;
2220
2221        let out_context = std::ptr::null_mut();
2222
2223        #[allow(
2224            clippy::undocumented_unsafe_blocks,
2225            reason = "Force documented unsafe blocks in Starnix"
2226        )]
2227        let out = unsafe {
2228            storage_allocator(
2229                0 as zxio_object_type_t,
2230                &mut out_storage_ptr as *mut *mut zxio_storage_t,
2231                out_context,
2232            )
2233        };
2234        assert_eq!(out, zx::sys::ZX_ERR_NO_MEMORY);
2235    }
2236}