blob: 4eb89c4fe1fcca94453cdd04177f20c7c494e09d [file] [log] [blame]
// Copyright 2023 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.
//! Server support for symbolic links.
use {
crate::{
common::{
inherit_rights_for_clone, rights_to_posix_mode_bits, send_on_open_with_error, IntoAny,
},
directory::entry::{DirectoryEntry, EntryInfo},
execution_scope::ExecutionScope,
object_request::Representation,
path::Path,
ObjectRequest, ProtocolsExt, ToObjectRequest,
},
async_trait::async_trait,
fidl::endpoints::{ControlHandle as _, RequestStream, ServerEnd},
fidl_fuchsia_io as fio, fuchsia_zircon as zx,
futures::StreamExt,
std::sync::Arc,
};
#[async_trait]
pub trait Symlink: Send + Sync {
async fn read_target(&self) -> Result<Vec<u8>, zx::Status>;
/// Returns node attributes (io2).
async fn get_attributes(
&self,
_requested_attributes: fio::NodeAttributesQuery,
) -> Result<fio::NodeAttributes2, zx::Status> {
Err(zx::Status::NOT_SUPPORTED)
}
}
pub struct Connection {
symlink: Arc<dyn Symlink>,
scope: ExecutionScope,
}
pub struct SymlinkOptions;
// Returns null attributes that can be used in error cases.
fn null_node_attributes() -> fio::NodeAttributes {
fio::NodeAttributes {
mode: 0,
id: fio::INO_UNKNOWN,
content_size: 0,
storage_size: 0,
link_count: 0,
creation_time: 0,
modification_time: 0,
}
}
enum HandleRequestError {
ShutdownWithEpitaph(zx::Status),
Other,
}
impl<E: std::error::Error> From<E> for HandleRequestError {
fn from(_: E) -> HandleRequestError {
HandleRequestError::Other
}
}
impl Connection {
/// Spawns a new task to run the connection.
pub fn spawn(
scope: ExecutionScope,
symlink: Arc<dyn Symlink>,
options: SymlinkOptions,
object_request: ObjectRequest,
) {
scope.clone().spawn(Self::run(scope, symlink, options, object_request));
}
/// Processes requests for this connection until either `shutdown` is notfied, the connection is
/// closed, or an error with the connection is encountered.
pub async fn run(
scope: ExecutionScope,
symlink: Arc<dyn Symlink>,
_options: SymlinkOptions,
object_request: ObjectRequest,
) {
let mut connection = Connection { symlink, scope };
if let Ok(mut requests) = object_request.into_request_stream(&connection).await {
while let Some(Ok(request)) = requests.next().await {
let Some(_guard) = connection.scope.try_active_guard() else { break };
if let Err(e) = connection.handle_request(request).await {
if let HandleRequestError::ShutdownWithEpitaph(status) = e {
requests.control_handle().shutdown_with_epitaph(status);
}
break;
}
}
}
}
// Returns true if the connection should terminate.
async fn handle_request(&mut self, req: fio::SymlinkRequest) -> Result<(), HandleRequestError> {
match req {
fio::SymlinkRequest::Clone { flags, object, control_handle: _ } => {
self.handle_clone(flags, object);
}
fio::SymlinkRequest::Reopen {
rights_request: _,
object_request,
control_handle: _,
} => {
// TODO(https://fxbug.dev/77623): Handle unimplemented io2 method.
// Suppress any errors in the event a bad `object_request` channel was provided.
let _: Result<_, _> = object_request.close_with_epitaph(zx::Status::NOT_SUPPORTED);
}
fio::SymlinkRequest::Close { responder } => {
responder.send(Ok(()))?;
return Err(HandleRequestError::Other);
}
fio::SymlinkRequest::GetConnectionInfo { responder } => {
// TODO(https://fxbug.dev/77623): Restrict GET_ATTRIBUTES.
let rights = fio::Operations::GET_ATTRIBUTES;
responder
.send(fio::ConnectionInfo { rights: Some(rights), ..Default::default() })?;
}
fio::SymlinkRequest::Sync { responder } => {
responder.send(Ok(()))?;
}
fio::SymlinkRequest::GetAttr { responder } => match self.handle_get_attr().await {
Ok(attrs) => responder.send(zx::Status::OK.into_raw(), &attrs)?,
Err(status) => responder.send(status.into_raw(), &null_node_attributes())?,
},
fio::SymlinkRequest::SetAttr { responder, .. } => {
responder.send(zx::Status::ACCESS_DENIED.into_raw())?;
}
fio::SymlinkRequest::GetAttributes { query: _, responder } => {
// TODO(https://fxbug.dev/77623): Handle unimplemented io2 method.
responder.send(Err(zx::Status::NOT_SUPPORTED.into_raw()))?;
}
fio::SymlinkRequest::UpdateAttributes { payload: _, responder } => {
responder.send(Err(zx::Status::NOT_SUPPORTED.into_raw()))?;
}
fio::SymlinkRequest::ListExtendedAttributes { iterator, .. } => {
iterator.close_with_epitaph(zx::Status::NOT_SUPPORTED)?;
}
fio::SymlinkRequest::GetExtendedAttribute { responder, .. } => {
responder.send(Err(zx::Status::NOT_SUPPORTED.into_raw()))?;
}
fio::SymlinkRequest::SetExtendedAttribute { responder, .. } => {
responder.send(Err(zx::Status::NOT_SUPPORTED.into_raw()))?;
}
fio::SymlinkRequest::RemoveExtendedAttribute { responder, .. } => {
responder.send(Err(zx::Status::NOT_SUPPORTED.into_raw()))?;
}
fio::SymlinkRequest::Describe { responder } => match self.symlink.read_target().await {
Ok(target) => responder
.send(&fio::SymlinkInfo { target: Some(target), ..Default::default() })?,
Err(status) => return Err(HandleRequestError::ShutdownWithEpitaph(status)),
},
fio::SymlinkRequest::GetFlags { responder } => {
responder.send(zx::Status::NOT_SUPPORTED.into_raw(), fio::OpenFlags::empty())?;
}
fio::SymlinkRequest::SetFlags { responder, .. } => {
responder.send(zx::Status::ACCESS_DENIED.into_raw())?;
}
fio::SymlinkRequest::Query { responder } => {
responder.send(fio::SYMLINK_PROTOCOL_NAME.as_bytes())?;
}
fio::SymlinkRequest::QueryFilesystem { responder } => {
// TODO(fxbug.dev/123390): Support QueryFilesystem
responder.send(zx::Status::NOT_SUPPORTED.into_raw(), None)?;
}
}
Ok(())
}
fn handle_clone(&mut self, flags: fio::OpenFlags, server_end: ServerEnd<fio::NodeMarker>) {
let flags = match inherit_rights_for_clone(fio::OpenFlags::RIGHT_READABLE, flags) {
Ok(updated) => updated,
Err(status) => {
send_on_open_with_error(
flags.contains(fio::OpenFlags::DESCRIBE),
server_end,
status,
);
return;
}
};
flags.to_object_request(server_end).handle(|object_request| {
Self::spawn(
self.scope.clone(),
self.symlink.clone(),
flags.to_symlink_options()?,
object_request.take(),
);
Ok(())
});
}
async fn handle_get_attr(&mut self) -> Result<fio::NodeAttributes, zx::Status> {
// TODO(fxbug.dev/123390): Add support for more attributes such as timestamps
Ok(fio::NodeAttributes {
mode: fio::MODE_TYPE_SYMLINK
| rights_to_posix_mode_bits(/*r*/ true, /*w*/ false, /*x*/ false),
id: fio::INO_UNKNOWN,
content_size: self.symlink.read_target().await?.len() as u64,
storage_size: 0,
link_count: 1,
creation_time: 0,
modification_time: 0,
})
}
}
impl<T: IntoAny + Symlink + Send + Sync> DirectoryEntry for T {
fn open(
self: Arc<Self>,
scope: ExecutionScope,
flags: fio::OpenFlags,
path: Path,
server_end: ServerEnd<fio::NodeMarker>,
) {
flags.to_object_request(server_end).handle(|object_request| {
if !path.is_empty() {
return Err(zx::Status::NOT_DIR);
}
Connection::spawn(scope, self, flags.to_symlink_options()?, object_request.take());
Ok(())
});
}
fn entry_info(&self) -> EntryInfo {
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Symlink)
}
}
#[async_trait]
impl Representation for Connection {
type Protocol = fio::SymlinkMarker;
async fn get_representation(
&self,
requested_attributes: fio::NodeAttributesQuery,
) -> Result<fio::Representation, zx::Status> {
Ok(fio::Representation::Symlink(fio::SymlinkInfo {
attributes: Some(self.symlink.get_attributes(requested_attributes).await?),
target: Some(self.symlink.read_target().await?),
..Default::default()
}))
}
async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, zx::Status> {
Ok(fio::NodeInfoDeprecated::Symlink(fio::SymlinkObject {
target: self.symlink.read_target().await?,
}))
}
}
#[cfg(test)]
mod tests {
use {
super::{Connection, Symlink},
crate::{
common::rights_to_posix_mode_bits, execution_scope::ExecutionScope,
symlink::SymlinkOptions, ProtocolsExt, ToObjectRequest,
},
assert_matches::assert_matches,
async_trait::async_trait,
fidl::endpoints::create_proxy,
fidl_fuchsia_io as fio, fuchsia_zircon as zx,
futures::StreamExt,
std::sync::Arc,
};
struct TestSymlink;
#[async_trait]
impl Symlink for TestSymlink {
async fn read_target(&self) -> Result<Vec<u8>, zx::Status> {
Ok(b"target".to_vec())
}
}
#[fuchsia::test]
async fn test_read_target() {
let scope = ExecutionScope::new();
let (client_end, server_end) =
create_proxy::<fio::SymlinkMarker>().expect("create_proxy failed");
Connection::spawn(
scope,
Arc::new(TestSymlink),
SymlinkOptions,
fio::OpenFlags::RIGHT_READABLE.to_object_request(server_end),
);
assert_eq!(
client_end.describe().await.expect("fidl failed").target.expect("missing target"),
b"target"
);
}
#[fuchsia::test]
async fn test_validate_flags() {
let scope = ExecutionScope::new();
let check = |mut flags: fio::OpenFlags| {
let (client_end, server_end) =
create_proxy::<fio::SymlinkMarker>().expect("create_proxy failed");
flags |= fio::OpenFlags::DESCRIBE;
flags.to_object_request(server_end).handle(|object_request| {
Connection::spawn(
scope.clone(),
Arc::new(TestSymlink),
flags.to_symlink_options()?,
object_request.take(),
);
Ok(())
});
async move {
zx::Status::from_raw(
client_end
.take_event_stream()
.next()
.await
.expect("no event")
.expect("next failed")
.into_on_open_()
.expect("expected OnOpen")
.0,
)
}
};
for flags in [
fio::OpenFlags::RIGHT_WRITABLE,
fio::OpenFlags::RIGHT_EXECUTABLE,
fio::OpenFlags::CREATE,
fio::OpenFlags::CREATE_IF_ABSENT,
fio::OpenFlags::TRUNCATE,
fio::OpenFlags::APPEND,
fio::OpenFlags::POSIX_WRITABLE,
fio::OpenFlags::POSIX_EXECUTABLE,
fio::OpenFlags::CLONE_SAME_RIGHTS,
fio::OpenFlags::BLOCK_DEVICE,
] {
assert_eq!(check(flags).await, zx::Status::INVALID_ARGS, "{flags:?}");
}
assert_eq!(
check(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY).await,
zx::Status::OK
);
}
#[fuchsia::test]
async fn test_get_attr() {
let scope = ExecutionScope::new();
let (client_end, server_end) =
create_proxy::<fio::SymlinkMarker>().expect("create_proxy failed");
let flags = fio::OpenFlags::RIGHT_READABLE;
Connection::spawn(
scope,
Arc::new(TestSymlink),
flags.to_symlink_options().unwrap(),
flags.to_object_request(server_end.into_channel()),
);
assert_matches!(
client_end.get_attr().await.expect("fidl failed"),
(
0,
fio::NodeAttributes {
mode,
id: fio::INO_UNKNOWN,
content_size: 6,
storage_size: 0,
link_count: 1,
creation_time: 0,
modification_time: 0,
}
) if mode == fio::MODE_TYPE_SYMLINK
| rights_to_posix_mode_bits(/*r*/ true, /*w*/ false, /*x*/ false)
);
}
#[fuchsia::test]
async fn test_describe() {
let scope = ExecutionScope::new();
let (client_end, server_end) =
create_proxy::<fio::SymlinkMarker>().expect("create_proxy failed");
let flags = fio::OpenFlags::RIGHT_READABLE;
Connection::spawn(
scope,
Arc::new(TestSymlink),
flags.to_symlink_options().unwrap(),
flags.to_object_request(server_end),
);
assert_matches!(
client_end.describe().await.expect("fidl failed"),
fio::SymlinkInfo {
target: Some(target),
..
} if target == b"target"
);
}
}