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