fs/
lib.rs

1// SPDX-FileCopyrightText: 2023 Foundation Devices, Inc. <hello@foundation.xyz>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4//! Filesystem server API.
5//!
6//! The primary client handle is [`FileSystem`]. Use [`use_api!`] in app code to
7//! define local aliases for [`FileSystem`], [`File`], and [`Dir`] with the
8//! app's generated permissions type:
9//!
10//! ```rust,ignore
11//! fs::use_api!();
12//!
13//! let fs = FileSystem::default();
14//! let file = fs.open_file("state.json", fs::Location::AppData, fs::OpenFlags::read_only())?;
15//! ```
16//!
17//! The types in [`messages`] describe the wire protocol between the API handle
18//! and the filesystem server. Most app code should use [`FileSystem`] and the
19//! returned [`File`] / [`Dir`] handles directly.
20
21use std::io::{Read, Seek, Write};
22
23use num_derive::{FromPrimitive, ToPrimitive};
24use num_traits::{FromPrimitive, ToPrimitive};
25use server::{wrapped_scalar, CheckedConn, CheckedPermissions, MessageAllowed};
26use xous::{DropDeallocate, MemoryRange};
27
28pub mod adapter;
29pub mod error;
30mod flags;
31pub mod messages;
32
33pub use error::Error;
34use messages::*;
35
36// Enough space for the typical FAT32 cluster read (64 sectors of 512)
37pub const FILE_BUFFER_SIZE: usize = 64 * 512;
38pub const BLOCK_SIZE: u64 = 512;
39pub const SYSTEM_STATE_ROOT: &str = "state";
40
41/// Defines local filesystem API aliases with generated permissions.
42///
43/// The zero-argument form expands to:
44///
45/// - `FileSystem = fs::FileSystem<FileSystemPermissions>`
46/// - `File = fs::File<FileSystemPermissions>`
47/// - `Dir = fs::Dir<FileSystemPermissions>`
48///
49/// Use this from an app crate before constructing a [`FileSystem`] handle.
50#[macro_export]
51macro_rules! use_api {
52    ($fs:path, $server:path) => {
53        mod fs_permissions {
54            use fs::messages::*;
55            pub use $fs as fs;
56            use $server as server;
57            #[derive(Clone, Default, Debug, server::Permissions)]
58            #[server_name = "os/fs"]
59            pub struct FileSystemPermissions;
60        }
61        type FileSystem = fs_permissions::fs::FileSystem<fs_permissions::FileSystemPermissions>;
62        type File = fs_permissions::fs::File<fs_permissions::FileSystemPermissions>;
63        type Dir = fs_permissions::fs::Dir<fs_permissions::FileSystemPermissions>;
64    };
65    () => {
66        fs::use_api!(fs, server);
67    };
68}
69
70/// Client handle for the filesystem server.
71///
72/// This is the filesystem crate's high-level API surface. It owns a checked
73/// connection to `os/fs`, enforces location access checks, and creates [`File`]
74/// and [`Dir`] handles for file and directory operations.
75#[derive(Debug, Default, Clone)]
76pub struct FileSystem<P: CheckedPermissions> {
77    conn: CheckedConn<P>,
78    read_access_granted: flags::AccessFlags,
79    write_access_granted: flags::AccessFlags,
80}
81
82/// Permissions [`FileSystem::map_file`] requires. The device sends one map
83/// message; the hosted build has no page-mirroring syscall and reads the file
84/// instead, so there it needs the file read messages.
85#[cfg(keyos)]
86pub trait MapFilePermissions: MessageAllowed<MapFileMessage> {}
87#[cfg(keyos)]
88impl<P: MessageAllowed<MapFileMessage>> MapFilePermissions for P {}
89
90#[cfg(not(keyos))]
91pub trait MapFilePermissions:
92    MessageAllowed<OpenFileMessage> + MessageAllowed<CloseFile> + MessageAllowed<ReadFile>
93{
94}
95#[cfg(not(keyos))]
96impl<P> MapFilePermissions for P where
97    P: MessageAllowed<OpenFileMessage> + MessageAllowed<CloseFile> + MessageAllowed<ReadFile>
98{
99}
100
101impl<P: CheckedPermissions> FileSystem<P> {
102    pub fn open_file(
103        &self,
104        path: impl Into<String>,
105        location: Location,
106        flags: OpenFlags,
107    ) -> Result<File<P>, Error>
108    where
109        P: MessageAllowed<OpenFileMessage>,
110        P: MessageAllowed<CloseFile>,
111    {
112        if flags.read {
113            self.ensure_read_access(location)?;
114        }
115        if flags.write {
116            self.ensure_write_access(location)?;
117        }
118        Ok(File {
119            handle: self.conn.send_blocking_archive(OpenFileMessage {
120                path: path.into(),
121                location,
122                flags,
123            })?,
124            work_buf: DropDeallocate::new(
125                xous::map_memory(None, None, FILE_BUFFER_SIZE, xous::MemoryFlags::W)
126                    .map_err(|_| Error::FileNotOpen)?,
127            ),
128            conn: self.conn.clone(),
129        })
130    }
131
132    pub fn open_dir(&self, path: impl Into<String>, location: Location) -> Result<Dir<P>, Error>
133    where
134        P: MessageAllowed<OpenDirMessage>,
135        P: MessageAllowed<CloseDir>,
136    {
137        self.ensure_read_access(location)?;
138        Ok(Dir {
139            handle: self.conn.send_blocking_archive(OpenDirMessage { path: path.into(), location })?,
140            conn: self.conn.clone(),
141        })
142    }
143
144    pub fn create_dir(&self, path: impl Into<String>, location: Location) -> Result<Dir<P>, Error>
145    where
146        P: MessageAllowed<CreateDirMessage>,
147        P: MessageAllowed<CloseDir>,
148    {
149        self.ensure_write_access(location)?;
150        Ok(Dir {
151            handle: self.conn.send_blocking_archive(CreateDirMessage { path: path.into(), location })?,
152            conn: self.conn.clone(),
153        })
154    }
155
156    pub fn create_dir_async(
157        &self,
158        path: impl Into<String>,
159        location: Location,
160    ) -> Result<CreateDirMessage, Error>
161    where
162        P: MessageAllowed<CreateDirMessage>,
163        P: MessageAllowed<CloseDir>,
164    {
165        self.ensure_write_access(location)?;
166        Ok(CreateDirMessage { path: path.into(), location })
167    }
168
169    pub fn ensure_parent_dir_exists(&self, path: &str, location: Location) -> Result<(), Error>
170    where
171        P: MessageAllowed<CreateDirMessage>,
172        P: MessageAllowed<CloseDir>,
173    {
174        ensure_parent_dir_exists_impl(|dir| self.create_dir(dir, location).map(|_| ()), path)
175    }
176
177    pub fn remove(&self, path: impl Into<String>, location: Location) -> Result<(), Error>
178    where
179        P: MessageAllowed<Remove>,
180    {
181        let path = path.into();
182        self.ensure_write_access(location)?;
183        self.conn.send_blocking_archive(Remove { path, location })
184    }
185
186    pub fn remove_async(&self, path: impl Into<String>, location: Location) -> Result<Remove, Error>
187    where
188        P: MessageAllowed<Remove>,
189    {
190        let path = path.into();
191        self.ensure_write_access(location)?;
192        Ok(Remove { path, location })
193    }
194
195    /// Copy a source file/directory to a destination directory. If source is a directory, the copy is
196    /// recursive.
197    ///
198    /// Optionally, the copied file/directory can be renamed by providing the `rename` argument.
199    ///
200    /// The destination directory must be empty; copying into a non-empty directory returns
201    /// [`Error::FileAlreadyExists`].
202    pub fn atomic_copy(
203        &self,
204        src: impl Into<String>,
205        dest_dir: impl Into<String>,
206        rename: Option<impl Into<String>>,
207        location: Location,
208    ) -> Result<(), Error>
209    where
210        P: MessageAllowed<AtomicCopy>,
211    {
212        self.ensure_read_access(location)?;
213        self.ensure_write_access(location)?;
214
215        let src = src.into();
216        let dest_dir = dest_dir.into();
217        let rename = rename.map(|s| s.into());
218        self.conn.send_blocking_archive(AtomicCopy { src, dest_dir, rename, location })
219    }
220
221    pub fn metadata(&self, path: impl Into<String>, location: Location) -> Result<Metadata, Error>
222    where
223        P: MessageAllowed<GetMetadata>,
224    {
225        self.ensure_read_access(location)?;
226        self.conn.send_blocking_archive(GetMetadata::Path { path: path.into(), location })
227    }
228
229    pub fn rename(
230        &self,
231        from: impl Into<String>,
232        to: impl Into<String>,
233        location: Location,
234    ) -> Result<(), Error>
235    where
236        P: MessageAllowed<Rename>,
237    {
238        self.ensure_write_access(location)?;
239        self.conn.send_blocking_archive(Rename { from: from.into(), to: to.into(), location })
240    }
241
242    pub fn rename_async(
243        &self,
244        from: impl Into<String>,
245        to: impl Into<String>,
246        location: Location,
247    ) -> Result<Rename, Error>
248    where
249        P: MessageAllowed<Rename>,
250    {
251        self.ensure_write_access(location)?;
252        Ok(Rename { from: from.into(), to: to.into(), location })
253    }
254
255    pub fn map_file(&self, location: Location, path: impl Into<String>) -> Result<xous::MemoryRange, Error>
256    where
257        P: MapFilePermissions,
258    {
259        self.ensure_read_access(location)?;
260
261        #[cfg(keyos)]
262        {
263            let result = self.conn.send_blocking_archive(MapFileMessage { path: path.into(), location })?;
264            Ok(unsafe { xous::MemoryRange::new(result.addr, result.size).unwrap() })
265        }
266
267        // No hosted equivalent of mirror_memory_to_pid, so read the file into a
268        // page-aligned buffer (leaked like the device's mapping, never unmapped)
269        // and hand back a range over it.
270        #[cfg(not(keyos))]
271        {
272            let mut file = self.open_file(path, location, OpenFlags::READ_ONLY)?;
273            let mut bytes = Vec::new();
274            file.read_to_end(&mut bytes).map_err(|_| Error::Io)?;
275            if bytes.is_empty() {
276                return Err(Error::FileNotFound);
277            }
278
279            let mut buffer = xous::map_memory(None, None, bytes.len(), xous::MemoryFlags::W)
280                .map_err(|_| Error::OutOfMemory)?;
281            buffer.as_slice_mut()[..bytes.len()].copy_from_slice(&bytes);
282            Ok(buffer)
283        }
284    }
285
286    pub fn register_app_resources(
287        &self,
288        app_id: xous::AppId,
289        root: AppResourcesRoot,
290        app_dir: impl Into<String>,
291    ) -> Result<(), Error>
292    where
293        P: MessageAllowed<RegisterAppResources>,
294    {
295        self.conn.send_blocking_archive(RegisterAppResources { app_id, root, app_dir: app_dir.into() })
296    }
297
298    fn ensure_read_access(&self, location: Location) -> Result<(), Error> {
299        if self.read_access_granted.contains(location) {
300            return Ok(());
301        }
302        match location {
303            Location::CommonAssets | Location::AppData | Location::AppResources => return Ok(()),
304            Location::System => self.conn.unchecked().try_send_blocking_scalar(GetSystemReadAccess)?,
305            Location::SystemAppData => {
306                self.conn.unchecked().try_send_blocking_scalar(GetSystemAppDataReadAccess)?
307            }
308            Location::EncryptedRoot => {
309                self.conn.unchecked().try_send_blocking_scalar(GetEncryptedRootReadAccess)?
310            }
311            Location::Usb => self.conn.unchecked().try_send_blocking_scalar(GetUsbReadAccess)?,
312            Location::User => self.conn.unchecked().try_send_blocking_scalar(GetUserReadAccess)?,
313            Location::Airlock => self.conn.unchecked().try_send_blocking_scalar(GetAirlockReadAccess)?,
314            Location::Boot => self.conn.unchecked().try_send_blocking_scalar(GetBootReadAccess)?,
315        };
316        self.read_access_granted.insert(location);
317        Ok(())
318    }
319
320    fn ensure_write_access(&self, location: Location) -> Result<(), Error> {
321        if self.write_access_granted.contains(location) {
322            return Ok(());
323        }
324        match location {
325            Location::CommonAssets | Location::AppResources => return Err(Error::AccessDenied)?,
326            Location::AppData => return Ok(()),
327            Location::System => self.conn.unchecked().try_send_blocking_scalar(GetSystemWriteAccess)?,
328            Location::SystemAppData => {
329                self.conn.unchecked().try_send_blocking_scalar(GetSystemAppDataWriteAccess)?
330            }
331            Location::EncryptedRoot => {
332                self.conn.unchecked().try_send_blocking_scalar(GetEncryptedRootWriteAccess)?
333            }
334            Location::Usb => self.conn.unchecked().try_send_blocking_scalar(GetUsbWriteAccess)?,
335            Location::User => self.conn.unchecked().try_send_blocking_scalar(GetUserWriteAccess)?,
336            Location::Airlock => self.conn.unchecked().try_send_blocking_scalar(GetAirlockWriteAccess)?,
337            Location::Boot => self.conn.unchecked().try_send_blocking_scalar(GetBootWriteAccess)?,
338        };
339        self.write_access_granted.insert(location);
340        Ok(())
341    }
342
343    pub fn read_blocks(
344        &mut self,
345        location: Location,
346        block_index: u32,
347        block_count: usize,
348        buf: MemoryRange,
349    ) -> Result<usize, Error>
350    where
351        P: MessageAllowed<ReadBlocks>,
352    {
353        self.conn.lend_mut(ReadBlocks { buf, block_index, block_count, location })
354    }
355
356    pub fn write_blocks(
357        &mut self,
358        location: Location,
359        block_index: u32,
360        block_count: usize,
361        buf: MemoryRange,
362    ) -> Result<usize, Error>
363    where
364        P: MessageAllowed<WriteBlocks>,
365    {
366        self.conn.lend_mut(WriteBlocks { buf, block_index, block_count, location })
367    }
368
369    pub fn flush(&mut self, location: Location) -> Result<(), Error>
370    where
371        P: MessageAllowed<FlushFs>,
372    {
373        self.conn.try_send_blocking_scalar(FlushFs(location))?
374    }
375
376    pub fn block_count(&self, location: Location) -> Result<usize, Error>
377    where
378        P: MessageAllowed<BlockCount>,
379    {
380        self.conn.try_send_blocking_scalar(BlockCount(location))?
381    }
382
383    pub fn format_encrypted_volume(&self)
384    where
385        P: MessageAllowed<FormatEncryptedVolume>,
386    {
387        self.conn.send_blocking_scalar(FormatEncryptedVolume);
388    }
389
390    pub fn subscribe_filesystem_events<S>(&self, listener: &mut server::ServerContext<S>, location: Location)
391    where
392        S: server::Server + server::ScalarEventHandler<FileSystemEvent>,
393        P: MessageAllowed<SubscribeFilesystemEvent>,
394    {
395        self.conn.subscribe_scalar_infallible(SubscribeFilesystemEvent(location), listener)
396    }
397
398    pub fn wait_for_filesystem(&self, location: Location)
399    where
400        P: 'static,
401        P: MessageAllowed<SubscribeFilesystemEvent>,
402    {
403        server::listen(WaitForFs(self.clone(), location));
404    }
405
406    pub fn mount_airlock(&mut self) -> Result<(), Error>
407    where
408        P: MessageAllowed<MountAirlock>,
409    {
410        self.conn.send_blocking_scalar(MountAirlock(true))
411    }
412
413    pub fn unmount_airlock(&mut self) -> Result<(), Error>
414    where
415        P: MessageAllowed<MountAirlock>,
416    {
417        self.conn.send_blocking_scalar(MountAirlock(false))
418    }
419
420    pub fn format_airlock(&mut self) -> Result<(), Error>
421    where
422        P: MessageAllowed<FormatAirlock>,
423    {
424        self.conn.send_blocking_scalar(FormatAirlock)
425    }
426}
427
428#[derive(Debug)]
429pub struct File<P: CheckedPermissions + MessageAllowed<CloseFile>> {
430    handle: FileHandle,
431    conn: CheckedConn<P>,
432    work_buf: DropDeallocate,
433}
434
435impl<P: CheckedPermissions + MessageAllowed<CloseFile>> File<P> {
436    pub fn truncate(&mut self) -> Result<(), Error>
437    where
438        P: MessageAllowed<TruncateFile>,
439    {
440        self.conn.send_blocking_archive(TruncateFile(self.handle))
441    }
442
443    // if len is less than the current file size, the file is truncated
444    // if len is greater than the current file size, the file is extended with empty bytes
445    // the file's cursor does not move
446    pub fn set_len(&mut self, len: u64) -> Result<(), Error>
447    where
448        P: MessageAllowed<SetLen>,
449    {
450        self.conn.send_blocking_archive(SetLen { handle: self.handle, len })
451    }
452
453    pub fn metadata(&self) -> Result<Metadata, Error>
454    where
455        P: MessageAllowed<GetMetadata>,
456    {
457        self.conn.send_blocking_archive(GetMetadata::Handle { handle: self.handle })
458    }
459
460    pub fn set_mtime(&mut self, datetime: DateTime) -> Result<(), Error>
461    where
462        P: MessageAllowed<SetMtime>,
463    {
464        self.conn.send_blocking_archive(SetMtime { handle: self.handle, datetime })
465    }
466
467    /// Prepare a message that can be sent with slint_keyos_platform::async_archive
468    /// Less efficient than a regular read()
469    /// May return less bytes than requested.
470    /// Returns an empty buffer on EOF
471    pub fn async_read(&mut self, read_len: usize) -> AsyncRead { AsyncRead { handle: self.handle, read_len } }
472
473    /// Prepare a message that can be sent with slint_keyos_platform::async_archive
474    /// Less efficient than a regular write()
475    /// The actual bytes written is returned, may be less than the buffer size
476    pub fn async_write(&mut self, buffer: Vec<u8>) -> AsyncWrite {
477        AsyncWrite { handle: self.handle, buffer }
478    }
479
480    /// Prepare a message that can be sent with slint_keyos_platform::async_scalar
481    /// Efficiently copies between two open files.
482    /// Returns the numebr of actually copied bytes.
483    /// Returns Ok(0) on EOF
484    pub fn async_copy_block_to(&mut self, to: &mut Self, len: usize) -> AsyncCopyBlock {
485        AsyncCopyBlock { from: self.handle, to: to.handle, len }
486    }
487
488    pub fn copy_block_to(&mut self, to: &mut Self, len: usize) -> Result<usize, Error>
489    where
490        P: MessageAllowed<AsyncCopyBlock>,
491    {
492        self.conn.send_blocking_scalar(AsyncCopyBlock { from: self.handle, to: to.handle, len })
493    }
494
495    pub fn overwrite(&mut self, buf: &[u8]) -> Result<(), Error>
496    where
497        P: MessageAllowed<SeekFile>,
498        P: MessageAllowed<WriteFile>,
499        P: MessageAllowed<TruncateFile>,
500        P: MessageAllowed<Flush>,
501    {
502        self.seek(std::io::SeekFrom::Start(0))?;
503        self.write_all(buf)?;
504        self.truncate()?;
505        Ok(())
506    }
507
508    pub fn copy_to(&mut self, to: &mut Self) -> Result<(), Error>
509    where
510        P: MessageAllowed<SeekFile>,
511        P: MessageAllowed<WriteFile>,
512        P: MessageAllowed<TruncateFile>,
513        P: MessageAllowed<AsyncCopyBlock>,
514    {
515        to.seek(std::io::SeekFrom::Start(0))?;
516        while self.copy_block_to(to, 0x10000)? > 0 {}
517        to.truncate()?;
518        Ok(())
519    }
520}
521
522impl<P: CheckedPermissions + MessageAllowed<CloseFile>> Read for File<P>
523where
524    P: MessageAllowed<ReadFile>,
525{
526    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
527        #[cfg(keyos)]
528        if (buf.as_ptr() as usize) & (xous::keyos::PAGE_SIZE - 1) == 0 && buf.len() >= xous::keyos::PAGE_SIZE
529        {
530            let read_len = (buf.len() & !(xous::keyos::PAGE_SIZE - 1)).min(FILE_BUFFER_SIZE);
531            return Ok(self.conn.lend_mut(ReadFile {
532                buf: unsafe { xous::MemoryRange::new(buf.as_ptr() as usize, read_len).unwrap() },
533                handle: self.handle,
534                read_len,
535            })?);
536        }
537
538        let read_len = buf.len().min(FILE_BUFFER_SIZE);
539
540        let result = self.conn.lend_mut(ReadFile { buf: *self.work_buf, handle: self.handle, read_len })?;
541        buf[..result].copy_from_slice(&self.work_buf.as_slice()[..result]);
542        Ok(result)
543    }
544}
545
546impl<P: CheckedPermissions + MessageAllowed<CloseFile>> Seek for File<P>
547where
548    P: MessageAllowed<SeekFile>,
549{
550    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
551        self.conn.send_blocking_archive(SeekFile { file: self.handle, pos: pos.into() }).map_err(Into::into)
552    }
553}
554
555impl<P: CheckedPermissions + MessageAllowed<CloseFile>> Write for File<P>
556where
557    P: MessageAllowed<WriteFile>,
558    P: MessageAllowed<Flush>,
559{
560    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
561        #[cfg(keyos)]
562        if (buf.as_ptr() as usize) & (xous::keyos::PAGE_SIZE - 1) == 0 && buf.len() >= xous::keyos::PAGE_SIZE
563        {
564            let write_len = (buf.len() & !(xous::keyos::PAGE_SIZE - 1)).min(FILE_BUFFER_SIZE);
565            return Ok(self.conn.lend_mut(WriteFile {
566                buf: unsafe { xous::MemoryRange::new(buf.as_ptr() as usize, write_len).unwrap() },
567                handle: self.handle,
568                write_len,
569            })?);
570        }
571
572        let buf_len = buf.len().min(FILE_BUFFER_SIZE);
573
574        self.work_buf.as_slice_mut()[..buf_len].copy_from_slice(&buf[..buf_len]);
575        let result =
576            self.conn.lend_mut(WriteFile { buf: *self.work_buf, handle: self.handle, write_len: buf_len })?;
577        Ok(result)
578    }
579
580    fn flush(&mut self) -> std::io::Result<()> {
581        self.conn.try_send_blocking_scalar(Flush(self.handle)).map_err(|_| std::io::ErrorKind::Other)??;
582        Ok(())
583    }
584}
585
586impl<P: CheckedPermissions + MessageAllowed<CloseFile>> Drop for File<P> {
587    fn drop(&mut self) {
588        if let Err(e) = self.conn.try_send_blocking_scalar(CloseFile(self.handle)) {
589            log::error!("Failed to close file: {:?}", e);
590        }
591    }
592}
593
594#[derive(Debug)]
595pub struct Dir<P: CheckedPermissions + MessageAllowed<CloseDir>> {
596    handle: DirHandle,
597    conn: CheckedConn<P>,
598}
599
600impl<P: CheckedPermissions + MessageAllowed<CloseDir>> Dir<P> {
601    pub fn next_entry(&self) -> Result<Option<DirEntry>, Error>
602    where
603        P: MessageAllowed<NextEntry>,
604    {
605        self.conn.send_blocking_archive(NextEntry(self.handle))
606    }
607
608    pub fn next_entry_async(&self) -> NextEntry { NextEntry(self.handle) }
609
610    pub fn pick_next_filename(&self, filename: impl Into<String>, pad: Option<usize>) -> Result<String, Error>
611    where
612        P: MessageAllowed<NextEntry>,
613    {
614        let filename: String = filename.into();
615
616        // Name can't include subdirectories
617        if filename.contains('/') {
618            return Err(Error::InvalidPath);
619        }
620
621        let pad = pad.unwrap_or(3);
622
623        // Allow getting the next directory name
624        let (basename, ext) = match filename.rsplit_once('.') {
625            Some((base, ext)) => (base, Some(format!(".{}", ext))),
626            None => (filename.as_str(), None),
627        };
628
629        let mut highest = 0u32;
630        let prefix = format!("{}-", basename);
631
632        while let Some(entry) = self.next_entry().ok().flatten() {
633            let name = entry.name;
634
635            // If this entry starts with a match to our filename, get the rest, else ignore
636            let remainder = match name.strip_prefix(&prefix) {
637                Some(r) => r,
638                None => continue,
639            };
640
641            // If this entry has the same extension, get the remaining number, else ignore
642            let num = match &ext {
643                Some(e) => match remainder.strip_suffix(e) {
644                    Some(n) => n,
645                    None => continue,
646                },
647                None => remainder,
648            };
649
650            if let Ok(n) = num.parse::<u32>() {
651                highest = highest.max(n);
652            }
653        }
654
655        // Example: account.txt => account-001.txt
656        let number = highest + 1;
657        Ok(format!("{}-{number:0pad$}{}", basename, ext.clone().unwrap_or_default()))
658    }
659}
660
661impl<P: CheckedPermissions + MessageAllowed<CloseDir>> Drop for Dir<P> {
662    fn drop(&mut self) {
663        if let Err(e) = self.conn.try_send_blocking_scalar(CloseDir(self.handle)) {
664            log::error!("Failed to close dir: {:?}", e);
665        }
666    }
667}
668
669// WaitForFs helper for subscribing to filesystem events
670pub struct WaitForFs<P: CheckedPermissions>(pub FileSystem<P>, pub Location);
671
672impl<P: CheckedPermissions> server::ServerMessages for WaitForFs<P> {
673    const NAME: &'static str = "";
674
675    fn messages() -> &'static [server::MessageDef<Self>]
676    where
677        Self: Sized,
678    {
679        &[]
680    }
681}
682
683impl<P: CheckedPermissions> server::Server for WaitForFs<P>
684where
685    P: MessageAllowed<SubscribeFilesystemEvent>,
686{
687    fn on_start(&mut self, context: &mut server::ServerContext<Self>) {
688        self.0.subscribe_filesystem_events(context, self.1);
689    }
690}
691
692impl<P: CheckedPermissions> server::ScalarEventHandler<FileSystemEvent> for WaitForFs<P>
693where
694    P: MessageAllowed<SubscribeFilesystemEvent>,
695{
696    fn handle(
697        &mut self,
698        msg: FileSystemEvent,
699        _sender: xous::PID,
700        context: &mut server::ServerContext<Self>,
701    ) {
702        if msg.location == self.1 && msg.event_type == FileSystemEventType::Mounted {
703            context.shutdown();
704        }
705    }
706}
707
708// ==================== Data types ====================
709
710#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
711pub struct FileHandle(pub u32);
712
713wrapped_scalar!(FileHandle);
714
715impl FileHandle {
716    pub fn new(id: u32, location: Location) -> Self {
717        Self(((location.to_usize().unwrap() as u32) << 24) | (id & 0x00FF_FFFF))
718    }
719
720    pub fn id(self) -> u32 { self.0 & 0x00FF_FFFF }
721
722    pub fn location(self) -> Result<Location, Error> {
723        Location::from_usize((self.0 >> 24) as usize).ok_or(Error::FileNotOpen)
724    }
725}
726
727#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
728pub struct DirHandle(pub u32);
729
730wrapped_scalar!(DirHandle);
731
732impl DirHandle {
733    pub fn new(id: u32, location: Location) -> Self {
734        Self(((location.to_usize().unwrap() as u32) << 24) | (id & 0x00FF_FFFF))
735    }
736
737    pub fn id(self) -> u32 { self.0 & 0x00FF_FFFF }
738
739    pub fn location(self) -> Result<Location, Error> {
740        Location::from_usize((self.0 >> 24) as usize).ok_or(Error::FileNotOpen)
741    }
742}
743
744#[derive(
745    Debug,
746    Clone,
747    Copy,
748    PartialEq,
749    Eq,
750    Hash,
751    rkyv::Archive,
752    rkyv::Serialize,
753    rkyv::Deserialize,
754    FromPrimitive,
755    ToPrimitive,
756)]
757pub enum Location {
758    /// KeyOS common assets directory root.
759    /// Read-only. Available to all apps.
760    /// `<system-volume>/common`
761    CommonAssets = 1,
762
763    /// Currently running KeyOS app's RW data directory.
764    /// Available to all apps.
765    /// `<encrypted>/appdata/<app-id>/`
766    AppData,
767
768    /// Privileged access to System Volume.
769    /// `<system-volume>/`
770    System,
771
772    /// Privileged access to the whole encrypted partition
773    /// `<encrypted>/`
774    EncryptedRoot,
775
776    /// Privileged access to the Boot Volume. Should only be used by firmware upgrade/recovery
777    Boot,
778
779    /// Externally connected USB drive
780    Usb,
781
782    /// Encrypted user files
783    /// `<encrypted>/user`
784    User,
785
786    /// Virtual partition used to share files on USB.
787    Airlock,
788
789    /// Per-app unencrypted state directory on System Volume.
790    /// `<system-volume>/state/<app-id>/`
791    SystemAppData,
792
793    /// Currently running KeyOS app's read-only bundle resources directory.
794    /// Registered by app-manager before launch.
795    /// `<system-volume>/keyos/apps/<app-name>/resources/` for built-in apps, or
796    /// `<system-volume>/keyos/sideloaded-apps/<app-id>/resources/` for sideloaded apps.
797    AppResources,
798}
799
800impl server::AsScalar<1> for Location {
801    fn as_scalar(&self) -> [u32; 1] { [self.to_u32().unwrap()] }
802}
803
804impl server::FromScalar<1> for Location {
805    fn from_scalar([value]: [u32; 1]) -> Self { Self::from_u32(value).unwrap_or(Location::AppData) }
806}
807
808#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
809pub struct DirEntry {
810    pub name: String,
811    pub len: u64,
812    pub modified: DateTime,
813    pub is_dir: bool,
814    pub is_file: bool,
815}
816
817#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
818pub struct OpenFlags {
819    pub read: bool,
820    pub write: bool,
821    pub create: bool,
822}
823
824impl OpenFlags {
825    pub const CREATE: Self = Self { read: true, write: true, create: true };
826    pub const READ_ONLY: Self = Self { read: true, write: false, create: false };
827    pub const READ_WRITE: Self = Self { read: true, write: true, create: false };
828}
829
830#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
831pub enum SeekFrom {
832    Start(u64),
833    End(i64),
834    Current(i64),
835}
836
837impl From<SeekFrom> for std::io::SeekFrom {
838    fn from(from: SeekFrom) -> Self {
839        match from {
840            SeekFrom::Start(offset) => std::io::SeekFrom::Start(offset),
841            SeekFrom::End(offset) => std::io::SeekFrom::End(offset),
842            SeekFrom::Current(offset) => std::io::SeekFrom::Current(offset),
843        }
844    }
845}
846
847impl From<std::io::SeekFrom> for SeekFrom {
848    fn from(from: std::io::SeekFrom) -> Self {
849        match from {
850            std::io::SeekFrom::Start(offset) => SeekFrom::Start(offset),
851            std::io::SeekFrom::End(offset) => SeekFrom::End(offset),
852            std::io::SeekFrom::Current(offset) => SeekFrom::Current(offset),
853        }
854    }
855}
856
857#[derive(Debug, Clone)]
858pub struct FileSystemEvent {
859    pub location: Location,
860    pub event_type: FileSystemEventType,
861}
862
863#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
864pub enum FileSystemEventType {
865    Mounted,
866    Unmounted,
867    Error,
868}
869
870impl server::AsScalar<2> for FileSystemEvent {
871    fn as_scalar(&self) -> [u32; 2] {
872        let [location] = self.location.as_scalar();
873        [location, self.event_type.to_u32().unwrap()]
874    }
875}
876
877impl server::FromScalar<2> for FileSystemEvent {
878    fn from_scalar([location, event_type]: [u32; 2]) -> Self {
879        Self {
880            location: Location::from_scalar([location]),
881            event_type: FileSystemEventType::from_u32(event_type).unwrap_or(FileSystemEventType::Mounted),
882        }
883    }
884}
885
886#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
887pub struct MappedFileInTheirSpace {
888    pub addr: usize,
889    pub size: usize,
890}
891
892#[derive(Debug, Clone, Copy, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
893pub struct Metadata {
894    pub created: DateTime,
895    pub accessed: Date,
896    pub modified: DateTime,
897    pub size: u64,
898    pub is_dir: bool,
899}
900
901#[derive(Debug, Clone, Copy, Eq, PartialEq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
902pub struct Date {
903    pub year: u16,
904    pub month: u16,
905    pub day: u16,
906}
907
908impl Ord for Date {
909    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
910        self.year
911            .cmp(&other.year)
912            .then_with(|| self.month.cmp(&other.month))
913            .then_with(|| self.day.cmp(&other.day))
914    }
915}
916
917impl PartialOrd for Date {
918    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) }
919}
920
921#[derive(Debug, Clone, Copy, Eq, PartialEq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
922pub struct Time {
923    pub hour: u16,
924    pub min: u16,
925    pub sec: u16,
926    pub millis: u16,
927}
928
929impl Ord for Time {
930    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
931        self.hour
932            .cmp(&other.hour)
933            .then_with(|| self.min.cmp(&other.min))
934            .then_with(|| self.sec.cmp(&other.sec))
935            .then_with(|| self.millis.cmp(&other.millis))
936    }
937}
938
939impl PartialOrd for Time {
940    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) }
941}
942
943#[derive(Debug, Clone, Copy, Eq, PartialEq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
944pub struct DateTime {
945    pub date: Date,
946    pub time: Time,
947}
948
949impl Ord for DateTime {
950    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
951        self.date.cmp(&other.date).then_with(|| self.time.cmp(&other.time))
952    }
953}
954
955impl PartialOrd for DateTime {
956    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) }
957}
958
959pub(crate) fn ensure_parent_dir_exists_impl(
960    mut create_dir: impl FnMut(&str) -> Result<(), Error>,
961    path: &str,
962) -> Result<(), Error> {
963    fn recurse(create_dir: &mut impl FnMut(&str) -> Result<(), Error>, path: &str) -> Result<(), Error> {
964        if let Some(parent) = path.rsplit_once('/').map(|(parent, _)| parent) {
965            if !parent.is_empty() {
966                match create_dir(parent) {
967                    Ok(_) | Err(Error::FileAlreadyExists) => {}
968                    Err(Error::FileNotFound) => {
969                        recurse(create_dir, parent)?;
970                        match create_dir(parent) {
971                            Ok(_) | Err(Error::FileAlreadyExists) => {}
972                            Err(e) => return Err(e),
973                        }
974                    }
975                    Err(e) => return Err(e),
976                }
977            }
978        }
979        Ok(())
980    }
981
982    recurse(&mut create_dir, path)
983}