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