1use crate::error::{ClientResultExt, ZeroFsError};
2use crate::file::File;
3use crate::path::validate_name;
4use crate::session::{FidGuard, Session};
5use crate::types::{DirEntry, FileType, Metadata, NodeKind, OpenOptions, SetAttrs};
6use std::collections::VecDeque;
7use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, Ordering};
9
10pub struct Dir {
20 session: Arc<Session>,
21 guard: FidGuard,
22 closed: AtomicBool,
23 list: tokio::sync::Mutex<ListState>,
24 path: String,
25}
26
27struct ListState {
28 guard: Option<FidGuard>,
29 cookie: u64,
31 eof: bool,
32 buf: VecDeque<DirEntry>,
35}
36
37impl std::fmt::Debug for Dir {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 f.debug_struct("Dir")
40 .field("path", &self.path)
41 .field("closed", &self.closed.load(Ordering::Relaxed))
42 .finish_non_exhaustive()
43 }
44}
45
46impl Dir {
47 pub(crate) fn new(session: Arc<Session>, guard: FidGuard, path: String) -> Arc<Self> {
48 Arc::new(Self {
49 session,
50 guard,
51 closed: AtomicBool::new(false),
52 list: tokio::sync::Mutex::new(ListState {
53 guard: None,
54 cookie: 0,
55 eof: false,
56 buf: VecDeque::new(),
57 }),
58 path,
59 })
60 }
61
62 fn check(&self) -> Result<u32, ZeroFsError> {
63 if self.closed.load(Ordering::Acquire) {
64 Err(ZeroFsError::Closed)
65 } else {
66 Ok(self.guard.fid())
67 }
68 }
69
70 fn child_display(&self, name: &[u8]) -> String {
71 let base = self.path.trim_end_matches('/');
72 format!("{base}/{}", String::from_utf8_lossy(name))
73 }
74
75 fn at(&self, name: &[u8]) -> Result<(u32, String), ZeroFsError> {
77 let fid = self.check()?;
78 let display = self.child_display(name);
79 validate_name(name, &display)?;
80 Ok((fid, display))
81 }
82
83 pub async fn next_batch(&self, max_entries: Option<u32>) -> Result<Vec<DirEntry>, ZeroFsError> {
86 let fid = self.check()?;
87 let mut st = self.list.lock().await;
88
89 if st.guard.is_none() {
90 let flags = (libc::O_RDONLY | libc::O_DIRECTORY) as u32;
93 let g = self.session.alloc_guard();
94 let guard = match self
95 .session
96 .client
97 .open_clone(fid, g.fid(), flags, None)
98 .await
99 {
100 Ok((_list_fid, _, _)) => g,
101 Err(e) => {
102 g.discard();
103 return Err(ZeroFsError::from_client(&e, &self.path));
104 }
105 };
106 st.guard = Some(guard);
107 }
108 let list_fid = st.guard.as_ref().expect("listing fid just ensured").fid();
109
110 while st.buf.is_empty() && !st.eof {
112 let count = self.session.client.max_io();
113 if self.session.ext_v1() {
114 let entries = self
115 .session
116 .client
117 .readdirplus(list_fid, st.cookie, count)
118 .await
119 .ctx(&self.path)?;
120 match entries.last() {
121 Some(last) => st.cookie = last.offset,
122 None => st.eof = true,
123 }
124 st.buf.extend(
125 entries
126 .iter()
127 .filter(|e| e.name.data != b"." && e.name.data != b"..")
128 .map(DirEntry::from_plus),
129 );
130 } else {
131 let entries = self
132 .session
133 .client
134 .readdir(list_fid, st.cookie, count)
135 .await
136 .ctx(&self.path)?;
137 match entries.last() {
138 Some(last) => st.cookie = last.offset,
139 None => st.eof = true,
140 }
141 st.buf.extend(
142 entries
143 .iter()
144 .filter(|e| e.name.data != b"." && e.name.data != b"..")
145 .map(DirEntry::from_plain),
146 );
147 }
148 }
149
150 let want = max_entries.map_or(usize::MAX, |n| n as usize);
151 let take = want.min(st.buf.len());
152 Ok(st.buf.drain(..take).collect())
153 }
154
155 #[cfg(feature = "stream")]
160 pub fn entries(self: &Arc<Dir>) -> crate::stream::DirStream {
161 crate::stream::DirStream::new(Arc::clone(self))
162 }
163
164 pub async fn rewind(&self) -> Result<(), ZeroFsError> {
166 self.check()?;
167 let mut st = self.list.lock().await;
168 st.cookie = 0;
169 st.eof = false;
170 st.buf.clear();
171 Ok(())
172 }
173
174 pub async fn metadata(&self) -> Result<Metadata, ZeroFsError> {
176 let fid = self.check()?;
177 let stat = self.session.stat_fid(fid, &self.path).await?;
178 Ok(Metadata::from_stat(&stat))
179 }
180
181 pub async fn set_attr(&self, attrs: SetAttrs) -> Result<Metadata, ZeroFsError> {
183 let fid = self.check()?;
184 let stat = self.session.setattr_fid(fid, &attrs, &self.path).await?;
185 Ok(Metadata::from_stat(&stat))
186 }
187
188 pub async fn open_at(&self, name: &[u8], opts: OpenOptions) -> Result<Arc<File>, ZeroFsError> {
190 let (fid, display) = self.at(name)?;
191 let guard = self
192 .session
193 .open_relative(fid, name, &opts, &display)
194 .await?;
195 Ok(File::new(Arc::clone(&self.session), guard, display))
196 }
197
198 pub async fn open_dir_at(&self, name: &[u8]) -> Result<Arc<Dir>, ZeroFsError> {
200 let (fid, display) = self.at(name)?;
201 let (guard, stat) = self.session.walk_from(fid, &[name], &display).await?;
202 if let Some(stat) = &stat
203 && FileType::from_mode(stat.mode) != FileType::Dir
204 {
205 return Err(ZeroFsError::NotADirectory { path: display });
206 }
207 Ok(Dir::new(Arc::clone(&self.session), guard, display))
208 }
209
210 pub async fn metadata_at(&self, name: &[u8]) -> Result<Metadata, ZeroFsError> {
212 let (fid, display) = self.at(name)?;
213 let (_guard, stat) = self.session.walk_stat_from(fid, &[name], &display).await?;
214 Ok(Metadata::from_stat(&stat))
215 }
216
217 pub async fn set_attr_at(&self, name: &[u8], attrs: SetAttrs) -> Result<Metadata, ZeroFsError> {
220 let (fid, display) = self.at(name)?;
221 let (guard, _) = self.session.walk_from(fid, &[name], &display).await?;
222 let stat = self
223 .session
224 .setattr_fid(guard.fid(), &attrs, &display)
225 .await?;
226 Ok(Metadata::from_stat(&stat))
227 }
228
229 pub async fn create_dir_at(&self, name: &[u8], mode: u32) -> Result<Metadata, ZeroFsError> {
231 let (fid, display) = self.at(name)?;
232 self.session.mkdir_at(fid, name, mode, &display).await
233 }
234
235 pub async fn symlink_at(&self, name: &[u8], target: &[u8]) -> Result<Metadata, ZeroFsError> {
237 let (fid, display) = self.at(name)?;
238 self.session.symlink_at(fid, name, target, &display).await
239 }
240
241 pub async fn link_at(
244 &self,
245 original_dir: &Dir,
246 original_name: &[u8],
247 new_name: &[u8],
248 ) -> Result<Metadata, ZeroFsError> {
249 let (fid, display) = self.at(new_name)?;
250 let (original_fid, original_display) = original_dir.at(original_name)?;
251
252 let (target_guard, _) = self
253 .session
254 .walk_from(original_fid, &[original_name], &original_display)
255 .await?;
256 self.session
257 .link_at(fid, target_guard.fid(), new_name, &display)
258 .await
259 }
260
261 pub async fn mknod_at(
263 &self,
264 name: &[u8],
265 kind: NodeKind,
266 mode: u32,
267 ) -> Result<Metadata, ZeroFsError> {
268 let (fid, display) = self.at(name)?;
269 self.session.mknod_at(fid, name, kind, mode, &display).await
270 }
271
272 pub async fn remove_file_at(&self, name: &[u8]) -> Result<(), ZeroFsError> {
274 let (fid, display) = self.at(name)?;
275 self.session
276 .client
277 .unlinkat(fid, name, 0)
278 .await
279 .ctx(&display)
280 }
281
282 pub async fn remove_dir_at(&self, name: &[u8]) -> Result<(), ZeroFsError> {
284 let (fid, display) = self.at(name)?;
285 self.session
286 .client
287 .unlinkat(fid, name, libc::AT_REMOVEDIR as u32)
288 .await
289 .ctx(&display)
290 }
291
292 pub async fn rename_at(
294 &self,
295 old_name: &[u8],
296 new_dir: &Dir,
297 new_name: &[u8],
298 ) -> Result<(), ZeroFsError> {
299 let (fid, old_display) = self.at(old_name)?;
300 let (new_fid, _) = new_dir.at(new_name)?;
301 self.session
302 .client
303 .renameat(fid, old_name, new_fid, new_name)
304 .await
305 .ctx(&old_display)
306 }
307
308 pub async fn read_link_at(&self, name: &[u8]) -> Result<Vec<u8>, ZeroFsError> {
310 let (fid, display) = self.at(name)?;
311 let (guard, _) = self.session.walk_from(fid, &[name], &display).await?;
312 self.session
313 .client
314 .readlink(guard.fid())
315 .await
316 .ctx(&display)
317 }
318
319 pub async fn close(&self) {
323 self.closed.store(true, Ordering::Release);
326 }
327}