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