1#![warn(missing_docs)]
8
9use crate::common::IntoAny;
10use crate::directory::entry_container::Directory;
11use crate::execution_scope::ExecutionScope;
12use crate::file::{self, FileLike};
13use crate::object_request::ObjectRequestSend;
14use crate::path::Path;
15use crate::service::{self, ServiceLike};
16use crate::symlink::{self, Symlink};
17use crate::{ObjectRequestRef, ToObjectRequest};
18
19use flex_client::fidl::ClientEnd;
20use flex_fuchsia_io as fio;
21use std::fmt;
22use std::future::Future;
23use std::sync::Arc;
24use zx_status::Status;
25
26#[derive(PartialEq, Eq, Clone)]
30pub struct EntryInfo(u64, fio::DirentType);
31
32impl EntryInfo {
33 pub fn new(inode: u64, type_: fio::DirentType) -> Self {
35 Self(inode, type_)
36 }
37
38 pub fn inode(&self) -> u64 {
40 let Self(inode, _type) = self;
41 *inode
42 }
43
44 pub fn type_(&self) -> fio::DirentType {
46 let Self(_inode, type_) = self;
47 *type_
48 }
49}
50
51impl fmt::Debug for EntryInfo {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 let Self(inode, type_) = self;
54 if *inode == fio::INO_UNKNOWN {
55 write!(f, "{:?}(fio::INO_UNKNOWN)", type_)
56 } else {
57 write!(f, "{:?}({})", type_, inode)
58 }
59 }
60}
61
62pub trait GetEntryInfo {
64 fn entry_info(&self) -> EntryInfo;
66}
67
68pub trait DirectoryEntry: GetEntryInfo + IntoAny + Sync + Send + 'static {
74 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status>;
76
77 fn scope(&self) -> Option<ExecutionScope> {
83 None
84 }
85}
86
87pub trait DirectoryEntryAsync: DirectoryEntry {
89 fn open_entry_async(
91 self: Arc<Self>,
92 request: OpenRequest<'_>,
93 ) -> impl Future<Output = Result<(), Status>> + Send;
94}
95
96#[derive(Debug)]
98pub struct OpenRequest<'a> {
99 scope: ExecutionScope,
100 request_flags: RequestFlags,
101 path: Path,
102 object_request: ObjectRequestRef<'a>,
103}
104
105#[derive(Debug)]
110pub enum RequestFlags {
111 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
113 Open1(fio::OpenFlags),
114 Open3(fio::Flags),
116}
117
118#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
119impl From<fio::OpenFlags> for RequestFlags {
120 fn from(value: fio::OpenFlags) -> Self {
121 RequestFlags::Open1(value)
122 }
123}
124
125impl From<fio::Flags> for RequestFlags {
126 fn from(value: fio::Flags) -> Self {
127 RequestFlags::Open3(value)
128 }
129}
130
131impl<'a> OpenRequest<'a> {
132 pub fn new(
134 scope: ExecutionScope,
135 request_flags: impl Into<RequestFlags>,
136 path: Path,
137 object_request: ObjectRequestRef<'a>,
138 ) -> Self {
139 Self { scope, request_flags: request_flags.into(), path, object_request }
140 }
141
142 pub fn path(&self) -> &Path {
144 &self.path
145 }
146
147 pub fn prepend_path(&mut self, prefix: &Path) {
149 self.path = self.path.with_prefix(prefix);
150 }
151
152 pub fn set_path(&mut self, path: Path) {
154 self.path = path;
155 }
156
157 pub async fn wait_till_ready(&self) -> bool {
161 self.object_request.wait_till_ready().await
162 }
163
164 pub fn requires_event(&self) -> bool {
170 self.object_request.what_to_send() != ObjectRequestSend::Nothing
171 }
172
173 pub fn open_dir(self, dir: Arc<impl Directory>) -> Result<(), Status> {
175 let OpenRequest { scope, request_flags, path, object_request } = self;
176 match request_flags {
177 #[cfg(any(
178 fuchsia_api_level_at_least = "PLATFORM",
179 not(fuchsia_api_level_at_least = "32")
180 ))]
181 RequestFlags::Open1(flags) => {
182 dir.deprecated_open(scope, flags, path, object_request.take().into_server_end());
183 Ok(())
186 }
187 RequestFlags::Open3(flags) => dir.open(scope, path, flags, object_request),
188 }
189 }
190
191 pub fn open_file(self, file: Arc<impl FileLike>) -> Result<(), Status> {
193 let OpenRequest { scope, request_flags, path, object_request } = self;
194 if !path.is_empty() {
195 return Err(Status::NOT_DIR);
196 }
197 match request_flags {
198 #[cfg(any(
199 fuchsia_api_level_at_least = "PLATFORM",
200 not(fuchsia_api_level_at_least = "32")
201 ))]
202 RequestFlags::Open1(flags) => file::serve(file, scope, &flags, object_request),
203 RequestFlags::Open3(flags) => file::serve(file, scope, &flags, object_request),
204 }
205 }
206
207 pub fn open_symlink(self, symlink: Arc<impl Symlink>) -> Result<(), Status> {
209 let OpenRequest { scope, request_flags, path, object_request } = self;
210 if !path.is_empty() {
211 return Err(Status::NOT_DIR);
212 }
213 match request_flags {
214 #[cfg(any(
215 fuchsia_api_level_at_least = "PLATFORM",
216 not(fuchsia_api_level_at_least = "32")
217 ))]
218 RequestFlags::Open1(flags) => symlink::serve(symlink, scope, flags, object_request),
219 RequestFlags::Open3(flags) => symlink::serve(symlink, scope, flags, object_request),
220 }
221 }
222
223 pub fn open_service(self, service: Arc<impl ServiceLike>) -> Result<(), Status> {
225 let OpenRequest { scope, request_flags, path, object_request } = self;
226 if !path.is_empty() {
227 return Err(Status::NOT_DIR);
228 }
229 match request_flags {
230 #[cfg(any(
231 fuchsia_api_level_at_least = "PLATFORM",
232 not(fuchsia_api_level_at_least = "32")
233 ))]
234 RequestFlags::Open1(flags) => service::serve(service, scope, &flags, object_request),
235 RequestFlags::Open3(flags) => service::serve(service, scope, &flags, object_request),
236 }
237 }
238
239 pub fn open_remote(
241 self,
242 remote: Arc<impl crate::remote::RemoteLike + Send + Sync + 'static>,
243 ) -> Result<(), Status> {
244 match self {
245 #[cfg(any(
246 fuchsia_api_level_at_least = "PLATFORM",
247 not(fuchsia_api_level_at_least = "32")
248 ))]
249 OpenRequest {
250 scope,
251 request_flags: RequestFlags::Open1(flags),
252 path,
253 object_request,
254 } => {
255 if object_request.what_to_send() == ObjectRequestSend::Nothing && remote.lazy(&path)
256 {
257 let object_request = object_request.take();
258 scope.clone().spawn(async move {
259 if object_request.wait_till_ready().await {
260 remote.deprecated_open(
261 scope,
262 flags,
263 path,
264 object_request.into_server_end(),
265 );
266 }
267 });
268 } else {
269 remote.deprecated_open(
270 scope,
271 flags,
272 path,
273 object_request.take().into_server_end(),
274 );
275 }
276 Ok(())
277 }
278 OpenRequest {
279 scope,
280 request_flags: RequestFlags::Open3(flags),
281 path,
282 object_request,
283 } => {
284 if object_request.what_to_send() == ObjectRequestSend::Nothing && remote.lazy(&path)
285 {
286 let object_request = object_request.take();
287 scope.clone().spawn(async move {
288 if object_request.wait_till_ready().await {
289 object_request.handle(|object_request| {
290 remote.open(scope, path, flags, object_request)
291 });
292 }
293 });
294 Ok(())
295 } else {
296 remote.open(scope, path, flags, object_request)
297 }
298 }
299 }
300 }
301
302 pub fn spawn(self, entry: Arc<impl DirectoryEntryAsync>) {
304 let OpenRequest { scope, request_flags, path, object_request } = self;
305 let mut object_request = object_request.take();
306 scope.clone().spawn(async move {
307 if let Err(s) = entry
308 .open_entry_async(OpenRequest::new(scope, request_flags, path, &mut object_request))
309 .await
310 {
311 object_request.shutdown(s)
312 }
313 });
314 }
315
316 pub fn scope(&self) -> &ExecutionScope {
318 &self.scope
319 }
320
321 pub fn set_scope(&mut self, scope: ExecutionScope) {
324 self.scope = scope;
325 }
326}
327
328pub struct SubNode<T: ?Sized> {
331 parent: Arc<T>,
332 path: Path,
333 entry_type: fio::DirentType,
334}
335
336impl<T: DirectoryEntry + ?Sized> SubNode<T> {
337 pub fn new(parent: Arc<T>, path: Path, entry_type: fio::DirentType) -> SubNode<T> {
340 assert_eq!(parent.entry_info().type_(), fio::DirentType::Directory);
341 Self { parent, path, entry_type }
342 }
343}
344
345impl<T: DirectoryEntry + ?Sized> GetEntryInfo for SubNode<T> {
346 fn entry_info(&self) -> EntryInfo {
347 EntryInfo::new(fio::INO_UNKNOWN, self.entry_type)
348 }
349}
350
351impl<T: DirectoryEntry + ?Sized> DirectoryEntry for SubNode<T> {
352 fn open_entry(self: Arc<Self>, mut request: OpenRequest<'_>) -> Result<(), Status> {
353 request.path = request.path.with_prefix(&self.path);
354 self.parent.clone().open_entry(request)
355 }
356}
357
358pub fn serve_directory(
361 dir: Arc<impl DirectoryEntry + ?Sized>,
362 scope: &ExecutionScope,
363 flags: fio::Flags,
364) -> Result<ClientEnd<fio::DirectoryMarker>, Status> {
365 let client = scope.domain();
366 assert_eq!(dir.entry_info().type_(), fio::DirentType::Directory);
367 let (client, server) = client.create_endpoints::<fio::DirectoryMarker>();
368 flags
369 .to_object_request(server)
370 .handle(|object_request| {
371 Ok(dir.open_entry(OpenRequest::new(scope.clone(), flags, Path::dot(), object_request)))
372 })
373 .unwrap()?;
374 Ok(client)
375}
376
377#[cfg(test)]
378mod tests {
379 use super::{
380 DirectoryEntry, DirectoryEntryAsync, EntryInfo, OpenRequest, RequestFlags, SubNode,
381 };
382 use crate::directory::entry::GetEntryInfo;
383 use crate::file::read_only;
384 use crate::path::Path;
385 use crate::{ObjectRequest, assert_read, pseudo_directory};
386 use assert_matches::assert_matches;
387
388 use flex_fuchsia_io as fio;
389 use futures::StreamExt;
390 use std::sync::Arc;
391 use zx_status::Status;
392
393 #[fuchsia::test]
394 async fn sub_node() {
395 let root = pseudo_directory!(
396 "a" => pseudo_directory!(
397 "b" => pseudo_directory!(
398 "c" => pseudo_directory!(
399 "d" => read_only(b"foo")
400 )
401 )
402 )
403 );
404 let sub_node = Arc::new(SubNode::new(
405 root,
406 Path::validate_and_split("a/b").unwrap(),
407 fio::DirentType::Directory,
408 ));
409
410 let root2 = pseudo_directory!(
411 "e" => sub_node
412 );
413
414 #[cfg(feature = "fdomain")]
415 let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
416 #[cfg(not(feature = "fdomain"))]
417 let scope = crate::execution_scope::ExecutionScope::new();
418
419 let file_proxy = crate::serve_file(
420 root2,
421 Path::validate_and_split("e/c/d").unwrap(),
422 scope,
423 fio::PERM_READABLE,
424 );
425 assert_read!(file_proxy, "foo");
426 }
427
428 #[fuchsia::test]
429 async fn object_request_spawn() {
430 struct MockNode<F: Send + Sync + 'static>
431 where
432 for<'a> F: Fn(OpenRequest<'a>) -> Status,
433 {
434 callback: F,
435 }
436 impl<F: Send + Sync + 'static> DirectoryEntry for MockNode<F>
437 where
438 for<'a> F: Fn(OpenRequest<'a>) -> Status,
439 {
440 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
441 request.spawn(self);
442 Ok(())
443 }
444 }
445 impl<F: Send + Sync + 'static> GetEntryInfo for MockNode<F>
446 where
447 for<'a> F: Fn(OpenRequest<'a>) -> Status,
448 {
449 fn entry_info(&self) -> EntryInfo {
450 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Unknown)
451 }
452 }
453 impl<F: Send + Sync + 'static> DirectoryEntryAsync for MockNode<F>
454 where
455 for<'a> F: Fn(OpenRequest<'a>) -> Status,
456 {
457 async fn open_entry_async(
458 self: Arc<Self>,
459 request: OpenRequest<'_>,
460 ) -> Result<(), Status> {
461 Err((self.callback)(request))
462 }
463 }
464
465 #[cfg(feature = "fdomain")]
466 let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
467 #[cfg(not(feature = "fdomain"))]
468 let scope = crate::execution_scope::ExecutionScope::new();
469
470 #[cfg(feature = "fdomain")]
471 let (proxy, server) = {
472 let client = scope.domain();
473 client.create_proxy::<fio::NodeMarker>()
474 };
475 #[cfg(not(feature = "fdomain"))]
476 let (proxy, server) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
477 let flags = fio::Flags::PROTOCOL_FILE | fio::Flags::FILE_APPEND;
478 let mut object_request =
479 ObjectRequest::new(flags, &Default::default(), server.into_channel());
480
481 Arc::new(MockNode {
482 callback: move |request| {
483 assert_matches!(
484 request,
485 OpenRequest {
486 request_flags: RequestFlags::Open3(f),
487 path,
488 ..
489 } if f == flags && path.as_ref() == "a/b/c"
490 );
491 Status::BAD_STATE
492 },
493 })
494 .open_entry(OpenRequest::new(
495 scope.clone(),
496 flags,
497 "a/b/c".try_into().unwrap(),
498 &mut object_request,
499 ))
500 .unwrap();
501
502 assert_matches!(
503 proxy.take_event_stream().next().await,
504 Some(Err(fidl::Error::ClientChannelClosed { epitaph, .. }))
505 if epitaph == Status::BAD_STATE
506 );
507 }
508}