1use crate::dir::Dir;
2use crate::error::{ClientResultExt, ZeroFsError};
3use crate::file::File;
4use crate::path::{components, display, display_path, split_parent};
5use crate::session::{FidGuard, Session};
6use crate::types::{
7 Capabilities, ConnectOptions, DirEntry, FileType, Metadata, NodeKind, OpenOptions, SetAttrs,
8 SetTime, StatFs,
9};
10use bytes::{Bytes, BytesMut};
11use ninep_client::{NOFID, NinePClient};
12use ninep_proto::Stat;
13use std::collections::VecDeque;
14use std::ffi::OsString;
15use std::os::unix::ffi::{OsStrExt, OsStringExt};
16use std::path::{Path, PathBuf};
17use std::sync::Arc;
18use std::sync::atomic::Ordering;
19use std::time::Duration;
20
21const DEFAULT_9P_PORT: u16 = 5564;
22
23const MAX_SYMLINK_HOPS: u32 = 40;
25
26pub struct Client {
35 session: Arc<Session>,
36}
37
38impl std::fmt::Debug for Client {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("Client")
41 .field("closed", &self.session.closed.load(Ordering::Relaxed))
42 .finish_non_exhaustive()
43 }
44}
45
46impl Client {
47 pub async fn connect(target: &str) -> Result<Arc<Client>, ZeroFsError> {
51 Self::connect_with(target, ConnectOptions::default()).await
52 }
53
54 pub async fn connect_with(
56 target: &str,
57 opts: ConnectOptions,
58 ) -> Result<Arc<Client>, ZeroFsError> {
59 let fut = Self::establish(target, &opts);
60 match opts.connect_timeout_ms {
61 Some(ms) => match tokio::time::timeout(Duration::from_millis(ms as u64), fut).await {
62 Ok(result) => result,
63 Err(_) => Err(ZeroFsError::ConnectFailed {
64 message: format!("connecting to {target}: timed out after {ms} ms"),
65 }),
66 },
67 None => fut.await,
68 }
69 }
70
71 async fn establish(target: &str, opts: &ConnectOptions) -> Result<Arc<Client>, ZeroFsError> {
72 let client = dial(target, opts.msize).await?;
73 let uid = opts.uid.unwrap_or_else(|| unsafe { libc::geteuid() });
74 let gid = opts.gid.unwrap_or_else(|| unsafe { libc::getegid() });
75 let uname = match &opts.uname {
76 Some(u) => u.clone(),
77 None => std::env::var("USER").unwrap_or_else(|_| uid.to_string()),
78 };
79 let root_fid = client.alloc_fid();
80 client
81 .attach(root_fid, NOFID, &uname, &opts.aname, uid)
82 .await
83 .map_err(|e| ZeroFsError::ConnectFailed {
84 message: format!("attach to {target} failed: {e}"),
85 })?;
86 let session = Session::new(client, root_fid, gid);
87 Ok(Arc::new(Client { session }))
88 }
89
90 pub fn capabilities(&self) -> Capabilities {
93 let c = &self.session.client;
94 Capabilities {
95 extensions_v1: c.extensions_enabled(),
96 extensions_v2: c.extensions_v2_enabled(),
97 msize: c.msize(),
98 max_read_chunk: c.max_io(),
99 max_write_chunk: c.max_write_payload(),
100 }
101 }
102
103 #[doc(hidden)]
107 pub fn outstanding_fids(&self) -> usize {
108 self.session.client.outstanding_fids()
109 }
110
111 pub async fn close(&self) {
117 if self.session.closed.swap(true, Ordering::AcqRel) {
118 return;
119 }
120 self.session.enqueue_clunk(self.session.root_fid);
121 }
122
123 pub async fn read(&self, path: impl AsRef<Path>) -> Result<Bytes, ZeroFsError> {
126 let path = path.as_ref();
127 let pd = display(path);
128 let (guard, stat) = self.open_read(path, &pd).await?;
129 let max = self.session.client.max_io().max(1);
130 let first = self
132 .session
133 .client
134 .read_bytes(guard.fid(), 0, max)
135 .await
136 .ctx(&pd)?;
137 if (first.len() as u32) < max {
138 return Ok(first);
139 }
140 let cap = stat
143 .as_ref()
144 .map_or(0, |s| s.size as usize)
145 .min((max as usize).saturating_mul(2));
146 let mut out = BytesMut::with_capacity(cap);
147 out.extend_from_slice(&first);
148 loop {
149 let data = self
150 .session
151 .client
152 .read_bytes(guard.fid(), out.len() as u64, max)
153 .await
154 .ctx(&pd)?;
155 let got = data.len();
156 out.extend_from_slice(&data);
157 if (got as u32) < max {
158 return Ok(out.freeze());
159 }
160 }
161 }
162
163 pub async fn read_range(
165 &self,
166 path: impl AsRef<Path>,
167 offset: u64,
168 len: u32,
169 ) -> Result<Bytes, ZeroFsError> {
170 let path = path.as_ref();
171 let pd = display(path);
172 let (guard, _) = self.open_read(path, &pd).await?;
173 self.session
174 .client
175 .read_bytes(guard.fid(), offset, len)
176 .await
177 .ctx(&pd)
178 }
179
180 pub async fn write(&self, path: impl AsRef<Path>, data: &[u8]) -> Result<(), ZeroFsError> {
183 let path = path.as_ref();
184 let pd = display(path);
185 let opts = OpenOptions::write_only().create(true).truncate(true);
186 let guard = self.open_relative_path(path, &pd, &opts).await?;
187 self.session.write_all(guard.fid(), 0, data, &pd).await
188 }
189
190 pub async fn append(&self, path: impl AsRef<Path>, data: &[u8]) -> Result<u64, ZeroFsError> {
194 let path = path.as_ref();
195 let pd = display(path);
196 let opts = OpenOptions::write_only().create(true);
197 let guard = self.open_relative_path(path, &pd, &opts).await?;
198 let stat = self.session.stat_fid(guard.fid(), &pd).await?;
199 self.session
200 .write_all(guard.fid(), stat.size, data, &pd)
201 .await?;
202 Ok(stat.size)
203 }
204
205 async fn parent_of<'a>(
208 &self,
209 path: &'a Path,
210 pd: &str,
211 ) -> Result<(FidGuard, &'a [u8]), ZeroFsError> {
212 self.session.check_open()?;
213 let names = components(path)?;
214 let (parents, name) = split_parent(&names, pd)?;
215 let (guard, _) = self.session.walk(parents, pd).await?;
216 Ok((guard, name))
217 }
218
219 async fn open_read(
222 &self,
223 path: &Path,
224 pd: &str,
225 ) -> Result<(FidGuard, Option<Stat>), ZeroFsError> {
226 self.session.check_open()?;
227 let names = components(path)?;
228 let (guard, stat) = self.session.walk(&names, pd).await?;
229 self.session
230 .lopen(guard.fid(), libc::O_RDONLY as u32, pd)
231 .await?;
232 Ok((guard, stat))
233 }
234
235 async fn open_relative_path(
237 &self,
238 path: &Path,
239 pd: &str,
240 opts: &OpenOptions,
241 ) -> Result<FidGuard, ZeroFsError> {
242 self.open_relative_path_op_id(path, pd, opts, [0u8; 16])
243 .await
244 }
245
246 async fn open_relative_path_op_id(
249 &self,
250 path: &Path,
251 pd: &str,
252 opts: &OpenOptions,
253 op_id: [u8; 16],
254 ) -> Result<FidGuard, ZeroFsError> {
255 let (dir_guard, name) = self.parent_of(path, pd).await?;
256 self.session
257 .open_relative_op_id(dir_guard.fid(), name, opts, pd, op_id)
258 .await
259 }
260
261 pub async fn stat(&self, path: impl AsRef<Path>) -> Result<Metadata, ZeroFsError> {
266 let path = path.as_ref();
267 let pd = display(path);
268 self.session.check_open()?;
269 let names = components(path)?;
270 let (_guard, stat) = self
271 .session
272 .walk_stat_from(self.session.root_fid, &names, &pd)
273 .await?;
274 Ok(Metadata::from_stat(&stat))
275 }
276
277 pub async fn metadata(&self, path: impl AsRef<Path>) -> Result<Metadata, ZeroFsError> {
280 let path = path.as_ref();
281 let pd = display(path);
282 self.session.check_open()?;
283 let (_, stat) = self.resolve(path, &pd).await?;
284 Ok(Metadata::from_stat(&stat))
285 }
286
287 pub async fn canonicalize(&self, path: impl AsRef<Path>) -> Result<PathBuf, ZeroFsError> {
291 let path = path.as_ref();
292 let pd = display(path);
293 self.session.check_open()?;
294 let (stack, _) = self.resolve(path, &pd).await?;
295 let mut buf = Vec::new();
296 for comp in &stack {
297 buf.push(b'/');
298 buf.extend_from_slice(comp);
299 }
300 if buf.is_empty() {
301 buf.push(b'/');
302 }
303 Ok(PathBuf::from(OsString::from_vec(buf)))
304 }
305
306 pub async fn exists(&self, path: impl AsRef<Path>) -> Result<bool, ZeroFsError> {
309 match self.stat(path).await {
310 Ok(_) => Ok(true),
311 Err(ZeroFsError::NotFound { .. }) => Ok(false),
312 Err(e) => Err(e),
313 }
314 }
315
316 pub async fn set_attr(
318 &self,
319 path: impl AsRef<Path>,
320 attrs: SetAttrs,
321 ) -> Result<Metadata, ZeroFsError> {
322 let path = path.as_ref();
323 let pd = display(path);
324 self.session.check_open()?;
325 let names = components(path)?;
326 let (guard, _) = self.session.walk(&names, &pd).await?;
327 let stat = self.session.setattr_fid(guard.fid(), &attrs, &pd).await?;
328 Ok(Metadata::from_stat(&stat))
329 }
330
331 pub async fn chmod(&self, path: impl AsRef<Path>, mode: u32) -> Result<Metadata, ZeroFsError> {
333 self.set_attr(
334 path,
335 SetAttrs {
336 mode: Some(mode),
337 ..Default::default()
338 },
339 )
340 .await
341 }
342
343 pub async fn chown(
345 &self,
346 path: impl AsRef<Path>,
347 uid: Option<u32>,
348 gid: Option<u32>,
349 ) -> Result<Metadata, ZeroFsError> {
350 self.set_attr(
351 path,
352 SetAttrs {
353 uid,
354 gid,
355 ..Default::default()
356 },
357 )
358 .await
359 }
360
361 pub async fn truncate(
363 &self,
364 path: impl AsRef<Path>,
365 size: u64,
366 ) -> Result<Metadata, ZeroFsError> {
367 self.set_attr(
368 path,
369 SetAttrs {
370 size: Some(size),
371 ..Default::default()
372 },
373 )
374 .await
375 }
376
377 pub async fn set_times(
379 &self,
380 path: impl AsRef<Path>,
381 atime: Option<SetTime>,
382 mtime: Option<SetTime>,
383 ) -> Result<Metadata, ZeroFsError> {
384 self.set_attr(
385 path,
386 SetAttrs {
387 atime,
388 mtime,
389 ..Default::default()
390 },
391 )
392 .await
393 }
394
395 pub async fn statfs(&self) -> Result<StatFs, ZeroFsError> {
397 self.session.check_open()?;
398 let r = self
399 .session
400 .client
401 .statfs(self.session.root_fid)
402 .await
403 .ctx("/")?;
404 Ok(StatFs::from_wire(&r))
405 }
406
407 pub async fn sync(&self) -> Result<(), ZeroFsError> {
411 self.session.check_open()?;
412 self.session
413 .client
414 .fsync(self.session.root_fid, 0)
415 .await
416 .ctx("/")
417 }
418
419 pub async fn create_dir(
421 &self,
422 path: impl AsRef<Path>,
423 mode: u32,
424 ) -> Result<Metadata, ZeroFsError> {
425 self.create_dir_op_id(path, mode, [0u8; 16]).await
426 }
427
428 pub async fn create_dir_op_id(
432 &self,
433 path: impl AsRef<Path>,
434 mode: u32,
435 op_id: [u8; 16],
436 ) -> Result<Metadata, ZeroFsError> {
437 let path = path.as_ref();
438 let pd = display(path);
439 let (dir_guard, name) = self.parent_of(path, &pd).await?;
440 self.session
441 .mkdir_at_op_id(dir_guard.fid(), name, mode, &pd, op_id)
442 .await
443 }
444
445 pub async fn create_dir_all(
447 &self,
448 path: impl AsRef<Path>,
449 mode: u32,
450 ) -> Result<(), ZeroFsError> {
451 let path = path.as_ref();
452 self.session.check_open()?;
453 let names = components(path)?;
454 for depth in 1..=names.len() {
455 let prefix = &names[..depth];
456 let pd = display_path(prefix);
457 let (parents, name) = split_parent(prefix, &pd)?;
458 let (dir_guard, _) = self.session.walk(parents, &pd).await?;
459 match self
460 .session
461 .mkdir_at(dir_guard.fid(), name, mode, &pd)
462 .await
463 {
464 Ok(_) | Err(ZeroFsError::AlreadyExists { .. }) => {}
465 Err(e) => return Err(e),
466 }
467 }
468 if !names.is_empty() {
472 let meta = self.metadata(path).await?;
473 if !meta.is_dir() {
474 return Err(ZeroFsError::NotADirectory {
475 path: display(path),
476 });
477 }
478 }
479 Ok(())
480 }
481
482 pub async fn remove_file(&self, path: impl AsRef<Path>) -> Result<(), ZeroFsError> {
484 self.remove_file_op_id(path, [0u8; 16]).await
485 }
486
487 pub async fn remove_file_op_id(
490 &self,
491 path: impl AsRef<Path>,
492 op_id: [u8; 16],
493 ) -> Result<(), ZeroFsError> {
494 let path = path.as_ref();
495 let pd = display(path);
496 let (dir_guard, name) = self.parent_of(path, &pd).await?;
497 self.session
498 .client
499 .unlinkat_op_id(dir_guard.fid(), name, 0, op_id)
500 .await
501 .ctx(&pd)
502 }
503
504 pub async fn remove_dir(&self, path: impl AsRef<Path>) -> Result<(), ZeroFsError> {
506 self.remove_dir_op_id(path, [0u8; 16]).await
507 }
508
509 pub async fn remove_dir_op_id(
512 &self,
513 path: impl AsRef<Path>,
514 op_id: [u8; 16],
515 ) -> Result<(), ZeroFsError> {
516 let path = path.as_ref();
517 let pd = display(path);
518 let (dir_guard, name) = self.parent_of(path, &pd).await?;
519 self.session
520 .client
521 .unlinkat_op_id(dir_guard.fid(), name, libc::AT_REMOVEDIR as u32, op_id)
522 .await
523 .ctx(&pd)
524 }
525
526 pub async fn remove_dir_all(&self, path: impl AsRef<Path>) -> Result<(), ZeroFsError> {
529 let path = path.as_ref();
530 self.session.check_open()?;
531 if components(path)?.is_empty() {
532 return Err(ZeroFsError::InvalidArgument {
533 message: "refusing to remove the attach root".to_string(),
534 });
535 }
536 let dir = self.open_dir(path).await?;
537 let result = remove_dir_contents(&dir).await;
538 dir.close().await;
539 result?;
540 self.remove_dir(path).await
541 }
542
543 pub async fn rename(
546 &self,
547 from: impl AsRef<Path>,
548 to: impl AsRef<Path>,
549 ) -> Result<(), ZeroFsError> {
550 self.rename_op_id(from, to, [0u8; 16]).await
551 }
552
553 pub async fn rename_op_id(
556 &self,
557 from: impl AsRef<Path>,
558 to: impl AsRef<Path>,
559 op_id: [u8; 16],
560 ) -> Result<(), ZeroFsError> {
561 let (from, to) = (from.as_ref(), to.as_ref());
562 let (fd, td) = (display(from), display(to));
563 let (from_guard, from_name) = self.parent_of(from, &fd).await?;
564 let (to_guard, to_name) = self.parent_of(to, &td).await?;
565 self.session
566 .client
567 .renameat_op_id(from_guard.fid(), from_name, to_guard.fid(), to_name, op_id)
568 .await
569 .ctx(&fd)
570 }
571
572 pub async fn hard_link(
574 &self,
575 original: impl AsRef<Path>,
576 link: impl AsRef<Path>,
577 ) -> Result<Metadata, ZeroFsError> {
578 self.hard_link_op_id(original, link, [0u8; 16]).await
579 }
580
581 pub async fn hard_link_op_id(
584 &self,
585 original: impl AsRef<Path>,
586 link: impl AsRef<Path>,
587 op_id: [u8; 16],
588 ) -> Result<Metadata, ZeroFsError> {
589 let (original, link) = (original.as_ref(), link.as_ref());
590 let (od, ld) = (display(original), display(link));
591 let (dir_guard, link_name) = self.parent_of(link, &ld).await?;
592 let orig_names = components(original)?;
593 let (orig_guard, _) = self.session.walk(&orig_names, &od).await?;
594 self.session
595 .link_at_op_id(dir_guard.fid(), orig_guard.fid(), link_name, &ld, op_id)
596 .await
597 }
598
599 pub async fn symlink(
602 &self,
603 target: impl AsRef<Path>,
604 link_path: impl AsRef<Path>,
605 ) -> Result<Metadata, ZeroFsError> {
606 self.symlink_op_id(target, link_path, [0u8; 16]).await
607 }
608
609 pub async fn symlink_op_id(
612 &self,
613 target: impl AsRef<Path>,
614 link_path: impl AsRef<Path>,
615 op_id: [u8; 16],
616 ) -> Result<Metadata, ZeroFsError> {
617 let link_path = link_path.as_ref();
618 let ld = display(link_path);
619 let (dir_guard, name) = self.parent_of(link_path, &ld).await?;
620 self.session
621 .symlink_at_op_id(
622 dir_guard.fid(),
623 name,
624 target.as_ref().as_os_str().as_bytes(),
625 &ld,
626 op_id,
627 )
628 .await
629 }
630
631 pub async fn read_link(&self, path: impl AsRef<Path>) -> Result<PathBuf, ZeroFsError> {
633 let path = path.as_ref();
634 let pd = display(path);
635 self.session.check_open()?;
636 let names = components(path)?;
637 let (guard, _) = self.session.walk(&names, &pd).await?;
638 let target = self.session.client.readlink(guard.fid()).await.ctx(&pd)?;
639 Ok(PathBuf::from(OsString::from_vec(target)))
640 }
641
642 pub async fn mknod(
645 &self,
646 path: impl AsRef<Path>,
647 kind: NodeKind,
648 mode: u32,
649 ) -> Result<Metadata, ZeroFsError> {
650 self.mknod_op_id(path, kind, mode, [0u8; 16]).await
651 }
652
653 pub async fn mknod_op_id(
656 &self,
657 path: impl AsRef<Path>,
658 kind: NodeKind,
659 mode: u32,
660 op_id: [u8; 16],
661 ) -> Result<Metadata, ZeroFsError> {
662 let path = path.as_ref();
663 let pd = display(path);
664 let (dir_guard, name) = self.parent_of(path, &pd).await?;
665 self.session
666 .mknod_at_op_id(dir_guard.fid(), name, kind, mode, &pd, op_id)
667 .await
668 }
669
670 pub async fn read_dir(&self, path: impl AsRef<Path>) -> Result<Vec<DirEntry>, ZeroFsError> {
673 let dir = self.open_dir(path).await?;
674 let mut out = Vec::new();
675 let result = loop {
676 match dir.next_batch(None).await {
677 Ok(batch) if batch.is_empty() => break Ok(out),
678 Ok(batch) => out.extend(batch),
679 Err(e) => break Err(e),
680 }
681 };
682 dir.close().await;
683 result
684 }
685
686 pub async fn open_dir(&self, path: impl AsRef<Path>) -> Result<Arc<Dir>, ZeroFsError> {
689 let path = path.as_ref();
690 let pd = display(path);
691 self.session.check_open()?;
692 let names = components(path)?;
693 let (guard, stat) = self.session.walk(&names, &pd).await?;
694 if let Some(stat) = &stat
695 && FileType::from_mode(stat.mode) != FileType::Dir
696 {
697 return Err(ZeroFsError::NotADirectory { path: pd });
698 }
699 Ok(Dir::new(
700 Arc::clone(&self.session),
701 guard,
702 display_path(&names),
703 ))
704 }
705
706 pub async fn open(
708 &self,
709 path: impl AsRef<Path>,
710 opts: OpenOptions,
711 ) -> Result<Arc<File>, ZeroFsError> {
712 let path = path.as_ref();
713 let pd = display(path);
714 self.session.check_open()?;
715 let guard = self.open_relative_path(path, &pd, &opts).await?;
716 Ok(File::new(Arc::clone(&self.session), guard, pd))
717 }
718
719 pub async fn create(&self, path: impl AsRef<Path>) -> Result<Arc<File>, ZeroFsError> {
721 self.create_op_id(path, [0u8; 16]).await
722 }
723
724 pub async fn create_op_id(
727 &self,
728 path: impl AsRef<Path>,
729 op_id: [u8; 16],
730 ) -> Result<Arc<File>, ZeroFsError> {
731 let path = path.as_ref();
732 let pd = display(path);
733 self.session.check_open()?;
734 let opts = OpenOptions::read_write().create(true).truncate(true);
735 let guard = self
736 .open_relative_path_op_id(path, &pd, &opts, op_id)
737 .await?;
738 Ok(File::new(Arc::clone(&self.session), guard, pd))
739 }
740
741 pub(crate) async fn open_guard(
745 &self,
746 path: &Path,
747 opts: &OpenOptions,
748 ) -> Result<(Arc<Session>, FidGuard), ZeroFsError> {
749 self.open_guard_op_id(path, opts, [0u8; 16]).await
750 }
751
752 pub(crate) async fn open_guard_op_id(
754 &self,
755 path: &Path,
756 opts: &OpenOptions,
757 op_id: [u8; 16],
758 ) -> Result<(Arc<Session>, FidGuard), ZeroFsError> {
759 let pd = display(path);
760 self.session.check_open()?;
761 let guard = self
762 .open_relative_path_op_id(path, &pd, opts, op_id)
763 .await?;
764 Ok((Arc::clone(&self.session), guard))
765 }
766
767 async fn resolve(&self, path: &Path, pd: &str) -> Result<(Vec<Vec<u8>>, Stat), ZeroFsError> {
772 let session = &self.session;
773
774 let literal: Vec<&[u8]> = components(path)?;
778 if let Ok((_guard, stat)) = session.walk_stat_from(session.root_fid, &literal, pd).await
779 && FileType::from_mode(stat.mode) != FileType::Symlink
780 {
781 return Ok((literal.iter().map(|c| c.to_vec()).collect(), stat));
782 }
783
784 let mut todo: VecDeque<Vec<u8>> = literal.iter().map(|c| c.to_vec()).collect();
785 let mut stack: Vec<Vec<u8>> = Vec::new();
786 let mut hops = 0u32;
787 let (mut cur, _) = session.walk(&[], pd).await?;
790 let mut cur_stat = session.stat_fid(cur.fid(), pd).await?;
791
792 while let Some(name) = todo.pop_front() {
793 if name == b".." {
794 stack.pop();
797 let refs: Vec<&[u8]> = stack.iter().map(|c| c.as_slice()).collect();
798 let (guard, stat) = session.walk_stat_from(session.root_fid, &refs, pd).await?;
799 cur = guard;
800 cur_stat = stat;
801 continue;
802 }
803
804 let (guard, stat) = session
805 .walk_stat_from(cur.fid(), &[name.as_slice()], pd)
806 .await?;
807 if FileType::from_mode(stat.mode) == FileType::Symlink {
808 hops += 1;
809 if hops > MAX_SYMLINK_HOPS {
810 return Err(ZeroFsError::TooManySymlinks {
811 path: pd.to_string(),
812 });
813 }
814 let target = session.client.readlink(guard.fid()).await.ctx(pd)?;
815 if target.first() == Some(&b'/') {
816 stack.clear();
817 let (root_clone, _) = session.walk(&[], pd).await?;
818 cur = root_clone;
819 cur_stat = session.stat_fid(cur.fid(), pd).await?;
820 }
821 for comp in target
823 .split(|&b| b == b'/')
824 .filter(|c| !c.is_empty() && *c != b".")
825 .rev()
826 {
827 todo.push_front(comp.to_vec());
828 }
829 } else {
830 stack.push(name);
831 cur = guard;
832 cur_stat = stat;
833 }
834 }
835
836 Ok((stack, cur_stat))
837 }
838}
839
840fn remove_dir_contents<'a>(
843 dir: &'a Dir,
844) -> std::pin::Pin<Box<dyn Future<Output = Result<(), ZeroFsError>> + Send + 'a>> {
845 Box::pin(async move {
846 loop {
847 dir.rewind().await?;
848 let batch = dir.next_batch(None).await?;
849 if batch.is_empty() {
850 return Ok(());
851 }
852 for entry in batch {
853 if entry.file_type == FileType::Dir {
854 let child = dir.open_dir_at(&entry.name_bytes).await?;
855 let result = remove_dir_contents(&child).await;
856 child.close().await;
857 result?;
858 dir.remove_dir_at(&entry.name_bytes).await?;
859 } else {
860 dir.remove_file_at(&entry.name_bytes).await?;
861 }
862 }
863 }
864 })
865}
866
867async fn dial(target: &str, msize: u32) -> Result<Arc<NinePClient>, ZeroFsError> {
869 let connect_failed = |message: String| ZeroFsError::ConnectFailed { message };
870
871 if let Some(rest) = target.strip_prefix("unix:") {
872 let path = rest.strip_prefix("//").unwrap_or(rest);
873 return NinePClient::connect_unix(path, msize)
874 .await
875 .map_err(|e| connect_failed(format!("9P unix socket {path}: {e}")));
876 }
877
878 let hostport = target.strip_prefix("tcp://").unwrap_or(target);
879
880 if hostport.starts_with('/') || hostport.starts_with('.') {
882 return NinePClient::connect_unix(hostport, msize)
883 .await
884 .map_err(|e| connect_failed(format!("9P unix socket {hostport}: {e}")));
885 }
886
887 let addr = resolve_addr(hostport).await?;
888 NinePClient::connect_tcp(addr, msize)
889 .await
890 .map_err(|e| connect_failed(format!("9P server {addr}: {e}")))
891}
892
893async fn resolve_addr(s: &str) -> Result<std::net::SocketAddr, ZeroFsError> {
894 if let Ok(addr) = s.parse::<std::net::SocketAddr>() {
895 return Ok(addr);
896 }
897 let with_port = if s.contains(':') {
898 s.to_string()
899 } else {
900 format!("{s}:{DEFAULT_9P_PORT}")
901 };
902 tokio::net::lookup_host(&with_port)
903 .await
904 .map_err(|e| ZeroFsError::ConnectFailed {
905 message: format!("resolving {with_port}: {e}"),
906 })?
907 .next()
908 .ok_or_else(|| ZeroFsError::ConnectFailed {
909 message: format!("no addresses resolved for {with_port}"),
910 })
911}