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