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