vfs/
node.rs

1// Copyright 2023 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//! Implementation of a (limited) node connection.
6
7use crate::common::IntoAny;
8use crate::directory::entry::GetEntryInfo;
9use crate::directory::entry_container::MutableDirectory;
10use crate::execution_scope::ExecutionScope;
11use crate::name::Name;
12use crate::object_request::{ConnectionCreator, Representation, run_synchronous_future_or_spawn};
13use crate::protocols::ToNodeOptions;
14use crate::request_handler::{RequestHandler, RequestListener};
15use crate::{ObjectRequest, ObjectRequestRef};
16use anyhow::Error;
17use fidl::endpoints::{DiscoverableProtocolMarker as _, ServerEnd};
18use fidl_fuchsia_io as fio;
19use libc::{S_IRUSR, S_IWUSR};
20use std::future::{Future, ready};
21use std::ops::ControlFlow;
22use std::pin::Pin;
23use std::sync::Arc;
24use zx_status::Status;
25
26/// POSIX emulation layer access attributes for all services created with service().
27#[cfg(not(target_os = "macos"))]
28pub const POSIX_READ_WRITE_PROTECTION_ATTRIBUTES: u32 = S_IRUSR | S_IWUSR;
29#[cfg(target_os = "macos")]
30pub const POSIX_READ_WRITE_PROTECTION_ATTRIBUTES: u16 = S_IRUSR | S_IWUSR;
31
32#[derive(Clone, Copy)]
33pub struct NodeOptions {
34    pub rights: fio::Operations,
35}
36
37impl From<&NodeOptions> for fio::Flags {
38    fn from(options: &NodeOptions) -> Self {
39        // There is 1:1 mapping between `fio::Operations` and `fio::Flags`.
40        fio::Flags::PROTOCOL_NODE | fio::Flags::from_bits_truncate(options.rights.bits())
41    }
42}
43
44/// All nodes must implement this trait.
45pub trait Node: GetEntryInfo + IntoAny + Send + Sync + 'static {
46    /// Returns node attributes (io2).
47    fn get_attributes(
48        &self,
49        requested_attributes: fio::NodeAttributesQuery,
50    ) -> impl Future<Output = Result<fio::NodeAttributes2, Status>> + Send
51    where
52        Self: Sized;
53
54    /// Called when the node is about to be opened as the node protocol.  Implementers can use this
55    /// to perform any initialization or reference counting.  Errors here will result in the open
56    /// failing.  By default, this forwards to the infallible will_clone.
57    fn will_open_as_node(&self) -> Result<(), Status> {
58        self.will_clone();
59        Ok(())
60    }
61
62    /// Called when the node is about to be cloned (and also by the default implementation of
63    /// will_open_as_node).  Implementations that perform their own open count can use this.  Each
64    /// call to `will_clone` will be accompanied by an eventual call to `close`.
65    fn will_clone(&self) {}
66
67    /// Called when the node is closed.
68    fn close(self: Arc<Self>) {}
69
70    fn link_into(
71        self: Arc<Self>,
72        _destination_dir: Arc<dyn MutableDirectory>,
73        _name: Name,
74    ) -> impl Future<Output = Result<(), Status>> + Send
75    where
76        Self: Sized,
77    {
78        ready(Err(Status::NOT_SUPPORTED))
79    }
80
81    /// Returns information about the filesystem.
82    fn query_filesystem(&self) -> Result<fio::FilesystemInfo, Status> {
83        Err(Status::NOT_SUPPORTED)
84    }
85
86    /// Opens the node using the node protocol.
87    fn open_as_node(
88        self: Arc<Self>,
89        scope: ExecutionScope,
90        options: NodeOptions,
91        object_request: ObjectRequestRef<'_>,
92    ) -> Result<(), Status>
93    where
94        Self: Sized,
95    {
96        self.will_open_as_node()?;
97        Connection::create_sync(scope, self, options, object_request.take());
98        Ok(())
99    }
100
101    /// List extended attributes.
102    fn list_extended_attributes(&self) -> impl Future<Output = Result<Vec<Vec<u8>>, Status>> + Send
103    where
104        Self: Sized,
105    {
106        ready(Err(Status::NOT_SUPPORTED))
107    }
108
109    /// Get the value for an extended attribute.
110    fn get_extended_attribute(
111        &self,
112        _name: Vec<u8>,
113    ) -> impl Future<Output = Result<Vec<u8>, Status>> + Send
114    where
115        Self: Sized,
116    {
117        ready(Err(Status::NOT_SUPPORTED))
118    }
119
120    /// Set the value for an extended attribute.
121    fn set_extended_attribute(
122        &self,
123        _name: Vec<u8>,
124        _value: Vec<u8>,
125        _mode: fio::SetExtendedAttributeMode,
126    ) -> impl Future<Output = Result<(), Status>> + Send
127    where
128        Self: Sized,
129    {
130        ready(Err(Status::NOT_SUPPORTED))
131    }
132
133    /// Remove the value for an extended attribute.
134    fn remove_extended_attribute(
135        &self,
136        _name: Vec<u8>,
137    ) -> impl Future<Output = Result<(), Status>> + Send
138    where
139        Self: Sized,
140    {
141        ready(Err(Status::NOT_SUPPORTED))
142    }
143}
144
145/// Represents a FIDL (limited) node connection.
146pub struct Connection<N: Node> {
147    // Execution scope this connection and any async operations and connections it creates will
148    // use.
149    scope: ExecutionScope,
150
151    // The underlying node.
152    node: OpenNode<N>,
153
154    // Node options.
155    options: NodeOptions,
156}
157
158/// Return type for [`handle_request()`] functions.
159enum ConnectionState {
160    /// Connection is still alive.
161    Alive,
162    /// Connection have received Node::Close message, it was dropped by the peer, or an error had
163    /// occurred.  As we do not perform any actions, except for closing our end we do not
164    /// distinguish those cases, unlike file and directory connections.
165    Closed,
166}
167
168impl<N: Node> Connection<N> {
169    /// Creates a new connection to serve the node. The node will be served from a new async `Task`,
170    /// not from the current `Task`. Errors in constructing the connection are not guaranteed to be
171    /// returned, they may be sent directly to the client end of the connection. This method should
172    /// be called from within an `ObjectRequest` handler to ensure that errors are sent to the
173    /// client end of the connection.
174    pub async fn create(
175        scope: ExecutionScope,
176        node: Arc<N>,
177        options: impl ToNodeOptions,
178        object_request: ObjectRequestRef<'_>,
179    ) -> Result<(), Status> {
180        let node = OpenNode::new(node);
181        let options = options.to_node_options(node.entry_info().type_())?;
182        let connection = Connection { scope: scope.clone(), node, options };
183        if let Ok(requests) = object_request.take().into_request_stream(&connection).await {
184            scope.spawn(RequestListener::new(requests, connection));
185        }
186        Ok(())
187    }
188
189    /// Similar to `create` but optimized for nodes whose implementation is synchronous and creating
190    /// the connection is being done from a non-async context.
191    pub fn create_sync(
192        scope: ExecutionScope,
193        node: Arc<N>,
194        options: impl ToNodeOptions,
195        object_request: ObjectRequest,
196    ) {
197        run_synchronous_future_or_spawn(
198            scope.clone(),
199            object_request.handle_async(async |object_request| {
200                Self::create(scope, node, options, object_request).await
201            }),
202        )
203    }
204
205    /// Handle a [`NodeRequest`].
206    async fn handle_request(&mut self, req: fio::NodeRequest) -> Result<ConnectionState, Error> {
207        match req {
208            #[cfg(any(
209                fuchsia_api_level_at_least = "PLATFORM",
210                not(fuchsia_api_level_at_least = "29")
211            ))]
212            fio::NodeRequest::DeprecatedClone { flags, object, control_handle: _ } => {
213                crate::common::send_on_open_with_error(
214                    flags.contains(fio::OpenFlags::DESCRIBE),
215                    object,
216                    Status::NOT_SUPPORTED,
217                );
218            }
219            fio::NodeRequest::Clone { request, control_handle: _ } => {
220                // Suppress any errors in the event a bad `request` channel was provided.
221                self.handle_clone(ServerEnd::new(request.into_channel()));
222            }
223            fio::NodeRequest::Close { responder } => {
224                responder.send(Ok(()))?;
225                return Ok(ConnectionState::Closed);
226            }
227            fio::NodeRequest::Sync { responder } => {
228                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
229            }
230            #[cfg(fuchsia_api_level_at_least = "28")]
231            fio::NodeRequest::DeprecatedGetAttr { responder } => {
232                let (status, attrs) =
233                    crate::common::io2_to_io1_attrs(self.node.as_ref(), self.options.rights).await;
234                responder.send(status.into_raw(), &attrs)?;
235            }
236            #[cfg(not(fuchsia_api_level_at_least = "28"))]
237            fio::NodeRequest::GetAttr { responder } => {
238                let (status, attrs) =
239                    crate::common::io2_to_io1_attrs(self.node.as_ref(), self.options.rights).await;
240                responder.send(status.into_raw(), &attrs)?;
241            }
242            #[cfg(fuchsia_api_level_at_least = "28")]
243            fio::NodeRequest::DeprecatedSetAttr { flags: _, attributes: _, responder } => {
244                responder.send(Status::BAD_HANDLE.into_raw())?;
245            }
246            #[cfg(not(fuchsia_api_level_at_least = "28"))]
247            fio::NodeRequest::SetAttr { flags: _, attributes: _, responder } => {
248                responder.send(Status::BAD_HANDLE.into_raw())?;
249            }
250            fio::NodeRequest::GetAttributes { query, responder } => {
251                let result = self.node.get_attributes(query).await;
252                responder.send(
253                    result
254                        .as_ref()
255                        .map(|attrs| (&attrs.mutable_attributes, &attrs.immutable_attributes))
256                        .map_err(|status| status.into_raw()),
257                )?;
258            }
259            fio::NodeRequest::UpdateAttributes { payload: _, responder } => {
260                responder.send(Err(Status::BAD_HANDLE.into_raw()))?;
261            }
262            fio::NodeRequest::ListExtendedAttributes { iterator, .. } => {
263                iterator.close_with_epitaph(Status::NOT_SUPPORTED)?;
264            }
265            fio::NodeRequest::GetExtendedAttribute { responder, .. } => {
266                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
267            }
268            fio::NodeRequest::SetExtendedAttribute { responder, .. } => {
269                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
270            }
271            fio::NodeRequest::RemoveExtendedAttribute { responder, .. } => {
272                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
273            }
274            #[cfg(fuchsia_api_level_at_least = "27")]
275            fio::NodeRequest::GetFlags { responder } => {
276                responder.send(Ok(fio::Flags::from(&self.options)))?;
277            }
278            #[cfg(fuchsia_api_level_at_least = "27")]
279            fio::NodeRequest::SetFlags { flags: _, responder } => {
280                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
281            }
282            #[cfg(fuchsia_api_level_at_least = "27")]
283            fio::NodeRequest::DeprecatedGetFlags { responder } => {
284                responder.send(Status::OK.into_raw(), fio::OpenFlags::NODE_REFERENCE)?;
285            }
286            #[cfg(fuchsia_api_level_at_least = "27")]
287            fio::NodeRequest::DeprecatedSetFlags { flags: _, responder } => {
288                responder.send(Status::BAD_HANDLE.into_raw())?;
289            }
290            #[cfg(not(fuchsia_api_level_at_least = "27"))]
291            fio::NodeRequest::GetFlags { responder } => {
292                responder.send(Status::OK.into_raw(), fio::OpenFlags::NODE_REFERENCE)?;
293            }
294            #[cfg(not(fuchsia_api_level_at_least = "27"))]
295            fio::NodeRequest::SetFlags { flags: _, responder } => {
296                responder.send(Status::BAD_HANDLE.into_raw())?;
297            }
298            fio::NodeRequest::Query { responder } => {
299                responder.send(fio::NodeMarker::PROTOCOL_NAME.as_bytes())?;
300            }
301            fio::NodeRequest::QueryFilesystem { responder } => {
302                responder.send(Status::NOT_SUPPORTED.into_raw(), None)?;
303            }
304            fio::NodeRequest::_UnknownMethod { .. } => (),
305        }
306        Ok(ConnectionState::Alive)
307    }
308
309    fn handle_clone(&mut self, server_end: ServerEnd<fio::NodeMarker>) {
310        self.node.will_clone();
311        let connection = Self {
312            scope: self.scope.clone(),
313            node: OpenNode::new(self.node.clone()),
314            options: self.options,
315        };
316        self.scope.spawn(RequestListener::new(server_end.into_stream(), connection));
317    }
318}
319
320impl<N: Node> RequestHandler for Connection<N> {
321    type Request = Result<fio::NodeRequest, fidl::Error>;
322
323    async fn handle_request(self: Pin<&mut Self>, request: Self::Request) -> ControlFlow<()> {
324        let this = self.get_mut();
325        if let Some(_guard) = this.scope.try_active_guard() {
326            match request {
327                Ok(request) => match this.handle_request(request).await {
328                    Ok(ConnectionState::Alive) => ControlFlow::Continue(()),
329                    Ok(ConnectionState::Closed) | Err(_) => ControlFlow::Break(()),
330                },
331                Err(_) => ControlFlow::Break(()),
332            }
333        } else {
334            ControlFlow::Break(())
335        }
336    }
337}
338
339impl<N: Node> Representation for Connection<N> {
340    type Protocol = fio::NodeMarker;
341
342    #[cfg(fuchsia_api_level_at_least = "27")]
343    async fn get_representation(
344        &self,
345        requested_attributes: fio::NodeAttributesQuery,
346    ) -> Result<fio::Representation, Status> {
347        Ok(fio::Representation::Node(fio::NodeInfo {
348            attributes: if requested_attributes.is_empty() {
349                None
350            } else {
351                Some(self.node.get_attributes(requested_attributes).await?)
352            },
353            ..Default::default()
354        }))
355    }
356
357    #[cfg(not(fuchsia_api_level_at_least = "27"))]
358    async fn get_representation(
359        &self,
360        requested_attributes: fio::NodeAttributesQuery,
361    ) -> Result<fio::Representation, Status> {
362        Ok(fio::Representation::Connector(fio::ConnectorInfo {
363            attributes: if requested_attributes.is_empty() {
364                None
365            } else {
366                Some(self.node.get_attributes(requested_attributes).await?)
367            },
368            ..Default::default()
369        }))
370    }
371
372    async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
373        Ok(fio::NodeInfoDeprecated::Service(fio::Service))
374    }
375}
376
377impl<N: Node> ConnectionCreator<N> for Connection<N> {
378    async fn create<'a>(
379        scope: ExecutionScope,
380        node: Arc<N>,
381        protocols: impl crate::ProtocolsExt,
382        object_request: ObjectRequestRef<'a>,
383    ) -> Result<(), Status> {
384        Self::create(scope, node, protocols, object_request).await
385    }
386}
387
388/// This struct is a RAII wrapper around a node that will call close() on it when dropped.
389pub struct OpenNode<T: Node> {
390    node: Arc<T>,
391}
392
393impl<T: Node> OpenNode<T> {
394    pub fn new(node: Arc<T>) -> Self {
395        Self { node }
396    }
397}
398
399impl<T: Node> Drop for OpenNode<T> {
400    fn drop(&mut self) {
401        self.node.clone().close();
402    }
403}
404
405impl<T: Node> std::ops::Deref for OpenNode<T> {
406    type Target = Arc<T>;
407
408    fn deref(&self) -> &Self::Target {
409        &self.node
410    }
411}