1use crate::common::{
8 decode_extended_attribute_value, encode_extended_attribute_value, extended_attributes_sender,
9};
10use crate::execution_scope::ExecutionScope;
11use crate::name::parse_name;
12use crate::node::Node;
13use crate::object_request::{ConnectionCreator, Representation, run_synchronous_future_or_spawn};
14use crate::request_handler::{RequestHandler, RequestListener};
15use crate::{ObjectRequest, ObjectRequestRef, ProtocolsExt, ToObjectRequest};
16use flex_client::fidl::{
17 ControlHandle as _, DiscoverableProtocolMarker as _, Responder, ServerEnd,
18};
19use flex_fuchsia_io as fio;
20use std::future::Future;
21use std::ops::ControlFlow;
22use std::pin::Pin;
23use std::sync::Arc;
24use storage_trace::{self as trace, TraceFutureExt};
25use zx_status::Status;
26
27pub trait Symlink: Node {
28 fn read_target(&self) -> impl Future<Output = Result<Vec<u8>, Status>> + Send;
29}
30
31pub struct Connection<T> {
32 scope: ExecutionScope,
33 symlink: Arc<T>,
34}
35
36pub struct SymlinkOptions;
37
38impl<T: Symlink> Connection<T> {
39 pub async fn create(
45 scope: ExecutionScope,
46 symlink: Arc<T>,
47 protocols: impl ProtocolsExt,
48 object_request: ObjectRequestRef<'_>,
49 ) -> Result<(), Status> {
50 let _options = protocols.to_symlink_options()?;
51 let connection = Self { scope: scope.clone(), symlink };
52 if let Ok(requests) = object_request.take().into_request_stream(&connection).await {
53 scope.spawn(RequestListener::new(requests, connection));
54 }
55 Ok(())
56 }
57
58 pub fn create_sync(
61 scope: ExecutionScope,
62 symlink: Arc<T>,
63 options: impl ProtocolsExt,
64 object_request: ObjectRequest,
65 ) {
66 run_synchronous_future_or_spawn(
67 scope.clone(),
68 object_request.handle_async(async |object_request| {
69 Self::create(scope, symlink, options, object_request).await
70 }),
71 )
72 }
73
74 async fn handle_request(&mut self, req: fio::SymlinkRequest) -> Result<bool, fidl::Error> {
76 match req {
77 #[cfg(any(
78 fuchsia_api_level_at_least = "PLATFORM",
79 not(fuchsia_api_level_at_least = "29")
80 ))]
81 fio::SymlinkRequest::DeprecatedClone { flags, object, control_handle: _ } => {
82 crate::common::send_on_open_with_error(
83 flags.contains(fio::OpenFlags::DESCRIBE),
84 object,
85 Status::NOT_SUPPORTED,
86 );
87 }
88 fio::SymlinkRequest::Clone { request, control_handle: _ } => {
89 self.handle_clone(ServerEnd::new(request.into_channel()))
90 .trace(trace::trace_future_args!("storage", "Symlink::Clone"))
91 .await
92 }
93 fio::SymlinkRequest::Close { responder } => {
94 trace::duration!("storage", "Symlink::Close");
95 responder.send(Ok(()))?;
96 return Ok(true);
97 }
98 fio::SymlinkRequest::LinkInto { dst_parent_token, dst, responder } => {
99 async move {
100 responder.send(
101 self.handle_link_into(dst_parent_token, dst)
102 .await
103 .map_err(|s| s.into_raw()),
104 )
105 }
106 .trace(trace::trace_future_args!("storage", "Symlink::LinkInto"))
107 .await?;
108 }
109 fio::SymlinkRequest::Sync { responder } => {
110 trace::duration!("storage", "Symlink::Sync");
111 responder.send(Ok(()))?;
112 }
113 #[cfg(fuchsia_api_level_at_least = "28")]
114 fio::SymlinkRequest::DeprecatedGetAttr { responder } => {
115 let (status, attrs) = crate::common::io2_to_io1_attrs(
117 self.symlink.as_ref(),
118 fio::Rights::GET_ATTRIBUTES,
119 )
120 .await;
121 responder.send(status.into_raw(), &attrs)?;
122 }
123 #[cfg(not(fuchsia_api_level_at_least = "28"))]
124 fio::SymlinkRequest::GetAttr { responder } => {
125 let (status, attrs) = crate::common::io2_to_io1_attrs(
127 self.symlink.as_ref(),
128 fio::Rights::GET_ATTRIBUTES,
129 )
130 .await;
131 responder.send(status.into_raw(), &attrs)?;
132 }
133 #[cfg(fuchsia_api_level_at_least = "28")]
134 fio::SymlinkRequest::DeprecatedSetAttr { responder, .. } => {
135 responder.send(Status::ACCESS_DENIED.into_raw())?;
136 }
137 #[cfg(not(fuchsia_api_level_at_least = "28"))]
138 fio::SymlinkRequest::SetAttr { responder, .. } => {
139 responder.send(Status::ACCESS_DENIED.into_raw())?;
140 }
141 fio::SymlinkRequest::GetAttributes { query, responder } => {
142 async move {
143 let attrs = self.symlink.get_attributes(query).await;
145 responder.send(
146 attrs
147 .as_ref()
148 .map(|attrs| (&attrs.mutable_attributes, &attrs.immutable_attributes))
149 .map_err(|status| status.into_raw()),
150 )
151 }
152 .trace(trace::trace_future_args!("storage", "Symlink::GetAttributes"))
153 .await?;
154 }
155 fio::SymlinkRequest::UpdateAttributes { payload: _, responder } => {
156 trace::duration!("storage", "Symlink::UpdateAttributes");
157 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
158 }
159 fio::SymlinkRequest::ListExtendedAttributes { iterator, control_handle: _ } => {
160 self.handle_list_extended_attribute(iterator)
161 .trace(trace::trace_future_args!("storage", "Symlink::ListExtendedAttributes"))
162 .await;
163 }
164 fio::SymlinkRequest::GetExtendedAttribute { responder, name } => {
165 async move {
166 let res = self.handle_get_extended_attribute(name).await;
167 responder.send(res.map_err(Status::into_raw))
168 }
169 .trace(trace::trace_future_args!("storage", "Symlink::GetExtendedAttribute"))
170 .await?;
171 }
172 fio::SymlinkRequest::SetExtendedAttribute { responder, name, value, mode } => {
173 async move {
174 let res = self.handle_set_extended_attribute(name, value, mode).await;
175 responder.send(res.map_err(Status::into_raw))
176 }
177 .trace(trace::trace_future_args!("storage", "Symlink::SetExtendedAttribute"))
178 .await?;
179 }
180 fio::SymlinkRequest::RemoveExtendedAttribute { responder, name } => {
181 async move {
182 let res = self.handle_remove_extended_attribute(name).await;
183 responder.send(res.map_err(Status::into_raw))
184 }
185 .trace(trace::trace_future_args!("storage", "Symlink::RemoveExtendedAttribute"))
186 .await?;
187 }
188 fio::SymlinkRequest::Describe { responder } => {
189 return async move {
190 match self.symlink.read_target().await {
191 Ok(target) => {
192 responder.send(&fio::SymlinkInfo {
193 target: Some(target),
194 ..Default::default()
195 })?;
196 Ok(false)
197 }
198 Err(status) => {
199 responder.control_handle().shutdown_with_epitaph(status);
200 Ok(true)
201 }
202 }
203 }
204 .trace(trace::trace_future_args!("storage", "Symlink::Describe"))
205 .await;
206 }
207 fio::SymlinkRequest::GetFlags { responder } => {
208 trace::duration!("storage", "Symlink::GetFlags");
209 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
210 }
211 fio::SymlinkRequest::SetFlags { flags: _, responder } => {
212 trace::duration!("storage", "Symlink::SetFlags");
213 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
214 }
215 fio::SymlinkRequest::DeprecatedGetFlags { responder } => {
216 responder.send(Status::NOT_SUPPORTED.into_raw(), fio::OpenFlags::empty())?;
217 }
218 fio::SymlinkRequest::DeprecatedSetFlags { responder, .. } => {
219 responder.send(Status::ACCESS_DENIED.into_raw())?;
220 }
221 fio::SymlinkRequest::Query { responder } => {
222 trace::duration!("storage", "Symlink::Query");
223 responder.send(fio::SymlinkMarker::PROTOCOL_NAME.as_bytes())?;
224 }
225 fio::SymlinkRequest::QueryFilesystem { responder } => {
226 trace::duration!("storage", "Symlink::QueryFilesystem");
227 match self.symlink.query_filesystem() {
228 Err(status) => responder.send(status.into_raw(), None)?,
229 Ok(info) => responder.send(0, Some(&info))?,
230 }
231 }
232 #[cfg(fuchsia_api_level_at_least = "HEAD")]
233 fio::SymlinkRequest::Open { object, .. } => {
234 use fidl::epitaph::ChannelEpitaphExt;
235 let _ = object.close_with_epitaph(Status::NOT_DIR);
236 }
237 fio::SymlinkRequest::_UnknownMethod { ordinal: _ordinal, .. } => {
238 #[cfg(any(test, feature = "use_log"))]
239 log::warn!(_ordinal; "Received unknown method")
240 }
241 }
242 Ok(false)
243 }
244
245 async fn handle_clone(&mut self, server_end: ServerEnd<fio::SymlinkMarker>) {
246 let flags = fio::Flags::PROTOCOL_SYMLINK | fio::Flags::PERM_GET_ATTRIBUTES;
247 flags
248 .to_object_request(server_end)
249 .handle_async(async |object_request| {
250 Self::create(self.scope.clone(), self.symlink.clone(), flags, object_request).await
251 })
252 .await;
253 }
254
255 async fn handle_link_into(
256 &mut self,
257 target_parent_token: flex_client::Event,
258 target_name: String,
259 ) -> Result<(), Status> {
260 let target_name = parse_name(target_name).map_err(|_| Status::INVALID_ARGS)?;
261
262 let (target_parent, target_rights) = self
263 .scope
264 .token_registry()
265 .get_owner_and_rights(target_parent_token.into())?
266 .ok_or(Err(Status::NOT_FOUND))?;
267
268 if !target_rights.contains(fio::Rights::MODIFY_DIRECTORY) {
269 return Err(Status::ACCESS_DENIED);
270 }
271
272 self.symlink.clone().link_into(target_parent, target_name).await
273 }
274
275 async fn handle_list_extended_attribute(
276 &self,
277 iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
278 ) {
279 let attributes = match self.symlink.list_extended_attributes().await {
280 Ok(attributes) => attributes,
281 Err(status) => {
282 #[cfg(any(test, feature = "use_log"))]
283 log::error!(status:?; "list extended attributes failed");
284 #[allow(clippy::unnecessary_lazy_evaluations)]
285 iterator.close_with_epitaph(status).unwrap_or_else(|_error| {
286 #[cfg(any(test, feature = "use_log"))]
287 log::error!(_error:?; "failed to send epitaph")
288 });
289 return;
290 }
291 };
292 self.scope.spawn(extended_attributes_sender(iterator, attributes));
293 }
294
295 async fn handle_get_extended_attribute(
296 &self,
297 name: Vec<u8>,
298 ) -> Result<fio::ExtendedAttributeValue, Status> {
299 let value = self.symlink.get_extended_attribute(name).await?;
300 encode_extended_attribute_value(value)
301 }
302
303 async fn handle_set_extended_attribute(
304 &self,
305 name: Vec<u8>,
306 value: fio::ExtendedAttributeValue,
307 mode: fio::SetExtendedAttributeMode,
308 ) -> Result<(), Status> {
309 if name.contains(&0) {
310 return Err(Status::INVALID_ARGS);
311 }
312 let val = decode_extended_attribute_value(value)?;
313 self.symlink.set_extended_attribute(name, val, mode).await
314 }
315
316 async fn handle_remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
317 self.symlink.remove_extended_attribute(name).await
318 }
319}
320
321impl<T: Symlink> RequestHandler for Connection<T> {
322 type Request = Result<fio::SymlinkRequest, fidl::Error>;
323
324 async fn handle_request(self: Pin<&mut Self>, request: Self::Request) -> ControlFlow<()> {
325 let this = self.get_mut();
326 if let Some(_guard) = this.scope.try_active_guard() {
327 match request {
328 Ok(request) => match this.handle_request(request).await {
329 Ok(false) => ControlFlow::Continue(()),
330 Ok(true) | Err(_) => ControlFlow::Break(()),
331 },
332 Err(_) => ControlFlow::Break(()),
333 }
334 } else {
335 ControlFlow::Break(())
336 }
337 }
338}
339
340impl<T: Symlink> Representation for Connection<T> {
341 type Protocol = fio::SymlinkMarker;
342
343 async fn get_representation(
344 &self,
345 requested_attributes: fio::NodeAttributesQuery,
346 ) -> Result<fio::Representation, Status> {
347 Ok(fio::Representation::Symlink(fio::SymlinkInfo {
348 attributes: if requested_attributes.is_empty() {
349 None
350 } else {
351 Some(self.symlink.get_attributes(requested_attributes).await?)
352 },
353 target: Some(self.symlink.read_target().await?),
354 ..Default::default()
355 }))
356 }
357
358 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
359 async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
360 Ok(fio::NodeInfoDeprecated::Symlink(fio::SymlinkObject {
361 target: self.symlink.read_target().await?,
362 }))
363 }
364}
365
366impl<T: Symlink> ConnectionCreator<T> for Connection<T> {
367 async fn create<'a>(
368 scope: ExecutionScope,
369 node: Arc<T>,
370 protocols: impl ProtocolsExt,
371 object_request: ObjectRequestRef<'a>,
372 ) -> Result<(), Status> {
373 Self::create(scope, node, protocols, object_request).await
374 }
375}
376
377pub fn serve(
379 link: Arc<impl Symlink>,
380 scope: ExecutionScope,
381 protocols: impl ProtocolsExt,
382 object_request: ObjectRequestRef<'_>,
383) -> Result<(), Status> {
384 if protocols.is_node() {
385 let options = protocols.to_node_options(link.entry_info().type_())?;
386 link.open_as_node(scope, options, object_request)
387 } else {
388 Connection::create_sync(scope, link, protocols, object_request.take());
389 Ok(())
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use super::{Connection, ExecutionScope, Symlink};
396 use crate::ToObjectRequest;
397 use crate::directory::entry::{EntryInfo, GetEntryInfo};
398 use crate::node::Node;
399 use assert_matches::assert_matches;
400 use flex_client::fidl::ServerEnd;
401 use flex_fuchsia_io as fio;
402 use fuchsia_sync::Mutex;
403 use futures::StreamExt;
404 use std::collections::HashMap;
405 use std::sync::Arc;
406 use zx_status::Status;
407
408 fn test_scope() -> ExecutionScope {
409 #[cfg(feature = "fdomain")]
410 let client = flex_local::local_client_empty();
411 #[cfg(feature = "fdomain")]
412 return ExecutionScope::new(client);
413 #[cfg(not(feature = "fdomain"))]
414 return ExecutionScope::new();
415 }
416
417 const TARGET: &[u8] = b"target";
418
419 struct TestSymlink {
420 xattrs: Mutex<HashMap<Vec<u8>, Vec<u8>>>,
421 }
422
423 impl TestSymlink {
424 fn new() -> Self {
425 TestSymlink { xattrs: Mutex::new(HashMap::new()) }
426 }
427 }
428
429 impl Symlink for TestSymlink {
430 async fn read_target(&self) -> Result<Vec<u8>, Status> {
431 Ok(TARGET.to_vec())
432 }
433 }
434
435 impl Node for TestSymlink {
436 async fn get_attributes(
437 &self,
438 requested_attributes: fio::NodeAttributesQuery,
439 ) -> Result<fio::NodeAttributes2, Status> {
440 Ok(immutable_attributes!(
441 requested_attributes,
442 Immutable {
443 content_size: TARGET.len() as u64,
444 storage_size: TARGET.len() as u64,
445 protocols: fio::NodeProtocolKinds::SYMLINK,
446 abilities: fio::Abilities::GET_ATTRIBUTES,
447 }
448 ))
449 }
450 async fn list_extended_attributes(&self) -> Result<Vec<Vec<u8>>, Status> {
451 let map = self.xattrs.lock();
452 Ok(map.values().map(|x| x.clone()).collect())
453 }
454 async fn get_extended_attribute(&self, name: Vec<u8>) -> Result<Vec<u8>, Status> {
455 let map = self.xattrs.lock();
456 map.get(&name).map(|x| x.clone()).ok_or(Status::NOT_FOUND)
457 }
458 async fn set_extended_attribute(
459 &self,
460 name: Vec<u8>,
461 value: Vec<u8>,
462 _mode: fio::SetExtendedAttributeMode,
463 ) -> Result<(), Status> {
464 let mut map = self.xattrs.lock();
465 map.insert(name, value);
468 Ok(())
469 }
470 async fn remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
471 let mut map = self.xattrs.lock();
472 map.remove(&name);
473 Ok(())
474 }
475 }
476
477 impl GetEntryInfo for TestSymlink {
478 fn entry_info(&self) -> EntryInfo {
479 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Symlink)
480 }
481 }
482
483 async fn serve_test_symlink(client: &flex_client::ClientArg) -> fio::SymlinkProxy {
484 let (client_end, server_end) = client.create_proxy::<fio::SymlinkMarker>();
485 let flags = fio::PERM_READABLE | fio::Flags::PROTOCOL_SYMLINK;
486
487 #[cfg(feature = "fdomain")]
488 let scope = crate::execution_scope::ExecutionScope::new(client.clone());
489 #[cfg(not(feature = "fdomain"))]
490 let scope = crate::execution_scope::ExecutionScope::new();
491
492 Connection::create_sync(
493 scope,
494 Arc::new(TestSymlink::new()),
495 flags,
496 flags.to_object_request(server_end),
497 );
498
499 client_end
500 }
501
502 #[fuchsia::test]
503 async fn test_read_target() {
504 let client = flex_local::local_client_empty();
505 let client_end = serve_test_symlink(&client).await;
506
507 assert_eq!(
508 client_end.describe().await.expect("fidl failed").target.expect("missing target"),
509 b"target"
510 );
511 }
512
513 #[fuchsia::test]
514 async fn test_validate_flags() {
515 let scope = test_scope();
516
517 let check = |mut flags: fio::Flags| {
518 let (client_end, server_end) = scope.domain().create_proxy::<fio::SymlinkMarker>();
519 flags |= fio::Flags::FLAG_SEND_REPRESENTATION;
520 flags.to_object_request(server_end).create_connection_sync::<Connection<_>, _>(
521 scope.clone(),
522 Arc::new(TestSymlink::new()),
523 flags,
524 );
525
526 async move { client_end.take_event_stream().next().await.expect("no event") }
527 };
528
529 for flags in [
530 fio::Flags::PROTOCOL_DIRECTORY,
531 fio::Flags::PROTOCOL_FILE,
532 fio::Flags::PROTOCOL_SERVICE,
533 ] {
534 assert_matches!(
535 check(fio::PERM_READABLE | flags).await,
536 Err(fidl::Error::ClientChannelClosed { status: Status::WRONG_TYPE, .. }),
537 "{flags:?}"
538 );
539 }
540
541 assert_matches!(
542 check(fio::PERM_READABLE | fio::Flags::PROTOCOL_SYMLINK)
543 .await
544 .expect("error from next")
545 .into_on_representation()
546 .expect("expected on representation"),
547 fio::Representation::Symlink(fio::SymlinkInfo { .. })
548 );
549 assert_matches!(
550 check(fio::PERM_READABLE)
551 .await
552 .expect("error from next")
553 .into_on_representation()
554 .expect("expected on representation"),
555 fio::Representation::Symlink(fio::SymlinkInfo { .. })
556 );
557 }
558
559 #[fuchsia::test]
560 async fn test_get_attr() {
561 let client = flex_local::local_client_empty();
562 let client_end = serve_test_symlink(&client).await;
563
564 let (mutable_attrs, immutable_attrs) = client_end
565 .get_attributes(fio::NodeAttributesQuery::all())
566 .await
567 .expect("fidl failed")
568 .expect("GetAttributes failed");
569
570 assert_eq!(mutable_attrs, Default::default());
571 assert_eq!(
572 immutable_attrs,
573 fio::ImmutableNodeAttributes {
574 content_size: Some(TARGET.len() as u64),
575 storage_size: Some(TARGET.len() as u64),
576 protocols: Some(fio::NodeProtocolKinds::SYMLINK),
577 abilities: Some(fio::Abilities::GET_ATTRIBUTES),
578 ..Default::default()
579 }
580 );
581 }
582
583 #[fuchsia::test]
584 async fn test_clone() {
585 let client = flex_local::local_client_empty();
586 let client_end = serve_test_symlink(&client).await;
587
588 let orig_attrs = client_end
589 .get_attributes(fio::NodeAttributesQuery::all())
590 .await
591 .expect("fidl failed")
592 .unwrap();
593 let (cloned_client, cloned_server) = client.create_proxy::<fio::SymlinkMarker>();
595 client_end.clone(ServerEnd::new(cloned_server.into_channel())).unwrap();
596 let cloned_attrs = cloned_client
597 .get_attributes(fio::NodeAttributesQuery::all())
598 .await
599 .expect("fidl failed")
600 .unwrap();
601 assert_eq!(orig_attrs, cloned_attrs);
602 }
603
604 #[fuchsia::test]
605 async fn test_describe() {
606 let client = flex_local::local_client_empty();
607 let client_end = serve_test_symlink(&client).await;
608
609 assert_matches!(
610 client_end.describe().await.expect("fidl failed"),
611 fio::SymlinkInfo {
612 target: Some(target),
613 ..
614 } if target == b"target"
615 );
616 }
617
618 #[fuchsia::test]
619 async fn test_xattrs() {
620 let client = flex_local::local_client_empty();
621 let client_end = serve_test_symlink(&client).await;
622
623 client_end
624 .set_extended_attribute(
625 b"foo",
626 fio::ExtendedAttributeValue::Bytes(b"bar".to_vec()),
627 fio::SetExtendedAttributeMode::Set,
628 )
629 .await
630 .unwrap()
631 .unwrap();
632 assert_eq!(
633 client_end.get_extended_attribute(b"foo").await.unwrap().unwrap(),
634 fio::ExtendedAttributeValue::Bytes(b"bar".to_vec()),
635 );
636 let (iterator_client_end, iterator_server_end) =
637 client.create_proxy::<fio::ExtendedAttributeIteratorMarker>();
638 client_end.list_extended_attributes(iterator_server_end).unwrap();
639 assert_eq!(
640 iterator_client_end.get_next().await.unwrap().unwrap(),
641 (vec![b"bar".to_vec()], true)
642 );
643 client_end.remove_extended_attribute(b"foo").await.unwrap().unwrap();
644 assert_eq!(
645 client_end.get_extended_attribute(b"foo").await.unwrap().unwrap_err(),
646 Status::NOT_FOUND.into_raw(),
647 );
648 }
649
650 #[cfg(fuchsia_api_level_at_least = "HEAD")]
651 #[fuchsia::test]
652 async fn test_open() {
653 let client = flex_local::local_client_empty();
654 let client_end = serve_test_symlink(&client).await;
655
656 #[cfg(feature = "fdomain")]
657 let (object, server_end) = client.create_channel();
658 #[cfg(not(feature = "fdomain"))]
659 let (object, server_end) = fidl::Channel::create();
660 client_end
661 .open("path", fio::Flags::empty(), &fio::Options::default(), server_end)
662 .expect("fidl failed");
663
664 #[cfg(feature = "fdomain")]
665 let requests = fio::NodeProxy::new(object);
666 #[cfg(not(feature = "fdomain"))]
667 let requests = {
668 use fidl::endpoints::Proxy;
669 fio::NodeProxy::from_channel(fuchsia_async::Channel::from_channel(object))
670 };
671
672 let error = requests
673 .take_event_stream()
674 .next()
675 .await
676 .expect("no event")
677 .expect_err("error expected");
678
679 assert_matches!(error, fidl::Error::ClientChannelClosed { status: Status::NOT_DIR, .. });
680 }
681}