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