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 async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
353 Ok(fio::NodeInfoDeprecated::Symlink(fio::SymlinkObject {
354 target: self.symlink.read_target().await?,
355 }))
356 }
357}
358
359impl<T: Symlink> ConnectionCreator<T> for Connection<T> {
360 async fn create<'a>(
361 scope: ExecutionScope,
362 node: Arc<T>,
363 protocols: impl ProtocolsExt,
364 object_request: ObjectRequestRef<'a>,
365 ) -> Result<(), Status> {
366 Self::create(scope, node, protocols, object_request).await
367 }
368}
369
370pub fn serve(
372 link: Arc<impl Symlink>,
373 scope: ExecutionScope,
374 protocols: impl ProtocolsExt,
375 object_request: ObjectRequestRef<'_>,
376) -> Result<(), Status> {
377 if protocols.is_node() {
378 let options = protocols.to_node_options(link.entry_info().type_())?;
379 link.open_as_node(scope, options, object_request)
380 } else {
381 Connection::create_sync(scope, link, protocols, object_request.take());
382 Ok(())
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::{Connection, Symlink};
389 use crate::ToObjectRequest;
390 use crate::directory::entry::{EntryInfo, GetEntryInfo};
391 use crate::execution_scope::ExecutionScope;
392 use crate::node::Node;
393 use assert_matches::assert_matches;
394 use fidl::endpoints::{ServerEnd, create_proxy};
395 use fidl_fuchsia_io as fio;
396 use fuchsia_sync::Mutex;
397 use futures::StreamExt;
398 use std::collections::HashMap;
399 use std::sync::Arc;
400 use zx_status::Status;
401
402 const TARGET: &[u8] = b"target";
403
404 struct TestSymlink {
405 xattrs: Mutex<HashMap<Vec<u8>, Vec<u8>>>,
406 }
407
408 impl TestSymlink {
409 fn new() -> Self {
410 TestSymlink { xattrs: Mutex::new(HashMap::new()) }
411 }
412 }
413
414 impl Symlink for TestSymlink {
415 async fn read_target(&self) -> Result<Vec<u8>, Status> {
416 Ok(TARGET.to_vec())
417 }
418 }
419
420 impl Node for TestSymlink {
421 async fn get_attributes(
422 &self,
423 requested_attributes: fio::NodeAttributesQuery,
424 ) -> Result<fio::NodeAttributes2, Status> {
425 Ok(immutable_attributes!(
426 requested_attributes,
427 Immutable {
428 content_size: TARGET.len() as u64,
429 storage_size: TARGET.len() as u64,
430 protocols: fio::NodeProtocolKinds::SYMLINK,
431 abilities: fio::Abilities::GET_ATTRIBUTES,
432 }
433 ))
434 }
435 async fn list_extended_attributes(&self) -> Result<Vec<Vec<u8>>, Status> {
436 let map = self.xattrs.lock();
437 Ok(map.values().map(|x| x.clone()).collect())
438 }
439 async fn get_extended_attribute(&self, name: Vec<u8>) -> Result<Vec<u8>, Status> {
440 let map = self.xattrs.lock();
441 map.get(&name).map(|x| x.clone()).ok_or(Status::NOT_FOUND)
442 }
443 async fn set_extended_attribute(
444 &self,
445 name: Vec<u8>,
446 value: Vec<u8>,
447 _mode: fio::SetExtendedAttributeMode,
448 ) -> Result<(), Status> {
449 let mut map = self.xattrs.lock();
450 map.insert(name, value);
453 Ok(())
454 }
455 async fn remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
456 let mut map = self.xattrs.lock();
457 map.remove(&name);
458 Ok(())
459 }
460 }
461
462 impl GetEntryInfo for TestSymlink {
463 fn entry_info(&self) -> EntryInfo {
464 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Symlink)
465 }
466 }
467
468 async fn serve_test_symlink() -> fio::SymlinkProxy {
469 let (client_end, server_end) = create_proxy::<fio::SymlinkMarker>();
470 let flags = fio::PERM_READABLE | fio::Flags::PROTOCOL_SYMLINK;
471
472 Connection::create_sync(
473 ExecutionScope::new(),
474 Arc::new(TestSymlink::new()),
475 flags,
476 flags.to_object_request(server_end),
477 );
478
479 client_end
480 }
481
482 #[fuchsia::test]
483 async fn test_read_target() {
484 let client_end = serve_test_symlink().await;
485
486 assert_eq!(
487 client_end.describe().await.expect("fidl failed").target.expect("missing target"),
488 b"target"
489 );
490 }
491
492 #[fuchsia::test]
493 async fn test_validate_flags() {
494 let scope = ExecutionScope::new();
495
496 let check = |mut flags: fio::Flags| {
497 let (client_end, server_end) = create_proxy::<fio::SymlinkMarker>();
498 flags |= fio::Flags::FLAG_SEND_REPRESENTATION;
499 flags.to_object_request(server_end).create_connection_sync::<Connection<_>, _>(
500 scope.clone(),
501 Arc::new(TestSymlink::new()),
502 flags,
503 );
504
505 async move { client_end.take_event_stream().next().await.expect("no event") }
506 };
507
508 for flags in [
509 fio::Flags::PROTOCOL_DIRECTORY,
510 fio::Flags::PROTOCOL_FILE,
511 fio::Flags::PROTOCOL_SERVICE,
512 ] {
513 assert_matches!(
514 check(fio::PERM_READABLE | flags).await,
515 Err(fidl::Error::ClientChannelClosed { status: Status::WRONG_TYPE, .. }),
516 "{flags:?}"
517 );
518 }
519
520 assert_matches!(
521 check(fio::PERM_READABLE | fio::Flags::PROTOCOL_SYMLINK)
522 .await
523 .expect("error from next")
524 .into_on_representation()
525 .expect("expected on representation"),
526 fio::Representation::Symlink(fio::SymlinkInfo { .. })
527 );
528 assert_matches!(
529 check(fio::PERM_READABLE)
530 .await
531 .expect("error from next")
532 .into_on_representation()
533 .expect("expected on representation"),
534 fio::Representation::Symlink(fio::SymlinkInfo { .. })
535 );
536 }
537
538 #[fuchsia::test]
539 async fn test_get_attr() {
540 let client_end = serve_test_symlink().await;
541
542 let (mutable_attrs, immutable_attrs) = client_end
543 .get_attributes(fio::NodeAttributesQuery::all())
544 .await
545 .expect("fidl failed")
546 .expect("GetAttributes failed");
547
548 assert_eq!(mutable_attrs, Default::default());
549 assert_eq!(
550 immutable_attrs,
551 fio::ImmutableNodeAttributes {
552 content_size: Some(TARGET.len() as u64),
553 storage_size: Some(TARGET.len() as u64),
554 protocols: Some(fio::NodeProtocolKinds::SYMLINK),
555 abilities: Some(fio::Abilities::GET_ATTRIBUTES),
556 ..Default::default()
557 }
558 );
559 }
560
561 #[fuchsia::test]
562 async fn test_clone() {
563 let client_end = serve_test_symlink().await;
564
565 let orig_attrs = client_end
566 .get_attributes(fio::NodeAttributesQuery::all())
567 .await
568 .expect("fidl failed")
569 .unwrap();
570 let (cloned_client, cloned_server) = create_proxy::<fio::SymlinkMarker>();
572 client_end.clone(ServerEnd::new(cloned_server.into_channel())).unwrap();
573 let cloned_attrs = cloned_client
574 .get_attributes(fio::NodeAttributesQuery::all())
575 .await
576 .expect("fidl failed")
577 .unwrap();
578 assert_eq!(orig_attrs, cloned_attrs);
579 }
580
581 #[fuchsia::test]
582 async fn test_describe() {
583 let client_end = serve_test_symlink().await;
584
585 assert_matches!(
586 client_end.describe().await.expect("fidl failed"),
587 fio::SymlinkInfo {
588 target: Some(target),
589 ..
590 } if target == b"target"
591 );
592 }
593
594 #[fuchsia::test]
595 async fn test_xattrs() {
596 let client_end = serve_test_symlink().await;
597
598 client_end
599 .set_extended_attribute(
600 b"foo",
601 fio::ExtendedAttributeValue::Bytes(b"bar".to_vec()),
602 fio::SetExtendedAttributeMode::Set,
603 )
604 .await
605 .unwrap()
606 .unwrap();
607 assert_eq!(
608 client_end.get_extended_attribute(b"foo").await.unwrap().unwrap(),
609 fio::ExtendedAttributeValue::Bytes(b"bar".to_vec()),
610 );
611 let (iterator_client_end, iterator_server_end) =
612 create_proxy::<fio::ExtendedAttributeIteratorMarker>();
613 client_end.list_extended_attributes(iterator_server_end).unwrap();
614 assert_eq!(
615 iterator_client_end.get_next().await.unwrap().unwrap(),
616 (vec![b"bar".to_vec()], true)
617 );
618 client_end.remove_extended_attribute(b"foo").await.unwrap().unwrap();
619 assert_eq!(
620 client_end.get_extended_attribute(b"foo").await.unwrap().unwrap_err(),
621 Status::NOT_FOUND.into_raw(),
622 );
623 }
624
625 #[cfg(fuchsia_api_level_at_least = "HEAD")]
626 #[fuchsia::test]
627 async fn test_open() {
628 use fidl::endpoints::Proxy;
629
630 let client_end = serve_test_symlink().await;
631
632 let (object, server_end) = fidl::Channel::create();
633 client_end
634 .open("path", fio::Flags::empty(), &fio::Options::default(), server_end)
635 .expect("fidl failed");
636
637 let requests = fio::NodeProxy::from_channel(fuchsia_async::Channel::from_channel(object));
638
639 let error = requests
640 .take_event_stream()
641 .next()
642 .await
643 .expect("no event")
644 .expect_err("error expected");
645
646 assert_matches!(error, fidl::Error::ClientChannelClosed { status: Status::NOT_DIR, .. });
647 }
648}