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