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