1use fidl::endpoints::SynchronousProxy;
10use fidl_fuchsia_io as fio;
11use fuchsia_sync::Mutex;
12use smallvec::SmallVec;
13use std::ops::{ControlFlow, Range};
14use syncio::{AllocateMode, zxio_fsverity_descriptor_t, zxio_node_attributes_t};
15use zerocopy::FromBytes;
16
17fn zxio_attr_from_fidl(
21 mutable: &fio::MutableNodeAttributes,
22 immutable: &fio::ImmutableNodeAttributes,
23) -> zxio_node_attributes_t {
24 let mut out_attr = zxio_node_attributes_t::default();
25 if let Some(protocols) = immutable.protocols {
26 out_attr.protocols = protocols.bits();
27 out_attr.has.protocols = true;
28 }
29 if let Some(abilities) = immutable.abilities {
30 out_attr.abilities = abilities.bits();
31 out_attr.has.abilities = true;
32 }
33 if let Some(id) = immutable.id {
34 out_attr.id = id;
35 out_attr.has.id = true;
36 }
37 if let Some(content_size) = immutable.content_size {
38 out_attr.content_size = content_size;
39 out_attr.has.content_size = true;
40 }
41 if let Some(storage_size) = immutable.storage_size {
42 out_attr.storage_size = storage_size;
43 out_attr.has.storage_size = true;
44 }
45 if let Some(link_count) = immutable.link_count {
46 out_attr.link_count = link_count;
47 out_attr.has.link_count = true;
48 }
49 if let Some(creation_time) = mutable.creation_time {
50 out_attr.creation_time = creation_time;
51 out_attr.has.creation_time = true;
52 }
53 if let Some(modification_time) = mutable.modification_time {
54 out_attr.modification_time = modification_time;
55 out_attr.has.modification_time = true;
56 }
57 if let Some(access_time) = mutable.access_time {
58 out_attr.access_time = access_time;
59 out_attr.has.access_time = true;
60 }
61 if let Some(mode) = mutable.mode {
62 out_attr.mode = mode;
63 out_attr.has.mode = true;
64 }
65 if let Some(uid) = mutable.uid {
66 out_attr.uid = uid;
67 out_attr.has.uid = true;
68 }
69 if let Some(gid) = mutable.gid {
70 out_attr.gid = gid;
71 out_attr.has.gid = true;
72 }
73 if let Some(rdev) = mutable.rdev {
74 out_attr.rdev = rdev;
75 out_attr.has.rdev = true;
76 }
77 if let Some(change_time) = immutable.change_time {
78 out_attr.change_time = change_time;
79 out_attr.has.change_time = true;
80 }
81 if let Some(casefold) = mutable.casefold {
82 out_attr.casefold = casefold;
83 out_attr.has.casefold = true;
84 }
85 if let Some(verity_enabled) = immutable.verity_enabled {
86 out_attr.fsverity_enabled = verity_enabled;
87 out_attr.has.fsverity_enabled = true;
88 }
89 if let Some(policy) = mutable.encryption_policy {
90 out_attr.encryption_policy.key_identifier = policy.key_identifier;
91 out_attr.encryption_policy.flags = policy.flags.bits();
92 out_attr.has.encryption_policy = true;
93 }
94 out_attr
95}
96
97pub trait SyncIoDirectory: Send + Sync + 'static {
103 fn seek(&self, new_index: u64) -> Result<u64, zx::Status>;
108
109 fn readdir<B, S: FnMut(u64, fio::DirentType, &[u8]) -> ControlFlow<B, ()>>(
116 &self,
117 sink: S,
118 ) -> Result<Option<B>, zx::Status>;
119
120 fn sync(&self) -> Result<(), zx::Status>;
122
123 fn clone_proxy(&self) -> Result<Option<fio::DirectorySynchronousProxy>, zx::Status>;
126}
127
128pub trait Factory<T> {
130 type Result;
131
132 fn create_node(self, io: T, info: fio::NodeInfo) -> Self::Result;
133 fn create_directory(self, io: T, info: fio::DirectoryInfo) -> Self::Result;
134 fn create_file(self, io: T, info: fio::FileInfo) -> Self::Result;
135 fn create_symlink(self, io: T, info: fio::SymlinkInfo) -> Self::Result;
136}
137
138pub fn create_with_on_representation<F: Factory<RemoteIo>>(
145 proxy: fio::NodeSynchronousProxy,
146 factory: F,
147) -> Result<F::Result, zx::Status> {
148 match proxy.wait_for_event(zx::MonotonicInstant::INFINITE) {
149 Ok(fio::NodeEvent::OnRepresentation { payload }) => match payload {
150 fio::Representation::Node(info) => Ok(factory.create_node(RemoteIo::new(proxy), info)),
151 fio::Representation::Directory(info) => {
152 Ok(factory.create_directory(RemoteIo::new(proxy), info))
153 }
154 fio::Representation::File(mut info) => {
155 let io = RemoteIo {
156 proxy,
157 stream: info
158 .stream
159 .take()
160 .map(zx::Stream::from)
161 .unwrap_or_else(|| zx::NullableHandle::invalid().into()),
162 };
163 Ok(factory.create_file(io, info))
164 }
165 fio::Representation::Symlink(info) => {
166 Ok(factory.create_symlink(RemoteIo::new(proxy), info))
167 }
168 _ => Err(zx::Status::NOT_SUPPORTED),
169 },
170 Err(fidl::Error::ClientChannelClosed { epitaph, .. }) => match epitaph.into() {
171 Err(status) => Err(status),
172 Ok(()) => Err(zx::Status::PEER_CLOSED),
173 },
174 _ => Err(zx::Status::IO),
175 }
176}
177
178pub trait SyncIo: Send + Sync + Sized + 'static {
180 type Directory: SyncIoDirectory;
181
182 fn attr_get(
184 &self,
185 query: fio::NodeAttributesQuery,
186 ) -> Result<(fio::MutableNodeAttributes, fio::ImmutableNodeAttributes), zx::Status>;
187
188 fn attr_set(&self, attributes: fio::MutableNodeAttributes) -> Result<(), zx::Status>;
190
191 fn query_filesystem(&self) -> Result<fio::FilesystemInfo, zx::Status>;
193
194 fn open<F: Factory<Self>>(
196 &self,
197 path: &str,
198 flags: fio::Flags,
199 create_attributes: Option<fio::MutableNodeAttributes>,
200 query: fio::NodeAttributesQuery,
201 factory: F,
202 ) -> Result<F::Result, zx::Status>;
203
204 fn open_pipelined<F: Factory<Self>>(
208 &self,
209 paths: &[&str],
210 flags: fio::Flags,
211 query: fio::NodeAttributesQuery,
212 factory_fn: impl FnMut() -> F,
213 ) -> impl Iterator<Item = Result<F::Result, zx::Status>>;
214
215 fn link_into(&self, target_dir: &Self, name: &str) -> Result<(), zx::Status>;
217
218 fn unlink(&self, name: &str, flags: fio::UnlinkFlags) -> Result<(), zx::Status>;
220
221 fn rename(
223 &self,
224 old_path: &str,
225 new_directory: &Self,
226 new_path: &str,
227 ) -> Result<(), zx::Status>;
228
229 fn create_symlink(&self, name: &str, target: &[u8]) -> Result<Self, zx::Status>;
231
232 fn read_partial(&self, offset: u64, max: usize) -> Result<(Vec<u8>, bool), zx::Status>;
236
237 fn read<E>(
242 &self,
243 offset: u64,
244 len: usize,
245 mut callback: impl FnMut(Vec<u8>) -> Result<usize, E>,
246 map_err: impl FnOnce(zx::Status) -> E,
247 ) -> Result<usize, E> {
248 let mut total = 0;
249 while total < len {
250 match self.read_partial(offset + total as u64, len - total) {
251 Ok((data, eof)) => {
252 if data.is_empty() {
253 break;
254 }
255 let data_len = data.len();
256 let written = callback(data)?;
257 total += written;
258 if eof || written < data_len {
259 break;
260 }
261 }
262 Err(e) => {
263 if total > 0 {
264 break;
265 }
266 return Err(map_err(e));
267 }
268 }
269 }
270 Ok(total)
271 }
272
273 fn write(&self, offset: u64, data: &[u8]) -> Result<usize, zx::Status>;
275
276 fn supports_vectored(&self) -> bool;
278
279 unsafe fn readv(
286 &self,
287 offset: u64,
288 iovecs: &mut [zx::sys::zx_iovec_t],
289 ) -> Result<usize, zx::Status>;
290
291 fn writev(&self, offset: u64, iovecs: &[zx::sys::zx_iovec_t]) -> Result<usize, zx::Status>;
294
295 fn truncate(&self, length: u64) -> Result<(), zx::Status>;
297
298 fn vmo_get(&self, flags: zx::VmarFlags) -> Result<zx::Vmo, zx::Status>;
300
301 fn sync(&self) -> Result<(), zx::Status>;
303
304 fn enable_verity(&self, descriptor: &zxio_fsverity_descriptor_t) -> Result<(), zx::Status>;
306
307 fn allocate(&self, offset: u64, len: u64, mode: AllocateMode) -> Result<(), zx::Status>;
309
310 fn xattr_get(&self, name: &[u8]) -> Result<Vec<u8>, zx::Status>;
312
313 fn xattr_set(
315 &self,
316 name: &[u8],
317 value: &[u8],
318 mode: syncio::XattrSetMode,
319 ) -> Result<(), zx::Status>;
320
321 fn xattr_remove(&self, name: &[u8]) -> Result<(), zx::Status>;
323
324 fn xattr_list(&self) -> Result<Vec<Vec<u8>>, zx::Status>;
326
327 fn open_directory(&self) -> Result<Self::Directory, zx::Status>;
329}
330
331pub struct RemoteIo {
337 proxy: fio::NodeSynchronousProxy,
338 stream: zx::Stream,
341}
342
343impl RemoteIo {
344 pub fn new(proxy: fio::NodeSynchronousProxy) -> Self {
345 Self { proxy, stream: zx::NullableHandle::invalid().into() }
346 }
347
348 pub fn with_stream(proxy: fio::NodeSynchronousProxy, stream: zx::Stream) -> Self {
349 Self { proxy, stream }
350 }
351
352 pub fn into_proxy(self) -> fio::NodeSynchronousProxy {
353 self.proxy
354 }
355
356 pub fn clone_proxy(&self) -> Result<fio::NodeSynchronousProxy, zx::Status> {
358 let (client_end, server_end) = zx::Channel::create();
359 self.proxy.clone(server_end.into()).map_err(|_| zx::Status::IO)?;
360 Ok(client_end.into())
361 }
362
363 pub fn attr_get_zxio(
368 &self,
369 query: fio::NodeAttributesQuery,
370 ) -> Result<zxio_node_attributes_t, zx::Status> {
371 self.attr_get(query).map(|(m, i)| zxio_attr_from_fidl(&m, &i))
372 }
373
374 fn cast_proxy<T: From<zx::Channel> + Into<zx::NullableHandle>>(&self) -> zx::Unowned<'_, T> {
375 zx::Unowned::new(self.proxy.as_channel())
376 }
377}
378
379impl SyncIo for RemoteIo {
380 type Directory = RemoteDirectory;
381
382 fn attr_get(
384 &self,
385 query: fio::NodeAttributesQuery,
386 ) -> Result<(fio::MutableNodeAttributes, fio::ImmutableNodeAttributes), zx::Status> {
387 self.proxy
388 .get_attributes(query, zx::MonotonicInstant::INFINITE)
389 .map_err(|_| zx::Status::IO)?
390 .map_err(zx::Status::err_from_raw)
391 }
392
393 fn attr_set(&self, attributes: fio::MutableNodeAttributes) -> Result<(), zx::Status> {
395 self.proxy
396 .update_attributes(&attributes, zx::MonotonicInstant::INFINITE)
397 .map_err(|_| zx::Status::IO)?
398 .map_err(zx::Status::err_from_raw)
399 }
400
401 fn query_filesystem(&self) -> Result<fio::FilesystemInfo, zx::Status> {
402 let dir_proxy = self.cast_proxy::<fio::DirectorySynchronousProxy>();
403 let (status, info) = dir_proxy
404 .query_filesystem(zx::MonotonicInstant::INFINITE)
405 .map_err(|_| zx::Status::IO)?;
406 zx::Status::ok(status)?;
407 info.map(|b| *b).ok_or(zx::Status::NOT_SUPPORTED)
408 }
409
410 fn open<F: Factory<Self>>(
412 &self,
413 path: &str,
414 flags: fio::Flags,
415 create_attributes: Option<fio::MutableNodeAttributes>,
416 query: fio::NodeAttributesQuery,
417 factory: F,
418 ) -> Result<F::Result, zx::Status> {
419 let (client_end, server_end) = zx::Channel::create();
420 let dir_proxy = self.cast_proxy::<fio::DirectorySynchronousProxy>();
421 dir_proxy
422 .open(
423 path,
424 flags | fio::Flags::FLAG_SEND_REPRESENTATION,
425 &fio::Options {
426 attributes: (!query.is_empty()).then_some(query),
427 create_attributes,
428 ..Default::default()
429 },
430 server_end,
431 )
432 .map_err(|_| zx::Status::IO)?;
433 create_with_on_representation(client_end.into(), factory)
434 }
435
436 fn open_pipelined<F: Factory<Self>>(
448 &self,
449 paths: &[&str],
450 flags: fio::Flags,
451 query: fio::NodeAttributesQuery,
452 mut factory_fn: impl FnMut() -> F,
453 ) -> impl Iterator<Item = Result<F::Result, zx::Status>> {
454 let mut proxy_to_result = move |proxy: fio::NodeSynchronousProxy| {
455 create_with_on_representation(proxy, factory_fn())
456 };
457
458 let mut proxies = SmallVec::<[fio::NodeSynchronousProxy; 8]>::new();
459 let (client_end, server_end) = zx::Channel::create();
460 proxies.push(fio::NodeSynchronousProxy::new(client_end));
461 let mut next_server_end = Some(server_end);
462
463 for (i, path) in paths.iter().enumerate().rev() {
464 let server_end = next_server_end.take().unwrap();
465 let channel = if i == 0 {
466 self.proxy.as_channel()
467 } else {
468 let (client_end, server_end) = zx::Channel::create();
469 next_server_end = Some(server_end);
470 proxies.push(fio::NodeSynchronousProxy::new(client_end));
471 proxies.last().unwrap().as_channel()
472 };
473 let dir_proxy = zx::Unowned::<fio::DirectorySynchronousProxy>::new(channel);
474
475 let mut open_flags = flags | fio::Flags::FLAG_SEND_REPRESENTATION;
476 if i < paths.len() - 1 {
477 open_flags |= fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::PROTOCOL_SYMLINK;
478 }
479
480 if let Err(_) = dir_proxy.open(
481 path,
482 open_flags,
483 &fio::Options {
484 attributes: (!query.is_empty()).then_some(query),
485 ..Default::default()
486 },
487 server_end,
488 ) {
489 debug_assert!(i == 0);
492 }
495 }
496
497 let mut proxies = proxies.into_iter().rev();
498 std::iter::successors(proxies.next().map(&mut proxy_to_result), move |prev| match prev {
499 Err(_) => None,
500 Ok(_) => proxies.next().map(&mut proxy_to_result),
501 })
502 }
503
504 fn read_partial(&self, offset: u64, max: usize) -> Result<(Vec<u8>, bool), zx::Status> {
508 if self.stream.is_invalid() {
509 let file_proxy = self.cast_proxy::<fio::FileSynchronousProxy>();
510 let max = std::cmp::min(max as u64, fio::MAX_TRANSFER_SIZE);
511 let data = file_proxy
512 .read_at(max, offset, zx::MonotonicInstant::INFINITE)
513 .map_err(|_| zx::Status::IO)?
514 .map_err(zx::Status::err_from_raw)?;
515 let eof = (data.len() as u64) < max;
516 Ok((data, eof))
517 } else {
518 let bytes =
520 self.stream.read_at_to_vec(zx::StreamReadOptions::empty(), offset as u64, max)?;
521 let eof = bytes.len() < max;
522 Ok((bytes, eof))
523 }
524 }
525
526 fn write(&self, offset: u64, data: &[u8]) -> Result<usize, zx::Status> {
528 let file_proxy = self.cast_proxy::<fio::FileSynchronousProxy>();
529 let mut total_written = 0;
530 for chunk in data.chunks(fio::MAX_TRANSFER_SIZE as usize) {
531 let result = file_proxy
532 .write_at(chunk, offset + total_written as u64, zx::MonotonicInstant::INFINITE)
533 .map_err(|_| zx::Status::IO)
534 .and_then(|res| res.map_err(zx::Status::err_from_raw));
535 match result {
536 Ok(actual) => {
537 let actual = actual as usize;
538 total_written += actual;
539 if actual < chunk.len() {
540 return Ok(total_written);
541 }
542 }
543 Err(e) => {
544 if total_written > 0 {
545 break;
546 }
547 return Err(e);
548 }
549 }
550 }
551 Ok(total_written)
552 }
553
554 fn supports_vectored(&self) -> bool {
556 !self.stream.is_invalid()
558 }
559
560 unsafe fn readv(
567 &self,
568 offset: u64,
569 iovecs: &mut [zx::sys::zx_iovec_t],
570 ) -> Result<usize, zx::Status> {
571 if self.stream.is_invalid() {
572 return Err(zx::Status::NOT_SUPPORTED);
573 }
574 unsafe { self.stream.readv_at(zx::StreamReadOptions::empty(), offset as u64, iovecs) }
576 }
577
578 fn writev(&self, offset: u64, iovecs: &[zx::sys::zx_iovec_t]) -> Result<usize, zx::Status> {
581 if self.stream.is_invalid() {
582 return Err(zx::Status::NOT_SUPPORTED);
583 }
584 self.stream.writev_at(zx::StreamWriteOptions::empty(), offset, &iovecs)
585 }
586
587 fn truncate(&self, length: u64) -> Result<(), zx::Status> {
589 self.cast_proxy::<fio::FileSynchronousProxy>()
590 .resize(length, zx::MonotonicInstant::INFINITE)
591 .map_err(|_| zx::Status::IO)?
592 .map_err(zx::Status::err_from_raw)
593 }
594
595 fn vmo_get(&self, flags: zx::VmarFlags) -> Result<zx::Vmo, zx::Status> {
597 let mut fio_flags = fio::VmoFlags::empty();
598 if flags.contains(zx::VmarFlags::PERM_READ) {
599 fio_flags |= fio::VmoFlags::READ;
600 }
601 if flags.contains(zx::VmarFlags::PERM_WRITE) {
602 fio_flags |= fio::VmoFlags::WRITE;
603 }
604 if flags.contains(zx::VmarFlags::PERM_EXECUTE) {
605 fio_flags |= fio::VmoFlags::EXECUTE;
606 }
607 let file_proxy = self.cast_proxy::<fio::FileSynchronousProxy>();
608 let vmo = file_proxy
609 .get_backing_memory(fio_flags, zx::MonotonicInstant::INFINITE)
610 .map_err(|_| zx::Status::IO)?
611 .map_err(zx::Status::err_from_raw)?;
612 Ok(vmo)
613 }
614
615 fn sync(&self) -> Result<(), zx::Status> {
617 self.proxy
618 .sync(zx::MonotonicInstant::INFINITE)
619 .map_err(|_| zx::Status::IO)?
620 .map_err(zx::Status::err_from_raw)
621 }
622
623 fn link_into(&self, target_dir: &Self, name: &str) -> Result<(), zx::Status> {
625 let target_dir_proxy = target_dir.cast_proxy::<fio::DirectorySynchronousProxy>();
626 let (status, token) = target_dir_proxy
627 .deprecated_get_token(zx::MonotonicInstant::INFINITE)
628 .map_err(|_| zx::Status::IO)?;
629 zx::Status::ok(status)?;
630 let token = token.ok_or(zx::Status::NOT_SUPPORTED)?;
631
632 let linkable_proxy = self.cast_proxy::<fio::LinkableSynchronousProxy>();
634
635 linkable_proxy
636 .link_into(token.into(), name, zx::MonotonicInstant::INFINITE)
637 .map_err(|_| zx::Status::IO)?
638 .map_err(zx::Status::err_from_raw)
639 }
640
641 fn unlink(&self, name: &str, flags: fio::UnlinkFlags) -> Result<(), zx::Status> {
643 let options = fio::UnlinkOptions { flags: Some(flags), ..Default::default() };
644 let dir_proxy = self.cast_proxy::<fio::DirectorySynchronousProxy>();
645 dir_proxy
646 .unlink(name, &options, zx::MonotonicInstant::INFINITE)
647 .map_err(|_| zx::Status::IO)?
648 .map_err(zx::Status::err_from_raw)
649 }
650
651 fn rename(
653 &self,
654 old_path: &str,
655 new_directory: &Self,
656 new_path: &str,
657 ) -> Result<(), zx::Status> {
658 let new_dir_proxy = new_directory.cast_proxy::<fio::DirectorySynchronousProxy>();
659 let (status, token) = new_dir_proxy
660 .deprecated_get_token(zx::MonotonicInstant::INFINITE)
661 .map_err(|_| zx::Status::IO)?;
662 zx::Status::ok(status)?;
663 let token = token.ok_or(zx::Status::NOT_SUPPORTED)?;
664 let dir_proxy = self.cast_proxy::<fio::DirectorySynchronousProxy>();
665 dir_proxy
666 .rename(old_path, token.into(), new_path, zx::MonotonicInstant::INFINITE)
667 .map_err(|_| zx::Status::IO)?
668 .map_err(zx::Status::err_from_raw)
669 }
670
671 fn create_symlink(&self, name: &str, target: &[u8]) -> Result<RemoteIo, zx::Status> {
673 let dir_proxy = self.cast_proxy::<fio::DirectorySynchronousProxy>();
674 let (client_end, server_end) = zx::Channel::create();
675 dir_proxy
676 .create_symlink(name, target, Some(server_end.into()), zx::MonotonicInstant::INFINITE)
677 .map_err(|_| zx::Status::IO)?
678 .map_err(zx::Status::err_from_raw)?;
679 Ok(RemoteIo::new(client_end.into()))
680 }
681
682 fn enable_verity(&self, descriptor: &zxio_fsverity_descriptor_t) -> Result<(), zx::Status> {
684 let file_proxy = self.cast_proxy::<fio::FileSynchronousProxy>();
685 let options = fio::VerificationOptions {
686 hash_algorithm: Some(match descriptor.hash_algorithm {
687 1 => fio::HashAlgorithm::Sha256,
688 2 => fio::HashAlgorithm::Sha512,
689 _ => return Err(zx::Status::INVALID_ARGS),
690 }),
691 salt: Some(descriptor.salt[..descriptor.salt_size as usize].to_vec()),
692 ..Default::default()
693 };
694 file_proxy
695 .enable_verity(&options, zx::MonotonicInstant::INFINITE)
696 .map_err(|_| zx::Status::IO)?
697 .map_err(zx::Status::err_from_raw)
698 }
699
700 fn allocate(&self, offset: u64, len: u64, mode: AllocateMode) -> Result<(), zx::Status> {
702 let file_proxy = self.cast_proxy::<fio::FileSynchronousProxy>();
703 let mut fio_mode = fio::AllocateMode::empty();
704 if mode.contains(AllocateMode::KEEP_SIZE) {
705 fio_mode |= fio::AllocateMode::KEEP_SIZE;
706 }
707 if mode.contains(AllocateMode::UNSHARE_RANGE) {
708 fio_mode |= fio::AllocateMode::UNSHARE_RANGE;
709 }
710 if mode.contains(AllocateMode::PUNCH_HOLE) {
711 fio_mode |= fio::AllocateMode::PUNCH_HOLE;
712 }
713 if mode.contains(AllocateMode::COLLAPSE_RANGE) {
714 fio_mode |= fio::AllocateMode::COLLAPSE_RANGE;
715 }
716 if mode.contains(AllocateMode::ZERO_RANGE) {
717 fio_mode |= fio::AllocateMode::ZERO_RANGE;
718 }
719 if mode.contains(AllocateMode::INSERT_RANGE) {
720 fio_mode |= fio::AllocateMode::INSERT_RANGE;
721 }
722 file_proxy
723 .allocate(offset, len, fio_mode, zx::MonotonicInstant::INFINITE)
724 .map_err(|_| zx::Status::IO)?
725 .map_err(zx::Status::err_from_raw)
726 }
727
728 fn xattr_get(&self, name: &[u8]) -> Result<Vec<u8>, zx::Status> {
730 let name_str = std::str::from_utf8(name.as_ref()).map_err(|_| zx::Status::INVALID_ARGS)?;
731 let result = self
732 .proxy
733 .get_extended_attribute(name_str.as_bytes(), zx::MonotonicInstant::INFINITE)
734 .map_err(|_| zx::Status::IO)?
735 .map_err(zx::Status::err_from_raw)?;
736 match result {
737 fio::ExtendedAttributeValue::Bytes(bytes) => Ok(bytes),
738 fio::ExtendedAttributeValue::Buffer(vmo) => {
739 let size = vmo.get_content_size().map_err(|_| zx::Status::IO)?;
740 let mut bytes = vec![0u8; size as usize];
741 vmo.read(&mut bytes, 0).map_err(|_| zx::Status::IO)?;
742 Ok(bytes)
743 }
744 _ => Err(zx::Status::NOT_SUPPORTED),
745 }
746 }
747
748 fn xattr_set(
750 &self,
751 name: &[u8],
752 value: &[u8],
753 mode: syncio::XattrSetMode,
754 ) -> Result<(), zx::Status> {
755 let val = fio::ExtendedAttributeValue::Bytes(value.to_vec());
756 let fidl_mode = match mode {
757 syncio::XattrSetMode::Set => fio::SetExtendedAttributeMode::Set,
758 syncio::XattrSetMode::Create => fio::SetExtendedAttributeMode::Create,
759 syncio::XattrSetMode::Replace => fio::SetExtendedAttributeMode::Replace,
760 };
761 self.proxy
762 .set_extended_attribute(name, val, fidl_mode, zx::MonotonicInstant::INFINITE)
763 .map_err(|_| zx::Status::IO)?
764 .map_err(zx::Status::err_from_raw)
765 }
766
767 fn xattr_remove(&self, name: &[u8]) -> Result<(), zx::Status> {
769 let name_str = std::str::from_utf8(name.as_ref()).map_err(|_| zx::Status::INVALID_ARGS)?;
770 self.proxy
771 .remove_extended_attribute(name_str.as_bytes(), zx::MonotonicInstant::INFINITE)
772 .map_err(|_| zx::Status::IO)?
773 .map_err(zx::Status::err_from_raw)
774 }
775
776 fn xattr_list(&self) -> Result<Vec<Vec<u8>>, zx::Status> {
778 let (client_end, server_end) = zx::Channel::create();
779 self.proxy.list_extended_attributes(server_end.into()).map_err(|_| zx::Status::IO)?;
780 let iterator = fio::ExtendedAttributeIteratorSynchronousProxy::new(client_end);
781 let mut all_attrs = vec![];
782 loop {
783 let (attributes, last) = iterator
784 .get_next(zx::MonotonicInstant::INFINITE)
785 .map_err(|_| zx::Status::IO)?
786 .map_err(zx::Status::err_from_raw)?;
787 all_attrs.extend(attributes);
788 if last {
789 break;
790 }
791 }
792 Ok(all_attrs)
793 }
794
795 fn open_directory(&self) -> Result<Self::Directory, zx::Status> {
796 let proxy = self.clone_proxy()?;
797 Ok(RemoteDirectory::new(proxy.into_channel().into()))
798 }
799}
800
801pub struct RemoteDirectory {
806 proxy: fio::DirectorySynchronousProxy,
807 state: Mutex<State>,
808}
809
810#[derive(Default)]
811struct State {
812 buffer: Vec<u8>,
814
815 offset: usize,
817
818 pending_entry: Entry,
821
822 current_index: u64,
824}
825
826impl State {
827 fn name(&self, range: Range<usize>) -> &[u8] {
828 &self.buffer[range]
829 }
830
831 fn next(&mut self, proxy: &fio::DirectorySynchronousProxy) -> Result<Entry, zx::Status> {
834 let mut next_dirent = || -> Result<Entry, zx::Status> {
835 if self.offset >= self.buffer.len() {
836 match proxy.read_dirents(fio::MAX_BUF, zx::MonotonicInstant::INFINITE) {
837 Ok((status, dirents)) => {
838 zx::Status::ok(status)?;
839 if dirents.is_empty() {
840 return Ok(Entry::None);
841 }
842 self.buffer = dirents;
843 self.offset = 0;
844 }
845 Err(_) => return Err(zx::Status::IO),
846 }
847 }
848
849 #[repr(C, packed)]
850 #[derive(FromBytes)]
851 struct DirectoryEntry {
852 ino: u64,
853 name_len: u8,
854 entry_type: u8,
855 }
856
857 let Some((ino, name, entry_type)) =
858 DirectoryEntry::read_from_prefix(&self.buffer[self.offset..]).ok().and_then(
859 |(DirectoryEntry { ino, name_len, entry_type }, remainder)| {
860 let name_len = name_len as usize;
861 let name_start = self.offset + std::mem::size_of::<DirectoryEntry>();
862 (remainder.len() >= name_len).then_some((
863 ino,
864 name_start..name_start + name_len,
865 entry_type,
866 ))
867 },
868 )
869 else {
870 return Ok(Entry::None);
872 };
873
874 self.offset = name.end;
875
876 Ok(Entry::Some {
877 ino,
878 entry_type: fio::DirentType::from_primitive(entry_type).ok_or(zx::Status::IO)?,
879 name,
880 })
881 };
882
883 let mut next = self.pending_entry.take();
884 if let Entry::None = next {
885 next = next_dirent()?;
886 }
887 match &next {
891 Entry::Some { name, .. } if self.name(name.clone()) == b"." => {
892 self.pending_entry = Entry::DotDot;
893 }
894 _ => {}
895 }
896 self.current_index += 1;
897 Ok(next)
898 }
899
900 fn rewind(&mut self, proxy: &fio::DirectorySynchronousProxy) -> Result<(), zx::Status> {
901 self.pending_entry = Entry::None;
902 let status = proxy.rewind(zx::MonotonicInstant::INFINITE).map_err(|_| zx::Status::IO)?;
903 zx::Status::ok(status)?;
904 self.buffer.clear();
905 self.offset = 0;
906 self.current_index = 0;
907 Ok(())
908 }
909}
910
911#[derive(Default)]
912enum Entry {
913 #[default]
915 None,
916
917 Some {
918 ino: u64,
919 entry_type: fio::DirentType,
920 name: Range<usize>,
921 },
922
923 DotDot,
925}
926
927impl Entry {
928 fn take(&mut self) -> Entry {
929 std::mem::replace(self, Entry::None)
930 }
931}
932
933impl RemoteDirectory {
934 pub fn new(proxy: fio::DirectorySynchronousProxy) -> Self {
935 Self { proxy, state: Mutex::default() }
936 }
937}
938
939impl SyncIoDirectory for RemoteDirectory {
940 fn seek(&self, new_index: u64) -> Result<u64, zx::Status> {
942 let mut state = self.state.lock();
943
944 if new_index < state.current_index {
945 state.rewind(&self.proxy)?;
948 state.current_index = 0;
949 }
950
951 for i in state.current_index..new_index {
953 match state.next(&self.proxy) {
954 Ok(Entry::Some { .. } | Entry::DotDot) => {}
955 Ok(Entry::None) => break, Err(_) => {
957 return Ok(i);
963 }
964 }
965 }
966
967 Ok(new_index)
968 }
969
970 fn readdir<B, S: FnMut(u64, fio::DirentType, &[u8]) -> ControlFlow<B, ()>>(
974 &self,
975 mut sink: S,
976 ) -> Result<Option<B>, zx::Status> {
977 let mut state = self.state.lock();
978 loop {
979 let entry = state.next(&self.proxy)?;
980 if let ControlFlow::Break(b) = match &entry {
981 Entry::Some { ino, entry_type, name } => {
982 sink(*ino, *entry_type, state.name(name.clone()))
983 }
984 Entry::DotDot => sink(0, fio::DirentType::Directory, b".."),
985 Entry::None => break,
986 } {
987 state.pending_entry = entry;
988 return Ok(Some(b));
989 }
990 }
991 Ok(None)
992 }
993
994 fn sync(&self) -> Result<(), zx::Status> {
996 self.proxy
997 .sync(zx::MonotonicInstant::INFINITE)
998 .map_err(|_| zx::Status::IO)?
999 .map_err(zx::Status::err_from_raw)
1000 }
1001
1002 fn clone_proxy(&self) -> Result<Option<fio::DirectorySynchronousProxy>, zx::Status> {
1004 let (client_end, server_end) = zx::Channel::create();
1005 self.proxy.clone(server_end.into()).map_err(|_| zx::Status::IO)?;
1006 Ok(Some(client_end.into()))
1007 }
1008}
1009
1010#[cfg(test)]
1011mod tests {
1012 use super::*;
1013 use fidl::endpoints::RequestStream;
1014 use fuchsia_async as fasync;
1015 use futures::StreamExt;
1016 use std::sync::Arc;
1017 use std::sync::atomic::{AtomicU64, Ordering};
1018
1019 #[fuchsia::test]
1020 async fn test_read_chunking() {
1021 let (client, mut stream) = fidl::endpoints::create_request_stream::<fio::FileMarker>();
1022 let content = vec![0xAB; (fio::MAX_TRANSFER_SIZE + 100) as usize];
1023 let content_clone = content.clone();
1024
1025 let _server_task = fasync::Task::spawn(async move {
1026 while let Some(Ok(request)) = stream.next().await {
1027 match request {
1028 fio::FileRequest::ReadAt { count, offset, responder } => {
1029 let start = offset as usize;
1030 let end = std::cmp::min(start + count as usize, content_clone.len());
1031 let data = if start < content_clone.len() {
1032 &content_clone[start..end]
1033 } else {
1034 &[]
1035 };
1036 responder.send(Ok(data)).unwrap();
1037 }
1038 _ => panic!("Unexpected request: {:?}", request),
1039 }
1040 }
1041 });
1042
1043 let io = RemoteIo::new(client.into_channel().into());
1044 fasync::unblock(move || {
1045 let mut data = Vec::new();
1046 let actual = io
1047 .read(
1048 0,
1049 content.len(),
1050 |chunk| -> Result<usize, zx::Status> {
1051 let len = chunk.len();
1052 data.extend(chunk);
1053 Ok(len)
1054 },
1055 |status| status,
1056 )
1057 .unwrap();
1058 assert_eq!(actual, content.len());
1059 assert_eq!(data, content);
1060 })
1061 .await;
1062 }
1063
1064 #[fuchsia::test]
1065 async fn test_read_error_after_data() {
1066 let (client, mut stream) = fidl::endpoints::create_request_stream::<fio::FileMarker>();
1067 let chunk_size = 100;
1068 let content = vec![0xAA; chunk_size];
1069 let content_clone = content.clone();
1070
1071 let _server_task = fasync::Task::spawn(async move {
1072 let mut request_count = 0;
1073 while let Some(Ok(request)) = stream.next().await {
1074 match request {
1075 fio::FileRequest::ReadAt { count: _, offset: _, responder } => {
1076 request_count += 1;
1077 if request_count == 1 {
1078 responder.send(Ok(&content_clone)).unwrap();
1079 } else {
1080 responder.send(Err(zx::sys::ZX_ERR_IO)).unwrap();
1081 }
1082 }
1083 _ => panic!("Unexpected request: {:?}", request),
1084 }
1085 }
1086 });
1087
1088 let io = RemoteIo::new(client.into_channel().into());
1089 fasync::unblock(move || {
1090 let mut data = Vec::new();
1091 let actual = io
1093 .read(
1094 0,
1095 chunk_size * 2,
1096 |chunk| -> Result<usize, zx::Status> {
1097 let len = chunk.len();
1098 data.extend(chunk);
1099 Ok(len)
1100 },
1101 |status| status,
1102 )
1103 .expect("read should succeed even if later chunks fail");
1104 assert_eq!(actual, chunk_size);
1105 assert_eq!(data.len(), chunk_size);
1106 assert_eq!(data, content);
1107 })
1108 .await;
1109 }
1110
1111 #[fuchsia::test]
1112 async fn test_write_chunking() {
1113 let (client, mut stream) = fidl::endpoints::create_request_stream::<fio::FileMarker>();
1114 let content = vec![0xCD; (fio::MAX_TRANSFER_SIZE + 100) as usize];
1115 let content2 = content.clone();
1116
1117 let server_task = fasync::Task::spawn(async move {
1118 let mut written = vec![0; content2.len()];
1119 while let Some(Ok(request)) = stream.next().await {
1120 match request {
1121 fio::FileRequest::WriteAt { offset, data, responder, .. } => {
1122 let offset = offset as usize;
1123 written[offset..offset + data.len()].copy_from_slice(&data);
1124 responder.send(Ok(data.len() as u64)).unwrap();
1125 }
1126 _ => panic!("Unexpected request: {:?}", request),
1127 }
1128 }
1129 assert_eq!(written, content2);
1130 });
1131
1132 let io = RemoteIo::new(client.into_channel().into());
1133 fasync::unblock(move || {
1134 let written = io.write(0, &content).expect("write failed");
1135 assert_eq!(written, content.len());
1136 })
1137 .await;
1138
1139 server_task.await;
1140 }
1141
1142 #[fuchsia::test]
1143 async fn test_write_error_after_data() {
1144 let (client, mut stream) = fidl::endpoints::create_request_stream::<fio::FileMarker>();
1145 let chunk_size = fio::MAX_TRANSFER_SIZE as usize;
1146 let content = vec![0xEE; chunk_size * 2];
1147
1148 let _server_task = fasync::Task::spawn(async move {
1149 let mut request_count = 0;
1150 while let Some(Ok(request)) = stream.next().await {
1151 match request {
1152 fio::FileRequest::WriteAt { offset: _, data, responder, .. } => {
1153 request_count += 1;
1154 if request_count == 1 {
1155 responder.send(Ok(data.len() as u64)).unwrap();
1156 } else {
1157 responder.send(Err(zx::sys::ZX_ERR_IO)).unwrap();
1158 }
1159 }
1160 _ => panic!("Unexpected request: {:?}", request),
1161 }
1162 }
1163 });
1164
1165 let io = RemoteIo::new(client.into_channel().into());
1166 fasync::unblock(move || {
1167 let written = io.write(0, &content).expect("write should succeed partial");
1168 assert_eq!(written, chunk_size);
1169 })
1170 .await;
1171 }
1172
1173 #[fuchsia::test]
1174 async fn test_large_directory() {
1175 let (client, mut stream) = fidl::endpoints::create_request_stream::<fio::DirectoryMarker>();
1176 let num_entries = 2000;
1177
1178 let task = fasync::Task::spawn(async move {
1179 let mut sent_count = 0;
1180 let mut num_requests = 0;
1181 while let Some(Ok(request)) = stream.next().await {
1182 match request {
1183 fio::DirectoryRequest::ReadDirents { max_bytes, responder } => {
1184 num_requests += 1;
1185 let mut buffer = Vec::new();
1186 while sent_count < num_entries {
1187 let name = if sent_count == 0 {
1188 ".".to_string()
1189 } else {
1190 format!("file_{}", sent_count - 1)
1191 };
1192 let name_bytes = name.as_bytes();
1193 let entry_size = 10 + name_bytes.len();
1194 if buffer.len() + entry_size > max_bytes as usize {
1195 break;
1196 }
1197 buffer.extend_from_slice(&(sent_count as u64 + 1).to_le_bytes());
1198 buffer.push(name_bytes.len() as u8);
1199 let entry_type = if sent_count == 0 {
1200 fio::DirentType::Directory
1201 } else {
1202 fio::DirentType::File
1203 };
1204 buffer.push(entry_type.into_primitive());
1205 buffer.extend_from_slice(name_bytes);
1206 sent_count += 1;
1207 }
1208 let _ = responder.send(0, &buffer);
1209 }
1210 fio::DirectoryRequest::Rewind { responder } => {
1211 sent_count = 0;
1212 let _ = responder.send(0);
1213 }
1214 fio::DirectoryRequest::Close { responder } => {
1215 let _ = responder.send(Ok(()));
1216 }
1217 _ => {}
1218 }
1219 }
1220 assert!(num_requests > 0);
1221 });
1222
1223 let dir = RemoteDirectory::new(client.into_channel().into());
1224 let count = Arc::new(AtomicU64::new(0));
1225 let count2 = count.clone();
1226 fasync::unblock(move || {
1227 dir.readdir::<(), _>(|_ino, _type, _name| {
1228 count.fetch_add(1, Ordering::Relaxed);
1229 ControlFlow::Continue(())
1230 })
1231 .unwrap();
1232 })
1233 .await;
1234 assert_eq!(count2.load(Ordering::Relaxed), num_entries + 1);
1236 task.await;
1237 }
1238
1239 #[fuchsia::test]
1240 async fn test_seek_backwards() {
1241 let (client, mut stream) = fidl::endpoints::create_request_stream::<fio::DirectoryMarker>();
1242 let _server_task = fasync::Task::spawn(async move {
1243 let entries = vec![
1244 (1, fio::DirentType::Directory, "."),
1245 (2, fio::DirentType::File, "file_0"),
1246 (3, fio::DirentType::File, "file_1"),
1247 (4, fio::DirentType::File, "file_2"),
1248 ];
1249 let mut current_entry = 0;
1250
1251 while let Some(Ok(request)) = stream.next().await {
1252 match request {
1253 fio::DirectoryRequest::ReadDirents { max_bytes, responder } => {
1254 let mut buffer = Vec::new();
1255 while current_entry < entries.len() {
1256 let (ino, type_, name) = entries[current_entry];
1257 let name_bytes = name.as_bytes();
1258 let entry_size = 10 + name_bytes.len();
1259 if buffer.len() + entry_size > max_bytes as usize {
1260 break;
1261 }
1262 buffer.extend_from_slice(&(ino as u64).to_le_bytes());
1263 buffer.push(name_bytes.len() as u8);
1264 buffer.push(type_.into_primitive());
1265 buffer.extend_from_slice(name_bytes);
1266 current_entry += 1;
1267 }
1268 responder.send(0, &buffer).unwrap();
1269 }
1270 fio::DirectoryRequest::Rewind { responder } => {
1271 current_entry = 0;
1272 responder.send(0).unwrap();
1273 }
1274 _ => {}
1275 }
1276 }
1277 });
1278
1279 let dir = RemoteDirectory::new(client.into_channel().into());
1280 fasync::unblock(move || {
1281 let mut names = Vec::new();
1282 dir.readdir::<(), _>(|_ino, _type, name| {
1284 names.push(name.to_vec());
1285 if names.len() == 3 { ControlFlow::Break(()) } else { ControlFlow::Continue(()) }
1286 })
1287 .unwrap();
1288
1289 assert_eq!(names[0], b".");
1290 assert_eq!(names[1], b"..");
1291 assert_eq!(names[2], b"file_0");
1292
1293 dir.seek(1).unwrap();
1296
1297 let mut names_after_seek = Vec::new();
1298 dir.readdir::<(), _>(|_ino, _type, name| {
1300 names_after_seek.push(name.to_vec());
1301 if names_after_seek.len() == 2 {
1302 ControlFlow::Break(())
1303 } else {
1304 ControlFlow::Continue(())
1305 }
1306 })
1307 .unwrap();
1308
1309 assert_eq!(names_after_seek[0], b"..");
1310 assert_eq!(names_after_seek[1], b"file_0");
1311 })
1312 .await;
1313 }
1314
1315 struct DummyFactory;
1316
1317 impl Factory<RemoteIo> for DummyFactory {
1318 type Result = RemoteIo;
1319 fn create_node(self, io: RemoteIo, _info: fio::NodeInfo) -> Self::Result {
1320 io
1321 }
1322 fn create_directory(self, io: RemoteIo, _info: fio::DirectoryInfo) -> Self::Result {
1323 io
1324 }
1325 fn create_file(self, io: RemoteIo, _info: fio::FileInfo) -> Self::Result {
1326 io
1327 }
1328 fn create_symlink(self, io: RemoteIo, _info: fio::SymlinkInfo) -> Self::Result {
1329 io
1330 }
1331 }
1332
1333 #[fuchsia::test]
1334 async fn test_open_pipelined() {
1335 let (client, stream) = fidl::endpoints::create_request_stream::<fio::DirectoryMarker>();
1336
1337 fn serve_mock_directory(
1338 mut stream: fio::DirectoryRequestStream,
1339 mut expected_paths: Vec<String>,
1340 ) {
1341 fasync::Task::spawn(async move {
1342 if let Some(Ok(request)) = stream.next().await {
1343 match request {
1344 fio::DirectoryRequest::Open { path, flags, options: _, object, .. } => {
1345 if !expected_paths.is_empty() {
1346 let expected = expected_paths.remove(0);
1347 assert_eq!(path, expected);
1348 if expected == "path1" {
1349 assert!(flags.contains(fio::Flags::PROTOCOL_DIRECTORY));
1350 } else if expected == "path2" {
1351 assert!(!flags.contains(fio::Flags::PROTOCOL_DIRECTORY));
1352 }
1353 }
1354 let server_end =
1355 fidl::endpoints::ServerEnd::<fio::DirectoryMarker>::new(object);
1356 let dir_stream = server_end.into_stream();
1357 let control_handle = dir_stream.control_handle();
1358 let representation =
1359 fio::Representation::Directory(fio::DirectoryInfo::default());
1360 control_handle.send_on_representation(representation).unwrap();
1361
1362 serve_mock_directory(dir_stream, expected_paths);
1363 }
1364 _ => {}
1365 }
1366 }
1367 })
1368 .detach();
1369 }
1370
1371 serve_mock_directory(stream, vec!["path1".to_string(), "path2".to_string()]);
1372
1373 let io = RemoteIo::new(client.into_channel().into());
1374 fasync::unblock(move || {
1375 let results = io
1376 .open_pipelined(
1377 &["path1", "path2"],
1378 fio::Flags::empty(),
1379 fio::NodeAttributesQuery::empty(),
1380 || DummyFactory,
1381 )
1382 .collect::<Vec<_>>();
1383 assert_eq!(results.len(), 2);
1384 assert!(results.iter().all(|r| r.is_ok()));
1385 })
1386 .await;
1387 }
1388
1389 #[fuchsia::test]
1390 async fn test_open_pipelined_not_found() {
1391 let (client, stream) = fidl::endpoints::create_request_stream::<fio::DirectoryMarker>();
1392
1393 fn serve_mock_directory_not_found(mut stream: fio::DirectoryRequestStream) {
1394 fasync::Task::spawn(async move {
1395 if let Some(Ok(request)) = stream.next().await {
1396 match request {
1397 fio::DirectoryRequest::Open {
1398 path, flags: _, options: _, object, ..
1399 } => {
1400 let server_end =
1401 fidl::endpoints::ServerEnd::<fio::DirectoryMarker>::new(object);
1402 let dir_stream = server_end.into_stream();
1403 let control_handle = dir_stream.control_handle();
1404
1405 if path == "not_found" {
1406 control_handle.shutdown_with_epitaph(zx::Status::NOT_FOUND);
1407 } else {
1408 let representation =
1409 fio::Representation::Directory(fio::DirectoryInfo::default());
1410 control_handle.send_on_representation(representation).unwrap();
1411 serve_mock_directory_not_found(dir_stream);
1412 }
1413 }
1414 _ => {}
1415 }
1416 }
1417 })
1418 .detach();
1419 }
1420
1421 serve_mock_directory_not_found(stream);
1422
1423 let io = RemoteIo::new(client.into_channel().into());
1424 fasync::unblock(move || {
1425 let results = io
1426 .open_pipelined(
1427 &["path1", "not_found", "path3"],
1428 fio::Flags::empty(),
1429 fio::NodeAttributesQuery::empty(),
1430 || DummyFactory,
1431 )
1432 .collect::<Vec<_>>();
1433 assert_eq!(results.len(), 2);
1434 assert!(results[0].is_ok());
1435 assert_eq!(results[1].as_ref().err(), Some(&zx::Status::NOT_FOUND));
1436 })
1437 .await;
1438 }
1439
1440 #[fuchsia::test]
1441 async fn test_open_pipelined_peer_closed() {
1442 let (client, stream) = fidl::endpoints::create_request_stream::<fio::DirectoryMarker>();
1443
1444 fn serve_mock_directory_close_early(mut stream: fio::DirectoryRequestStream) {
1445 fasync::Task::spawn(async move {
1446 if let Some(Ok(request)) = stream.next().await {
1447 match request {
1448 fio::DirectoryRequest::Open { object, .. } => {
1449 drop(object);
1451 }
1452 _ => {}
1453 }
1454 }
1455 })
1456 .detach();
1457 }
1458
1459 serve_mock_directory_close_early(stream);
1460
1461 let io = RemoteIo::new(client.into_channel().into());
1462 fasync::unblock(move || {
1463 let results = io
1464 .open_pipelined(
1465 &["path1", "path2"],
1466 fio::Flags::empty(),
1467 fio::NodeAttributesQuery::empty(),
1468 || DummyFactory,
1469 )
1470 .collect::<Vec<_>>();
1471 assert_eq!(results.len(), 1);
1472 assert_eq!(results[0].as_ref().err(), Some(&zx::Status::PEER_CLOSED));
1473 })
1474 .await;
1475 }
1476
1477 #[fuchsia::test]
1478 async fn test_query_filesystem() {
1479 let (client, mut stream) = fidl::endpoints::create_request_stream::<fio::DirectoryMarker>();
1480 let _server_task = fasync::Task::spawn(async move {
1481 while let Some(Ok(request)) = stream.next().await {
1482 match request {
1483 fio::DirectoryRequest::QueryFilesystem { responder } => {
1484 let info = fio::FilesystemInfo {
1485 total_bytes: 1024,
1486 used_bytes: 512,
1487 total_nodes: 100,
1488 used_nodes: 10,
1489 free_shared_pool_bytes: 0,
1490 fs_id: 12345,
1491 block_size: 4096,
1492 max_filename_size: 255,
1493 fs_type: 0x66786673,
1494 padding: 0,
1495 name: [0; 32],
1496 };
1497 responder.send(0, Some(&info)).unwrap();
1498 }
1499 _ => {}
1500 }
1501 }
1502 });
1503
1504 let io = RemoteIo::new(client.into_channel().into());
1505 fasync::unblock(move || {
1506 let info = io.query_filesystem().expect("query_filesystem failed");
1507 assert_eq!(info.total_bytes, 1024);
1508 assert_eq!(info.fs_id, 12345);
1509 })
1510 .await;
1511 }
1512
1513 #[fuchsia::test]
1514 async fn test_open_directory_and_clone() {
1515 let (client, mut stream) = fidl::endpoints::create_request_stream::<fio::DirectoryMarker>();
1516 let _server_task = fasync::Task::spawn(async move {
1517 while let Some(Ok(request)) = stream.next().await {
1518 match request {
1519 fio::DirectoryRequest::Clone { request, .. } => {
1520 let _stream = request.into_stream();
1521 }
1522 _ => {}
1523 }
1524 }
1525 });
1526
1527 let io = RemoteIo::new(client.into_channel().into());
1528 fasync::unblock(move || {
1529 let dir = io.open_directory().expect("open_directory failed");
1530 let cloned = dir.clone_proxy().expect("clone_proxy failed");
1531 assert!(cloned.is_some());
1532 })
1533 .await;
1534 }
1535}