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