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