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