1use async_trait::async_trait;
50use std::io::Error as IoError;
51use std::path::{Path, PathBuf};
52use std::sync::Arc;
53
54use super::backend::FsBackend;
55use super::limits::{FsLimits, FsUsage};
56use super::traits::{DirEntry, FileSystem, FileSystemExt, Metadata, fs_errors};
57use crate::error::Result;
58
59pub struct PosixFs<B: FsBackend> {
89 backend: B,
90}
91
92impl<B: FsBackend> PosixFs<B> {
93 pub fn new(backend: B) -> Self {
95 Self { backend }
96 }
97
98 pub fn backend(&self) -> &B {
100 &self.backend
101 }
102
103 async fn check_parent_exists(&self, path: &Path) -> Result<()> {
105 if let Some(parent) = path.parent()
106 && parent != Path::new("/")
107 && parent != Path::new("")
108 && !self.backend.exists(parent).await?
109 {
110 return Err(fs_errors::parent_not_found());
111 }
112 Ok(())
113 }
114}
115
116#[async_trait]
117impl<B: FsBackend + 'static> FileSystem for PosixFs<B> {
118 async fn read_file(&self, path: &Path) -> Result<Vec<u8>> {
119 if let Ok(meta) = self.backend.stat(path).await
121 && meta.file_type.is_dir()
122 {
123 return Err(fs_errors::is_a_directory());
124 }
125 self.backend.read(path).await
126 }
127
128 async fn write_file(&self, path: &Path, content: &[u8]) -> Result<()> {
129 self.check_parent_exists(path).await?;
131
132 if let Ok(meta) = self.backend.stat(path).await
134 && meta.file_type.is_dir()
135 {
136 return Err(fs_errors::is_a_directory());
137 }
138
139 self.backend.write(path, content).await
140 }
141
142 async fn append_file(&self, path: &Path, content: &[u8]) -> Result<()> {
143 if let Ok(meta) = self.backend.stat(path).await
145 && meta.file_type.is_dir()
146 {
147 return Err(fs_errors::is_a_directory());
148 }
149
150 self.backend.append(path, content).await
151 }
152
153 async fn mkdir(&self, path: &Path, recursive: bool) -> Result<()> {
154 if let Ok(meta) = self.backend.stat(path).await {
156 if meta.file_type.is_dir() {
157 if recursive {
159 return Ok(()); } else {
161 return Err(fs_errors::already_exists("directory exists"));
162 }
163 } else {
164 return Err(fs_errors::already_exists("file exists"));
166 }
167 }
168
169 if recursive {
170 if let Some(parent) = path.parent() {
172 let mut current = PathBuf::from("/");
173 for component in parent.components().skip(1) {
174 current.push(component);
175 if let Ok(meta) = self.backend.stat(¤t).await
176 && !meta.file_type.is_dir()
177 {
178 return Err(fs_errors::already_exists("file exists"));
179 }
180 }
181 }
182 } else {
183 self.check_parent_exists(path).await?;
185 }
186
187 self.backend.mkdir(path, recursive).await
188 }
189
190 async fn remove(&self, path: &Path, recursive: bool) -> Result<()> {
191 self.backend.remove(path, recursive).await
192 }
193
194 async fn stat(&self, path: &Path) -> Result<Metadata> {
195 self.backend.stat(path).await
196 }
197
198 async fn read_dir(&self, path: &Path) -> Result<Vec<DirEntry>> {
199 if let Ok(meta) = self.backend.stat(path).await
201 && !meta.file_type.is_dir()
202 {
203 return Err(fs_errors::not_a_directory());
204 }
205 self.backend.read_dir(path).await
206 }
207
208 async fn exists(&self, path: &Path) -> Result<bool> {
209 self.backend.exists(path).await
210 }
211
212 async fn rename(&self, from: &Path, to: &Path) -> Result<()> {
213 self.backend.rename(from, to).await
214 }
215
216 async fn copy(&self, from: &Path, to: &Path) -> Result<()> {
217 if let Ok(meta) = self.backend.stat(from).await
219 && meta.file_type.is_dir()
220 {
221 return Err(IoError::other("cannot copy directory").into());
222 }
223 self.backend.copy(from, to).await
224 }
225
226 async fn symlink(&self, target: &Path, link: &Path) -> Result<()> {
227 self.backend.symlink(target, link).await
228 }
229
230 async fn read_link(&self, path: &Path) -> Result<PathBuf> {
231 self.backend.read_link(path).await
232 }
233
234 async fn chmod(&self, path: &Path, mode: u32) -> Result<()> {
235 self.backend.chmod(path, mode).await
236 }
237}
238
239#[async_trait]
240impl<B: FsBackend + 'static> FileSystemExt for PosixFs<B> {
241 fn usage(&self) -> FsUsage {
242 self.backend.usage()
243 }
244
245 fn limits(&self) -> FsLimits {
246 self.backend.limits()
247 }
248}
249
250impl<B: FsBackend + 'static> From<PosixFs<B>> for Arc<dyn FileSystem> {
252 fn from(fs: PosixFs<B>) -> Self {
253 Arc::new(fs)
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260 use crate::fs::InMemoryFs;
261 use std::path::Path;
262
263 #[tokio::test]
264 async fn test_posix_write_to_directory_fails() {
265 let fs = InMemoryFs::new();
268
269 fs.mkdir(Path::new("/tmp/testdir"), false)
271 .await
272 .expect("mkdir should succeed");
273
274 let result = fs.write_file(Path::new("/tmp/testdir"), b"test").await;
276 assert!(result.is_err());
277 assert!(
278 result
279 .expect_err("write_file should fail")
280 .to_string()
281 .contains("directory")
282 );
283 }
284
285 #[tokio::test]
286 async fn test_posix_mkdir_on_file_fails() {
287 let fs = InMemoryFs::new();
288
289 fs.write_file(Path::new("/tmp/testfile"), b"test")
291 .await
292 .expect("write_file should succeed");
293
294 let result = fs.mkdir(Path::new("/tmp/testfile"), false).await;
296 assert!(result.is_err());
297 }
298}