1use alloc::boxed::Box;
18use alloc::string::String;
19use alloc::sync::Arc;
20use alloc::vec::Vec;
21
22use rt::sync::SpinLock;
23
24use crate::policy::{fs_allowed, FS_READ, FS_WRITE};
25use crate::vfs::{self, Opened, Provider, Route, Stream};
26use crate::{OsFile, OsFileLock, FO_READ, FO_RW, FO_WRITE};
27
28pub fn file_exists(path: &str) -> u32 {
30 match vfs::route(path) {
31 Route::Os => if fs_allowed(path, FS_READ) { crate::file_exists_raw(path) } else { 0 },
32 Route::Vfs(p, rest) => p.kind(&rest),
33 Route::Bad => 0,
34 }
35}
36
37pub fn file_exists_checked(path: &str) -> Option<u32> {
39 match vfs::route(path) {
40 Route::Os => if fs_allowed(path, FS_READ) { crate::file_exists_checked_raw(path) } else { None },
41 Route::Vfs(p, rest) => Some(p.kind(&rest)),
42 Route::Bad => None,
43 }
44}
45
46pub fn mkdir(path: &str) -> bool {
48 match vfs::route(path) {
49 Route::Os => fs_allowed(path, FS_WRITE) && crate::mkdir_raw(path),
50 Route::Vfs(p, rest) => p.mkdir(&rest),
51 Route::Bad => false,
52 }
53}
54
55pub fn delete(path: &str) -> bool {
57 match vfs::route(path) {
58 Route::Os => fs_allowed(path, FS_WRITE) && crate::delete_raw(path),
59 Route::Vfs(p, rest) => p.delete(&rest),
60 Route::Bad => false,
61 }
62}
63
64pub fn trash(path: &str) -> bool {
68 match vfs::route(path) {
69 Route::Os => fs_allowed(path, FS_WRITE) && crate::trash::trash_raw(path),
70 Route::Vfs(p, rest) => !rest.is_empty() && p.os_path(&rest).is_some_and(|o| fs_allowed(&o, FS_WRITE) && crate::trash::trash_raw(&o)),
71 Route::Bad => false,
72 }
73}
74
75pub fn rename(old: &str, new: &str) -> bool {
77 match (vfs::route(old), vfs::route(new)) {
78 (Route::Os, Route::Os) => fs_allowed(old, FS_WRITE) && fs_allowed(new, FS_WRITE) && crate::rename_raw(old, new),
79 (Route::Vfs(p, a), Route::Vfs(q, b)) if Arc::ptr_eq(&p, &q) => p.rename(&a, &b),
80 _ => false,
81 }
82}
83
84pub fn dir_list(path: &str, f: &mut dyn FnMut(&str, bool, u32)) -> bool {
86 match vfs::route(path) {
87 Route::Os => fs_allowed(path, FS_READ) && crate::dir_list_raw(path, f),
88 Route::Vfs(p, rest) => p.list(&rest, &mut |n, d, s| f(n, d, s as u32)),
89 Route::Bad => false,
90 }
91}
92
93pub fn read_file(path: &str) -> Option<Vec<u8>> {
95 match vfs::route(path) {
96 Route::Os => if fs_allowed(path, FS_READ) { crate::read_file_raw(path) } else { None },
97 Route::Vfs(p, rest) => p.read(&rest),
98 Route::Bad => None,
99 }
100}
101
102pub fn write_file(path: &str, data: &[u8]) -> bool {
105 match vfs::route(path) {
106 Route::Os => fs_allowed(path, FS_WRITE) && crate::write_file_raw(path, data),
107 Route::Vfs(p, rest) => !rest.is_empty() && p.write(&rest, data),
108 Route::Bad => false,
109 }
110}
111
112pub struct File(Inner);
116
117enum Inner {
118 Os(OsFile),
119 Vfs(Box<dyn Stream>),
120}
121
122fn mode_ok(mode: u32) -> bool {
123 matches!(mode, FO_READ | FO_WRITE | FO_RW)
124}
125
126fn os_open(path: &str, mode: u32) -> Option<File> {
128 if mode_ok(mode) && fs_allowed(path, if mode == FO_READ { FS_READ } else { FS_WRITE }) {
129 OsFile::open_raw(path, mode).map(|f| File(Inner::Os(f)))
130 } else {
131 None
132 }
133}
134
135pub(crate) fn open_in(p: &Arc<dyn Provider>, path: &str, mode: u32) -> Option<File> {
137 if !mode_ok(mode) {
138 return None;
139 }
140 match p.open(path, mode) {
141 Opened::File(s) => Some(File(Inner::Vfs(s))),
142 Opened::Refused => None,
143 Opened::Generic => match p.os_path(path) {
144 Some(o) => os_open(&o, mode),
145 None => whole(p, path, mode),
146 },
147 }
148}
149
150fn whole(p: &Arc<dyn Provider>, path: &str, mode: u32) -> Option<File> {
152 let data = match (mode, p.kind(path)) {
153 (_, 2) => return None,
154 (FO_READ, 1) | (FO_RW, 1) => p.read(path)?,
155 (FO_READ, _) => return None,
156 _ => Vec::new(),
157 };
158 if mode != FO_READ && (path.is_empty() || !p.write(path, &data)) {
161 return None;
162 }
163 let back = (mode != FO_READ).then(|| (p.clone(), String::from(path)));
164 Some(File(Inner::Vfs(Box::new(Whole { writable: back.is_some(), back, st: SpinLock::new((data, false)) }))))
165}
166
167struct Whole {
170 back: Option<(Arc<dyn Provider>, String)>,
171 writable: bool,
172 st: SpinLock<(Vec<u8>, bool)>,
174}
175
176impl Whole {
177 fn anon() -> Whole {
178 Whole { back: None, writable: true, st: SpinLock::new((Vec::new(), false)) }
179 }
180}
181
182impl Stream for Whole {
183 fn read_at(&self, off: u64, buf: &mut [u8]) -> Option<usize> {
184 let g = self.st.lock();
185 let off = usize::try_from(off).unwrap_or(usize::MAX).min(g.0.len());
186 let n = (g.0.len() - off).min(buf.len());
187 buf[..n].copy_from_slice(&g.0[off..off + n]);
188 Some(n)
189 }
190
191 fn write_at(&self, off: u64, buf: &[u8]) -> Option<usize> {
192 if !self.writable {
193 return None;
194 }
195 let mut g = self.st.lock();
196 let n = vfs::write_vec(&mut g.0, off, buf)?;
197 g.1 = true;
198 Some(n)
199 }
200
201 fn size(&self) -> Option<u64> {
202 Some(self.st.lock().0.len() as u64)
203 }
204
205 fn set_len(&self, len: u64) -> bool {
206 if !self.writable {
207 return false;
208 }
209 let mut g = self.st.lock();
210 g.1 = true;
211 vfs::set_len_vec(&mut g.0, len)
212 }
213
214 fn sync(&self) -> bool {
215 let Some((p, path)) = &self.back else { return true };
216 let mut g = self.st.lock();
217 if !g.1 {
218 return true;
219 }
220 let ok = p.write(path, &g.0);
221 g.1 &= !ok;
222 ok
223 }
224}
225
226impl Drop for Whole {
227 fn drop(&mut self) {
228 self.sync();
229 }
230}
231
232impl File {
233 pub fn open(path: &str, mode: u32) -> Option<File> {
236 match vfs::route(path) {
237 Route::Os => os_open(path, mode),
238 Route::Vfs(p, rest) => open_in(&p, &rest, mode),
239 Route::Bad => None,
240 }
241 }
242
243 #[cfg(any(windows, target_os = "linux"))]
246 pub fn create_new(path: &str) -> Option<File> {
247 let new_os = |o: &str| if fs_allowed(o, FS_WRITE) { OsFile::create_new_raw(o).map(|f| File(Inner::Os(f))) } else { None };
248 match vfs::route(path) {
249 Route::Os => new_os(path),
250 Route::Vfs(p, rest) => match p.os_path(&rest) {
251 Some(o) => new_os(&o),
252 None => {
253 let _g = NEW_LOCK.lock();
254 if rest.is_empty() || p.kind(&rest) != 0 { None } else { open_in(&p, &rest, FO_WRITE) }
255 }
256 },
257 Route::Bad => None,
258 }
259 }
260
261 pub fn open_temp(dir: Option<&str>) -> Option<File> {
264 let temp_os = |d: &str| if fs_allowed(d, FS_WRITE) { OsFile::open_temp_raw(Some(d)).map(|f| File(Inner::Os(f))) } else { None };
265 let Some(d) = dir else { return OsFile::open_temp_raw(None).map(|f| File(Inner::Os(f))) };
266 match vfs::route(d) {
267 Route::Os => temp_os(d),
268 Route::Vfs(p, rest) => match p.os_path(&rest) {
269 Some(o) => temp_os(&o),
270 None => (p.kind(&rest) == 2).then(|| File(Inner::Vfs(Box::new(Whole::anon())))),
271 },
272 Route::Bad => None,
273 }
274 }
275
276 pub fn read_at(&self, off: u64, buf: &mut [u8]) -> Option<usize> {
278 match &self.0 {
279 Inner::Os(f) => f.read_at(off, buf),
280 Inner::Vfs(s) => s.read_at(off, buf),
281 }
282 }
283
284 pub fn write_at(&self, off: u64, buf: &[u8]) -> Option<usize> {
286 match &self.0 {
287 Inner::Os(f) => f.write_at(off, buf),
288 Inner::Vfs(s) => s.write_at(off, buf),
289 }
290 }
291
292 pub fn size(&self) -> Option<u64> {
293 match &self.0 {
294 Inner::Os(f) => f.size(),
295 Inner::Vfs(s) => s.size(),
296 }
297 }
298
299 pub fn sync(&self) -> bool {
301 match &self.0 {
302 Inner::Os(f) => f.sync(),
303 Inner::Vfs(s) => s.sync(),
304 }
305 }
306
307 pub fn set_len(&self, len: u64) -> bool {
309 match &self.0 {
310 #[cfg(any(windows, target_os = "linux", target_os = "android"))]
311 Inner::Os(f) => f.set_len(len),
312 #[cfg(not(any(windows, target_os = "linux", target_os = "android")))]
313 Inner::Os(_) => false,
314 Inner::Vfs(s) => s.set_len(len),
315 }
316 }
317}
318
319impl Stream for File {
320 fn read_at(&self, off: u64, buf: &mut [u8]) -> Option<usize> {
321 File::read_at(self, off, buf)
322 }
323 fn write_at(&self, off: u64, buf: &[u8]) -> Option<usize> {
324 File::write_at(self, off, buf)
325 }
326 fn size(&self) -> Option<u64> {
327 File::size(self)
328 }
329 fn set_len(&self, len: u64) -> bool {
330 File::set_len(self, len)
331 }
332 fn sync(&self) -> bool {
333 File::sync(self)
334 }
335}
336
337pub struct AtomicFile(Option<AtomicInner>);
348
349enum AtomicInner {
350 Os(crate::OsAtomic),
351 Vfs { p: Arc<dyn Provider>, tmp: String, dst: String, f: Option<File> },
352 InPlace { p: Arc<dyn Provider>, dst: String, buf: SpinLock<Vec<u8>> },
354}
355
356static ATOMIC_SEQ: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
357
358impl AtomicFile {
359 pub fn create(path: &str) -> Option<AtomicFile> {
362 match vfs::route(path) {
363 Route::Os => {
364 if !fs_allowed(path, FS_WRITE) {
365 return None;
366 }
367 crate::OsAtomic::begin(path).map(|a| AtomicFile(Some(AtomicInner::Os(a))))
368 }
369 Route::Vfs(p, rest) => {
370 if rest.is_empty() || p.kind(&rest) == 2 {
371 return None;
372 }
373 if !p.atomic_rename() {
374 return Some(AtomicFile(Some(AtomicInner::InPlace { p, dst: rest, buf: SpinLock::new(Vec::new()) })));
375 }
376 if let Some((dir, _)) = rest.rsplit_once('/') {
377 p.mkdir(dir);
378 }
379 for _ in 0..16 {
380 let seq = ATOMIC_SEQ.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
381 let tmp = alloc::format!("{rest}.tmp-{seq:x}-{:x}", crate::time_us() as u32);
382 if p.kind(&tmp) != 0 {
383 continue;
384 }
385 let f = open_in(&p, &tmp, FO_WRITE)?;
386 return Some(AtomicFile(Some(AtomicInner::Vfs { p, tmp, dst: rest, f: Some(f) })));
387 }
388 None
389 }
390 Route::Bad => None,
391 }
392 }
393
394 pub fn write_at(&self, off: u64, data: &[u8]) -> Option<usize> {
396 match self.0.as_ref()? {
397 AtomicInner::Os(a) => a.write_at(off, data),
398 AtomicInner::Vfs { f, .. } => f.as_ref()?.write_at(off, data),
399 AtomicInner::InPlace { buf, .. } => vfs::write_vec(&mut buf.lock(), off, data),
400 }
401 }
402
403 pub fn commit(mut self) -> bool {
405 match self.0.take() {
406 None => false,
407 Some(AtomicInner::Os(a)) => a.commit(),
408 Some(AtomicInner::Vfs { p, tmp, dst, f }) => {
409 let ok = f.is_some_and(|f| f.sync());
410 let ok = ok && (p.rename(&tmp, &dst) || p.read(&tmp).is_some_and(|d| p.write(&dst, &d)));
412 if p.kind(&tmp) == 1 {
413 p.delete(&tmp);
414 }
415 ok
416 }
417 Some(AtomicInner::InPlace { p, dst, buf }) => p.write(&dst, &buf.lock()),
418 }
419 }
420}
421
422impl Drop for AtomicFile {
423 fn drop(&mut self) {
424 if let Some(AtomicInner::Vfs { p, tmp, f, .. }) = &mut self.0 {
425 drop(f.take());
426 p.delete(tmp); }
428 }
429}
430
431#[cfg(any(windows, target_os = "linux"))]
433static NEW_LOCK: SpinLock<()> = SpinLock::new(());
434
435static VFS_LOCKS: SpinLock<Vec<(usize, String)>> = SpinLock::new(Vec::new());
437
438pub struct FileLock(LockInner);
442
443enum LockInner {
444 Os { _lock: OsFileLock },
445 Vfs { p: Arc<dyn Provider>, path: String },
446}
447
448impl FileLock {
449 pub fn acquire(path: &str) -> Option<Self> {
451 let os = |o: &str| if fs_allowed(o, FS_WRITE) { OsFileLock::acquire_raw(o).map(|l| FileLock(LockInner::Os { _lock: l })) } else { None };
452 let (p, rest) = match vfs::route(path) {
453 Route::Os => return os(path),
454 Route::Vfs(p, rest) => match p.os_path(&rest) {
455 Some(o) => return os(&o),
456 None => (p, rest),
457 },
458 Route::Bad => return None,
459 };
460 let key = Arc::as_ptr(&p) as *const () as usize;
461 let mut g = VFS_LOCKS.lock();
462 if rest.is_empty() || g.iter().any(|(k, n)| *k == key && *n == rest) {
463 return None;
464 }
465 match p.kind(&rest) {
466 1 => {}
467 0 if p.write(&rest, &[]) => {}
468 _ => return None,
469 }
470 g.push((key, rest.clone()));
471 Some(FileLock(LockInner::Vfs { p, path: rest }))
472 }
473}
474
475impl Drop for FileLock {
476 fn drop(&mut self) {
477 if let LockInner::Vfs { p, path } = &self.0 {
478 let key = Arc::as_ptr(p) as *const () as usize;
479 VFS_LOCKS.lock().retain(|(k, n)| !(*k == key && n == path));
480 }
481 }
482}