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