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, target_rights) = self
261 .scope
262 .token_registry()
263 .get_owner_and_rights(target_parent_token.into())?
264 .ok_or(Err(Status::NOT_FOUND))?;
265
266 if !target_rights.contains(fio::Rights::MODIFY_DIRECTORY) {
267 return Err(Status::ACCESS_DENIED);
268 }
269
270 self.symlink.clone().link_into(target_parent, target_name).await
271 }
272
273 async fn handle_list_extended_attribute(
274 &self,
275 iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
276 ) {
277 let attributes = match self.symlink.list_extended_attributes().await {
278 Ok(attributes) => attributes,
279 Err(status) => {
280 #[cfg(any(test, feature = "use_log"))]
281 log::error!(status:?; "list extended attributes failed");
282 #[allow(clippy::unnecessary_lazy_evaluations)]
283 iterator.close_with_epitaph(status).unwrap_or_else(|_error| {
284 #[cfg(any(test, feature = "use_log"))]
285 log::error!(_error:?; "failed to send epitaph")
286 });
287 return;
288 }
289 };
290 self.scope.spawn(extended_attributes_sender(iterator, attributes));
291 }
292
293 async fn handle_get_extended_attribute(
294 &self,
295 name: Vec<u8>,
296 ) -> Result<fio::ExtendedAttributeValue, Status> {
297 let value = self.symlink.get_extended_attribute(name).await?;
298 encode_extended_attribute_value(value)
299 }
300
301 async fn handle_set_extended_attribute(
302 &self,
303 name: Vec<u8>,
304 value: fio::ExtendedAttributeValue,
305 mode: fio::SetExtendedAttributeMode,
306 ) -> Result<(), Status> {
307 if name.contains(&0) {
308 return Err(Status::INVALID_ARGS);
309 }
310 let val = decode_extended_attribute_value(value)?;
311 self.symlink.set_extended_attribute(name, val, mode).await
312 }
313
314 async fn handle_remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
315 self.symlink.remove_extended_attribute(name).await
316 }
317}
318
319impl<T: Symlink> RequestHandler for Connection<T> {
320 type Request = Result<fio::SymlinkRequest, fidl::Error>;
321
322 async fn handle_request(self: Pin<&mut Self>, request: Self::Request) -> ControlFlow<()> {
323 let this = self.get_mut();
324 if let Some(_guard) = this.scope.try_active_guard() {
325 match request {
326 Ok(request) => match this.handle_request(request).await {
327 Ok(false) => ControlFlow::Continue(()),
328 Ok(true) | Err(_) => ControlFlow::Break(()),
329 },
330 Err(_) => ControlFlow::Break(()),
331 }
332 } else {
333 ControlFlow::Break(())
334 }
335 }
336}
337
338impl<T: Symlink> Representation for Connection<T> {
339 type Protocol = fio::SymlinkMarker;
340
341 async fn get_representation(
342 &self,
343 requested_attributes: fio::NodeAttributesQuery,
344 ) -> Result<fio::Representation, Status> {
345 Ok(fio::Representation::Symlink(fio::SymlinkInfo {
346 attributes: if requested_attributes.is_empty() {
347 None
348 } else {
349 Some(self.symlink.get_attributes(requested_attributes).await?)
350 },
351 target: Some(self.symlink.read_target().await?),
352 ..Default::default()
353 }))
354 }
355
356 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
357 async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
358 Ok(fio::NodeInfoDeprecated::Symlink(fio::SymlinkObject {
359 target: self.symlink.read_target().await?,
360 }))
361 }
362}
363
364impl<T: Symlink> ConnectionCreator<T> for Connection<T> {
365 async fn create<'a>(
366 scope: ExecutionScope,
367 node: Arc<T>,
368 protocols: impl ProtocolsExt,
369 object_request: ObjectRequestRef<'a>,
370 ) -> Result<(), Status> {
371 Self::create(scope, node, protocols, object_request).await
372 }
373}
374
375pub fn serve(
377 link: Arc<impl Symlink>,
378 scope: ExecutionScope,
379 protocols: impl ProtocolsExt,
380 object_request: ObjectRequestRef<'_>,
381) -> Result<(), Status> {
382 if protocols.is_node() {
383 let options = protocols.to_node_options(link.entry_info().type_())?;
384 link.open_as_node(scope, options, object_request)
385 } else {
386 Connection::create_sync(scope, link, protocols, object_request.take());
387 Ok(())
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::{Connection, Symlink};
394 use crate::ToObjectRequest;
395 use crate::directory::entry::{EntryInfo, GetEntryInfo};
396 use crate::execution_scope::ExecutionScope;
397 use crate::node::Node;
398 use assert_matches::assert_matches;
399 use fidl::endpoints::{ServerEnd, create_proxy};
400 use fidl_fuchsia_io as fio;
401 use fuchsia_sync::Mutex;
402 use futures::StreamExt;
403 use std::collections::HashMap;
404 use std::sync::Arc;
405 use zx_status::Status;
406
407 const TARGET: &[u8] = b"target";
408
409 struct TestSymlink {
410 xattrs: Mutex<HashMap<Vec<u8>, Vec<u8>>>,
411 }
412
413 impl TestSymlink {
414 fn new() -> Self {
415 TestSymlink { xattrs: Mutex::new(HashMap::new()) }
416 }
417 }
418
419 impl Symlink for TestSymlink {
420 async fn read_target(&self) -> Result<Vec<u8>, Status> {
421 Ok(TARGET.to_vec())
422 }
423 }
424
425 impl Node for TestSymlink {
426 async fn get_attributes(
427 &self,
428 requested_attributes: fio::NodeAttributesQuery,
429 ) -> Result<fio::NodeAttributes2, Status> {
430 Ok(immutable_attributes!(
431 requested_attributes,
432 Immutable {
433 content_size: TARGET.len() as u64,
434 storage_size: TARGET.len() as u64,
435 protocols: fio::NodeProtocolKinds::SYMLINK,
436 abilities: fio::Abilities::GET_ATTRIBUTES,
437 }
438 ))
439 }
440 async fn list_extended_attributes(&self) -> Result<Vec<Vec<u8>>, Status> {
441 let map = self.xattrs.lock();
442 Ok(map.values().map(|x| x.clone()).collect())
443 }
444 async fn get_extended_attribute(&self, name: Vec<u8>) -> Result<Vec<u8>, Status> {
445 let map = self.xattrs.lock();
446 map.get(&name).map(|x| x.clone()).ok_or(Status::NOT_FOUND)
447 }
448 async fn set_extended_attribute(
449 &self,
450 name: Vec<u8>,
451 value: Vec<u8>,
452 _mode: fio::SetExtendedAttributeMode,
453 ) -> Result<(), Status> {
454 let mut map = self.xattrs.lock();
455 map.insert(name, value);
458 Ok(())
459 }
460 async fn remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
461 let mut map = self.xattrs.lock();
462 map.remove(&name);
463 Ok(())
464 }
465 }
466
467 impl GetEntryInfo for TestSymlink {
468 fn entry_info(&self) -> EntryInfo {
469 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Symlink)
470 }
471 }
472
473 async fn serve_test_symlink() -> fio::SymlinkProxy {
474 let (client_end, server_end) = create_proxy::<fio::SymlinkMarker>();
475 let flags = fio::PERM_READABLE | fio::Flags::PROTOCOL_SYMLINK;
476
477 Connection::create_sync(
478 ExecutionScope::new(),
479 Arc::new(TestSymlink::new()),
480 flags,
481 flags.to_object_request(server_end),
482 );
483
484 client_end
485 }
486
487 #[fuchsia::test]
488 async fn test_read_target() {
489 let client_end = serve_test_symlink().await;
490
491 assert_eq!(
492 client_end.describe().await.expect("fidl failed").target.expect("missing target"),
493 b"target"
494 );
495 }
496
497 #[fuchsia::test]
498 async fn test_validate_flags() {
499 let scope = ExecutionScope::new();
500
501 let check = |mut flags: fio::Flags| {
502 let (client_end, server_end) = create_proxy::<fio::SymlinkMarker>();
503 flags |= fio::Flags::FLAG_SEND_REPRESENTATION;
504 flags.to_object_request(server_end).create_connection_sync::<Connection<_>, _>(
505 scope.clone(),
506 Arc::new(TestSymlink::new()),
507 flags,
508 );
509
510 async move { client_end.take_event_stream().next().await.expect("no event") }
511 };
512
513 for flags in [
514 fio::Flags::PROTOCOL_DIRECTORY,
515 fio::Flags::PROTOCOL_FILE,
516 fio::Flags::PROTOCOL_SERVICE,
517 ] {
518 assert_matches!(
519 check(fio::PERM_READABLE | flags).await,
520 Err(fidl::Error::ClientChannelClosed { status: Status::WRONG_TYPE, .. }),
521 "{flags:?}"
522 );
523 }
524
525 assert_matches!(
526 check(fio::PERM_READABLE | fio::Flags::PROTOCOL_SYMLINK)
527 .await
528 .expect("error from next")
529 .into_on_representation()
530 .expect("expected on representation"),
531 fio::Representation::Symlink(fio::SymlinkInfo { .. })
532 );
533 assert_matches!(
534 check(fio::PERM_READABLE)
535 .await
536 .expect("error from next")
537 .into_on_representation()
538 .expect("expected on representation"),
539 fio::Representation::Symlink(fio::SymlinkInfo { .. })
540 );
541 }
542
543 #[fuchsia::test]
544 async fn test_get_attr() {
545 let client_end = serve_test_symlink().await;
546
547 let (mutable_attrs, immutable_attrs) = client_end
548 .get_attributes(fio::NodeAttributesQuery::all())
549 .await
550 .expect("fidl failed")
551 .expect("GetAttributes failed");
552
553 assert_eq!(mutable_attrs, Default::default());
554 assert_eq!(
555 immutable_attrs,
556 fio::ImmutableNodeAttributes {
557 content_size: Some(TARGET.len() as u64),
558 storage_size: Some(TARGET.len() as u64),
559 protocols: Some(fio::NodeProtocolKinds::SYMLINK),
560 abilities: Some(fio::Abilities::GET_ATTRIBUTES),
561 ..Default::default()
562 }
563 );
564 }
565
566 #[fuchsia::test]
567 async fn test_clone() {
568 let client_end = serve_test_symlink().await;
569
570 let orig_attrs = client_end
571 .get_attributes(fio::NodeAttributesQuery::all())
572 .await
573 .expect("fidl failed")
574 .unwrap();
575 let (cloned_client, cloned_server) = create_proxy::<fio::SymlinkMarker>();
577 client_end.clone(ServerEnd::new(cloned_server.into_channel())).unwrap();
578 let cloned_attrs = cloned_client
579 .get_attributes(fio::NodeAttributesQuery::all())
580 .await
581 .expect("fidl failed")
582 .unwrap();
583 assert_eq!(orig_attrs, cloned_attrs);
584 }
585
586 #[fuchsia::test]
587 async fn test_describe() {
588 let client_end = serve_test_symlink().await;
589
590 assert_matches!(
591 client_end.describe().await.expect("fidl failed"),
592 fio::SymlinkInfo {
593 target: Some(target),
594 ..
595 } if target == b"target"
596 );
597 }
598
599 #[fuchsia::test]
600 async fn test_xattrs() {
601 let client_end = serve_test_symlink().await;
602
603 client_end
604 .set_extended_attribute(
605 b"foo",
606 fio::ExtendedAttributeValue::Bytes(b"bar".to_vec()),
607 fio::SetExtendedAttributeMode::Set,
608 )
609 .await
610 .unwrap()
611 .unwrap();
612 assert_eq!(
613 client_end.get_extended_attribute(b"foo").await.unwrap().unwrap(),
614 fio::ExtendedAttributeValue::Bytes(b"bar".to_vec()),
615 );
616 let (iterator_client_end, iterator_server_end) =
617 create_proxy::<fio::ExtendedAttributeIteratorMarker>();
618 client_end.list_extended_attributes(iterator_server_end).unwrap();
619 assert_eq!(
620 iterator_client_end.get_next().await.unwrap().unwrap(),
621 (vec![b"bar".to_vec()], true)
622 );
623 client_end.remove_extended_attribute(b"foo").await.unwrap().unwrap();
624 assert_eq!(
625 client_end.get_extended_attribute(b"foo").await.unwrap().unwrap_err(),
626 Status::NOT_FOUND.into_raw(),
627 );
628 }
629
630 #[cfg(fuchsia_api_level_at_least = "HEAD")]
631 #[fuchsia::test]
632 async fn test_open() {
633 use fidl::endpoints::Proxy;
634
635 let client_end = serve_test_symlink().await;
636
637 let (object, server_end) = fidl::Channel::create();
638 client_end
639 .open("path", fio::Flags::empty(), &fio::Options::default(), server_end)
640 .expect("fidl failed");
641
642 let requests = fio::NodeProxy::from_channel(fuchsia_async::Channel::from_channel(object));
643
644 let error = requests
645 .take_event_stream()
646 .next()
647 .await
648 .expect("no event")
649 .expect_err("error expected");
650
651 assert_matches!(error, fidl::Error::ClientChannelClosed { status: Status::NOT_DIR, .. });
652 }
653}