vfs/directory/
connection.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
5use crate::common::{
6    decode_extended_attribute_value, encode_extended_attribute_value, extended_attributes_sender,
7    send_on_open_with_error,
8};
9use crate::directory::common::check_child_connection_flags;
10use crate::directory::entry_container::{Directory, DirectoryWatcher};
11use crate::directory::traversal_position::TraversalPosition;
12use crate::directory::{DirectoryOptions, read_dirents};
13use crate::execution_scope::{ExecutionScope, yield_to_executor};
14use crate::node::OpenNode;
15use crate::object_request::Representation;
16use crate::path::Path;
17use fidl::endpoints::DiscoverableProtocolMarker as _;
18
19use anyhow::Error;
20use fidl::endpoints::ServerEnd;
21use fidl_fuchsia_io as fio;
22use storage_trace::{self as trace, TraceFutureExt};
23use zx_status::Status;
24
25use crate::common::CreationMode;
26use crate::{ObjectRequest, ObjectRequestRef, ProtocolsExt};
27
28/// Return type for `BaseConnection::handle_request`.
29pub enum ConnectionState {
30    /// Connection is still alive.
31    Alive,
32    /// Connection have received Node::Close message and should be closed.
33    Closed,
34}
35
36/// Handles functionality shared between mutable and immutable FIDL connections to a directory.  A
37/// single directory may contain multiple connections.  Instances of the `BaseConnection`
38/// will also hold any state that is "per-connection".  Currently that would be the access flags
39/// and the seek position.
40pub(in crate::directory) struct BaseConnection<DirectoryType: Directory> {
41    /// Execution scope this connection and any async operations and connections it creates will
42    /// use.
43    pub(in crate::directory) scope: ExecutionScope,
44
45    pub(in crate::directory) directory: OpenNode<DirectoryType>,
46
47    /// Flags set on this connection when it was opened or cloned.
48    pub(in crate::directory) options: DirectoryOptions,
49
50    /// Seek position for this connection to the directory.  We just store the element that was
51    /// returned last by ReadDirents for this connection.  Next call will look for the next element
52    /// in alphabetical order and resume from there.
53    ///
54    /// An alternative is to use an intrusive tree to have a dual index in both names and IDs that
55    /// are assigned to the entries in insertion order.  Then we can store an ID instead of the
56    /// full entry name.  This is what the C++ version is doing currently.
57    ///
58    /// It should be possible to do the same intrusive dual-indexing using, for example,
59    ///
60    ///     https://docs.rs/intrusive-collections/0.7.6/intrusive_collections/
61    ///
62    /// but, as, I think, at least for the pseudo directories, this approach is fine, and it simple
63    /// enough.
64    seek: TraversalPosition,
65}
66
67impl<DirectoryType: Directory> BaseConnection<DirectoryType> {
68    /// Constructs an instance of `BaseConnection` - to be used by derived connections, when they
69    /// need to create a nested `BaseConnection` "sub-object".  But when implementing
70    /// `create_connection`, derived connections should use the [`create_connection`] call.
71    pub(in crate::directory) fn new(
72        scope: ExecutionScope,
73        directory: OpenNode<DirectoryType>,
74        options: DirectoryOptions,
75    ) -> Self {
76        BaseConnection { scope, directory, options, seek: Default::default() }
77    }
78
79    /// Handle a [`DirectoryRequest`].  This function is responsible for handing all the basic
80    /// directory operations.
81    pub(in crate::directory) async fn handle_request(
82        &mut self,
83        request: fio::DirectoryRequest,
84    ) -> Result<ConnectionState, Error> {
85        match request {
86            #[cfg(any(
87                fuchsia_api_level_at_least = "PLATFORM",
88                not(fuchsia_api_level_at_least = "29")
89            ))]
90            fio::DirectoryRequest::DeprecatedClone { flags, object, control_handle: _ } => {
91                trace::duration!(c"storage", c"Directory::DeprecatedClone");
92                crate::common::send_on_open_with_error(
93                    flags.contains(fio::OpenFlags::DESCRIBE),
94                    object,
95                    Status::NOT_SUPPORTED,
96                );
97            }
98            fio::DirectoryRequest::Clone { request, control_handle: _ } => {
99                trace::duration!(c"storage", c"Directory::Clone");
100                self.handle_clone(request.into_channel());
101            }
102            fio::DirectoryRequest::Close { responder } => {
103                trace::duration!(c"storage", c"Directory::Close");
104                responder.send(Ok(()))?;
105                return Ok(ConnectionState::Closed);
106            }
107            #[cfg(fuchsia_api_level_at_least = "28")]
108            fio::DirectoryRequest::DeprecatedGetAttr { responder } => {
109                async move {
110                    let (status, attrs) = crate::common::io2_to_io1_attrs(
111                        self.directory.as_ref(),
112                        self.options.rights,
113                    )
114                    .await;
115                    responder.send(status.into_raw(), &attrs)
116                }
117                .trace(trace::trace_future_args!(c"storage", c"Directory::GetAttr"))
118                .await?;
119            }
120            #[cfg(not(fuchsia_api_level_at_least = "28"))]
121            fio::DirectoryRequest::GetAttr { responder } => {
122                async move {
123                    let (status, attrs) = crate::common::io2_to_io1_attrs(
124                        self.directory.as_ref(),
125                        self.options.rights,
126                    )
127                    .await;
128                    responder.send(status.into_raw(), &attrs)
129                }
130                .trace(trace::trace_future_args!(c"storage", c"Directory::GetAttr"))
131                .await?;
132            }
133            fio::DirectoryRequest::GetAttributes { query, responder } => {
134                async move {
135                    // TODO(https://fxbug.dev/346585458): Restrict or remove GET_ATTRIBUTES.
136                    let attrs = self.directory.get_attributes(query).await;
137                    responder.send(
138                        attrs
139                            .as_ref()
140                            .map(|attrs| (&attrs.mutable_attributes, &attrs.immutable_attributes))
141                            .map_err(|status| status.into_raw()),
142                    )
143                }
144                .trace(trace::trace_future_args!(c"storage", c"Directory::GetAttributes"))
145                .await?;
146            }
147            fio::DirectoryRequest::UpdateAttributes { payload: _, responder } => {
148                trace::duration!(c"storage", c"Directory::UpdateAttributes");
149                // TODO(https://fxbug.dev/324112547): Handle unimplemented io2 method.
150                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
151            }
152            fio::DirectoryRequest::ListExtendedAttributes { iterator, control_handle: _ } => {
153                self.handle_list_extended_attribute(iterator)
154                    .trace(trace::trace_future_args!(
155                        c"storage",
156                        c"Directory::ListExtendedAttributes"
157                    ))
158                    .await;
159            }
160            fio::DirectoryRequest::GetExtendedAttribute { name, responder } => {
161                async move {
162                    let res =
163                        self.handle_get_extended_attribute(name).await.map_err(Status::into_raw);
164                    responder.send(res)
165                }
166                .trace(trace::trace_future_args!(c"storage", c"Directory::GetExtendedAttribute"))
167                .await?;
168            }
169            fio::DirectoryRequest::SetExtendedAttribute { name, value, mode, responder } => {
170                async move {
171                    let res = self
172                        .handle_set_extended_attribute(name, value, mode)
173                        .await
174                        .map_err(Status::into_raw);
175                    responder.send(res)
176                }
177                .trace(trace::trace_future_args!(c"storage", c"Directory::SetExtendedAttribute"))
178                .await?;
179            }
180            fio::DirectoryRequest::RemoveExtendedAttribute { name, responder } => {
181                async move {
182                    let res =
183                        self.handle_remove_extended_attribute(name).await.map_err(Status::into_raw);
184                    responder.send(res)
185                }
186                .trace(trace::trace_future_args!(c"storage", c"Directory::RemoveExtendedAttribute"))
187                .await?;
188            }
189            #[cfg(fuchsia_api_level_at_least = "27")]
190            fio::DirectoryRequest::GetFlags { responder } => {
191                trace::duration!(c"storage", c"Directory::GetFlags");
192                responder.send(Ok(fio::Flags::from(&self.options)))?;
193            }
194            #[cfg(fuchsia_api_level_at_least = "27")]
195            fio::DirectoryRequest::SetFlags { flags: _, responder } => {
196                trace::duration!(c"storage", c"Directory::SetFlags");
197                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
198            }
199            #[cfg(fuchsia_api_level_at_least = "27")]
200            fio::DirectoryRequest::DeprecatedGetFlags { responder } => {
201                trace::duration!(c"storage", c"Directory::DeprecatedGetFlags");
202                responder.send(Status::OK.into_raw(), self.options.to_io1())?;
203            }
204            #[cfg(fuchsia_api_level_at_least = "27")]
205            fio::DirectoryRequest::DeprecatedSetFlags { flags: _, responder } => {
206                trace::duration!(c"storage", c"Directory::DeprecatedSetFlags");
207                responder.send(Status::NOT_SUPPORTED.into_raw())?;
208            }
209            #[cfg(not(fuchsia_api_level_at_least = "27"))]
210            fio::DirectoryRequest::GetFlags { responder } => {
211                trace::duration!(c"storage", c"Directory::GetFlags");
212                responder.send(Status::OK.into_raw(), self.options.to_io1())?;
213            }
214            #[cfg(not(fuchsia_api_level_at_least = "27"))]
215            fio::DirectoryRequest::SetFlags { flags: _, responder } => {
216                trace::duration!(c"storage", c"Directory::SetFlags");
217                responder.send(Status::NOT_SUPPORTED.into_raw())?;
218            }
219            #[cfg(fuchsia_api_level_at_least = "27")]
220            fio::DirectoryRequest::DeprecatedOpen {
221                flags,
222                mode: _,
223                path,
224                object,
225                control_handle: _,
226            } => {
227                {
228                    trace::duration!(c"storage", c"Directory::Open");
229                    self.handle_deprecated_open(flags, path, object);
230                }
231                // Since open typically spawns a task, yield to the executor now to give that task a
232                // chance to run before we try and process the next request for this directory.
233                yield_to_executor().await;
234            }
235            #[cfg(not(fuchsia_api_level_at_least = "27"))]
236            fio::DirectoryRequest::Open { flags, mode: _, path, object, control_handle: _ } => {
237                {
238                    trace::duration!(c"storage", c"Directory::Open");
239                    self.handle_deprecated_open(flags, path, object);
240                }
241                // Since open typically spawns a task, yield to the executor now to give that task a
242                // chance to run before we try and process the next request for this directory.
243                yield_to_executor().await;
244            }
245            fio::DirectoryRequest::AdvisoryLock { request: _, responder } => {
246                trace::duration!(c"storage", c"Directory::AdvisoryLock");
247                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
248            }
249            fio::DirectoryRequest::ReadDirents { max_bytes, responder } => {
250                async move {
251                    let (status, entries) = self.handle_read_dirents(max_bytes).await;
252                    responder.send(status.into_raw(), entries.as_slice())
253                }
254                .trace(trace::trace_future_args!(c"storage", c"Directory::ReadDirents"))
255                .await?;
256            }
257            fio::DirectoryRequest::Rewind { responder } => {
258                trace::duration!(c"storage", c"Directory::Rewind");
259                self.seek = Default::default();
260                responder.send(Status::OK.into_raw())?;
261            }
262            fio::DirectoryRequest::Link { src, dst_parent_token, dst, responder } => {
263                async move {
264                    let status: Status = self.handle_link(&src, dst_parent_token, dst).await.into();
265                    responder.send(status.into_raw())
266                }
267                .trace(trace::trace_future_args!(c"storage", c"Directory::Link"))
268                .await?;
269            }
270            fio::DirectoryRequest::Watch { mask, options, watcher, responder } => {
271                trace::duration!(c"storage", c"Directory::Watch");
272                let status = if options != 0 {
273                    Status::INVALID_ARGS
274                } else {
275                    self.handle_watch(mask, watcher.into()).into()
276                };
277                responder.send(status.into_raw())?;
278            }
279            fio::DirectoryRequest::Query { responder } => {
280                let () = responder.send(fio::DirectoryMarker::PROTOCOL_NAME.as_bytes())?;
281            }
282            fio::DirectoryRequest::QueryFilesystem { responder } => {
283                trace::duration!(c"storage", c"Directory::QueryFilesystem");
284                match self.directory.query_filesystem() {
285                    Err(status) => responder.send(status.into_raw(), None)?,
286                    Ok(info) => responder.send(0, Some(&info))?,
287                }
288            }
289            fio::DirectoryRequest::Unlink { name: _, options: _, responder } => {
290                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
291            }
292            fio::DirectoryRequest::GetToken { responder } => {
293                responder.send(Status::NOT_SUPPORTED.into_raw(), None)?;
294            }
295            fio::DirectoryRequest::Rename { src: _, dst_parent_token: _, dst: _, responder } => {
296                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
297            }
298            #[cfg(fuchsia_api_level_at_least = "28")]
299            fio::DirectoryRequest::DeprecatedSetAttr { flags: _, attributes: _, responder } => {
300                responder.send(Status::NOT_SUPPORTED.into_raw())?;
301            }
302            #[cfg(not(fuchsia_api_level_at_least = "28"))]
303            fio::DirectoryRequest::SetAttr { flags: _, attributes: _, responder } => {
304                responder.send(Status::NOT_SUPPORTED.into_raw())?;
305            }
306            fio::DirectoryRequest::Sync { responder } => {
307                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
308            }
309            fio::DirectoryRequest::CreateSymlink { responder, .. } => {
310                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
311            }
312            #[cfg(fuchsia_api_level_at_least = "27")]
313            fio::DirectoryRequest::Open { path, mut flags, options, object, control_handle: _ } => {
314                {
315                    // Remove POSIX flags when the respective rights are not available.
316                    if !self.options.rights.contains(fio::INHERITED_WRITE_PERMISSIONS) {
317                        flags &= !fio::Flags::PERM_INHERIT_WRITE;
318                    }
319                    if !self.options.rights.contains(fio::Rights::EXECUTE) {
320                        flags &= !fio::Flags::PERM_INHERIT_EXECUTE;
321                    }
322
323                    ObjectRequest::new(flags, &options, object)
324                        .handle_async(async |req| self.handle_open(path, flags, req).await)
325                        .trace(trace::trace_future_args!(c"storage", c"Directory::Open3"))
326                        .await;
327                }
328                // Since open typically spawns a task, yield to the executor now to give that task a
329                // chance to run before we try and process the next request for this directory.
330                yield_to_executor().await;
331            }
332            #[cfg(not(fuchsia_api_level_at_least = "27"))]
333            fio::DirectoryRequest::Open3 {
334                path,
335                mut flags,
336                options,
337                object,
338                control_handle: _,
339            } => {
340                {
341                    // Remove POSIX flags when the respective rights are not available.
342                    if !self.options.rights.contains(fio::INHERITED_WRITE_PERMISSIONS) {
343                        flags &= !fio::Flags::PERM_INHERIT_WRITE;
344                    }
345                    if !self.options.rights.contains(fio::Rights::EXECUTE) {
346                        flags &= !fio::Flags::PERM_INHERIT_EXECUTE;
347                    }
348
349                    ObjectRequest::new(flags, &options, object)
350                        .handle_async(async |req| self.handle_open(path, flags, req).await)
351                        .trace(trace::trace_future_args!(c"storage", c"Directory::Open3"))
352                        .await;
353                }
354                // Since open typically spawns a task, yield to the executor now to give that task a
355                // chance to run before we try and process the next request for this directory.
356                yield_to_executor().await;
357            }
358            fio::DirectoryRequest::_UnknownMethod { .. } => (),
359        }
360        Ok(ConnectionState::Alive)
361    }
362
363    fn handle_clone(&mut self, object: fidl::Channel) {
364        let flags = fio::Flags::from(&self.options);
365        ObjectRequest::new(flags, &Default::default(), object)
366            .handle(|req| self.directory.clone().open(self.scope.clone(), Path::dot(), flags, req));
367    }
368
369    fn handle_deprecated_open(
370        &self,
371        mut flags: fio::OpenFlags,
372        path: String,
373        server_end: ServerEnd<fio::NodeMarker>,
374    ) {
375        let describe = flags.intersects(fio::OpenFlags::DESCRIBE);
376
377        let path = match Path::validate_and_split(path) {
378            Ok(path) => path,
379            Err(status) => {
380                send_on_open_with_error(describe, server_end, status);
381                return;
382            }
383        };
384
385        if path.is_dir() {
386            flags |= fio::OpenFlags::DIRECTORY;
387        }
388
389        let flags = match check_child_connection_flags(self.options.to_io1(), flags) {
390            Ok(updated) => updated,
391            Err(status) => {
392                send_on_open_with_error(describe, server_end, status);
393                return;
394            }
395        };
396        if path.is_dot() {
397            if flags.intersects(fio::OpenFlags::NOT_DIRECTORY) {
398                send_on_open_with_error(describe, server_end, Status::INVALID_ARGS);
399                return;
400            }
401            if flags.intersects(fio::OpenFlags::CREATE_IF_ABSENT) {
402                send_on_open_with_error(describe, server_end, Status::ALREADY_EXISTS);
403                return;
404            }
405        }
406
407        // It is up to the open method to handle OPEN_FLAG_DESCRIBE from this point on.
408        let directory = self.directory.clone();
409        directory.deprecated_open(self.scope.clone(), flags, path, server_end);
410    }
411
412    async fn handle_open(
413        &self,
414        path: String,
415        flags: fio::Flags,
416        object_request: ObjectRequestRef<'_>,
417    ) -> Result<(), Status> {
418        let path = Path::validate_and_split(path)?;
419
420        // Child connection must have stricter or same rights as the parent connection.
421        if let Some(rights) = flags.rights() {
422            if rights.intersects(!self.options.rights) {
423                return Err(Status::ACCESS_DENIED);
424            }
425        }
426
427        // If requesting attributes, check permission.
428        if !object_request.attributes().is_empty()
429            && !self.options.rights.contains(fio::Operations::GET_ATTRIBUTES)
430        {
431            return Err(Status::ACCESS_DENIED);
432        }
433
434        match flags.creation_mode() {
435            CreationMode::Never => {
436                if object_request.create_attributes().is_some() {
437                    return Err(Status::INVALID_ARGS);
438                }
439            }
440            CreationMode::UnnamedTemporary | CreationMode::UnlinkableUnnamedTemporary => {
441                // We only support creating unnamed temporary files.
442                if !flags.intersects(fio::Flags::PROTOCOL_FILE) {
443                    return Err(Status::NOT_SUPPORTED);
444                }
445                // The parent connection must be able to modify directories if creating an object.
446                if !self.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
447                    return Err(Status::ACCESS_DENIED);
448                }
449                // The ability to create an unnamed temporary file is dependent on the filesystem.
450                // We won't know if the directory the path eventually leads to supports the creation
451                // of unnamed temporary files until we have fully traversed the path. The way that
452                // Rust VFS is set up is such that the filesystem is responsible for traversing the
453                // path, so it is the filesystem's responsibility to report if it does not support
454                // this feature.
455            }
456            CreationMode::AllowExisting | CreationMode::Always => {
457                // The parent connection must be able to modify directories if creating an object.
458                if !self.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
459                    return Err(Status::ACCESS_DENIED);
460                }
461
462                let protocol_flags = flags & fio::MASK_KNOWN_PROTOCOLS;
463                // If creating an object, exactly one protocol must be specified (the flags must be
464                // a power of two and non-zero).
465                if protocol_flags.is_empty()
466                    || (protocol_flags.bits() & (protocol_flags.bits() - 1)) != 0
467                {
468                    return Err(Status::INVALID_ARGS);
469                }
470                // Only a directory or file object can be created.
471                if !protocol_flags
472                    .intersects(fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::PROTOCOL_FILE)
473                {
474                    return Err(Status::NOT_SUPPORTED);
475                }
476            }
477        }
478
479        if path.is_dot() && flags.creation_mode() == CreationMode::Always {
480            return Err(Status::ALREADY_EXISTS);
481        }
482
483        self.directory.clone().open_async(self.scope.clone(), path, flags, object_request).await
484    }
485
486    async fn handle_read_dirents(&mut self, max_bytes: u64) -> (Status, Vec<u8>) {
487        async {
488            let (new_pos, sealed) =
489                self.directory.read_dirents(&self.seek, read_dirents::Sink::new(max_bytes)).await?;
490            self.seek = new_pos;
491            let read_dirents::Done { buf, status } = *sealed
492                .open()
493                .downcast::<read_dirents::Done>()
494                .map_err(|_: Box<dyn std::any::Any>| {
495                    #[cfg(debug)]
496                    panic!(
497                        "`read_dirents()` returned a `dirents_sink::Sealed`
498                        instance that is not an instance of the \
499                        `read_dirents::Done`. This is a bug in the \
500                        `read_dirents()` implementation."
501                    );
502                    Status::NOT_SUPPORTED
503                })?;
504            Ok((status, buf))
505        }
506        .await
507        .unwrap_or_else(|status| (status, Vec::new()))
508    }
509
510    async fn handle_link(
511        &self,
512        source_name: &str,
513        target_parent_token: fidl::Handle,
514        target_name: String,
515    ) -> Result<(), Status> {
516        if source_name.contains('/') || target_name.contains('/') {
517            return Err(Status::INVALID_ARGS);
518        }
519
520        // To avoid rights escalation, we must make sure that the connection to the source directory
521        // has the maximal set of file rights.  We do not check for EXECUTE because mutable
522        // filesystems that support link don't currently support EXECUTE rights.  The target rights
523        // are verified by virtue of the fact that it is not possible to get a token without the
524        // MODIFY_DIRECTORY right (see `MutableConnection::handle_get_token`).
525        if !self.options.rights.contains(fio::RW_STAR_DIR) {
526            return Err(Status::BAD_HANDLE);
527        }
528
529        let target_parent = self
530            .scope
531            .token_registry()
532            .get_owner(target_parent_token)?
533            .ok_or(Err(Status::NOT_FOUND))?;
534
535        target_parent.link(target_name, self.directory.clone().into_any(), source_name).await
536    }
537
538    fn handle_watch(
539        &mut self,
540        mask: fio::WatchMask,
541        watcher: DirectoryWatcher,
542    ) -> Result<(), Status> {
543        let directory = self.directory.clone();
544        directory.register_watcher(self.scope.clone(), mask, watcher)
545    }
546
547    async fn handle_list_extended_attribute(
548        &self,
549        iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
550    ) {
551        let attributes = match self.directory.list_extended_attributes().await {
552            Ok(attributes) => attributes,
553            Err(status) => {
554                #[cfg(any(test, feature = "use_log"))]
555                log::error!(status:?; "list extended attributes failed");
556                #[allow(clippy::unnecessary_lazy_evaluations)]
557                iterator.close_with_epitaph(status).unwrap_or_else(|_error| {
558                    #[cfg(any(test, feature = "use_log"))]
559                    log::error!(_error:?; "failed to send epitaph")
560                });
561                return;
562            }
563        };
564        self.scope.spawn(extended_attributes_sender(iterator, attributes));
565    }
566
567    async fn handle_get_extended_attribute(
568        &self,
569        name: Vec<u8>,
570    ) -> Result<fio::ExtendedAttributeValue, Status> {
571        let value = self.directory.get_extended_attribute(name).await?;
572        encode_extended_attribute_value(value)
573    }
574
575    async fn handle_set_extended_attribute(
576        &self,
577        name: Vec<u8>,
578        value: fio::ExtendedAttributeValue,
579        mode: fio::SetExtendedAttributeMode,
580    ) -> Result<(), Status> {
581        if name.contains(&0) {
582            return Err(Status::INVALID_ARGS);
583        }
584        let val = decode_extended_attribute_value(value)?;
585        self.directory.set_extended_attribute(name, val, mode).await
586    }
587
588    async fn handle_remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
589        self.directory.remove_extended_attribute(name).await
590    }
591}
592
593impl<DirectoryType: Directory> Representation for BaseConnection<DirectoryType> {
594    type Protocol = fio::DirectoryMarker;
595
596    async fn get_representation(
597        &self,
598        requested_attributes: fio::NodeAttributesQuery,
599    ) -> Result<fio::Representation, Status> {
600        Ok(fio::Representation::Directory(fio::DirectoryInfo {
601            attributes: if requested_attributes.is_empty() {
602                None
603            } else {
604                Some(self.directory.get_attributes(requested_attributes).await?)
605            },
606            ..Default::default()
607        }))
608    }
609
610    async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
611        Ok(fio::NodeInfoDeprecated::Directory(fio::DirectoryObject))
612    }
613}
614
615#[cfg(test)]
616mod tests {
617    use super::*;
618    use crate::directory::immutable::Simple;
619    use assert_matches::assert_matches;
620    use fidl_fuchsia_io as fio;
621
622    #[fuchsia::test]
623    async fn test_open_not_found() {
624        let dir = Simple::new();
625        let dir_proxy = crate::directory::serve(dir, fio::PERM_READABLE);
626
627        // Try to open a file that doesn't exist.
628        let node_proxy = fuchsia_fs::directory::open_async::<fio::NodeMarker>(
629            &dir_proxy,
630            "foo",
631            fio::PERM_READABLE,
632        )
633        .unwrap();
634
635        // The channel is closed with a NOT_FOUND epitaph.
636        assert_matches!(
637            node_proxy.query().await,
638            Err(fidl::Error::ClientChannelClosed {
639                status: Status::NOT_FOUND,
640                protocol_name: "fuchsia.io.Node",
641                ..
642            })
643        );
644    }
645
646    #[fuchsia::test]
647    async fn test_open_with_send_representation_not_found() {
648        let dir = Simple::new();
649        let dir_proxy = crate::directory::serve(dir, fio::PERM_READABLE);
650
651        // Try to open a file that doesn't exist.
652        let node_proxy = fuchsia_fs::directory::open_async::<fio::NodeMarker>(
653            &dir_proxy,
654            "foo",
655            fio::PERM_READABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
656        )
657        .unwrap();
658
659        // The channel is closed with a NOT_FOUND epitaph.
660        assert_matches!(
661            node_proxy.query().await,
662            Err(fidl::Error::ClientChannelClosed {
663                status: Status::NOT_FOUND,
664                protocol_name: "fuchsia.io.Node",
665                ..
666            })
667        );
668    }
669}