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