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