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(wrapping_key_id) = mutable.wrapping_key_id {
90 out_attr.wrapping_key_id = wrapping_key_id;
91 out_attr.has.wrapping_key_id = true;
92 }
93 out_attr
94}
95
96pub trait Factory {
98 type Result;
99
100 fn create_node(self, io: RemoteIo, info: fio::NodeInfo) -> Self::Result;
101 fn create_directory(self, io: RemoteIo, info: fio::DirectoryInfo) -> Self::Result;
102 fn create_file(self, io: RemoteIo, info: fio::FileInfo) -> Self::Result;
103 fn create_symlink(self, io: RemoteIo, info: fio::SymlinkInfo) -> Self::Result;
104}
105
106pub fn create_with_on_representation<F: Factory>(
113 proxy: fio::NodeSynchronousProxy,
114 factory: F,
115) -> Result<F::Result, zx::Status> {
116 match proxy.wait_for_event(zx::MonotonicInstant::INFINITE) {
117 Ok(fio::NodeEvent::OnRepresentation { payload }) => match payload {
118 fio::Representation::Node(info) => Ok(factory.create_node(RemoteIo::new(proxy), info)),
119 fio::Representation::Directory(info) => {
120 Ok(factory.create_directory(RemoteIo::new(proxy), info))
121 }
122 fio::Representation::File(mut info) => {
123 let io = RemoteIo {
124 proxy,
125 stream: info
126 .stream
127 .take()
128 .map(zx::Stream::from)
129 .unwrap_or_else(|| zx::NullableHandle::invalid().into()),
130 };
131 Ok(factory.create_file(io, info))
132 }
133 fio::Representation::Symlink(info) => {
134 Ok(factory.create_symlink(RemoteIo::new(proxy), info))
135 }
136 _ => Err(zx::Status::NOT_SUPPORTED),
137 },
138 Err(fidl::Error::ClientChannelClosed { epitaph, .. }) => match epitaph.into() {
139 Err(status) => Err(status),
140 Ok(()) => Err(zx::Status::PEER_CLOSED),
141 },
142 _ => Err(zx::Status::IO),
143 }
144}
145
146pub struct RemoteIo {
152 proxy: fio::NodeSynchronousProxy,
153 stream: zx::Stream,
156}
157
158impl RemoteIo {
159 pub fn new(proxy: fio::NodeSynchronousProxy) -> Self {
160 Self { proxy, stream: zx::NullableHandle::invalid().into() }
161 }
162
163 pub fn with_stream(proxy: fio::NodeSynchronousProxy, stream: zx::Stream) -> Self {
164 Self { proxy, stream }
165 }
166
167 pub fn into_proxy(self) -> fio::NodeSynchronousProxy {
168 self.proxy
169 }
170
171 fn cast_proxy<T: From<zx::Channel> + Into<zx::NullableHandle>>(&self) -> zx::Unowned<'_, T> {
172 zx::Unowned::new(self.proxy.as_channel())
173 }
174
175 pub fn attr_get(
177 &self,
178 query: fio::NodeAttributesQuery,
179 ) -> Result<(fio::MutableNodeAttributes, fio::ImmutableNodeAttributes), zx::Status> {
180 self.proxy
181 .get_attributes(query, zx::MonotonicInstant::INFINITE)
182 .map_err(|_| zx::Status::IO)?
183 .map_err(zx::Status::from_raw)
184 }
185
186 pub fn attr_get_zxio(
191 &self,
192 query: fio::NodeAttributesQuery,
193 ) -> Result<zxio_node_attributes_t, zx::Status> {
194 self.attr_get(query).map(|(m, i)| zxio_attr_from_fidl(&m, &i))
195 }
196
197 pub fn attr_set(&self, attributes: fio::MutableNodeAttributes) -> Result<(), zx::Status> {
199 self.proxy
200 .update_attributes(&attributes, zx::MonotonicInstant::INFINITE)
201 .map_err(|_| zx::Status::IO)?
202 .map_err(zx::Status::from_raw)
203 }
204
205 pub fn open<F: Factory>(
207 &self,
208 path: &str,
209 flags: fio::Flags,
210 create_attributes: Option<fio::MutableNodeAttributes>,
211 query: fio::NodeAttributesQuery,
212 factory: F,
213 ) -> Result<F::Result, zx::Status> {
214 let (client_end, server_end) = zx::Channel::create();
215 let dir_proxy = self.cast_proxy::<fio::DirectorySynchronousProxy>();
216 dir_proxy
217 .open(
218 path,
219 flags | fio::Flags::FLAG_SEND_REPRESENTATION,
220 &fio::Options {
221 attributes: (!query.is_empty()).then_some(query),
222 create_attributes,
223 ..Default::default()
224 },
225 server_end,
226 )
227 .map_err(|_| zx::Status::IO)?;
228 create_with_on_representation(client_end.into(), factory)
229 }
230
231 pub fn open_pipelined<F: Factory>(
243 &self,
244 paths: &[&str],
245 flags: fio::Flags,
246 query: fio::NodeAttributesQuery,
247 mut factory_fn: impl FnMut() -> F,
248 ) -> impl Iterator<Item = Result<F::Result, zx::Status>> {
249 let mut proxy_to_result = move |proxy: fio::NodeSynchronousProxy| {
250 create_with_on_representation(proxy, factory_fn())
251 };
252
253 let mut proxies = SmallVec::<[fio::NodeSynchronousProxy; 8]>::new();
254 let (client_end, server_end) = zx::Channel::create();
255 proxies.push(fio::NodeSynchronousProxy::new(client_end));
256 let mut next_server_end = Some(server_end);
257
258 for (i, path) in paths.iter().enumerate().rev() {
259 let server_end = next_server_end.take().unwrap();
260 let channel = if i == 0 {
261 self.proxy.as_channel()
262 } else {
263 let (client_end, server_end) = zx::Channel::create();
264 next_server_end = Some(server_end);
265 proxies.push(fio::NodeSynchronousProxy::new(client_end));
266 proxies.last().unwrap().as_channel()
267 };
268 let dir_proxy = zx::Unowned::<fio::DirectorySynchronousProxy>::new(channel);
269
270 let mut open_flags = flags | fio::Flags::FLAG_SEND_REPRESENTATION;
271 if i < paths.len() - 1 {
272 open_flags |= fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::PROTOCOL_SYMLINK;
273 }
274
275 if let Err(_) = dir_proxy.open(
276 path,
277 open_flags,
278 &fio::Options {
279 attributes: (!query.is_empty()).then_some(query),
280 ..Default::default()
281 },
282 server_end,
283 ) {
284 debug_assert!(i == 0);
287 }
290 }
291
292 let mut proxies = proxies.into_iter().rev();
293 std::iter::successors(proxies.next().map(&mut proxy_to_result), move |prev| match prev {
294 Err(_) => None,
295 Ok(_) => proxies.next().map(&mut proxy_to_result),
296 })
297 }
298
299 pub fn read_partial(&self, offset: u64, max: usize) -> Result<(Vec<u8>, bool), zx::Status> {
303 if self.stream.is_invalid() {
304 let file_proxy = self.cast_proxy::<fio::FileSynchronousProxy>();
305 let max = std::cmp::min(max as u64, fio::MAX_TRANSFER_SIZE);
306 let data = file_proxy
307 .read_at(max, offset, zx::MonotonicInstant::INFINITE)
308 .map_err(|_| zx::Status::IO)?
309 .map_err(zx::Status::from_raw)?;
310 let eof = (data.len() as u64) < max;
311 Ok((data, eof))
312 } else {
313 let bytes =
315 self.stream.read_at_to_vec(zx::StreamReadOptions::empty(), offset as u64, max)?;
316 let eof = bytes.len() < max;
317 Ok((bytes, eof))
318 }
319 }
320
321 pub fn read<E>(
326 &self,
327 offset: u64,
328 len: usize,
329 mut callback: impl FnMut(Vec<u8>) -> Result<usize, E>,
330 map_err: impl FnOnce(zx::Status) -> E,
331 ) -> Result<usize, E> {
332 let mut total = 0;
333 while total < len {
334 match self.read_partial(offset + total as u64, len - total) {
335 Ok((data, eof)) => {
336 if data.is_empty() {
337 break;
338 }
339 let data_len = data.len();
340 let written = callback(data)?;
341 total += written;
342 if eof || written < data_len {
343 break;
344 }
345 }
346 Err(e) => {
347 if total > 0 {
348 break;
349 }
350 return Err(map_err(e));
351 }
352 }
353 }
354 Ok(total)
355 }
356
357 pub fn write(&self, offset: u64, data: &[u8]) -> Result<usize, zx::Status> {
359 let file_proxy = self.cast_proxy::<fio::FileSynchronousProxy>();
360 let mut total_written = 0;
361 for chunk in data.chunks(fio::MAX_TRANSFER_SIZE as usize) {
362 let result = file_proxy
363 .write_at(chunk, offset + total_written as u64, zx::MonotonicInstant::INFINITE)
364 .map_err(|_| zx::Status::IO)
365 .and_then(|res| res.map_err(zx::Status::from_raw));
366 match result {
367 Ok(actual) => {
368 let actual = actual as usize;
369 total_written += actual;
370 if actual < chunk.len() {
371 return Ok(total_written);
372 }
373 }
374 Err(e) => {
375 if total_written > 0 {
376 break;
377 }
378 return Err(e);
379 }
380 }
381 }
382 Ok(total_written)
383 }
384
385 pub fn supports_vectored(&self) -> bool {
387 !self.stream.is_invalid()
389 }
390
391 pub unsafe fn readv(
398 &self,
399 offset: u64,
400 iovecs: &mut [zx::sys::zx_iovec_t],
401 ) -> Result<usize, zx::Status> {
402 if self.stream.is_invalid() {
403 return Err(zx::Status::NOT_SUPPORTED);
404 }
405 unsafe { self.stream.readv_at(zx::StreamReadOptions::empty(), offset as u64, iovecs) }
407 }
408
409 pub fn writev(&self, offset: u64, iovecs: &[zx::sys::zx_iovec_t]) -> Result<usize, zx::Status> {
412 if self.stream.is_invalid() {
413 return Err(zx::Status::NOT_SUPPORTED);
414 }
415 self.stream.writev_at(zx::StreamWriteOptions::empty(), offset, &iovecs)
416 }
417
418 pub fn truncate(&self, length: u64) -> Result<(), zx::Status> {
420 self.cast_proxy::<fio::FileSynchronousProxy>()
421 .resize(length, zx::MonotonicInstant::INFINITE)
422 .map_err(|_| zx::Status::IO)?
423 .map_err(zx::Status::from_raw)
424 }
425
426 pub fn vmo_get(&self, flags: zx::VmarFlags) -> Result<zx::Vmo, zx::Status> {
428 let mut fio_flags = fio::VmoFlags::empty();
429 if flags.contains(zx::VmarFlags::PERM_READ) {
430 fio_flags |= fio::VmoFlags::READ;
431 }
432 if flags.contains(zx::VmarFlags::PERM_WRITE) {
433 fio_flags |= fio::VmoFlags::WRITE;
434 }
435 if flags.contains(zx::VmarFlags::PERM_EXECUTE) {
436 fio_flags |= fio::VmoFlags::EXECUTE;
437 }
438 let file_proxy = self.cast_proxy::<fio::FileSynchronousProxy>();
439 let vmo = file_proxy
440 .get_backing_memory(fio_flags, zx::MonotonicInstant::INFINITE)
441 .map_err(|_| zx::Status::IO)?
442 .map_err(zx::Status::from_raw)?;
443 Ok(vmo)
444 }
445
446 pub fn sync(&self) -> Result<(), zx::Status> {
448 self.proxy
449 .sync(zx::MonotonicInstant::INFINITE)
450 .map_err(|_| zx::Status::IO)?
451 .map_err(zx::Status::from_raw)
452 }
453
454 pub fn close_and_update_access_time(self) {
456 let _ = self.proxy.get_attributes(
457 fio::NodeAttributesQuery::PENDING_ACCESS_TIME_UPDATE,
458 zx::MonotonicInstant::INFINITE_PAST,
459 );
460 }
461
462 pub fn clone_proxy(&self) -> Result<fio::NodeSynchronousProxy, zx::Status> {
464 let (client_end, server_end) = zx::Channel::create();
465 self.proxy.clone(server_end.into()).map_err(|_| zx::Status::IO)?;
466 Ok(client_end.into())
467 }
468
469 pub fn link_into(&self, target_dir: &Self, name: &str) -> Result<(), zx::Status> {
471 let target_dir_proxy = target_dir.cast_proxy::<fio::DirectorySynchronousProxy>();
472 let (status, token) = target_dir_proxy
473 .get_token(zx::MonotonicInstant::INFINITE)
474 .map_err(|_| zx::Status::IO)?;
475 zx::Status::ok(status)?;
476 let token = token.ok_or(zx::Status::NOT_SUPPORTED)?;
477
478 let linkable_proxy = self.cast_proxy::<fio::LinkableSynchronousProxy>();
480
481 linkable_proxy
482 .link_into(token.into(), name, zx::MonotonicInstant::INFINITE)
483 .map_err(|_| zx::Status::IO)?
484 .map_err(zx::Status::from_raw)
485 }
486
487 pub fn unlink(&self, name: &str, flags: fio::UnlinkFlags) -> Result<(), zx::Status> {
489 let options = fio::UnlinkOptions { flags: Some(flags), ..Default::default() };
490 let dir_proxy = self.cast_proxy::<fio::DirectorySynchronousProxy>();
491 dir_proxy
492 .unlink(name, &options, zx::MonotonicInstant::INFINITE)
493 .map_err(|_| zx::Status::IO)?
494 .map_err(zx::Status::from_raw)
495 }
496
497 pub fn rename(
499 &self,
500 old_path: &str,
501 new_directory: &Self,
502 new_path: &str,
503 ) -> Result<(), zx::Status> {
504 let new_dir_proxy = new_directory.cast_proxy::<fio::DirectorySynchronousProxy>();
505 let (status, token) =
506 new_dir_proxy.get_token(zx::MonotonicInstant::INFINITE).map_err(|_| zx::Status::IO)?;
507 zx::Status::ok(status)?;
508 let token = token.ok_or(zx::Status::NOT_SUPPORTED)?;
509 let dir_proxy = self.cast_proxy::<fio::DirectorySynchronousProxy>();
510 dir_proxy
511 .rename(old_path, token.into(), new_path, zx::MonotonicInstant::INFINITE)
512 .map_err(|_| zx::Status::IO)?
513 .map_err(zx::Status::from_raw)
514 }
515
516 pub fn create_symlink(&self, name: &str, target: &[u8]) -> Result<RemoteIo, zx::Status> {
518 let dir_proxy = self.cast_proxy::<fio::DirectorySynchronousProxy>();
519 let (client_end, server_end) = zx::Channel::create();
520 dir_proxy
521 .create_symlink(name, target, Some(server_end.into()), zx::MonotonicInstant::INFINITE)
522 .map_err(|_| zx::Status::IO)?
523 .map_err(zx::Status::from_raw)?;
524 Ok(RemoteIo::new(client_end.into()))
525 }
526
527 pub fn enable_verity(&self, descriptor: &zxio_fsverity_descriptor_t) -> Result<(), zx::Status> {
529 let file_proxy = self.cast_proxy::<fio::FileSynchronousProxy>();
530 let options = fio::VerificationOptions {
531 hash_algorithm: Some(match descriptor.hash_algorithm {
532 1 => fio::HashAlgorithm::Sha256,
533 2 => fio::HashAlgorithm::Sha512,
534 _ => return Err(zx::Status::INVALID_ARGS),
535 }),
536 salt: Some(descriptor.salt[..descriptor.salt_size as usize].to_vec()),
537 ..Default::default()
538 };
539 file_proxy
540 .enable_verity(&options, zx::MonotonicInstant::INFINITE)
541 .map_err(|_| zx::Status::IO)?
542 .map_err(zx::Status::from_raw)
543 }
544
545 pub fn allocate(&self, offset: u64, len: u64, mode: AllocateMode) -> Result<(), zx::Status> {
547 let file_proxy = self.cast_proxy::<fio::FileSynchronousProxy>();
548 let mut fio_mode = fio::AllocateMode::empty();
549 if mode.contains(AllocateMode::KEEP_SIZE) {
550 fio_mode |= fio::AllocateMode::KEEP_SIZE;
551 }
552 if mode.contains(AllocateMode::UNSHARE_RANGE) {
553 fio_mode |= fio::AllocateMode::UNSHARE_RANGE;
554 }
555 if mode.contains(AllocateMode::PUNCH_HOLE) {
556 fio_mode |= fio::AllocateMode::PUNCH_HOLE;
557 }
558 if mode.contains(AllocateMode::COLLAPSE_RANGE) {
559 fio_mode |= fio::AllocateMode::COLLAPSE_RANGE;
560 }
561 if mode.contains(AllocateMode::ZERO_RANGE) {
562 fio_mode |= fio::AllocateMode::ZERO_RANGE;
563 }
564 if mode.contains(AllocateMode::INSERT_RANGE) {
565 fio_mode |= fio::AllocateMode::INSERT_RANGE;
566 }
567 file_proxy
568 .allocate(offset, len, fio_mode, zx::MonotonicInstant::INFINITE)
569 .map_err(|_| zx::Status::IO)?
570 .map_err(zx::Status::from_raw)
571 }
572
573 pub fn xattr_get(&self, name: &[u8]) -> Result<Vec<u8>, zx::Status> {
575 let name_str = std::str::from_utf8(name.as_ref()).map_err(|_| zx::Status::INVALID_ARGS)?;
576 let result = self
577 .proxy
578 .get_extended_attribute(name_str.as_bytes(), zx::MonotonicInstant::INFINITE)
579 .map_err(|_| zx::Status::IO)?
580 .map_err(zx::Status::from_raw)?;
581 match result {
582 fio::ExtendedAttributeValue::Bytes(bytes) => Ok(bytes),
583 fio::ExtendedAttributeValue::Buffer(vmo) => {
584 let size = vmo.get_content_size().map_err(|_| zx::Status::IO)?;
585 let mut bytes = vec![0u8; size as usize];
586 vmo.read(&mut bytes, 0).map_err(|_| zx::Status::IO)?;
587 Ok(bytes)
588 }
589 _ => Err(zx::Status::NOT_SUPPORTED),
590 }
591 }
592
593 pub fn xattr_set(
595 &self,
596 name: &[u8],
597 value: &[u8],
598 mode: syncio::XattrSetMode,
599 ) -> Result<(), zx::Status> {
600 let val = fio::ExtendedAttributeValue::Bytes(value.to_vec());
601 let fidl_mode = match mode {
602 syncio::XattrSetMode::Set => fio::SetExtendedAttributeMode::Set,
603 syncio::XattrSetMode::Create => fio::SetExtendedAttributeMode::Create,
604 syncio::XattrSetMode::Replace => fio::SetExtendedAttributeMode::Replace,
605 };
606 self.proxy
607 .set_extended_attribute(name, val, fidl_mode, zx::MonotonicInstant::INFINITE)
608 .map_err(|_| zx::Status::IO)?
609 .map_err(zx::Status::from_raw)
610 }
611
612 pub fn xattr_remove(&self, name: &[u8]) -> Result<(), zx::Status> {
614 let name_str = std::str::from_utf8(name.as_ref()).map_err(|_| zx::Status::INVALID_ARGS)?;
615 self.proxy
616 .remove_extended_attribute(name_str.as_bytes(), zx::MonotonicInstant::INFINITE)
617 .map_err(|_| zx::Status::IO)?
618 .map_err(zx::Status::from_raw)
619 }
620
621 pub fn xattr_list(&self) -> Result<Vec<Vec<u8>>, zx::Status> {
623 let (client_end, server_end) = zx::Channel::create();
624 self.proxy.list_extended_attributes(server_end.into()).map_err(|_| zx::Status::IO)?;
625 let iterator = fio::ExtendedAttributeIteratorSynchronousProxy::new(client_end);
626 let mut all_attrs = vec![];
627 loop {
628 let (attributes, last) = iterator
629 .get_next(zx::MonotonicInstant::INFINITE)
630 .map_err(|_| zx::Status::IO)?
631 .map_err(zx::Status::from_raw)?;
632 all_attrs.extend(attributes);
633 if last {
634 break;
635 }
636 }
637 Ok(all_attrs)
638 }
639}
640
641pub struct RemoteDirectory {
646 proxy: fio::DirectorySynchronousProxy,
647 state: Mutex<State>,
648}
649
650#[derive(Default)]
651struct State {
652 buffer: Vec<u8>,
654
655 offset: usize,
657
658 pending_entry: Entry,
661
662 current_index: u64,
664}
665
666impl State {
667 fn name(&self, range: Range<usize>) -> &[u8] {
668 &self.buffer[range]
669 }
670
671 fn next(&mut self, proxy: &fio::DirectorySynchronousProxy) -> Result<Entry, zx::Status> {
674 let mut next_dirent = || -> Result<Entry, zx::Status> {
675 if self.offset >= self.buffer.len() {
676 match proxy.read_dirents(fio::MAX_BUF, zx::MonotonicInstant::INFINITE) {
677 Ok((status, dirents)) => {
678 zx::Status::ok(status)?;
679 if dirents.is_empty() {
680 return Ok(Entry::None);
681 }
682 self.buffer = dirents;
683 self.offset = 0;
684 }
685 Err(_) => return Err(zx::Status::IO),
686 }
687 }
688
689 #[repr(C, packed)]
690 #[derive(FromBytes)]
691 struct DirectoryEntry {
692 ino: u64,
693 name_len: u8,
694 entry_type: u8,
695 }
696
697 let Some((ino, name, entry_type)) =
698 DirectoryEntry::read_from_prefix(&self.buffer[self.offset..]).ok().and_then(
699 |(DirectoryEntry { ino, name_len, entry_type }, remainder)| {
700 let name_len = name_len as usize;
701 let name_start = self.offset + std::mem::size_of::<DirectoryEntry>();
702 (remainder.len() >= name_len).then_some((
703 ino,
704 name_start..name_start + name_len,
705 entry_type,
706 ))
707 },
708 )
709 else {
710 return Ok(Entry::None);
712 };
713
714 self.offset = name.end;
715
716 Ok(Entry::Some {
717 ino,
718 entry_type: fio::DirentType::from_primitive(entry_type).ok_or(zx::Status::IO)?,
719 name,
720 })
721 };
722
723 let mut next = self.pending_entry.take();
724 if let Entry::None = next {
725 next = next_dirent()?;
726 }
727 match &next {
731 Entry::Some { name, .. } if self.name(name.clone()) == b"." => {
732 self.pending_entry = Entry::DotDot;
733 }
734 _ => {}
735 }
736 self.current_index += 1;
737 Ok(next)
738 }
739
740 fn rewind(&mut self, proxy: &fio::DirectorySynchronousProxy) -> Result<(), zx::Status> {
741 self.pending_entry = Entry::None;
742 let status = proxy.rewind(zx::MonotonicInstant::INFINITE).map_err(|_| zx::Status::IO)?;
743 zx::Status::ok(status)?;
744 self.buffer.clear();
745 self.offset = 0;
746 self.current_index = 0;
747 Ok(())
748 }
749}
750
751#[derive(Default)]
752enum Entry {
753 #[default]
755 None,
756
757 Some {
758 ino: u64,
759 entry_type: fio::DirentType,
760 name: Range<usize>,
761 },
762
763 DotDot,
765}
766
767impl Entry {
768 fn take(&mut self) -> Entry {
769 std::mem::replace(self, Entry::None)
770 }
771}
772
773impl RemoteDirectory {
774 pub fn new(proxy: fio::DirectorySynchronousProxy) -> Self {
775 Self { proxy, state: Mutex::default() }
776 }
777
778 pub fn seek(&self, new_index: u64) -> Result<u64, zx::Status> {
780 let mut state = self.state.lock();
781
782 if new_index < state.current_index {
783 state.rewind(&self.proxy)?;
786 state.current_index = 0;
787 }
788
789 for i in state.current_index..new_index {
791 match state.next(&self.proxy) {
792 Ok(Entry::Some { .. } | Entry::DotDot) => {}
793 Ok(Entry::None) => break, Err(_) => {
795 return Ok(i);
801 }
802 }
803 }
804
805 Ok(new_index)
806 }
807
808 pub fn readdir<B, S: FnMut(u64, fio::DirentType, &[u8]) -> ControlFlow<B, ()>>(
812 &self,
813 mut sink: S,
814 ) -> Result<Option<B>, zx::Status> {
815 let mut state = self.state.lock();
816 loop {
817 let entry = state.next(&self.proxy)?;
818 if let ControlFlow::Break(b) = match &entry {
819 Entry::Some { ino, entry_type, name } => {
820 sink(*ino, *entry_type, state.name(name.clone()))
821 }
822 Entry::DotDot => sink(0, fio::DirentType::Directory, b".."),
823 Entry::None => break,
824 } {
825 state.pending_entry = entry;
826 return Ok(Some(b));
827 }
828 }
829 Ok(None)
830 }
831
832 pub fn sync(&self) -> Result<(), zx::Status> {
834 self.proxy
835 .sync(zx::MonotonicInstant::INFINITE)
836 .map_err(|_| zx::Status::IO)?
837 .map_err(zx::Status::from_raw)
838 }
839
840 pub fn clone_proxy(&self) -> Result<fio::DirectorySynchronousProxy, zx::Status> {
842 let (client_end, server_end) = zx::Channel::create();
843 self.proxy.clone(server_end.into()).map_err(|_| zx::Status::IO)?;
844 Ok(client_end.into())
845 }
846}
847
848#[cfg(test)]
849mod tests {
850 use super::*;
851 use fidl::endpoints::RequestStream;
852 use fuchsia_async as fasync;
853 use futures::StreamExt;
854 use std::sync::Arc;
855 use std::sync::atomic::{AtomicU64, Ordering};
856
857 #[fuchsia::test]
858 async fn test_read_chunking() {
859 let (client, mut stream) = fidl::endpoints::create_request_stream::<fio::FileMarker>();
860 let content = vec![0xAB; (fio::MAX_TRANSFER_SIZE + 100) as usize];
861 let content_clone = content.clone();
862
863 let _server_task = fasync::Task::spawn(async move {
864 while let Some(Ok(request)) = stream.next().await {
865 match request {
866 fio::FileRequest::ReadAt { count, offset, responder } => {
867 let start = offset as usize;
868 let end = std::cmp::min(start + count as usize, content_clone.len());
869 let data = if start < content_clone.len() {
870 &content_clone[start..end]
871 } else {
872 &[]
873 };
874 responder.send(Ok(data)).unwrap();
875 }
876 _ => panic!("Unexpected request: {:?}", request),
877 }
878 }
879 });
880
881 let io = RemoteIo::new(client.into_channel().into());
882 fasync::unblock(move || {
883 let mut data = Vec::new();
884 let actual = io
885 .read(
886 0,
887 content.len(),
888 |chunk| -> Result<usize, zx::Status> {
889 let len = chunk.len();
890 data.extend(chunk);
891 Ok(len)
892 },
893 |status| status,
894 )
895 .unwrap();
896 assert_eq!(actual, content.len());
897 assert_eq!(data, content);
898 })
899 .await;
900 }
901
902 #[fuchsia::test]
903 async fn test_read_error_after_data() {
904 let (client, mut stream) = fidl::endpoints::create_request_stream::<fio::FileMarker>();
905 let chunk_size = 100;
906 let content = vec![0xAA; chunk_size];
907 let content_clone = content.clone();
908
909 let _server_task = fasync::Task::spawn(async move {
910 let mut request_count = 0;
911 while let Some(Ok(request)) = stream.next().await {
912 match request {
913 fio::FileRequest::ReadAt { count: _, offset: _, responder } => {
914 request_count += 1;
915 if request_count == 1 {
916 responder.send(Ok(&content_clone)).unwrap();
917 } else {
918 responder.send(Err(zx::sys::ZX_ERR_IO)).unwrap();
919 }
920 }
921 _ => panic!("Unexpected request: {:?}", request),
922 }
923 }
924 });
925
926 let io = RemoteIo::new(client.into_channel().into());
927 fasync::unblock(move || {
928 let mut data = Vec::new();
929 let actual = io
931 .read(
932 0,
933 chunk_size * 2,
934 |chunk| -> Result<usize, zx::Status> {
935 let len = chunk.len();
936 data.extend(chunk);
937 Ok(len)
938 },
939 |status| status,
940 )
941 .expect("read should succeed even if later chunks fail");
942 assert_eq!(actual, chunk_size);
943 assert_eq!(data.len(), chunk_size);
944 assert_eq!(data, content);
945 })
946 .await;
947 }
948
949 #[fuchsia::test]
950 async fn test_write_chunking() {
951 let (client, mut stream) = fidl::endpoints::create_request_stream::<fio::FileMarker>();
952 let content = vec![0xCD; (fio::MAX_TRANSFER_SIZE + 100) as usize];
953 let content2 = content.clone();
954
955 let server_task = fasync::Task::spawn(async move {
956 let mut written = vec![0; content2.len()];
957 while let Some(Ok(request)) = stream.next().await {
958 match request {
959 fio::FileRequest::WriteAt { offset, data, responder, .. } => {
960 let offset = offset as usize;
961 written[offset..offset + data.len()].copy_from_slice(&data);
962 responder.send(Ok(data.len() as u64)).unwrap();
963 }
964 _ => panic!("Unexpected request: {:?}", request),
965 }
966 }
967 assert_eq!(written, content2);
968 });
969
970 let io = RemoteIo::new(client.into_channel().into());
971 fasync::unblock(move || {
972 let written = io.write(0, &content).expect("write failed");
973 assert_eq!(written, content.len());
974 })
975 .await;
976
977 server_task.await;
978 }
979
980 #[fuchsia::test]
981 async fn test_write_error_after_data() {
982 let (client, mut stream) = fidl::endpoints::create_request_stream::<fio::FileMarker>();
983 let chunk_size = fio::MAX_TRANSFER_SIZE as usize;
984 let content = vec![0xEE; chunk_size * 2];
985
986 let _server_task = fasync::Task::spawn(async move {
987 let mut request_count = 0;
988 while let Some(Ok(request)) = stream.next().await {
989 match request {
990 fio::FileRequest::WriteAt { offset: _, data, responder, .. } => {
991 request_count += 1;
992 if request_count == 1 {
993 responder.send(Ok(data.len() as u64)).unwrap();
994 } else {
995 responder.send(Err(zx::sys::ZX_ERR_IO)).unwrap();
996 }
997 }
998 _ => panic!("Unexpected request: {:?}", request),
999 }
1000 }
1001 });
1002
1003 let io = RemoteIo::new(client.into_channel().into());
1004 fasync::unblock(move || {
1005 let written = io.write(0, &content).expect("write should succeed partial");
1006 assert_eq!(written, chunk_size);
1007 })
1008 .await;
1009 }
1010
1011 #[fuchsia::test]
1012 async fn test_large_directory() {
1013 let (client, mut stream) = fidl::endpoints::create_request_stream::<fio::DirectoryMarker>();
1014 let num_entries = 2000;
1015
1016 let task = fasync::Task::spawn(async move {
1017 let mut sent_count = 0;
1018 let mut num_requests = 0;
1019 while let Some(Ok(request)) = stream.next().await {
1020 match request {
1021 fio::DirectoryRequest::ReadDirents { max_bytes, responder } => {
1022 num_requests += 1;
1023 let mut buffer = Vec::new();
1024 while sent_count < num_entries {
1025 let name = if sent_count == 0 {
1026 ".".to_string()
1027 } else {
1028 format!("file_{}", sent_count - 1)
1029 };
1030 let name_bytes = name.as_bytes();
1031 let entry_size = 10 + name_bytes.len();
1032 if buffer.len() + entry_size > max_bytes as usize {
1033 break;
1034 }
1035 buffer.extend_from_slice(&(sent_count as u64 + 1).to_le_bytes());
1036 buffer.push(name_bytes.len() as u8);
1037 let entry_type = if sent_count == 0 {
1038 fio::DirentType::Directory
1039 } else {
1040 fio::DirentType::File
1041 };
1042 buffer.push(entry_type.into_primitive());
1043 buffer.extend_from_slice(name_bytes);
1044 sent_count += 1;
1045 }
1046 let _ = responder.send(0, &buffer);
1047 }
1048 fio::DirectoryRequest::Rewind { responder } => {
1049 sent_count = 0;
1050 let _ = responder.send(0);
1051 }
1052 fio::DirectoryRequest::Close { responder } => {
1053 let _ = responder.send(Ok(()));
1054 }
1055 _ => {}
1056 }
1057 }
1058 assert!(num_requests > 0);
1059 });
1060
1061 let dir = RemoteDirectory::new(client.into_channel().into());
1062 let count = Arc::new(AtomicU64::new(0));
1063 let count2 = count.clone();
1064 fasync::unblock(move || {
1065 dir.readdir::<(), _>(|_ino, _type, _name| {
1066 count.fetch_add(1, Ordering::Relaxed);
1067 ControlFlow::Continue(())
1068 })
1069 .unwrap();
1070 })
1071 .await;
1072 assert_eq!(count2.load(Ordering::Relaxed), num_entries + 1);
1074 task.await;
1075 }
1076
1077 #[fuchsia::test]
1078 async fn test_seek_backwards() {
1079 let (client, mut stream) = fidl::endpoints::create_request_stream::<fio::DirectoryMarker>();
1080 let _server_task = fasync::Task::spawn(async move {
1081 let entries = vec![
1082 (1, fio::DirentType::Directory, "."),
1083 (2, fio::DirentType::File, "file_0"),
1084 (3, fio::DirentType::File, "file_1"),
1085 (4, fio::DirentType::File, "file_2"),
1086 ];
1087 let mut current_entry = 0;
1088
1089 while let Some(Ok(request)) = stream.next().await {
1090 match request {
1091 fio::DirectoryRequest::ReadDirents { max_bytes, responder } => {
1092 let mut buffer = Vec::new();
1093 while current_entry < entries.len() {
1094 let (ino, type_, name) = entries[current_entry];
1095 let name_bytes = name.as_bytes();
1096 let entry_size = 10 + name_bytes.len();
1097 if buffer.len() + entry_size > max_bytes as usize {
1098 break;
1099 }
1100 buffer.extend_from_slice(&(ino as u64).to_le_bytes());
1101 buffer.push(name_bytes.len() as u8);
1102 buffer.push(type_.into_primitive());
1103 buffer.extend_from_slice(name_bytes);
1104 current_entry += 1;
1105 }
1106 responder.send(0, &buffer).unwrap();
1107 }
1108 fio::DirectoryRequest::Rewind { responder } => {
1109 current_entry = 0;
1110 responder.send(0).unwrap();
1111 }
1112 _ => {}
1113 }
1114 }
1115 });
1116
1117 let dir = RemoteDirectory::new(client.into_channel().into());
1118 fasync::unblock(move || {
1119 let mut names = Vec::new();
1120 dir.readdir::<(), _>(|_ino, _type, name| {
1122 names.push(name.to_vec());
1123 if names.len() == 3 { ControlFlow::Break(()) } else { ControlFlow::Continue(()) }
1124 })
1125 .unwrap();
1126
1127 assert_eq!(names[0], b".");
1128 assert_eq!(names[1], b"..");
1129 assert_eq!(names[2], b"file_0");
1130
1131 dir.seek(1).unwrap();
1134
1135 let mut names_after_seek = Vec::new();
1136 dir.readdir::<(), _>(|_ino, _type, name| {
1138 names_after_seek.push(name.to_vec());
1139 if names_after_seek.len() == 2 {
1140 ControlFlow::Break(())
1141 } else {
1142 ControlFlow::Continue(())
1143 }
1144 })
1145 .unwrap();
1146
1147 assert_eq!(names_after_seek[0], b"..");
1148 assert_eq!(names_after_seek[1], b"file_0");
1149 })
1150 .await;
1151 }
1152
1153 struct DummyFactory;
1154
1155 impl Factory for DummyFactory {
1156 type Result = RemoteIo;
1157 fn create_node(self, io: RemoteIo, _info: fio::NodeInfo) -> Self::Result {
1158 io
1159 }
1160 fn create_directory(self, io: RemoteIo, _info: fio::DirectoryInfo) -> Self::Result {
1161 io
1162 }
1163 fn create_file(self, io: RemoteIo, _info: fio::FileInfo) -> Self::Result {
1164 io
1165 }
1166 fn create_symlink(self, io: RemoteIo, _info: fio::SymlinkInfo) -> Self::Result {
1167 io
1168 }
1169 }
1170
1171 #[fuchsia::test]
1172 async fn test_open_pipelined() {
1173 let (client, stream) = fidl::endpoints::create_request_stream::<fio::DirectoryMarker>();
1174
1175 fn serve_mock_directory(
1176 mut stream: fio::DirectoryRequestStream,
1177 mut expected_paths: Vec<String>,
1178 ) {
1179 fasync::Task::spawn(async move {
1180 if let Some(Ok(request)) = stream.next().await {
1181 match request {
1182 fio::DirectoryRequest::Open { path, flags, options: _, object, .. } => {
1183 if !expected_paths.is_empty() {
1184 let expected = expected_paths.remove(0);
1185 assert_eq!(path, expected);
1186 if expected == "path1" {
1187 assert!(flags.contains(fio::Flags::PROTOCOL_DIRECTORY));
1188 } else if expected == "path2" {
1189 assert!(!flags.contains(fio::Flags::PROTOCOL_DIRECTORY));
1190 }
1191 }
1192 let server_end =
1193 fidl::endpoints::ServerEnd::<fio::DirectoryMarker>::new(object);
1194 let dir_stream = server_end.into_stream();
1195 let control_handle = dir_stream.control_handle();
1196 let representation =
1197 fio::Representation::Directory(fio::DirectoryInfo::default());
1198 control_handle.send_on_representation(representation).unwrap();
1199
1200 serve_mock_directory(dir_stream, expected_paths);
1201 }
1202 _ => {}
1203 }
1204 }
1205 })
1206 .detach();
1207 }
1208
1209 serve_mock_directory(stream, vec!["path1".to_string(), "path2".to_string()]);
1210
1211 let io = RemoteIo::new(client.into_channel().into());
1212 fasync::unblock(move || {
1213 let results = io
1214 .open_pipelined(
1215 &["path1", "path2"],
1216 fio::Flags::empty(),
1217 fio::NodeAttributesQuery::empty(),
1218 || DummyFactory,
1219 )
1220 .collect::<Vec<_>>();
1221 assert_eq!(results.len(), 2);
1222 assert!(results.iter().all(|r| r.is_ok()));
1223 })
1224 .await;
1225 }
1226
1227 #[fuchsia::test]
1228 async fn test_open_pipelined_not_found() {
1229 let (client, stream) = fidl::endpoints::create_request_stream::<fio::DirectoryMarker>();
1230
1231 fn serve_mock_directory_not_found(mut stream: fio::DirectoryRequestStream) {
1232 fasync::Task::spawn(async move {
1233 if let Some(Ok(request)) = stream.next().await {
1234 match request {
1235 fio::DirectoryRequest::Open {
1236 path, flags: _, options: _, object, ..
1237 } => {
1238 let server_end =
1239 fidl::endpoints::ServerEnd::<fio::DirectoryMarker>::new(object);
1240 let dir_stream = server_end.into_stream();
1241 let control_handle = dir_stream.control_handle();
1242
1243 if path == "not_found" {
1244 control_handle.shutdown_with_epitaph(zx::Status::NOT_FOUND);
1245 } else {
1246 let representation =
1247 fio::Representation::Directory(fio::DirectoryInfo::default());
1248 control_handle.send_on_representation(representation).unwrap();
1249 serve_mock_directory_not_found(dir_stream);
1250 }
1251 }
1252 _ => {}
1253 }
1254 }
1255 })
1256 .detach();
1257 }
1258
1259 serve_mock_directory_not_found(stream);
1260
1261 let io = RemoteIo::new(client.into_channel().into());
1262 fasync::unblock(move || {
1263 let results = io
1264 .open_pipelined(
1265 &["path1", "not_found", "path3"],
1266 fio::Flags::empty(),
1267 fio::NodeAttributesQuery::empty(),
1268 || DummyFactory,
1269 )
1270 .collect::<Vec<_>>();
1271 assert_eq!(results.len(), 2);
1272 assert!(results[0].is_ok());
1273 assert_eq!(results[1].as_ref().err(), Some(&zx::Status::NOT_FOUND));
1274 })
1275 .await;
1276 }
1277
1278 #[fuchsia::test]
1279 async fn test_open_pipelined_peer_closed() {
1280 let (client, stream) = fidl::endpoints::create_request_stream::<fio::DirectoryMarker>();
1281
1282 fn serve_mock_directory_close_early(mut stream: fio::DirectoryRequestStream) {
1283 fasync::Task::spawn(async move {
1284 if let Some(Ok(request)) = stream.next().await {
1285 match request {
1286 fio::DirectoryRequest::Open { object, .. } => {
1287 drop(object);
1289 }
1290 _ => {}
1291 }
1292 }
1293 })
1294 .detach();
1295 }
1296
1297 serve_mock_directory_close_early(stream);
1298
1299 let io = RemoteIo::new(client.into_channel().into());
1300 fasync::unblock(move || {
1301 let results = io
1302 .open_pipelined(
1303 &["path1", "path2"],
1304 fio::Flags::empty(),
1305 fio::NodeAttributesQuery::empty(),
1306 || DummyFactory,
1307 )
1308 .collect::<Vec<_>>();
1309 assert_eq!(results.len(), 1);
1310 assert_eq!(results[0].as_ref().err(), Some(&zx::Status::PEER_CLOSED));
1311 })
1312 .await;
1313 }
1314}