blob: c3fec547bd353eee41358e7f6baa12683da2abd2 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Common utilities used by both directory and file traits.
use {
fidl::endpoints::ServerEnd, fidl::prelude::*, fidl_fuchsia_io as fio,
fuchsia_zircon_status::Status, futures::StreamExt as _, std::sync::Arc,
};
pub use vfs_macros::attribute_query;
/// Set of known rights.
const FS_RIGHTS: fio::OpenFlags = fio::OPEN_RIGHTS;
/// Returns true if the rights flags in `flags` do not exceed those in `parent_flags`.
pub fn stricter_or_same_rights(parent_flags: fio::OpenFlags, flags: fio::OpenFlags) -> bool {
let parent_rights = parent_flags & FS_RIGHTS;
let rights = flags & FS_RIGHTS;
return !rights.intersects(!parent_rights);
}
/// Common logic for rights processing during cloning a node, shared by both file and directory
/// implementations.
pub fn inherit_rights_for_clone(
parent_flags: fio::OpenFlags,
mut flags: fio::OpenFlags,
) -> Result<fio::OpenFlags, Status> {
if flags.intersects(fio::OpenFlags::CLONE_SAME_RIGHTS) && flags.intersects(FS_RIGHTS) {
return Err(Status::INVALID_ARGS);
}
// We preserve OPEN_FLAG_APPEND as this is what is the most convenient for the POSIX emulation.
//
// OPEN_FLAG_NODE_REFERENCE is enforced, according to our current FS permissions design.
flags |= parent_flags & (fio::OpenFlags::APPEND | fio::OpenFlags::NODE_REFERENCE);
// If CLONE_FLAG_SAME_RIGHTS is requested, cloned connection will inherit the same rights
// as those from the originating connection. We have ensured that no FS_RIGHTS flags are set
// above.
if flags.intersects(fio::OpenFlags::CLONE_SAME_RIGHTS) {
flags &= !fio::OpenFlags::CLONE_SAME_RIGHTS;
flags |= parent_flags & FS_RIGHTS;
}
if !stricter_or_same_rights(parent_flags, flags) {
return Err(Status::ACCESS_DENIED);
}
// Ignore the POSIX flags for clone.
flags &= !(fio::OpenFlags::POSIX_WRITABLE | fio::OpenFlags::POSIX_EXECUTABLE);
Ok(flags)
}
/// A helper method to send OnOpen event on the handle owned by the `server_end` in case `flags`
/// contains `OPEN_FLAG_STATUS`.
///
/// If the send operation fails for any reason, the error is ignored. This helper is used during
/// an Open() or a Clone() FIDL methods, and these methods have no means to propagate errors to the
/// caller. OnOpen event is the only way to do that, so there is nowhere to report errors in
/// OnOpen dispatch. `server_end` will be closed, so there will be some kind of indication of the
/// issue.
///
/// # Panics
/// If `status` is `Status::OK`. In this case `OnOpen` may need to contain a description of the
/// object, and server_end should not be dropped.
pub fn send_on_open_with_error(
describe: bool,
server_end: ServerEnd<fio::NodeMarker>,
status: Status,
) {
if status == Status::OK {
panic!("send_on_open_with_error() should not be used to respond with Status::OK");
}
if !describe {
// There is no reasonable way to report this error. Assuming the `server_end` has just
// disconnected or failed in some other way why we are trying to send OnOpen.
let _ = server_end.close_with_epitaph(status);
return;
}
match server_end.into_stream_and_control_handle() {
Ok((_, control_handle)) => {
// Same as above, ignore the error.
let _ = control_handle.send_on_open_(status.into_raw(), None);
control_handle.shutdown_with_epitaph(status);
}
Err(_) => {
// Same as above, ignore the error.
return;
}
}
}
/// Trait to be used as a supertrait when an object should allow dynamic casting to an Any.
///
/// Separate trait since [`into_any`] requires Self to be Sized, which cannot be satisfied in a
/// trait without preventing it from being object safe (thus disallowing dynamic dispatch).
/// Since we provide a generic implementation, the size of each concrete type is known.
pub trait IntoAny: std::any::Any {
/// Cast the given object into a `dyn std::any::Any`.
fn into_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static>;
}
impl<T: 'static + Send + Sync> IntoAny for T {
fn into_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
self as Arc<dyn std::any::Any + Send + Sync + 'static>
}
}
/// Returns equivalent POSIX mode/permission bits based on the specified rights.
/// Note that these only set the user bits.
pub fn rights_to_posix_mode_bits(readable: bool, writable: bool, executable: bool) -> u32 {
return (if readable { libc::S_IRUSR } else { 0 }
| if writable { libc::S_IWUSR } else { 0 }
| if executable { libc::S_IXUSR } else { 0 })
.into();
}
pub async fn extended_attributes_sender(
iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
attributes: Vec<Vec<u8>>,
) {
let mut stream = match iterator.into_stream() {
Ok(stream) => stream,
Err(error) => {
tracing::error!(
?error,
"failed to turn extended attributes iterator request into a stream!"
);
return;
}
};
let mut chunks = attributes.chunks(fio::MAX_LIST_ATTRIBUTES_CHUNK as usize).peekable();
while let Some(Ok(fio::ExtendedAttributeIteratorRequest::GetNext { responder })) =
stream.next().await
{
let (chunk, last) = match chunks.next() {
Some(chunk) => (chunk, chunks.peek().is_none()),
None => (&[][..], true),
};
responder.send(Ok((chunk, last))).unwrap_or_else(|error| {
tracing::error!(?error, "list extended attributes failed to send a chunk");
});
if last {
break;
}
}
}
pub fn encode_extended_attribute_value(
value: Vec<u8>,
) -> Result<fio::ExtendedAttributeValue, Status> {
let size = value.len() as u64;
if size > fio::MAX_INLINE_ATTRIBUTE_VALUE {
#[cfg(target_os = "fuchsia")]
{
let vmo = fidl::Vmo::create(size)?;
vmo.write(&value, 0)?;
Ok(fio::ExtendedAttributeValue::Buffer(vmo))
}
#[cfg(not(target_os = "fuchsia"))]
Err(Status::NOT_SUPPORTED)
} else {
Ok(fio::ExtendedAttributeValue::Bytes(value))
}
}
pub fn decode_extended_attribute_value(
value: fio::ExtendedAttributeValue,
) -> Result<Vec<u8>, Status> {
match value {
fio::ExtendedAttributeValue::Bytes(val) => Ok(val),
#[cfg(target_os = "fuchsia")]
fio::ExtendedAttributeValue::Buffer(vmo) => {
let length = vmo.get_content_size()?;
vmo.read_to_vec(0, length)
}
#[cfg(not(target_os = "fuchsia"))]
fio::ExtendedAttributeValue::Buffer(_) => Err(Status::NOT_SUPPORTED),
}
}
// TODO(https://fxbug.dev/42075328): Consolidate with other implementations that do the same thing.
pub(crate) mod io2_conversions {
use super::fio;
// This code is adapted from /src/sys/lib/routing/src/rights.rs.
//
// Unlike that implementation, this is stricter: a particular io1 right is present iff all its
// constituent io2 rights are present.
pub fn io2_to_io1(rights: fio::Operations) -> fio::OpenFlags {
let mut flags = fio::OpenFlags::empty();
if rights.contains(fio::R_STAR_DIR) {
flags |= fio::OpenFlags::RIGHT_READABLE;
}
if rights.contains(fio::W_STAR_DIR) {
flags |= fio::OpenFlags::RIGHT_WRITABLE;
}
if rights.contains(fio::X_STAR_DIR) {
flags |= fio::OpenFlags::RIGHT_EXECUTABLE;
}
flags
}
pub fn io1_to_io2(flags: fio::OpenFlags) -> fio::Operations {
// Using Open1 requires GET_ATTRIBUTES as this is not expressible via [`fio::OpenFlags`].
// TODO(https://fxbug.dev/324080764): Restrict GET_ATTRIBUTES.
let mut operations = fio::Operations::GET_ATTRIBUTES;
if flags.contains(fio::OpenFlags::RIGHT_READABLE) {
operations |= fio::R_STAR_DIR;
}
if flags.contains(fio::OpenFlags::RIGHT_WRITABLE) {
operations |= fio::W_STAR_DIR;
}
if flags.contains(fio::OpenFlags::RIGHT_EXECUTABLE) {
operations |= fio::X_STAR_DIR;
}
operations
}
}
/// Helper for building [`fio::NodeAttributes2`]` given `requested` attributes. Code will only run
/// for `requested` attributes.
///
/// Example:
///
/// attributes!(
/// requested,
/// Mutable { creation_time: 123, modification_time: 456 },
/// Immutable { content_size: 789 }
/// );
///
#[macro_export]
macro_rules! attributes {
($requested:expr,
Mutable {$($mut_a:ident: $mut_v:expr),* $(,)?},
Immutable {$($immut_a:ident: $immut_v:expr),* $(,)?}) => (
{
use $crate::common::attribute_query;
fio::NodeAttributes2 {
mutable_attributes: fio::MutableNodeAttributes {
$($mut_a: if $requested.contains(attribute_query!($mut_a)) {
Option::from($mut_v)
} else {
None
}),*,
..Default::default()
},
immutable_attributes: fio::ImmutableNodeAttributes {
$($immut_a: if $requested.contains(attribute_query!($immut_a)) {
Option::from($immut_v)
} else {
None
}),*,
..Default::default()
}
}
}
)
}
/// Helper for building [`fio::NodeAttributes2`]` given immutable attributes in `requested`
/// Code will only run for `requested` attributes. Mutable attributes in `requested` are ignored.
///
/// Example:
///
/// immutable_attributes!(
/// requested,
/// Immutable { content_size: 789 }
/// );
///
#[macro_export]
macro_rules! immutable_attributes {
($requested:expr,
Immutable {$($immut_a:ident: $immut_v:expr),* $(,)?}) => (
{
use $crate::common::attribute_query;
fio::NodeAttributes2 {
mutable_attributes: Default::default(),
immutable_attributes: fio::ImmutableNodeAttributes {
$($immut_a: if $requested.contains(attribute_query!($immut_a)) {
Option::from($immut_v)
} else {
None
}),*,
..Default::default()
},
}
}
)
}
/// Wrapper for [`fio::CreationMode`] which replaces [`fio::OpenMode`] at API level 19. Can be
/// removed when we stop supporting API level 18, and replaced with [`fio::CreationMode`] directly.
#[derive(PartialEq, Eq)]
pub enum CreationMode {
Never,
AllowExisting,
Always,
}
#[cfg(fuchsia_api_level_less_than = "19")]
impl From<fio::OpenMode> for CreationMode {
fn from(value: fio::OpenMode) -> Self {
match value {
fio::OpenMode::OpenExisting => CreationMode::Never,
fio::OpenMode::MaybeCreate => CreationMode::AllowExisting,
fio::OpenMode::AlwaysCreate => CreationMode::Always,
}
}
}
#[cfg(fuchsia_api_level_less_than = "19")]
impl From<CreationMode> for fio::OpenMode {
fn from(value: CreationMode) -> Self {
match value {
CreationMode::Never => fio::OpenMode::OpenExisting,
CreationMode::AllowExisting => fio::OpenMode::MaybeCreate,
CreationMode::Always => fio::OpenMode::AlwaysCreate,
}
}
}
#[cfg(fuchsia_api_level_at_least = "19")]
impl From<fio::CreationMode> for CreationMode {
fn from(value: fio::CreationMode) -> Self {
match value {
fio::CreationMode::Never | fio::CreationMode::NeverDeprecated => CreationMode::Never,
fio::CreationMode::AllowExisting => CreationMode::AllowExisting,
fio::CreationMode::Always => CreationMode::Always,
}
}
}
#[cfg(fuchsia_api_level_at_least = "19")]
impl From<CreationMode> for fio::CreationMode {
fn from(value: CreationMode) -> Self {
match value {
CreationMode::Never => fio::CreationMode::Never,
CreationMode::AllowExisting => fio::CreationMode::AllowExisting,
CreationMode::Always => fio::CreationMode::Always,
}
}
}
#[cfg(fuchsia_api_level_at_least = "19")]
type CreationModeFidl = fio::CreationMode;
#[cfg(fuchsia_api_level_less_than = "19")]
type CreationModeFidl = fio::OpenMode;
impl PartialEq<CreationModeFidl> for CreationMode {
fn eq(&self, other: &CreationModeFidl) -> bool {
*self == CreationMode::from(*other)
}
}
impl PartialEq<CreationMode> for CreationModeFidl {
fn eq(&self, other: &CreationMode) -> bool {
CreationMode::from(*self) == *other
}
}
#[cfg(test)]
mod tests {
use super::inherit_rights_for_clone;
use fidl_fuchsia_io as fio;
// TODO This should be converted into a function as soon as backtrace support is in place.
// The only reason this is a macro is to generate error messages that point to the test
// function source location in their top stack frame.
macro_rules! irfc_ok {
($parent_flags:expr, $flags:expr, $expected_new_flags:expr $(,)*) => {{
let res = inherit_rights_for_clone($parent_flags, $flags);
match res {
Ok(new_flags) => assert_eq!(
$expected_new_flags, new_flags,
"`inherit_rights_for_clone` returned unexpected set of flags.\n\
Expected: {:X}\n\
Actual: {:X}",
$expected_new_flags, new_flags
),
Err(status) => panic!("`inherit_rights_for_clone` failed. Status: {status}"),
}
}};
}
#[test]
fn node_reference_is_inherited() {
irfc_ok!(
fio::OpenFlags::NODE_REFERENCE,
fio::OpenFlags::empty(),
fio::OpenFlags::NODE_REFERENCE
);
}
}