Skip to main content

ax_fs/fs/
ext4fs.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
15use alloc::{
16    collections::BTreeMap,
17    format,
18    string::{String, ToString},
19    sync::Arc,
20    vec::Vec,
21};
22
23use ax_fs_vfs::{
24    VfsDirEntry, VfsError, VfsNodeAttr, VfsNodeOps, VfsNodePerm, VfsNodeRef, VfsNodeType, VfsOps,
25    VfsResult,
26};
27use rsext4::{
28    Ext4Error, Ext4FileSystem as Rsext4FileSystem, Ext4Result, Ext4Timestamp, Jbd2Dev,
29    api::{OpenFile, fs_mount, lseek, open, read_at},
30    dir::{get_inode_with_num, mkdir},
31    entries::classic_dir::list_entries,
32    file::{delete_dir, mkfile, mv, truncate, unlink, write_file},
33    loopfile::resolve_inode_block_allextend,
34};
35use spin::Mutex;
36
37use crate::dev::{Disk, Partition};
38
39/// Block size for ext4 filesystem operations
40pub const BLOCK_SIZE: usize = 4096;
41
42/// Ext4 filesystem implementation that works with a disk device
43#[allow(dead_code)]
44pub struct Ext4FileSystem {
45    inner: Arc<Mutex<Jbd2Dev<Disk>>>,
46    fs: Arc<Mutex<Rsext4FileSystem>>,
47}
48
49/// Ext4FileSystem that works with a partition
50pub struct Ext4FileSystemPartition {
51    inner: Arc<Mutex<Jbd2Dev<Partition>>>,
52    fs: Arc<Mutex<Rsext4FileSystem>>,
53}
54
55unsafe impl Sync for Ext4FileSystem {}
56unsafe impl Send for Ext4FileSystem {}
57
58unsafe impl Sync for Ext4FileSystemPartition {}
59unsafe impl Send for Ext4FileSystemPartition {}
60
61impl Ext4FileSystem {
62    /// Create a new ext4 filesystem from a disk device
63    #[allow(dead_code)]
64    pub fn new(disk: Disk) -> Self {
65        info!(
66            "Got Disk size:{}, position:{}",
67            disk.size(),
68            disk.position()
69        );
70        let mut inner = Jbd2Dev::initial_jbd2dev(0, disk, false);
71        let fs = fs_mount(&mut inner).expect("failed to initialize EXT4 filesystem");
72        Self {
73            inner: Arc::new(Mutex::new(inner)),
74            fs: Arc::new(Mutex::new(fs)),
75        }
76    }
77
78    /// Create a new ext4 filesystem from a partition
79    pub fn from_partition(partition: Partition) -> Ext4FileSystemPartition {
80        info!(
81            "Got Partition size:{}, position:{}",
82            partition.size(),
83            partition.position()
84        );
85        let mut inner = Jbd2Dev::initial_jbd2dev(0, partition, false);
86        let fs = fs_mount(&mut inner).expect("failed to initialize EXT4 filesystem on partition");
87        Ext4FileSystemPartition {
88            inner: Arc::new(Mutex::new(inner)),
89            fs: Arc::new(Mutex::new(fs)),
90        }
91    }
92}
93
94/// The [`VfsOps`] trait provides operations on a filesystem.
95impl VfsOps for Ext4FileSystem {
96    fn root_dir(&self) -> VfsNodeRef {
97        debug!("Get root_dir");
98        Arc::new(FileWrapper::new(
99            "/",
100            Ext4Inner::Disk(Arc::clone(&self.inner)),
101            Arc::clone(&self.fs),
102        ))
103    }
104}
105
106/// The [`VfsOps`] trait provides operations on a filesystem.
107impl VfsOps for Ext4FileSystemPartition {
108    fn root_dir(&self) -> VfsNodeRef {
109        debug!("Get root_dir");
110        Arc::new(FileWrapper::new(
111            "/",
112            Ext4Inner::Partition(Arc::clone(&self.inner)),
113            Arc::clone(&self.fs),
114        ))
115    }
116}
117
118/// Inner state for ext4 filesystem, either backed by a full disk or a partition
119#[derive(Clone)]
120pub enum Ext4Inner {
121    /// Full disk device
122    Disk(Arc<Mutex<Jbd2Dev<Disk>>>),
123    /// Partition device
124    Partition(Arc<Mutex<Jbd2Dev<Partition>>>),
125}
126
127/// Wrapper for files and directories in the ext4 filesystem
128pub struct FileWrapper {
129    path: String,
130    file: Mutex<Option<OpenFile>>,
131    inner: Ext4Inner,
132    fs: Arc<Mutex<Rsext4FileSystem>>,
133}
134
135unsafe impl Send for FileWrapper {}
136unsafe impl Sync for FileWrapper {}
137
138impl FileWrapper {
139    fn new(path: &str, inner: Ext4Inner, fs: Arc<Mutex<Rsext4FileSystem>>) -> Self {
140        debug!("FileWrapper new {}", path);
141        Self {
142            path: path.to_string(),
143            file: Mutex::new(None),
144            inner,
145            fs,
146        }
147    }
148
149    fn path_deal_with(&self, path: &str) -> String {
150        if path.starts_with('/') {
151            debug!("path_deal_with: {}", path);
152        }
153        let trim_path = path.trim_matches('/');
154        if trim_path.is_empty() || trim_path == "." {
155            return self.path.to_string();
156        }
157
158        if let Some(rest) = trim_path.strip_prefix("./") {
159            // if starts with "./"
160            return self.path_deal_with(rest);
161        }
162        let rest_p = trim_path.replace("//", "/");
163        if trim_path != rest_p {
164            return self.path_deal_with(&rest_p);
165        }
166
167        let base_path = self.path.trim_end_matches('/');
168        if base_path == "/" {
169            format!("/{}", trim_path)
170        } else {
171            format!("{}/{}", base_path, trim_path)
172        }
173    }
174}
175
176/// The [`VfsNodeOps`] trait provides operations on a file or a directory.
177impl VfsNodeOps for FileWrapper {
178    fn get_attr(&self) -> VfsResult<VfsNodeAttr> {
179        let mut fs = self.fs.lock();
180        let perm = VfsNodePerm::from_bits_truncate(0o755);
181        let (_inode_num, inode) = match self.inner {
182            Ext4Inner::Disk(ref inner) => {
183                let mut inner = inner.lock();
184                get_inode_with_num(&mut fs, &mut inner, &self.path)
185                    .map_err(|_| VfsError::Io)?
186                    .ok_or(VfsError::NotFound)?
187            }
188            Ext4Inner::Partition(ref inner) => {
189                let mut inner = inner.lock();
190                get_inode_with_num(&mut fs, &mut inner, &self.path)
191                    .map_err(|_| VfsError::Io)?
192                    .ok_or(VfsError::NotFound)?
193            }
194        };
195        let vtype = if inode.is_dir() {
196            VfsNodeType::Dir
197        } else {
198            VfsNodeType::File
199        };
200        let size = inode.size();
201        let blocks = inode.blocks_count();
202
203        trace!(
204            "get_attr of {:?}, size: {}, blocks: {}",
205            self.path, size, blocks
206        );
207
208        Ok(VfsNodeAttr::new(perm, vtype, size, blocks))
209    }
210
211    fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult {
212        debug!("create {:?} on Ext4fs: {}", ty, path);
213        let fpath = self.path_deal_with(path);
214        if fpath.is_empty() {
215            return Ok(());
216        }
217
218        let mut fs = self.fs.lock();
219        match self.inner {
220            Ext4Inner::Disk(ref inner) => {
221                let mut inner = inner.lock();
222                match ty {
223                    VfsNodeType::Dir => {
224                        let _ = mkdir(&mut inner, &mut fs, &fpath);
225                    }
226                    _ => {
227                        let _ = mkfile(&mut inner, &mut fs, &fpath, None, None);
228                    }
229                }
230            }
231            Ext4Inner::Partition(ref inner) => {
232                let mut inner = inner.lock();
233                match ty {
234                    VfsNodeType::Dir => {
235                        let _ = mkdir(&mut inner, &mut fs, &fpath);
236                    }
237                    _ => {
238                        let _ = mkfile(&mut inner, &mut fs, &fpath, None, None);
239                    }
240                }
241            }
242        }
243        Ok(())
244    }
245
246    fn remove(&self, path: &str) -> VfsResult {
247        debug!("remove ext4fs: {}", path);
248        let fpath = self.path_deal_with(path);
249        assert!(!fpath.is_empty()); // already check at `root.rs`
250
251        let mut fs = self.fs.lock();
252        let (_inode_num, inode) = match self.inner {
253            Ext4Inner::Disk(ref inner) => {
254                let mut inner = inner.lock();
255                get_inode_with_num(&mut fs, &mut inner, &fpath)
256                    .map_err(|_| VfsError::Io)?
257                    .ok_or(VfsError::NotFound)?
258            }
259            Ext4Inner::Partition(ref inner) => {
260                let mut inner = inner.lock();
261                get_inode_with_num(&mut fs, &mut inner, &fpath)
262                    .map_err(|_| VfsError::Io)?
263                    .ok_or(VfsError::NotFound)?
264            }
265        };
266
267        match self.inner {
268            Ext4Inner::Disk(ref inner) => {
269                let mut inner = inner.lock();
270                if inode.is_dir() {
271                    let _ = delete_dir(&mut fs, &mut inner, &fpath);
272                } else {
273                    let _ = unlink(&mut fs, &mut inner, &fpath);
274                }
275            }
276            Ext4Inner::Partition(ref inner) => {
277                let mut inner = inner.lock();
278                if inode.is_dir() {
279                    let _ = delete_dir(&mut fs, &mut inner, &fpath);
280                } else {
281                    let _ = unlink(&mut fs, &mut inner, &fpath);
282                }
283            }
284        }
285        Ok(())
286    }
287
288    /// Get the parent directory of this directory.
289    /// Return `None` if the node is a file.
290    fn parent(&self) -> Option<VfsNodeRef> {
291        let path = &self.path;
292        debug!("Get the parent dir of {}", path);
293        let path = path.trim_end_matches('/').trim_end_matches(|c| c != '/');
294        if !path.is_empty() {
295            return Some(Arc::new(Self::new(
296                path,
297                self.inner.clone(),
298                Arc::clone(&self.fs),
299            )));
300        }
301        None
302    }
303
304    /// Read directory entries into `dirents`, starting from `start_idx`.
305    fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult<usize> {
306        let mut fs = self.fs.lock();
307        let (_inode_num, mut inode) = match self.inner {
308            Ext4Inner::Disk(ref inner) => {
309                let mut inner = inner.lock();
310                get_inode_with_num(&mut fs, &mut inner, &self.path)
311                    .map_err(|_| VfsError::Io)?
312                    .ok_or(VfsError::NotFound)?
313            }
314            Ext4Inner::Partition(ref inner) => {
315                let mut inner = inner.lock();
316                get_inode_with_num(&mut fs, &mut inner, &self.path)
317                    .map_err(|_| VfsError::Io)?
318                    .ok_or(VfsError::NotFound)?
319            }
320        };
321
322        if !inode.is_dir() {
323            return Err(VfsError::Unsupported);
324        }
325
326        let blocks = match self.inner {
327            Ext4Inner::Disk(ref inner) => {
328                let mut inner = inner.lock();
329                resolve_inode_block_allextend(&mut fs, &mut inner, &mut inode)
330                    .map_err(|_| VfsError::Io)?
331            }
332            Ext4Inner::Partition(ref inner) => {
333                let mut inner = inner.lock();
334                resolve_inode_block_allextend(&mut fs, &mut inner, &mut inode)
335                    .map_err(|_| VfsError::Io)?
336            }
337        };
338
339        let mut data = Vec::new();
340        for (_, phys_block) in blocks {
341            let cached = match self.inner {
342                Ext4Inner::Disk(ref inner) => {
343                    let mut inner = inner.lock();
344                    fs.datablock_cache
345                        .get_or_load(&mut inner, phys_block)
346                        .map_err(|_| VfsError::Io)?
347                }
348                Ext4Inner::Partition(ref inner) => {
349                    let mut inner = inner.lock();
350                    fs.datablock_cache
351                        .get_or_load(&mut inner, phys_block)
352                        .map_err(|_| VfsError::Io)?
353                }
354            };
355            data.extend_from_slice(&cached.data);
356        }
357
358        let entries = list_entries(&data);
359        let mut unique = BTreeMap::new();
360        for entry in entries {
361            if let Some(name) = entry.name_str()
362                && name != "."
363                && name != ".."
364            {
365                unique.insert(name.to_string(), entry.file_type);
366            }
367        }
368        let unique_vec: Vec<_> = unique.into_iter().collect();
369        let mut count = 0;
370        for (name, file_type) in unique_vec.iter().skip(start_idx) {
371            if count >= dirents.len() {
372                break;
373            }
374            let ty = match *file_type {
375                2 => VfsNodeType::Dir,
376                _ => VfsNodeType::File,
377            };
378            dirents[count] = VfsDirEntry::new(name, ty);
379            count += 1;
380        }
381        Ok(count)
382    }
383
384    /// Lookup the node with given `path` in the directory.
385    /// Return the node if found.
386    fn lookup(self: Arc<Self>, path: &str) -> VfsResult<VfsNodeRef> {
387        trace!("lookup ext4fs: {}, {}", self.path, path);
388        let fpath = self.path_deal_with(path);
389        if fpath.is_empty() {
390            return Ok(self.clone());
391        }
392
393        let mut fs = self.fs.lock();
394        let exists = match self.inner {
395            Ext4Inner::Disk(ref inner) => {
396                let mut inner = inner.lock();
397                get_inode_with_num(&mut fs, &mut inner, &fpath)
398                    .map_err(|_| VfsError::Io)?
399                    .is_some()
400            }
401            Ext4Inner::Partition(ref inner) => {
402                let mut inner = inner.lock();
403                get_inode_with_num(&mut fs, &mut inner, &fpath)
404                    .map_err(|_| VfsError::Io)?
405                    .is_some()
406            }
407        };
408
409        if exists {
410            Ok(Arc::new(Self::new(
411                &fpath,
412                self.inner.clone(),
413                Arc::clone(&self.fs),
414            )))
415        } else {
416            Err(VfsError::NotFound)
417        }
418    }
419
420    fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult<usize> {
421        let mut file_guard = self.file.lock();
422        if file_guard.is_none() {
423            let mut fs = self.fs.lock();
424            *file_guard = match self.inner {
425                Ext4Inner::Disk(ref inner) => {
426                    let mut inner = inner.lock();
427                    open(&mut inner, &mut fs, &self.path, false).ok()
428                }
429                Ext4Inner::Partition(ref inner) => {
430                    let mut inner = inner.lock();
431                    open(&mut inner, &mut fs, &self.path, false).ok()
432                }
433            };
434        }
435
436        if let Some(ref mut file) = *file_guard {
437            let mut fs = self.fs.lock();
438            let _ = lseek(file, offset);
439            let data = match self.inner {
440                Ext4Inner::Disk(ref inner) => {
441                    let mut inner = inner.lock();
442                    read_at(&mut inner, &mut fs, file, buf.len()).map_err(|_| VfsError::Io)?
443                }
444                Ext4Inner::Partition(ref inner) => {
445                    let mut inner = inner.lock();
446                    read_at(&mut inner, &mut fs, file, buf.len()).map_err(|_| VfsError::Io)?
447                }
448            };
449            let len = data.len().min(buf.len());
450            buf[..len].copy_from_slice(&data[..len]);
451            Ok(len)
452        } else {
453            Err(VfsError::NotFound)
454        }
455    }
456
457    fn write_at(&self, offset: u64, buf: &[u8]) -> VfsResult<usize> {
458        let mut fs = self.fs.lock();
459        match self.inner {
460            Ext4Inner::Disk(ref inner) => {
461                let mut inner = inner.lock();
462                write_file(&mut inner, &mut fs, &self.path, offset, buf)
463                    .map_err(|_| VfsError::Io)?;
464            }
465            Ext4Inner::Partition(ref inner) => {
466                let mut inner = inner.lock();
467                write_file(&mut inner, &mut fs, &self.path, offset, buf)
468                    .map_err(|_| VfsError::Io)?;
469            }
470        };
471        Ok(buf.len())
472    }
473
474    fn truncate(&self, size: u64) -> VfsResult {
475        let mut fs = self.fs.lock();
476        match self.inner {
477            Ext4Inner::Disk(ref inner) => {
478                let mut inner = inner.lock();
479                let _ = truncate(&mut inner, &mut fs, &self.path, size);
480            }
481            Ext4Inner::Partition(ref inner) => {
482                let mut inner = inner.lock();
483                let _ = truncate(&mut inner, &mut fs, &self.path, size);
484            }
485        }
486        Ok(())
487    }
488
489    fn rename(&self, src_path: &str, dst_path: &str) -> VfsResult {
490        debug!("rename from {} to {}", src_path, dst_path);
491
492        let src_fpath = self.path_deal_with(src_path);
493        let dst_fpath = self.path_deal_with(dst_path);
494
495        let mut fs = self.fs.lock();
496        match self.inner {
497            Ext4Inner::Disk(ref inner) => {
498                let mut inner = inner.lock();
499                let _ = mv(&mut fs, &mut inner, &src_fpath, &dst_fpath);
500            }
501            Ext4Inner::Partition(ref inner) => {
502                let mut inner = inner.lock();
503                let _ = mv(&mut fs, &mut inner, &src_fpath, &dst_fpath);
504            }
505        }
506        Ok(())
507    }
508
509    fn as_any(&self) -> &dyn core::any::Any {
510        self as &dyn core::any::Any
511    }
512}
513
514impl Drop for FileWrapper {
515    fn drop(&mut self) {
516        debug!("Drop struct FileWrapper {:?}", self.path);
517        // File will be automatically closed when OpenFile is dropped
518    }
519}
520
521impl rsext4::BlockDevice for Disk {
522    fn write(
523        &mut self,
524        buffer: &[u8],
525        block_id: rsext4::bmalloc::AbsoluteBN,
526        count: u32,
527    ) -> Ext4Result<()> {
528        // RVlwext4 uses 4096 byte blocks, but Disk uses 512 byte blocks
529        self.set_position(block_id.raw() * BLOCK_SIZE as u64);
530        let mut total_written = 0;
531        let to_write = count as usize * BLOCK_SIZE;
532
533        while total_written < to_write {
534            let remaining = &buffer[total_written..];
535            let written = self.write_one(remaining).map_err(|_| Ext4Error::io())?;
536            total_written += written;
537        }
538
539        Ok(())
540    }
541
542    fn read(
543        &mut self,
544        buffer: &mut [u8],
545        block_id: rsext4::bmalloc::AbsoluteBN,
546        count: u32,
547    ) -> Ext4Result<()> {
548        self.set_position(block_id.raw() * BLOCK_SIZE as u64);
549        let mut total_read = 0;
550        let to_read = count as usize * BLOCK_SIZE;
551
552        while total_read < to_read {
553            let remaining = &mut buffer[total_read..];
554            let read = self.read_one(remaining).map_err(|_| Ext4Error::io())?;
555            total_read += read;
556        }
557
558        Ok(())
559    }
560
561    fn open(&mut self) -> Ext4Result<()> {
562        Ok(())
563    }
564
565    fn close(&mut self) -> Ext4Result<()> {
566        Ok(())
567    }
568
569    fn total_blocks(&self) -> u64 {
570        // RVlwext4 uses 4096 byte blocks
571        self.size() / BLOCK_SIZE as u64
572    }
573
574    fn current_time(&self) -> Ext4Result<Ext4Timestamp> {
575        let now = ax_hal::time::wall_time();
576        let sec =
577            i64::try_from(now.as_secs()).map_err(|_| Ext4Error::from(rsext4::Errno::EOVERFLOW))?;
578        Ok(Ext4Timestamp::new(sec, now.subsec_nanos()))
579    }
580}
581
582impl rsext4::BlockDevice for Partition {
583    fn write(
584        &mut self,
585        buffer: &[u8],
586        block_id: rsext4::bmalloc::AbsoluteBN,
587        count: u32,
588    ) -> Ext4Result<()> {
589        self.set_position(block_id.raw() * BLOCK_SIZE as u64);
590        let mut total_written = 0;
591        let to_write = count as usize * BLOCK_SIZE;
592
593        while total_written < to_write {
594            let remaining = &buffer[total_written..];
595            let written = self.write_one(remaining).map_err(|_| Ext4Error::io())?;
596            total_written += written;
597        }
598
599        Ok(())
600    }
601
602    fn read(
603        &mut self,
604        buffer: &mut [u8],
605        block_id: rsext4::bmalloc::AbsoluteBN,
606        count: u32,
607    ) -> Ext4Result<()> {
608        self.set_position(block_id.raw() * BLOCK_SIZE as u64);
609        let mut total_read = 0;
610        let to_read = count as usize * BLOCK_SIZE;
611
612        while total_read < to_read {
613            let remaining = &mut buffer[total_read..];
614            let read = self.read_one(remaining).map_err(|_| Ext4Error::io())?;
615            total_read += read;
616        }
617
618        Ok(())
619    }
620
621    fn open(&mut self) -> Ext4Result<()> {
622        Ok(())
623    }
624
625    fn close(&mut self) -> Ext4Result<()> {
626        Ok(())
627    }
628
629    fn total_blocks(&self) -> u64 {
630        // RVlwext4 uses 4096 byte blocks
631        self.size() / BLOCK_SIZE as u64
632    }
633
634    fn current_time(&self) -> Ext4Result<Ext4Timestamp> {
635        let now = ax_hal::time::wall_time();
636        let sec =
637            i64::try_from(now.as_secs()).map_err(|_| Ext4Error::from(rsext4::Errno::EOVERFLOW))?;
638        Ok(Ext4Timestamp::new(sec, now.subsec_nanos()))
639    }
640}