Skip to main content

ax_fs/
fops.rs

1// Copyright 2025 The Axvisor Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Low-level filesystem operations.
16
17use core::fmt;
18
19use ax_cap_access::{Cap, WithCap};
20use ax_errno::{AxError, AxResult, ax_err_type};
21use ax_fs_vfs::VfsNodeRef;
22use ax_io::SeekFrom;
23
24/// Alias of [`ax_fs_vfs::VfsNodeType`].
25pub type FileType = ax_fs_vfs::VfsNodeType;
26/// Alias of [`ax_fs_vfs::VfsDirEntry`].
27pub type DirEntry = ax_fs_vfs::VfsDirEntry;
28/// Alias of [`ax_fs_vfs::VfsNodeAttr`].
29pub type FileAttr = ax_fs_vfs::VfsNodeAttr;
30/// Alias of [`ax_fs_vfs::VfsNodePerm`].
31pub type FilePerm = ax_fs_vfs::VfsNodePerm;
32
33/// An opened file object, with open permissions and a cursor.
34pub struct File {
35    node: WithCap<VfsNodeRef>,
36    is_append: bool,
37    offset: u64,
38}
39
40/// An opened directory object, with open permissions and a cursor for
41/// [`read_dir`](Directory::read_dir).
42pub struct Directory {
43    node: WithCap<VfsNodeRef>,
44    entry_idx: usize,
45}
46
47/// Options and flags which can be used to configure how a file is opened.
48#[derive(Clone)]
49pub struct OpenOptions {
50    // generic
51    read: bool,
52    write: bool,
53    append: bool,
54    truncate: bool,
55    create: bool,
56    create_new: bool,
57    // system-specific
58    _custom_flags: i32,
59    _mode: u32,
60}
61
62impl Default for OpenOptions {
63    fn default() -> Self {
64        Self::new()
65    }
66}
67
68impl OpenOptions {
69    /// Creates a blank new set of options ready for configuration.
70    pub const fn new() -> Self {
71        Self {
72            // generic
73            read: false,
74            write: false,
75            append: false,
76            truncate: false,
77            create: false,
78            create_new: false,
79            // system-specific
80            _custom_flags: 0,
81            _mode: 0o666,
82        }
83    }
84    /// Sets the option for read access.
85    pub fn read(&mut self, read: bool) {
86        self.read = read;
87    }
88    /// Sets the option for write access.
89    pub fn write(&mut self, write: bool) {
90        self.write = write;
91    }
92    /// Sets the option for the append mode.
93    pub fn append(&mut self, append: bool) {
94        self.append = append;
95    }
96    /// Sets the option for truncating a previous file.
97    pub fn truncate(&mut self, truncate: bool) {
98        self.truncate = truncate;
99    }
100    /// Sets the option to create a new file, or open it if it already exists.
101    pub fn create(&mut self, create: bool) {
102        self.create = create;
103    }
104    /// Sets the option to create a new file, failing if it already exists.
105    pub fn create_new(&mut self, create_new: bool) {
106        self.create_new = create_new;
107    }
108
109    const fn is_valid(&self) -> bool {
110        if !self.read && !self.write && !self.append {
111            return false;
112        }
113        match (self.write, self.append) {
114            (true, false) => {}
115            (false, false) => {
116                if self.truncate || self.create || self.create_new {
117                    return false;
118                }
119            }
120            (_, true) => {
121                if self.truncate && !self.create_new {
122                    return false;
123                }
124            }
125        }
126        true
127    }
128}
129
130impl File {
131    fn access_node(&self, cap: Cap) -> AxResult<&VfsNodeRef> {
132        self.node.access_or_err(cap, AxError::PermissionDenied)
133    }
134
135    fn _open_at(dir: Option<&VfsNodeRef>, path: &str, opts: &OpenOptions) -> AxResult<Self> {
136        debug!("open file: {} {:?}", path, opts);
137        if !opts.is_valid() {
138            return Err(AxError::InvalidInput);
139        }
140
141        let node_option = crate::root::lookup(dir, path);
142        let node = if opts.create || opts.create_new {
143            match node_option {
144                Ok(node) => {
145                    // already exists
146                    if opts.create_new {
147                        return Err(AxError::AlreadyExists);
148                    }
149                    node
150                }
151                // not exists, create new
152                Err(AxError::NotFound) => crate::root::create_file(dir, path)?,
153                Err(e) => return Err(e),
154            }
155        } else {
156            // just open the existing
157            node_option?
158        };
159
160        let attr = node.get_attr()?;
161        if attr.is_dir()
162            && (opts.create || opts.create_new || opts.write || opts.append || opts.truncate)
163        {
164            return Err(AxError::IsADirectory);
165        }
166        let access_cap = opts.into();
167        if !perm_to_cap(attr.perm()).contains(access_cap) {
168            return Err(AxError::PermissionDenied);
169        }
170
171        node.open()?;
172        if opts.truncate {
173            node.truncate(0)?;
174        }
175        Ok(Self {
176            node: WithCap::new(node, access_cap),
177            is_append: opts.append,
178            offset: 0,
179        })
180    }
181
182    /// Opens a file at the path relative to the current directory. Returns a
183    /// [`File`] object.
184    pub fn open(path: &str, opts: &OpenOptions) -> AxResult<Self> {
185        Self::_open_at(None, path, opts)
186    }
187
188    /// Truncates the file to the specified size.
189    pub fn truncate(&self, size: u64) -> AxResult {
190        self.access_node(Cap::WRITE)?.truncate(size)?;
191        Ok(())
192    }
193
194    /// Reads the file at the current position. Returns the number of bytes
195    /// read.
196    ///
197    /// After the read, the cursor will be advanced by the number of bytes read.
198    pub fn read(&mut self, buf: &mut [u8]) -> AxResult<usize> {
199        let node = self.access_node(Cap::READ)?;
200        let read_len = node.read_at(self.offset, buf)?;
201        self.offset += read_len as u64;
202        Ok(read_len)
203    }
204
205    /// Reads the file at the given position. Returns the number of bytes read.
206    ///
207    /// It does not update the file cursor.
208    pub fn read_at(&self, offset: u64, buf: &mut [u8]) -> AxResult<usize> {
209        let node = self.access_node(Cap::READ)?;
210        let read_len = node.read_at(offset, buf)?;
211        Ok(read_len)
212    }
213
214    /// Writes the file at the current position. Returns the number of bytes
215    /// written.
216    ///
217    /// After the write, the cursor will be advanced by the number of bytes
218    /// written.
219    pub fn write(&mut self, buf: &[u8]) -> AxResult<usize> {
220        let offset = if self.is_append {
221            self.get_attr()?.size()
222        } else {
223            self.offset
224        };
225        let node = self.access_node(Cap::WRITE)?;
226        let write_len = node.write_at(offset, buf)?;
227        self.offset = offset + write_len as u64;
228        Ok(write_len)
229    }
230
231    /// Writes the file at the given position. Returns the number of bytes
232    /// written.
233    ///
234    /// It does not update the file cursor.
235    pub fn write_at(&self, offset: u64, buf: &[u8]) -> AxResult<usize> {
236        let node = self.access_node(Cap::WRITE)?;
237        let write_len = node.write_at(offset, buf)?;
238        Ok(write_len)
239    }
240
241    /// Flushes the file, writes all buffered data to the underlying device.
242    pub fn flush(&self) -> AxResult {
243        self.access_node(Cap::WRITE)?.fsync()?;
244        Ok(())
245    }
246
247    /// Sets the cursor of the file to the specified offset. Returns the new
248    /// position after the seek.
249    pub fn seek(&mut self, pos: SeekFrom) -> AxResult<u64> {
250        let size = self.get_attr()?.size();
251        let new_offset = match pos {
252            SeekFrom::Start(pos) => Some(pos),
253            SeekFrom::Current(off) => self.offset.checked_add_signed(off),
254            SeekFrom::End(off) => size.checked_add_signed(off),
255        }
256        .ok_or_else(|| ax_err_type!(InvalidInput))?;
257        self.offset = new_offset;
258        Ok(new_offset)
259    }
260
261    /// Gets the file attributes.
262    pub fn get_attr(&self) -> AxResult<FileAttr> {
263        self.access_node(Cap::empty())?.get_attr()
264    }
265}
266
267impl Directory {
268    fn access_node(&self, cap: Cap) -> AxResult<&VfsNodeRef> {
269        self.node.access_or_err(cap, AxError::PermissionDenied)
270    }
271
272    fn _open_dir_at(dir: Option<&VfsNodeRef>, path: &str, opts: &OpenOptions) -> AxResult<Self> {
273        debug!("open dir: {}", path);
274        if !opts.read {
275            return Err(AxError::InvalidInput);
276        }
277        if opts.create || opts.create_new || opts.write || opts.append || opts.truncate {
278            return Err(AxError::InvalidInput);
279        }
280
281        let node = crate::root::lookup(dir, path)?;
282        let attr = node.get_attr()?;
283        if !attr.is_dir() {
284            return Err(AxError::NotADirectory);
285        }
286        let access_cap = opts.into();
287        if !perm_to_cap(attr.perm()).contains(access_cap) {
288            return Err(AxError::PermissionDenied);
289        }
290
291        node.open()?;
292        Ok(Self {
293            node: WithCap::new(node, access_cap),
294            entry_idx: 0,
295        })
296    }
297
298    fn access_at(&self, path: &str) -> AxResult<Option<&VfsNodeRef>> {
299        if path.starts_with('/') {
300            Ok(None)
301        } else {
302            Ok(Some(self.access_node(Cap::EXECUTE)?))
303        }
304    }
305
306    /// Opens a directory at the path relative to the current directory.
307    /// Returns a [`Directory`] object.
308    pub fn open_dir(path: &str, opts: &OpenOptions) -> AxResult<Self> {
309        Self::_open_dir_at(None, path, opts)
310    }
311
312    /// Opens a directory at the path relative to this directory. Returns a
313    /// [`Directory`] object.
314    pub fn open_dir_at(&self, path: &str, opts: &OpenOptions) -> AxResult<Self> {
315        Self::_open_dir_at(self.access_at(path)?, path, opts)
316    }
317
318    /// Opens a file at the path relative to this directory. Returns a [`File`]
319    /// object.
320    pub fn open_file_at(&self, path: &str, opts: &OpenOptions) -> AxResult<File> {
321        File::_open_at(self.access_at(path)?, path, opts)
322    }
323
324    /// Creates an empty file at the path relative to this directory.
325    pub fn create_file(&self, path: &str) -> AxResult<VfsNodeRef> {
326        crate::root::create_file(self.access_at(path)?, path)
327    }
328
329    /// Creates an empty directory at the path relative to this directory.
330    pub fn create_dir(&self, path: &str) -> AxResult {
331        crate::root::create_dir(self.access_at(path)?, path)
332    }
333
334    /// Removes a file at the path relative to this directory.
335    pub fn remove_file(&self, path: &str) -> AxResult {
336        crate::root::remove_file(self.access_at(path)?, path)
337    }
338
339    /// Removes a directory at the path relative to this directory.
340    pub fn remove_dir(&self, path: &str) -> AxResult {
341        crate::root::remove_dir(self.access_at(path)?, path)
342    }
343
344    /// Reads directory entries starts from the current position into the
345    /// given buffer. Returns the number of entries read.
346    ///
347    /// After the read, the cursor will be advanced by the number of entries
348    /// read.
349    pub fn read_dir(&mut self, dirents: &mut [DirEntry]) -> AxResult<usize> {
350        let n = self
351            .access_node(Cap::READ)?
352            .read_dir(self.entry_idx, dirents)?;
353        self.entry_idx += n;
354        Ok(n)
355    }
356
357    /// Rename a file or directory to a new name.
358    /// Delete the original file if `old` already exists.
359    ///
360    /// This only works then the new path is in the same mounted fs.
361    pub fn rename(&self, old: &str, new: &str) -> AxResult {
362        crate::root::rename(old, new)
363    }
364}
365
366impl Drop for File {
367    fn drop(&mut self) {
368        unsafe { self.node.access_unchecked().release().ok() };
369    }
370}
371
372impl Drop for Directory {
373    fn drop(&mut self) {
374        unsafe { self.node.access_unchecked().release().ok() };
375    }
376}
377
378impl fmt::Debug for OpenOptions {
379    #[allow(unused_assignments)]
380    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381        let mut written = false;
382        macro_rules! fmt_opt {
383            ($field:ident, $label:literal) => {
384                if self.$field {
385                    if written {
386                        write!(f, " | ")?;
387                    }
388                    write!(f, $label)?;
389                    written = true;
390                }
391            };
392        }
393        fmt_opt!(read, "READ");
394        fmt_opt!(write, "WRITE");
395        fmt_opt!(append, "APPEND");
396        fmt_opt!(truncate, "TRUNC");
397        fmt_opt!(create, "CREATE");
398        fmt_opt!(create_new, "CREATE_NEW");
399        Ok(())
400    }
401}
402
403impl From<&OpenOptions> for Cap {
404    fn from(opts: &OpenOptions) -> Cap {
405        let mut cap = Cap::empty();
406        if opts.read {
407            cap |= Cap::READ;
408        }
409        if opts.write | opts.append {
410            cap |= Cap::WRITE;
411        }
412        cap
413    }
414}
415
416fn perm_to_cap(perm: FilePerm) -> Cap {
417    let mut cap = Cap::empty();
418    if perm.owner_readable() {
419        cap |= Cap::READ;
420    }
421    if perm.owner_writable() {
422        cap |= Cap::WRITE;
423    }
424    if perm.owner_executable() {
425        cap |= Cap::EXECUTE;
426    }
427    cap
428}