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