Skip to main content

vfs/
common.rs

1// Copyright 2019 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
5//! Common utilities used by both directory and file traits.
6
7use crate::node::Node;
8use flex_client::fidl::ServerEnd;
9
10use flex_fuchsia_io as fio;
11use futures::StreamExt as _;
12use std::sync::Arc;
13use zx_status::Status;
14
15/// Set of known rights.
16#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
17const FS_RIGHTS: fio::OpenFlags = fio::OPEN_RIGHTS;
18
19/// Returns true if the rights flags in `flags` do not exceed those in `parent_flags`.
20#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
21pub(crate) fn stricter_or_same_rights(parent_flags: fio::OpenFlags, flags: fio::OpenFlags) -> bool {
22    let parent_rights = parent_flags & FS_RIGHTS;
23    let rights = flags & FS_RIGHTS;
24    return !rights.intersects(!parent_rights);
25}
26
27/// A helper method to send OnOpen event on the handle owned by the `server_end` in case `flags`
28/// contains `OPEN_FLAG_STATUS`.
29///
30/// If the send operation fails for any reason, the error is ignored.  This helper is used during
31/// an Open() or a Clone() FIDL methods, and these methods have no means to propagate errors to the
32/// caller.  OnOpen event is the only way to do that, so there is nowhere to report errors in
33/// OnOpen dispatch.  `server_end` will be closed, so there will be some kind of indication of the
34/// issue.
35///
36/// # Panics
37/// If `status` is not an error.  In this case `OnOpen` may need to contain a description of the
38/// object, and server_end should not be dropped.
39#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
40pub fn send_on_open_with_error(
41    describe: bool,
42    server_end: ServerEnd<fio::NodeMarker>,
43    status: Status,
44) {
45    if !describe {
46        // There is no reasonable way to report this error.  Assuming the `server_end` has just
47        // disconnected or failed in some other way why we are trying to send OnOpen.
48        let _ = server_end.close_with_epitaph(status);
49        return;
50    }
51
52    let (_, control_handle) = server_end.into_stream_and_control_handle();
53    // Same as above, ignore the error.
54    let _ = control_handle.send_on_open_(status.into_raw(), None);
55    control_handle.shutdown_with_epitaph(status);
56}
57
58/// Trait to be used as a supertrait when an object should allow dynamic casting to an Any.
59///
60/// Separate trait since [`into_any`] requires Self to be Sized, which cannot be satisfied in a
61/// trait without preventing it from being object safe (thus disallowing dynamic dispatch).
62/// Since we provide a generic implementation, the size of each concrete type is known.
63pub trait IntoAny: std::any::Any {
64    /// Cast the given object into a `dyn std::any::Any`.
65    fn into_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static>;
66}
67
68impl<T: 'static + Send + Sync> IntoAny for T {
69    fn into_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
70        self as Arc<dyn std::any::Any + Send + Sync + 'static>
71    }
72}
73
74pub async fn extended_attributes_sender(
75    iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
76    attributes: Vec<Vec<u8>>,
77) {
78    let mut stream = iterator.into_stream();
79
80    let mut chunks = attributes.chunks(fio::MAX_LIST_ATTRIBUTES_CHUNK as usize).peekable();
81
82    while let Some(Ok(fio::ExtendedAttributeIteratorRequest::GetNext { responder })) =
83        stream.next().await
84    {
85        let (chunk, last) = match chunks.next() {
86            Some(chunk) => (chunk, chunks.peek().is_none()),
87            None => (&[][..], true),
88        };
89        #[allow(clippy::unnecessary_lazy_evaluations)]
90        responder.send(Ok((chunk, last))).unwrap_or_else(|_error| {
91            #[cfg(any(test, feature = "use_log"))]
92            log::error!(_error:?; "list extended attributes failed to send a chunk");
93        });
94        if last {
95            break;
96        }
97    }
98}
99
100pub fn encode_extended_attribute_value(
101    value: Vec<u8>,
102) -> Result<fio::ExtendedAttributeValue, Status> {
103    let size = value.len() as u64;
104    if size > fio::MAX_INLINE_ATTRIBUTE_VALUE {
105        #[cfg(target_os = "fuchsia")]
106        {
107            let vmo = fidl::Vmo::create(size)?;
108            vmo.write(&value, 0)?;
109            Ok(fio::ExtendedAttributeValue::Buffer(vmo))
110        }
111        #[cfg(not(target_os = "fuchsia"))]
112        Err(Status::NOT_SUPPORTED)
113    } else {
114        Ok(fio::ExtendedAttributeValue::Bytes(value))
115    }
116}
117
118pub fn decode_extended_attribute_value(
119    value: fio::ExtendedAttributeValue,
120) -> Result<Vec<u8>, Status> {
121    match value {
122        fio::ExtendedAttributeValue::Bytes(val) => Ok(val),
123        #[cfg(target_os = "fuchsia")]
124        fio::ExtendedAttributeValue::Buffer(vmo) => {
125            let length = vmo.get_content_size()?;
126            vmo.read_to_vec(0, length)
127        }
128        #[cfg(not(target_os = "fuchsia"))]
129        fio::ExtendedAttributeValue::Buffer(_) => Err(Status::NOT_SUPPORTED),
130        fio::ExtendedAttributeValue::__SourceBreaking { .. } => Err(Status::NOT_SUPPORTED),
131    }
132}
133
134/// Helper for building [`fio::NodeAttributes2`]` given `requested` attributes. Code will only run
135/// for `requested` attributes.
136///
137/// Example:
138///
139///   attributes!(
140///       requested,
141///       Mutable { creation_time: 123, modification_time: 456 },
142///       Immutable { content_size: 789 }
143///   );
144///
145#[macro_export]
146macro_rules! attributes {
147    (
148        $requested:expr,
149        Mutable {$($mut_a:ident: $mut_v:expr),* $(,)?},
150        Immutable {$($immut_a:ident: $immut_v:expr),* $(,)?}
151    ) => {
152        fio::NodeAttributes2 {
153            mutable_attributes: fio::MutableNodeAttributes {
154                $($mut_a: if $requested.contains($crate::__attribute_query!($mut_a)) {
155                    Option::from($mut_v)
156                } else {
157                    None
158                },)*
159                ..Default::default()
160            },
161            immutable_attributes: fio::ImmutableNodeAttributes {
162                $($immut_a: if $requested.contains($crate::__attribute_query!($immut_a)) {
163                    Option::from($immut_v)
164                } else {
165                    None
166                },)*
167                ..Default::default()
168            }
169        }
170    };
171}
172
173/// Helper for building [`fio::NodeAttributes2`]` given immutable attributes in `requested`
174/// Code will only run for `requested` attributes. Mutable attributes in `requested` are ignored.
175///
176/// Example:
177///
178///   immutable_attributes!(
179///       requested,
180///       Immutable { content_size: 789 }
181///   );
182///
183#[macro_export]
184macro_rules! immutable_attributes {
185    (
186        $requested:expr,
187        Immutable {$($immut_a:ident: $immut_v:expr),* $(,)?}
188    ) => {
189        fio::NodeAttributes2 {
190            mutable_attributes: Default::default(),
191            immutable_attributes: fio::ImmutableNodeAttributes {
192                $($immut_a: if $requested.contains($crate::__attribute_query!($immut_a)) {
193                    Option::from($immut_v)
194                } else {
195                    None
196                },)*
197                ..Default::default()
198            },
199        }
200    };
201}
202
203#[doc(hidden)]
204pub mod __private {
205    pub use paste::paste;
206}
207
208#[doc(hidden)]
209#[macro_export]
210macro_rules! __attribute_query {
211    ($attr:ident) => {
212        $crate::common::__private::paste! { fio::NodeAttributesQuery::[< $attr:upper >] }
213    };
214}
215
216/// Represents if and how objects should be created with an open request.
217#[derive(Debug, PartialEq, Eq)]
218pub enum CreationMode {
219    // Never create object.
220    Never,
221    // Object will be created if it does not exist.
222    AllowExisting,
223    // Create the object, will fail if it does exist.
224    Always,
225    // Create the object as an unnamed and temporary object.
226    UnnamedTemporary,
227    // Create the object as an unnamed, temporary, and unlinkable object.
228    UnlinkableUnnamedTemporary,
229}
230
231/// Used to translate fuchsia.io/Node.SetAttr calls (io1) to fuchsia.io/Node.UpdateAttributes (io2).
232pub(crate) fn io1_to_io2_attrs(
233    flags: fio::NodeAttributeFlags,
234    attrs: fio::NodeAttributes,
235) -> fio::MutableNodeAttributes {
236    fio::MutableNodeAttributes {
237        creation_time: flags
238            .contains(fio::NodeAttributeFlags::CREATION_TIME)
239            .then_some(attrs.creation_time),
240        modification_time: flags
241            .contains(fio::NodeAttributeFlags::MODIFICATION_TIME)
242            .then_some(attrs.modification_time),
243        ..Default::default()
244    }
245}
246
247/// The set of attributes that must be queried to fulfill an io1 GetAttrs request.
248const ALL_IO1_ATTRIBUTES: fio::NodeAttributesQuery = fio::NodeAttributesQuery::PROTOCOLS
249    .union(fio::NodeAttributesQuery::ABILITIES)
250    .union(fio::NodeAttributesQuery::ID)
251    .union(fio::NodeAttributesQuery::CONTENT_SIZE)
252    .union(fio::NodeAttributesQuery::STORAGE_SIZE)
253    .union(fio::NodeAttributesQuery::LINK_COUNT)
254    .union(fio::NodeAttributesQuery::CREATION_TIME)
255    .union(fio::NodeAttributesQuery::MODIFICATION_TIME);
256
257/// Default set of attributes to send to an io1 GetAttr request upon failure.
258const DEFAULT_IO1_ATTRIBUTES: fio::NodeAttributes = fio::NodeAttributes {
259    mode: 0,
260    id: fio::INO_UNKNOWN,
261    content_size: 0,
262    storage_size: 0,
263    link_count: 0,
264    creation_time: 0,
265    modification_time: 0,
266};
267
268const DEFAULT_LINK_COUNT: u64 = 1;
269
270/// Approximate a set of POSIX mode bits based on a node's protocols and abilities. This follows the
271/// C++ VFS implementation, and is only used for io1 GetAttrs calls where the filesystem doesn't
272/// support POSIX mode bits. Returns 0 if the mode bits could not be approximated.
273const fn approximate_posix_mode(
274    protocols: Option<fio::NodeProtocolKinds>,
275    abilities: fio::Abilities,
276) -> u32 {
277    let Some(protocols) = protocols else {
278        return 0;
279    };
280    match protocols {
281        fio::NodeProtocolKinds::DIRECTORY => {
282            let mut mode = libc::S_IFDIR;
283            if abilities.contains(fio::Abilities::ENUMERATE) {
284                mode |= libc::S_IRUSR;
285            }
286            if abilities.contains(fio::Abilities::MODIFY_DIRECTORY) {
287                mode |= libc::S_IWUSR;
288            }
289            if abilities.contains(fio::Abilities::TRAVERSE) {
290                mode |= libc::S_IXUSR;
291            }
292            mode
293        }
294        fio::NodeProtocolKinds::FILE => {
295            let mut mode = libc::S_IFREG;
296            if abilities.contains(fio::Abilities::READ_BYTES) {
297                mode |= libc::S_IRUSR;
298            }
299            if abilities.contains(fio::Abilities::WRITE_BYTES) {
300                mode |= libc::S_IWUSR;
301            }
302            if abilities.contains(fio::Abilities::EXECUTE) {
303                mode |= libc::S_IXUSR;
304            }
305            mode
306        }
307        fio::NodeProtocolKinds::CONNECTOR => 0,
308        #[cfg(fuchsia_api_level_at_least = "HEAD")]
309        fio::NodeProtocolKinds::SYMLINK => libc::S_IFLNK | libc::S_IRUSR,
310        _ => 0,
311    }
312}
313
314/// Used to translate fuchsia.io/Node.GetAttributes calls (io2) to fuchsia.io/Node.GetAttrs (io1).
315/// We don't return a Result since the fuchsia.io/Node.GetAttrs method doesn't use FIDL errors, and
316/// thus requires we return a status code and set of default attributes for the failure case.
317pub async fn io2_to_io1_attrs<T: Node>(
318    node: &T,
319    rights: fio::Rights,
320) -> (zx_status::sys::zx_status_t, fio::NodeAttributes) {
321    if !rights.contains(fio::Rights::GET_ATTRIBUTES) {
322        return (Status::BAD_HANDLE.into_raw(), DEFAULT_IO1_ATTRIBUTES);
323    }
324
325    let attributes = node.get_attributes(ALL_IO1_ATTRIBUTES).await;
326    let Ok(fio::NodeAttributes2 {
327        mutable_attributes: mut_attrs,
328        immutable_attributes: immut_attrs,
329    }) = attributes
330    else {
331        return (attributes.unwrap_err().into_raw(), DEFAULT_IO1_ATTRIBUTES);
332    };
333
334    (
335        zx_status::sys::ZX_OK,
336        fio::NodeAttributes {
337            // If the node has POSIX mode bits, use those directly, otherwise synthesize a set based
338            // on the node's protocols/abilities if available.
339            mode: mut_attrs.mode.unwrap_or_else(|| {
340                approximate_posix_mode(
341                    immut_attrs.protocols,
342                    immut_attrs.abilities.unwrap_or_default(),
343                )
344            }),
345            id: immut_attrs.id.unwrap_or(fio::INO_UNKNOWN),
346            content_size: immut_attrs.content_size.unwrap_or_default(),
347            storage_size: immut_attrs.storage_size.unwrap_or_default(),
348            link_count: immut_attrs.link_count.unwrap_or(DEFAULT_LINK_COUNT),
349            creation_time: mut_attrs.creation_time.unwrap_or_default(),
350            modification_time: mut_attrs.modification_time.unwrap_or_default(),
351        },
352    )
353}
354
355pub fn mutable_node_attributes_to_query(
356    attributes: &fio::MutableNodeAttributes,
357) -> fio::NodeAttributesQuery {
358    let mut query = fio::NodeAttributesQuery::empty();
359
360    if attributes.creation_time.is_some() {
361        query |= fio::NodeAttributesQuery::CREATION_TIME;
362    }
363    if attributes.modification_time.is_some() {
364        query |= fio::NodeAttributesQuery::MODIFICATION_TIME;
365    }
366    if attributes.access_time.is_some() {
367        query |= fio::NodeAttributesQuery::ACCESS_TIME;
368    }
369    if attributes.mode.is_some() {
370        query |= fio::NodeAttributesQuery::MODE;
371    }
372    if attributes.uid.is_some() {
373        query |= fio::NodeAttributesQuery::UID;
374    }
375    if attributes.gid.is_some() {
376        query |= fio::NodeAttributesQuery::GID;
377    }
378    if attributes.rdev.is_some() {
379        query |= fio::NodeAttributesQuery::RDEV;
380    }
381    query
382}