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